feat(license): dual-path opsx-licence client alongside HMAC

Wire LIC-/LICOFF- activate/verify/heartbeat to licence.rlwj.cn/api,
keep legacy HMAC accepted with deprecation banner, add instance_id
fingerprint and licensing docs (product lokistack still needs admin create).
This commit is contained in:
opsx committed 2026-11-01 02:55:37 +08:00
1 parent 5f42d9f696
commit af0298e994
13 files changed
+888 -37

No files matched your search

+8
View File
@@ -8,6 +8,14 @@ export interface LicenseInfo {
customer?: string
expiresAt?: number
expired?: boolean
source?: 'hmac' | 'opsx' | string
planType?: string
maxDevices?: number
deprecationNotice?: string
inGrace?: boolean
graceDaysLeft?: number
lastVerifiedAt?: number
instanceId?: string
}
export const licenseApi = {
status: () => http.get<{ license: LicenseInfo; pro: boolean; features: string[] }>('/system/license'),
+5 -2
View File
@@ -7,18 +7,21 @@ export default {
expiry: 'Valid until',
customer: 'Licensed to',
seats: 'Seats',
instances: 'Instance limit',
permanent: 'perpetual',
personalTitle: 'Personal',
teamTitle: 'Team',
keyPlaceholder: 'Enter license key to activate Pro…',
keyPlaceholder: 'Enter LIC- / LICOFF- key to activate Pro…',
activate: 'Activate',
activating: 'Activating…',
activated: 'Pro activated — all features unlocked',
activateFailed: 'Activation failed',
legacyBanner: 'Legacy HMAC keys are in a migration window. Please switch to a LIC- / LICOFF- key. Old keys stay valid until the window ends — never silently invalidated.',
graceBanner: 'Offline grace: {{days}} day(s) left. Reconnect to re-verify.',
gate: {
title: '{{feature}} is a Pro feature',
desc: 'Upgrade to Pro to unlock the full analytics suite. The Free plan keeps log query, deployment management and user/LDAP administration.',
keyPlaceholder: 'Paste license key…',
keyPlaceholder: 'Paste LIC- / LICOFF- key…',
activate: 'Activate',
toSettings: 'Open settings',
freeHint: 'Free plan includes: log query / deployment management / users / LDAP',
+5 -2
View File
@@ -7,18 +7,21 @@ export default {
expiry: '有效期',
customer: '授权对象',
seats: '席位',
instances: '实例上限',
permanent: '永久',
personalTitle: '个人版',
teamTitle: '团队版',
keyPlaceholder: '输入许可码激活 Pro…',
keyPlaceholder: '输入 LIC- / LICOFF- 许可码激活 Pro…',
activate: '激活',
activating: '激活中…',
activated: 'Pro 已激活,全部功能已解锁',
activateFailed: '激活失败',
legacyBanner: '旧版 HMAC 许可码迁移窗口:请尽快换发 LIC- / LICOFF- 授权码,旧码在窗口内仍有效,不会静默失效。',
graceBanner: '离线宽限中:剩余 {{days}} 天,恢复网络后续验。',
gate: {
title: '{{feature}} 是 Pro 功能',
desc: '升级 Pro 解锁平台全部分析能力;免费版可继续使用日志查询、部署管理与用户授权。',
keyPlaceholder: '粘贴许可码…',
keyPlaceholder: '粘贴 LIC- / LICOFF- 许可码…',
activate: '激活',
toSettings: '到设置激活',
freeHint: '免费版保留:日志查询 / 部署管理 / 用户管理 / LDAP 授权',
@@ -39,6 +39,9 @@ export const LicenseSection = memo(function LicenseSection() {
: t('permanent')
: null
const showLegacy = pro && (info?.source === 'hmac' || Boolean(info?.deprecationNotice))
const showGrace = Boolean(info?.inGrace)
return (
<Card>
<CardHeader className="pb-3">
@@ -51,12 +54,25 @@ export const LicenseSection = memo(function LicenseSection() {
</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
{showLegacy && (
<p className="rounded-md border border-warning/40 bg-warning/10 px-3 py-2 text-sm text-warning-foreground">
{info?.deprecationNotice || t('legacyBanner')}
</p>
)}
{showGrace && (
<p className="rounded-md border border-warning/40 bg-warning/10 px-3 py-2 text-sm text-warning-foreground">
{t('graceBanner', { days: info?.graceDaysLeft ?? 0 })}
</p>
)}
<div className="text-sm">
{pro ? (
<div className="grid gap-1 text-muted-foreground sm:grid-cols-2">
<p>{t('edition')}: {info?.edition === 'team' ? t('teamTitle') : t('personalTitle')}</p>
<p>{t('expiry')}: {expiry}</p>
{info?.customer && <p>{t('customer')}: {info.customer}</p>}
{Boolean(info?.maxDevices || info?.instances) && (
<p>{t('instances')}: {info?.maxDevices || info?.instances}</p>
)}
{Boolean(info?.seats) && <p>{t('seats')}: {info?.seats}</p>}
</div>
) : (