diff --git a/manager_dashboard/src/hooks/useLicenseStatus.js b/manager_dashboard/src/hooks/useLicenseStatus.js index 117831c..94dcda1 100644 --- a/manager_dashboard/src/hooks/useLicenseStatus.js +++ b/manager_dashboard/src/hooks/useLicenseStatus.js @@ -15,11 +15,18 @@ import client from '../api/client' * grace_expires_at string | null (ISO) * grace_days_remaining number | null * sync_failed bool + * offline_days number | null days since the last successful cloud contact + * license_problem "clock" | "unverified" | "expired" | "inactive" | null + * grace_over bool + * + * The site enforces its signed license offline (KI-006): it keeps running + * until expiry however long it has no internet — offline_days is informational. * * Derived helpers: - * showExpiryWarning bool — true when 0 < days_until_expiry <= 5 + * showExpiryWarning bool — true when 0 <= days_until_expiry <= 14 * inGracePeriod bool — true when expired but grace not yet over - * isBlocked bool — locked=true OR (unlicensed AND grace over) + * isBlocked bool — locked=true OR unlicensed + * longOffline bool — no cloud contact for 7+ days (renewal needs it) */ export default function useLicenseStatus() { const { data } = useQuery({ @@ -41,9 +48,13 @@ export default function useLicenseStatus() { grace_expires_at: null, grace_days_remaining: null, sync_failed: false, + offline_days: null, + license_problem: null, + grace_over: false, showExpiryWarning: false, inGracePeriod: false, isBlocked: false, + longOffline: false, } } @@ -57,16 +68,21 @@ export default function useLicenseStatus() { grace_expires_at = null, grace_days_remaining = null, sync_failed = false, + offline_days = null, + license_problem = null, + grace_over = false, } = data const showExpiryWarning = - days_until_expiry != null && days_until_expiry >= 0 && days_until_expiry <= 5 + days_until_expiry != null && days_until_expiry >= 0 && days_until_expiry <= 14 const inGracePeriod = - days_until_expiry != null && days_until_expiry < 0 && licensed + days_until_expiry != null && days_until_expiry < 0 && licensed && !grace_over const isBlocked = locked || !licensed + const longOffline = offline_days != null && offline_days >= 7 + return { licensed, locked, @@ -77,8 +93,12 @@ export default function useLicenseStatus() { grace_expires_at, grace_days_remaining, sync_failed, + offline_days, + license_problem, + grace_over, showExpiryWarning, inGracePeriod, isBlocked, + longOffline, } } diff --git a/manager_dashboard/src/layouts/AppLayout.jsx b/manager_dashboard/src/layouts/AppLayout.jsx index 5bf62ab..699fb1c 100644 --- a/manager_dashboard/src/layouts/AppLayout.jsx +++ b/manager_dashboard/src/layouts/AppLayout.jsx @@ -83,7 +83,10 @@ function PageTitle() { function LicenseBanner({ license }) { const { lock_reason, locked, lock_pending, days_until_expiry, expires_at, - grace_days_remaining, showExpiryWarning, inGracePeriod, isBlocked } = license + grace_days_remaining, showExpiryWarning, inGracePeriod, isBlocked, + sync_failed, offline_days, longOffline } = license + // Renewal needs the cloud: say so whenever the site can't reach it right now + const connectHint = sync_failed ? ' Συνδέστε τον server στο internet για να ανανεωθεί.' : '' function fmtDate(iso) { if (!iso) return '' @@ -103,12 +106,30 @@ function LicenseBanner({ license }) { ) } + if (lock_reason === 'clock') { + return ( +
+ + Η ώρα του server έχει γυρίσει πίσω. Διορθώστε την ημερομηνία/ώρα του server ή συνδέστε τον στο internet. +
+ ) + } + + if (lock_reason === 'unverified') { + return ( +
+ + Δεν υπάρχει επιβεβαιωμένη άδεια χρήσης. Συνδέστε τον server στο internet για να ενεργοποιηθεί. +
+ ) + } + if (isBlocked && lock_reason === 'expired') { const daysAgo = days_until_expiry != null ? Math.abs(days_until_expiry) : '?' return (
- Η άδεια χρήσης έληξε πριν {daysAgo} {daysAgo === 1 ? 'μέρα' : 'μέρες'} ({fmtDate(expires_at)}). Ανανεώστε την άδεια ή επικοινωνήστε με την υποστήριξη. + Η άδεια χρήσης έληξε πριν {daysAgo} {daysAgo === 1 ? 'μέρα' : 'μέρες'} ({fmtDate(expires_at)}). Ανανεώστε την άδεια ή επικοινωνήστε με την υποστήριξη.{connectHint}
) } @@ -119,7 +140,7 @@ function LicenseBanner({ license }) { return (
- Η άδεια χρήσης έληξε στις {fmtDate(expires_at)} (πριν {daysAgo} {daysAgo === 1 ? 'μέρα' : 'μέρες'}). Απομένουν {remaining} {remaining === 1 ? 'μέρα' : 'μέρες'} περιόδου χάριτος. Ανανεώστε την άδεια για να αποφύγετε το κλείδωμα. + Η άδεια χρήσης έληξε στις {fmtDate(expires_at)} (πριν {daysAgo} {daysAgo === 1 ? 'μέρα' : 'μέρες'}). Απομένουν {remaining} {remaining === 1 ? 'μέρα' : 'μέρες'} περιόδου χάριτος. Ανανεώστε την άδεια για να αποφύγετε το κλείδωμα.{connectHint}
) } @@ -129,7 +150,17 @@ function LicenseBanner({ license }) { return (
- Η άδεια χρήσης λήγει σε {days} {days === 1 ? 'μέρα' : 'μέρες'} ({fmtDate(expires_at)}). Ανανεώστε έγκαιρα. + Η άδεια χρήσης λήγει σε {days} {days === 1 ? 'μέρα' : 'μέρες'} ({fmtDate(expires_at)}). Ανανεώστε έγκαιρα.{connectHint} +
+ ) + } + + // Informational: the license keeps working offline, but renewal needs the cloud + if (longOffline) { + return ( +
+ + Χωρίς σύνδεση με το cloud εδώ και {offline_days} μέρες. Το σύστημα λειτουργεί κανονικά — η άδεια ισχύει έως {fmtDate(expires_at)}.
) }