From edd9677f432f0042955c9b4234706abfbf2e52a8 Mon Sep 17 00:00:00 2001 From: "Leon.Schmidt" <159480018+leonw21342315@users.noreply.github.com> Date: Fri, 21 Aug 2026 17:28:30 +0200 Subject: [PATCH 1/4] ADD - add in-game phone administration and configuration (#21) * ADD - build in-game admin command center * ADD - open admin panel by command * ENH - rebuild admin panel as standalone editor * ENH - compact admin workspace navigation * ENH - expand admin device controls * ENH - refine admin panel navigation style * ADD - add SQL phone configurator * FIX - expose complete phone configuration * FIX - make company jobs freely configurable * FIX - align configurator tables to left * ENH - organize configurator collection controls * ENH - organize nested configurator sections * ENH - add configurator field descriptions * FIX - hide fixed configurator add controls * ENH - refine configurator editing experience * ENH - refine admin configurator controls * FIX - apply configurator settings instantly * FIX - close live activity command registration --- AGENTS.md | 17 + README.md | 34 +- frontend/src/App.vue | 39 +- .../src/components/AdminConfigValueEditor.vue | 1671 ++++++ .../components/AdminPanel.contract.test.ts | 455 ++ frontend/src/components/AdminPanel.vue | 4609 +++++++++++++++++ frontend/src/components/AppIcon.vue | 7 +- frontend/src/config/apps.ts | 1 + frontend/src/directives/configInputWidth.ts | 26 + frontend/src/phoneInventory.contract.test.ts | 35 +- frontend/src/phoneNavigation.contract.test.ts | 6 + frontend/src/stores/admin.ts | 238 + frontend/src/stores/app-store.test.ts | 7 + frontend/src/stores/app-store.ts | 28 +- .../src/stores/phone-locale-contract.test.ts | 23 + frontend/src/stores/phone.ts | 622 ++- frontend/src/testdata.contract.test.ts | 13 +- frontend/src/types/admin.ts | 179 + frontend/src/types/apps.ts | 1 + .../adminConfiguratorDescription.test.ts | 62 + .../src/utils/adminConfiguratorDescription.ts | 103 + frontend/src/utils/appStorePreviewImages.ts | 5 +- frontend/src/utils/appStorePreviews.test.ts | 6 +- frontend/src/views/PhoneAppWindow.vue | 2 +- frontend/src/views/apps/AppStoreApp.vue | 3 +- frontend/testserver/configurator-fixture.cjs | 668 +++ .../testserver/configurator-fixture.test.ts | 194 + frontend/testserver/index.cjs | 445 +- sky_phone/config/config.lua | 20 +- sky_phone/config/locales/de.lua | 95 + sky_phone/config/locales/en.lua | 95 + sky_phone/config/media.lua | 5 +- sky_phone/fxmanifest.lua | 6 +- sky_phone/source/bridge/client/framework.lua | 19 +- sky_phone/source/bridge/client/radio.lua | 19 +- sky_phone/source/bridge/server/housing.lua | 5 + sky_phone/source/client/animations.lua | 6 + sky_phone/source/client/custom_apps.lua | 48 +- sky_phone/source/client/focus.lua | 58 +- sky_phone/source/client/garage.lua | 5 + sky_phone/source/client/main.lua | 213 +- sky_phone/source/client/notifications.lua | 5 + sky_phone/source/client/nui_server_bridge.lua | 4 + sky_phone/source/client/payphones.lua | 34 +- .../source/client/phone_configurator.lua | 83 + sky_phone/source/client/public_api.lua | 12 + sky_phone/source/client/radio.lua | 19 + sky_phone/source/server/admin.lua | 930 ++++ sky_phone/source/server/calls.lua | 16 +- sky_phone/source/server/companies.lua | 19 +- sky_phone/source/server/crewlink.lua | 4 + sky_phone/source/server/crypto.lua | 17 +- .../source/server/custom_app_storage.lua | 8 +- sky_phone/source/server/custom_apps.lua | 50 +- sky_phone/source/server/db_migrate.lua | 42 + sky_phone/source/server/fliptok.lua | 65 +- sky_phone/source/server/marketplace.lua | 20 +- sky_phone/source/server/media_import.lua | 20 +- sky_phone/source/server/music.lua | 6 + sky_phone/source/server/pages.lua | 19 +- sky_phone/source/server/phone.lua | 62 +- .../source/server/phone_configurator.lua | 1172 +++++ .../server/phone_configurator_schema.lua | 23 + sky_phone/source/server/phone_persistence.lua | 120 +- sky_phone/source/server/phone_security.lua | 4 + sky_phone/source/server/picstagram.lua | 46 +- sky_phone/source/server/public_api.lua | 12 + sky_phone/source/server/sim.lua | 130 +- sky_phone/source/server/skyride.lua | 34 +- sky_phone/source/server/testdata.lua | 38 +- sky_phone/source/server/weazel_news.lua | 39 +- sky_phone/source/shared/custom_apps.lua | 1 + sky_phone/sql/install.sql | 27 + tests/server_phone_modules.lua | 17 +- 74 files changed, 12862 insertions(+), 329 deletions(-) create mode 100644 frontend/src/components/AdminConfigValueEditor.vue create mode 100644 frontend/src/components/AdminPanel.contract.test.ts create mode 100644 frontend/src/components/AdminPanel.vue create mode 100644 frontend/src/directives/configInputWidth.ts create mode 100644 frontend/src/stores/admin.ts create mode 100644 frontend/src/types/admin.ts create mode 100644 frontend/src/utils/adminConfiguratorDescription.test.ts create mode 100644 frontend/src/utils/adminConfiguratorDescription.ts create mode 100644 frontend/testserver/configurator-fixture.cjs create mode 100644 frontend/testserver/configurator-fixture.test.ts create mode 100644 sky_phone/source/client/phone_configurator.lua create mode 100644 sky_phone/source/server/admin.lua create mode 100644 sky_phone/source/server/phone_configurator.lua create mode 100644 sky_phone/source/server/phone_configurator_schema.lua diff --git a/AGENTS.md b/AGENTS.md index 7f5f0d7..d410344 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -92,6 +92,23 @@ as project instructions, not as optional documentation. proof. - Do not hand-edit generated frontend output. +## Phone Configurator parity (mandatory) + +- Every new, changed, renamed, moved, or removed configurable value in + `sky_phone/config/config.lua` or `sky_phone/config/media.lua` **must** be + reflected in the in-game Phone Configurator in the same change. Config-only + changes without matching Configurator support are incomplete and must not be + committed or merged. +- Keep the Configurator's runtime schema, defaults, value types, fixed versus + extensible collection rules, labels, descriptions, English and German + locales, SQL persistence, validation, and save/load roundtrip aligned with + the Lua configuration. +- The Configurator must remain complete when file-based configuration is + disabled. Do not introduce a setting that can only be managed by editing Lua + after `Config.PhoneConfigurator.Enabled` is enabled. +- Add or update contract/fixture coverage so configuration parity regressions + fail automated checks. + ## Working method and verification 1. Trace the relevant client, server, NUI, configuration, persistence, and event diff --git a/README.md b/README.md index 11fc060..7597172 100644 --- a/README.md +++ b/README.md @@ -218,6 +218,34 @@ The files contain clearly separated sections for: Restart `sky_phone` after changing Lua configuration. +### In-game phone configurator + +Set the switch at the beginning of `config/config.lua` to use SQL-backed configuration: + +```lua +Config.PhoneConfigurator = { + Enabled = true, +} +``` + +When enabled, `config.lua` and the server-only `media.lua` are first-run defaults. Sky Phone creates +the `sky_phone_configurator` table automatically, loads its saved values before framework and phone +modules initialize, and exposes the editor through `/phonepanel`. Nothing autosaves: stage changes +in the Phone Configurator tool and press the green check. Saving verifies both SQL payloads and then +applies the new server, client, media, app, item, command, provider, animation, and UI values through +Sky Phone's internal runtime refresh. It does not execute a resource restart command. + +Every `Config.*` value from `config.lua`, including server-only sections, and every value from +`Config.Media` is discovered automatically. The bootstrap switch above intentionally remains +file-owned because it decides whether SQL configuration is loaded. Lists, nested objects, vectors, +and numeric-keyed Lua tables use structured editors instead of raw JSON. Shipped schema rows stay +editable but cannot be renamed, converted, or removed. Every list and table still accepts any number +of additional rows; administrator-added rows remain removable. Company job keys are intentionally +fully removable because `Config.Companies.Definitions` is a freely managed job collection. + +Media API keys and server peppers are never returned in plaintext to the NUI. Existing secrets are +shown only as configured and are replaced only when an administrator enters a new value. + ### Language Available locales: @@ -541,7 +569,11 @@ Select `rtx`, `quasar`, `vms`, `rx`, `nolag`, `sn`, `esx_property`, or `qbx_prop ### Companies -Company jobs, public profiles, service numbers, services, permissions, locations, and default availability are configured under `Config.Companies.Definitions`. +Company jobs, public profiles, service numbers, services, permissions, locations, and default +availability are configured under `Config.Companies.Definitions`. Definitions are not limited to the +shipped jobs: add any number of company IDs in the in-game configurator and fill the freely +configurable `Job` value in the automatically generated full company template. Existing job keys can +also be removed; the remaining Companies settings stay available as normal individual fields. ### Weazel News diff --git a/frontend/src/App.vue b/frontend/src/App.vue index 21f575a..095f0cf 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -11,6 +11,7 @@ import { import { useRoute, useRouter } from 'vue-router' import { SkyProvider } from '@/ui' +import AdminPanel from '@/components/AdminPanel.vue' import PhoneHomeIndicator from '@/components/PhoneHomeIndicator.vue' import PhoneControlCenter from '@/components/PhoneControlCenter.vue' import PhoneDynamicIsland from '@/components/PhoneDynamicIsland.vue' @@ -115,8 +116,13 @@ type AppMessage = { | CustomAppCatalogEventData | CustomAppEventData | NavigationEventData + | AdminPanelOpenPayload } +type AdminPanelOpenPayload = Required< + Pick +> + type CustomAppCatalogEventData = { apps?: unknown } @@ -357,6 +363,9 @@ const appTransitionName = computed(() => route.query.transition === 'app-switch' ? 'app-switch' : 'app-window', ) const isLocked = ref(false) +const adminPanelOpen = ref( + isDevelopment && developmentParameters.has('adminPanel'), +) const springboardEditing = ref(false) const isUnlocking = ref(false) const passcodeBusy = ref(false) @@ -630,6 +639,8 @@ function loadUnlockedPhoneData(): void { } function completePhoneSetup(): void { + const requestedRoute = pendingUnlockRoute.value + pendingUnlockRoute.value = null setupPreviewDismissed.value = true setupAppearanceSelected.value = false isLocked.value = false @@ -637,7 +648,7 @@ function completePhoneSetup(): void { passcodeVisible.value = false passcodeRequired.value = false controlCenterOpened.value = false - void router.replace('/') + void router.replace(requestedRoute ?? '/') loadUnlockedPhoneData() } @@ -721,7 +732,15 @@ function openDevelopmentPayphonePreview(): void { function onMessage(event: MessageEvent): void { if (!isTrustedRootMessageSource(event.source, window)) return - if (event.data?.type === 'custom-apps:catalog') { + if (event.data?.type === 'admin:open') { + const data = event.data.data as AdminPanelOpenPayload | undefined + if (data?.lang && data.locales && data.fallbackLocales) { + phone.setLocale(data.lang, data.locales, data.fallbackLocales) + } + adminPanelOpen.value = true + } else if (event.data?.type === 'admin:close') { + adminPanelOpen.value = false + } else if (event.data?.type === 'custom-apps:catalog') { appCatalog.replaceCatalog(event.data.data) const catalogPayload = event.data.data as | { apps?: unknown; debug?: unknown } @@ -766,7 +785,12 @@ function onMessage(event: MessageEvent): void { isPhoneAppId(data.appId) && appStore.isInstalled(data.appId) ) { - void router.push(`/apps/${data.appId}`) + const requestedRoute = `/apps/${data.appId}` + if (setupRequired.value || isLocked.value) { + pendingUnlockRoute.value = requestedRoute + } else { + void router.push(requestedRoute) + } } else { console.error('[Navigation] Ignored an unavailable app target.') } @@ -1731,6 +1755,15 @@ onBeforeUnmount(() => {