feat(license): Lizenz-System mit Ed25519-Verify gegen license.netcell-it.com

Portiert mail-gateway/internal/license (Verify, Cache, Trial, Signature)
+ DB-Mirror (internal/services/license) + REST-Handler (status/verify/key/clear)
+ UI-Page /license (Activate, Status, Limits, Features, Re-verify)
+ <LicenseBanner /> neben UpdateBanner (trial-expiring, expired, verify-failed)
+ Scheduler: täglich Re-verify (24h-Tick)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Debian
2026-05-11 13:41:16 +02:00
parent 1324a34f11
commit 62505d547c
17 changed files with 1278 additions and 10 deletions

View File

@@ -5,6 +5,7 @@ import { useTranslation } from 'react-i18next'
import Sidebar from './Sidebar'
import Header from './Header'
import UpdateBanner from '../UpdateBanner'
import LicenseBanner from '../LicenseBanner'
// PAGE_TITLES maps the pathname to an i18n nav key. Header reads
// this to render "where you are". Empty fallback = app.title.
@@ -16,6 +17,7 @@ const PAGE_TITLES: Record<string, string> = {
'/networks': 'nav.networks',
'/ip-addresses': 'nav.ipAddresses',
'/cluster': 'nav.cluster',
'/license': 'nav.license',
'/settings': 'nav.settings',
}
@@ -42,6 +44,7 @@ export default function AppLayout() {
<main className="main-content">
<Header pageTitle={title} onMenuToggle={() => setSidebarOpen(true)} />
<LicenseBanner />
<UpdateBanner />
<div className="content-area">
<Outlet />

View File

@@ -5,6 +5,7 @@ import {
ClockCircleOutlined,
CloudServerOutlined,
ClusterOutlined,
CrownOutlined,
DashboardOutlined,
DatabaseOutlined,
FireOutlined,
@@ -70,12 +71,13 @@ const NAV: NavSection[] = [
labelKey: 'nav.section.system',
items: [
{ path: '/cluster', labelKey: 'nav.cluster', icon: <ApartmentOutlined /> },
{ path: '/license', labelKey: 'nav.license', icon: <CrownOutlined /> },
{ path: '/settings', labelKey: 'nav.settings', icon: <SettingOutlined /> },
],
},
]
const VERSION = '1.0.46'
const VERSION = '1.0.47'
export default function Sidebar({ isOpen, onClose }: SidebarProps) {
const { t } = useTranslation()