feat: complete release preparation, 10+ ad mediation, CI/CD, and docker deployment
Some checks failed
CI Pipeline / Code Quality & Typecheck (push) Waiting to run
CI Pipeline / Test Suite (macos-latest) (push) Blocked by required conditions
CI Pipeline / Test Suite (ubuntu-latest) (push) Blocked by required conditions
CI Pipeline / Test Suite (windows-latest) (push) Blocked by required conditions
CI Pipeline / Build Validation (admin) (push) Blocked by required conditions
CI Pipeline / Build Validation (desktop) (push) Blocked by required conditions
Deploy Landing Page / deploy (push) Blocked by required conditions
Deploy Landing Page / build (push) Waiting to run
Release & Packaging Pipeline / Build & Publish Admin Docker Image (push) Failing after 8s
Release & Code Signing CA Pipeline / build-and-sign-windows (push) Failing after 1m51s
Build macOS / Build & Package (macOS) (push) Failing after 4s
Build macOS / Build & Package (macOS)-1 (push) Failing after 5s
Release & Code Signing CA Pipeline / build-and-sign-macos (push) Failing after 3s
Release & Packaging Pipeline / Package macOS Desktop App (push) Failing after 4s
Release & Packaging Pipeline / Package Windows Desktop App (push) Failing after 2m28s
Release & Packaging Pipeline / Publish Official GitHub Release (push) Has been skipped
Some checks failed
CI Pipeline / Code Quality & Typecheck (push) Waiting to run
CI Pipeline / Test Suite (macos-latest) (push) Blocked by required conditions
CI Pipeline / Test Suite (ubuntu-latest) (push) Blocked by required conditions
CI Pipeline / Test Suite (windows-latest) (push) Blocked by required conditions
CI Pipeline / Build Validation (admin) (push) Blocked by required conditions
CI Pipeline / Build Validation (desktop) (push) Blocked by required conditions
Deploy Landing Page / deploy (push) Blocked by required conditions
Deploy Landing Page / build (push) Waiting to run
Release & Packaging Pipeline / Build & Publish Admin Docker Image (push) Failing after 8s
Release & Code Signing CA Pipeline / build-and-sign-windows (push) Failing after 1m51s
Build macOS / Build & Package (macOS) (push) Failing after 4s
Build macOS / Build & Package (macOS)-1 (push) Failing after 5s
Release & Code Signing CA Pipeline / build-and-sign-macos (push) Failing after 3s
Release & Packaging Pipeline / Package macOS Desktop App (push) Failing after 4s
Release & Packaging Pipeline / Package Windows Desktop App (push) Failing after 2m28s
Release & Packaging Pipeline / Publish Official GitHub Release (push) Has been skipped
This commit is contained in:
parent
5cd1de6859
commit
708e20f747
406 changed files with 42464 additions and 6199 deletions
236
apps/admin/src/components/license-issuer-dialog.tsx
Normal file
236
apps/admin/src/components/license-issuer-dialog.tsx
Normal file
|
|
@ -0,0 +1,236 @@
|
|||
'use client'
|
||||
|
||||
// apps/admin/src/components/license-issuer-dialog.tsx
|
||||
// D3RO Voice Admin — Ed25519 비대칭 암호화 라이선스 발급 다이얼로그
|
||||
|
||||
import { useState } from 'react'
|
||||
import {
|
||||
Dialog,
|
||||
DialogTitle,
|
||||
DialogContent,
|
||||
Box,
|
||||
Typography,
|
||||
IconButton,
|
||||
TextField,
|
||||
FormControl,
|
||||
InputLabel,
|
||||
Select,
|
||||
MenuItem,
|
||||
Button,
|
||||
Alert,
|
||||
} from '@mui/material'
|
||||
import {
|
||||
issueSignedLicenseKey,
|
||||
DEFAULT_LICENSE_PRIVATE_KEY,
|
||||
} from '@d3ro/core/utils/crypto-license'
|
||||
import type { LicenseTier } from '@d3ro/core/types'
|
||||
import { d3roPalette, d3roFontMono, d3roRadius } from '@d3ro/ui/theme'
|
||||
import { C, FONT_SANS, FONT_MONO, primaryButtonSx } from '@/lib/console-theme'
|
||||
|
||||
interface LicenseIssuerDialogProps {
|
||||
open: boolean
|
||||
onClose: () => void
|
||||
}
|
||||
|
||||
export function LicenseIssuerDialog({ open, onClose }: LicenseIssuerDialogProps): React.ReactElement {
|
||||
const [customerEmail, setCustomerEmail] = useState('')
|
||||
const [tier, setTier] = useState<LicenseTier>('pro_plus')
|
||||
const [validity, setValidity] = useState<'30d' | '365d' | 'lifetime'>('365d')
|
||||
const [machineId, setMachineId] = useState('')
|
||||
const [teamId, setTeamId] = useState('')
|
||||
const [generatedKey, setGeneratedKey] = useState<string | null>(null)
|
||||
const [copied, setCopied] = useState(false)
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
|
||||
const handleGenerate = () => {
|
||||
setError(null)
|
||||
setCopied(false)
|
||||
if (!customerEmail.trim()) {
|
||||
setError('Customer Email is required')
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const now = Date.now()
|
||||
let expiresAt: number | null = null
|
||||
if (validity === '30d') {
|
||||
expiresAt = now + 30 * 24 * 60 * 60 * 1000
|
||||
} else if (validity === '365d') {
|
||||
expiresAt = now + 365 * 24 * 60 * 60 * 1000
|
||||
}
|
||||
|
||||
const payload = {
|
||||
licenseId: `lic-${Date.now().toString(36)}-${Math.random().toString(36).substring(2, 6)}`,
|
||||
tier,
|
||||
customerEmail: customerEmail.trim(),
|
||||
issuedAt: now,
|
||||
expiresAt,
|
||||
machineId: machineId.trim() || null,
|
||||
teamId: teamId.trim() || undefined,
|
||||
maxDevices: tier === 'enterprise' ? 999 : tier === 'team' ? 25 : tier === 'pro_plus' ? 5 : 3,
|
||||
}
|
||||
|
||||
const key = issueSignedLicenseKey(payload, DEFAULT_LICENSE_PRIVATE_KEY)
|
||||
setGeneratedKey(key)
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : 'Failed to generate license key')
|
||||
}
|
||||
}
|
||||
|
||||
const handleCopy = () => {
|
||||
if (!generatedKey) return
|
||||
navigator.clipboard.writeText(generatedKey)
|
||||
setCopied(true)
|
||||
setTimeout(() => setCopied(false), 3000)
|
||||
}
|
||||
|
||||
const inputSx = {
|
||||
'& .MuiInputBase-root': {
|
||||
fontFamily: d3roFontMono,
|
||||
fontSize: 13,
|
||||
color: d3roPalette.text.primary,
|
||||
bgcolor: d3roPalette.bg.inset,
|
||||
borderRadius: d3roRadius.button,
|
||||
},
|
||||
'& .MuiInputLabel-root': {
|
||||
fontFamily: FONT_MONO,
|
||||
fontSize: 12,
|
||||
color: C.dim,
|
||||
},
|
||||
}
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
open={open}
|
||||
onClose={onClose}
|
||||
maxWidth="sm"
|
||||
fullWidth
|
||||
PaperProps={{
|
||||
sx: {
|
||||
bgcolor: '#0a0d14',
|
||||
border: '1px solid rgba(255, 255, 255, 0.1)',
|
||||
borderRadius: '16px',
|
||||
boxShadow: '0 20px 40px rgba(0, 0, 0, 0.8)',
|
||||
p: 1,
|
||||
},
|
||||
}}
|
||||
>
|
||||
<DialogTitle sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', pb: 1 }}>
|
||||
<Box>
|
||||
<Typography sx={{ fontFamily: FONT_SANS, fontSize: '18px', fontWeight: 700, color: C.bright }}>
|
||||
Issue Cryptographic License Key
|
||||
</Typography>
|
||||
<Typography sx={{ fontFamily: FONT_MONO, fontSize: '11px', color: C.dim }}>
|
||||
ED25519 ASYMMETRIC SIGNED OFFLINE / ENTERPRISE TOKEN
|
||||
</Typography>
|
||||
</Box>
|
||||
<IconButton onClick={onClose} size="small" sx={{ color: C.dim }}>
|
||||
✕
|
||||
</IconButton>
|
||||
</DialogTitle>
|
||||
|
||||
<DialogContent sx={{ display: 'flex', flexDirection: 'column', gap: 2, pt: 1 }}>
|
||||
<TextField
|
||||
fullWidth
|
||||
label="Customer Email *"
|
||||
placeholder="enterprise-client@company.com"
|
||||
value={customerEmail}
|
||||
onChange={(e) => setCustomerEmail(e.target.value)}
|
||||
sx={inputSx}
|
||||
/>
|
||||
|
||||
<Box sx={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 2 }}>
|
||||
<FormControl fullWidth sx={inputSx}>
|
||||
<InputLabel>Plan / Tier</InputLabel>
|
||||
<Select value={tier} onChange={(e) => setTier(e.target.value as LicenseTier)} label="Plan / Tier">
|
||||
<MenuItem value="pro">Pro ($9.9 / ₩12,900)</MenuItem>
|
||||
<MenuItem value="pro_plus">Pro+ ($19.9 / ₩24,900)</MenuItem>
|
||||
<MenuItem value="team">Team ($25 / ₩32,000)</MenuItem>
|
||||
<MenuItem value="enterprise">Enterprise (Custom)</MenuItem>
|
||||
</Select>
|
||||
</FormControl>
|
||||
|
||||
<FormControl fullWidth sx={inputSx}>
|
||||
<InputLabel>Validity Period</InputLabel>
|
||||
<Select value={validity} onChange={(e) => setValidity(e.target.value as '30d' | '365d' | 'lifetime')} label="Validity Period">
|
||||
<MenuItem value="30d">30 Days (Monthly)</MenuItem>
|
||||
<MenuItem value="365d">1 Year (Annual)</MenuItem>
|
||||
<MenuItem value="lifetime">Lifetime (Permanent)</MenuItem>
|
||||
</Select>
|
||||
</FormControl>
|
||||
</Box>
|
||||
|
||||
{(tier === 'team' || tier === 'enterprise') && (
|
||||
<TextField
|
||||
fullWidth
|
||||
label="Team / Org Name (Optional)"
|
||||
placeholder="Engineering AI Squad"
|
||||
value={teamId}
|
||||
onChange={(e) => setTeamId(e.target.value)}
|
||||
sx={inputSx}
|
||||
/>
|
||||
)}
|
||||
|
||||
<TextField
|
||||
fullWidth
|
||||
label="Target Machine ID (Optional Hardware Lock)"
|
||||
placeholder="e.g. 94dbaa34... (Leave blank for any device)"
|
||||
value={machineId}
|
||||
onChange={(e) => setMachineId(e.target.value)}
|
||||
sx={inputSx}
|
||||
/>
|
||||
|
||||
{error && <Alert severity="error" sx={{ fontFamily: FONT_MONO, fontSize: '12px' }}>{error}</Alert>}
|
||||
|
||||
<Button
|
||||
variant="contained"
|
||||
onClick={handleGenerate}
|
||||
sx={{
|
||||
...primaryButtonSx,
|
||||
py: 1.2,
|
||||
background: 'linear-gradient(135deg, #a855f7 0%, #3b82f6 100%)',
|
||||
fontWeight: 700,
|
||||
}}
|
||||
>
|
||||
⚡ Generate Ed25519 Signed License
|
||||
</Button>
|
||||
|
||||
{generatedKey && (
|
||||
<Box sx={{ mt: 1, p: 2, bgcolor: 'rgba(0, 0, 0, 0.4)', borderRadius: '10px', border: '1px solid rgba(168, 85, 247, 0.4)' }}>
|
||||
<Typography sx={{ fontFamily: FONT_MONO, fontSize: '11px', color: '#a855f7', fontWeight: 700, mb: 0.5 }}>
|
||||
SIGNED LICENSE KEY (Copy and paste into D3RO Voice Desktop App):
|
||||
</Typography>
|
||||
<Typography
|
||||
sx={{
|
||||
fontFamily: FONT_MONO,
|
||||
fontSize: '11px',
|
||||
color: C.bright,
|
||||
p: 1.5,
|
||||
bgcolor: 'rgba(255, 255, 255, 0.05)',
|
||||
borderRadius: '6px',
|
||||
wordBreak: 'break-all',
|
||||
userSelect: 'all',
|
||||
mb: 1.5,
|
||||
}}
|
||||
>
|
||||
{generatedKey}
|
||||
</Typography>
|
||||
<Button
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
onClick={handleCopy}
|
||||
sx={{
|
||||
fontFamily: FONT_MONO,
|
||||
fontSize: '12px',
|
||||
borderColor: copied ? '#10b981' : '#a855f7',
|
||||
color: copied ? '#10b981' : C.bright,
|
||||
}}
|
||||
>
|
||||
{copied ? '✓ Copied to Clipboard!' : '📋 Copy License Key'}
|
||||
</Button>
|
||||
</Box>
|
||||
)}
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue