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:
1 parent
5f42d9f696
commit
af0298e994
13 files changed
+888
-37
No files matched your search
@@ -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'),
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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>
|
||||
) : (
|
||||
|
||||
Reference in new issue
Block a user