mirror of
https://github.com/duckietm/Nitro-V3.git
synced 2026-06-19 15:06:20 +00:00
eeab548917
Adds the Housekeeping in-client admin panel — a Modtools-adjacent
surface that runs entirely inside the React client, talking to the
emulator over the existing wire instead of a separate REST/CMS layer.
Surface:
- `src/components/housekeeping/` — panel shell + 5 tabs (Dashboard,
Users, Rooms, Economy, Audit). Each tab drives one domain of the
matching emulator handlers (find/sanction/admin/economy/catalog/
hotel-wide).
- `src/api/housekeeping/` — composer/parser orchestration:
`HousekeepingApi.ts` exposes 30+ typed actions, each one running
through `runHkAction()` which awaits the shared
`HousekeepingActionResultEvent` correlated by action key.
- `src/hooks/housekeeping/` — `useHousekeeping` (the public hook),
`useHousekeepingStore` (useBetween singleton: shared selection +
audit polling + sanction templates), `useHousekeepingActions`,
`useHousekeepingConfirm`.
- `src/api/nitro/awaitMessageEvent.ts` — Promise adapter over
`CommunicationManager.subscribeMessage` with a sync `select`
callback that snapshots the parser INSIDE the subscribe handler
before the renderer recycles the parser instance after the
Promise resolves.
- `public/configuration/housekeeping-texts-{en,it}.example` —
149 EN + 149 IT i18n keys under `housekeeping.*` for every panel
string + every server-side error slug the emulator may emit.
Wiring (additive only):
- `src/components/MainView.tsx` — `<HousekeepingView />` mounted
alongside `<ModToolsView />`.
- `src/api/index.ts`, `src/hooks/index.ts`, `src/api/nitro/index.ts`
— added the `housekeeping` and `awaitMessageEvent` re-exports.
Wire contract: pairs against the Arcturus PR (#120 on
duckietm/Arcturus-Morningstar-Extended) and the renderer PR (#77 on
duckietm/Nitro_Render_V3). Incoming events 9100..9129, outgoing
composers 9200..9207. Permission gate `acc_housekeeping` enforced
server-side; the panel is hidden client-side via
`housekeeping.enabled` in the runtime ui-config.
61 lines
2.0 KiB
TypeScript
61 lines
2.0 KiB
TypeScript
const SECOND = 1;
|
|
const MINUTE = 60 * SECOND;
|
|
const HOUR = 60 * MINUTE;
|
|
const DAY = 24 * HOUR;
|
|
|
|
/**
|
|
* "5d 12h 3m" — compact uptime display for the dashboard. We don't
|
|
* use the existing `friendlyTime` helper because that one is tuned
|
|
* for "how long ago" (past tense, single-unit), while uptime needs
|
|
* multi-unit forward-looking output and to handle seconds-only
|
|
* fresh-boot cases.
|
|
*/
|
|
export const formatUptime = (seconds: number): string =>
|
|
{
|
|
if(!Number.isFinite(seconds) || seconds < 0) return '—';
|
|
if(seconds < MINUTE) return `${ Math.floor(seconds) }s`;
|
|
|
|
const d = Math.floor(seconds / DAY);
|
|
const h = Math.floor((seconds % DAY) / HOUR);
|
|
const m = Math.floor((seconds % HOUR) / MINUTE);
|
|
|
|
if(d > 0) return `${ d }d ${ h }h ${ m }m`;
|
|
if(h > 0) return `${ h }h ${ m }m`;
|
|
|
|
return `${ m }m`;
|
|
};
|
|
|
|
/**
|
|
* "5m ago", "2h ago", "3d ago" — past-tense relative formatter for
|
|
* audit-log timestamps. Anything older than a day rolls to a fixed
|
|
* date string so the log entries stay scannable even after a week.
|
|
*/
|
|
export const formatRelativePast = (timestampMs: number, nowMs: number = Date.now()): string =>
|
|
{
|
|
if(!Number.isFinite(timestampMs) || timestampMs <= 0) return '—';
|
|
|
|
const deltaSeconds = Math.max(0, Math.floor((nowMs - timestampMs) / 1000));
|
|
|
|
if(deltaSeconds < 5) return 'now';
|
|
if(deltaSeconds < MINUTE) return `${ deltaSeconds }s ago`;
|
|
if(deltaSeconds < HOUR) return `${ Math.floor(deltaSeconds / MINUTE) }m ago`;
|
|
if(deltaSeconds < DAY) return `${ Math.floor(deltaSeconds / HOUR) }h ago`;
|
|
if(deltaSeconds < 7 * DAY) return `${ Math.floor(deltaSeconds / DAY) }d ago`;
|
|
|
|
const date = new Date(timestampMs);
|
|
|
|
return date.toISOString().slice(0, 10);
|
|
};
|
|
|
|
export const formatCompactNumber = (value: number): string =>
|
|
{
|
|
if(!Number.isFinite(value)) return '—';
|
|
|
|
const abs = Math.abs(value);
|
|
|
|
if(abs >= 1_000_000) return `${ (value / 1_000_000).toFixed(abs >= 10_000_000 ? 0 : 1) }M`;
|
|
if(abs >= 1_000) return `${ (value / 1_000).toFixed(abs >= 10_000 ? 0 : 1) }K`;
|
|
|
|
return value.toString();
|
|
};
|