feat: board info logging, anomaly detection, re-flash & firmware update tokens
- Flash.jsx: replace esp-web-tools with direct esptool-js integration → reads MAC address + chip type before flash via Web Serial API → step-by-step UI (connect → board info → download → flash → done) → retry button on failure with remaining-attempt counter → firmware update token request after successful flash - Schema: FlashToken (maxAttempts/attemptCount/isLocked/isUpdateToken), FlashLog (startedAt/completedAt/durationMs/chipId/flashSize), FlashAnomaly model (RATE_LIMIT_IP/HIGH_VOLUME_IP/MAC_REUSE/TOKEN_LOCK/SUSPICIOUS_DURATION), ProjectFile.firmwareVersion - flash.js: new POST /start (board info + IP log + anomaly detection), updated POST /consume (timing, lock on exhaustion), GET returns firmwareFiles - orders.js: POST /request-reflash (free firmware update token for paid orders), updated to flashTokens[] relation - admin.js: GET /flash/metrics, GET/PUT /flash/anomalies, POST /flash/tokens/:id/unlock - Admin/FlashMetrics.jsx: dashboard with today stats, recent logs table, top-IP chart, anomaly management with resolve button Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -73,18 +73,19 @@ model Project {
|
||||
}
|
||||
|
||||
model ProjectFile {
|
||||
id String @id @default(uuid())
|
||||
projectId String
|
||||
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
|
||||
fileType String
|
||||
url String
|
||||
thumbnailUrl String?
|
||||
fileSize Int
|
||||
mimeType String
|
||||
originalName String?
|
||||
flashOffset String @default("0x0")
|
||||
displayOrder Int @default(0)
|
||||
createdAt DateTime @default(now())
|
||||
id String @id @default(uuid())
|
||||
projectId String
|
||||
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
|
||||
fileType String
|
||||
url String
|
||||
thumbnailUrl String?
|
||||
fileSize Int
|
||||
mimeType String
|
||||
originalName String?
|
||||
flashOffset String @default("0x0")
|
||||
firmwareVersion String? // e.g. "1.0.0" — firmware 파일에만 사용
|
||||
displayOrder Int @default(0)
|
||||
createdAt DateTime @default(now())
|
||||
}
|
||||
|
||||
model Product {
|
||||
@@ -121,38 +122,79 @@ model Order {
|
||||
refundedAt DateTime?
|
||||
refundReason String?
|
||||
|
||||
flashToken FlashToken?
|
||||
review Review?
|
||||
flashTokens FlashToken[] // 원본 + 업데이트 토큰 복수 허용
|
||||
review Review?
|
||||
}
|
||||
|
||||
model FlashToken {
|
||||
id String @id @default(uuid())
|
||||
token String @unique @default(uuid())
|
||||
orderId String @unique
|
||||
order Order @relation(fields: [orderId], references: [id])
|
||||
isUsed Boolean @default(false)
|
||||
usedAt DateTime?
|
||||
macAddress String?
|
||||
chipFamily String?
|
||||
expiresAt DateTime
|
||||
createdAt DateTime @default(now())
|
||||
id String @id @default(uuid())
|
||||
token String @unique @default(uuid())
|
||||
orderId String
|
||||
order Order @relation(fields: [orderId], references: [id])
|
||||
isUsed Boolean @default(false) // 성공적으로 플래시된 경우 true
|
||||
usedAt DateTime?
|
||||
macAddress String?
|
||||
chipFamily String?
|
||||
expiresAt DateTime
|
||||
createdAt DateTime @default(now())
|
||||
maxAttempts Int @default(3) // 최대 시도 횟수
|
||||
attemptCount Int @default(0) // 현재 시도 횟수 (실패 포함)
|
||||
lastAttemptAt DateTime?
|
||||
isLocked Boolean @default(false) // maxAttempts 초과 시 잠금
|
||||
lockedReason String?
|
||||
isUpdateToken Boolean @default(false) // 펌웨어 업데이트용 재발급 토큰
|
||||
|
||||
flashLog FlashLog?
|
||||
flashLogs FlashLog[]
|
||||
|
||||
@@index([orderId])
|
||||
}
|
||||
|
||||
model FlashLog {
|
||||
id String @id @default(uuid())
|
||||
flashTokenId String @unique
|
||||
flashToken FlashToken @relation(fields: [flashTokenId], references: [id])
|
||||
macAddress String
|
||||
chipFamily String
|
||||
firmwareName String
|
||||
firmwareId String
|
||||
success Boolean
|
||||
errorMessage String?
|
||||
clientIp String
|
||||
userAgent String?
|
||||
flashedAt DateTime @default(now())
|
||||
id String @id @default(uuid())
|
||||
flashTokenId String
|
||||
flashToken FlashToken @relation(fields: [flashTokenId], references: [id])
|
||||
attemptNumber Int @default(1)
|
||||
macAddress String? // 플래시 전 보드에서 읽은 MAC 주소
|
||||
chipFamily String? // 칩 종류 (esptool-js 에서 읽음)
|
||||
chipId String? // 칩 ID
|
||||
flashSize String? // 플래시 메모리 크기
|
||||
firmwareName String
|
||||
firmwareId String
|
||||
success Boolean
|
||||
errorMessage String?
|
||||
clientIp String
|
||||
userAgent String?
|
||||
startedAt DateTime? // 플래시 시작 시각
|
||||
completedAt DateTime? // 플래시 완료/실패 시각
|
||||
durationMs Int? // 소요 시간 ms
|
||||
flashedAt DateTime @default(now())
|
||||
|
||||
@@index([flashTokenId])
|
||||
@@index([clientIp])
|
||||
@@index([macAddress])
|
||||
@@index([flashedAt])
|
||||
}
|
||||
|
||||
// 이상 감지 이벤트
|
||||
model FlashAnomaly {
|
||||
id String @id @default(uuid())
|
||||
type String // RATE_LIMIT_IP | HIGH_VOLUME_IP | MAC_REUSE | TOKEN_LOCK | SUSPICIOUS_DURATION
|
||||
severity String @default("medium") // low | medium | high
|
||||
description String
|
||||
clientIp String?
|
||||
macAddress String?
|
||||
flashTokenId String?
|
||||
flashLogId String?
|
||||
metadata Json?
|
||||
resolved Boolean @default(false)
|
||||
resolvedAt DateTime?
|
||||
resolvedBy String?
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
@@index([type])
|
||||
@@index([clientIp])
|
||||
@@index([resolved])
|
||||
@@index([createdAt])
|
||||
}
|
||||
|
||||
model Review {
|
||||
|
||||
Reference in New Issue
Block a user