From 85214e6d2d2ed657024d19d063b9a0a9b1aad714 Mon Sep 17 00:00:00 2001 From: Buster Neece Date: Mon, 7 Aug 2023 05:10:49 -0500 Subject: [PATCH] Merge commit '752d8d679f8cf075ed2d8608071dad654c293f93' --- config/routes/admin.php | 47 +--- config/routes/api_admin.php | 39 +++ config/routes/base.php | 43 ++-- config/routes/stations.php | 1 + frontend/js/layout.js | 21 +- frontend/tsconfig.json | 3 +- frontend/vue/components/Admin/Debug.vue | 75 +++--- .../vue/components/Admin/Debug/TaskOutput.vue | 109 +++++++++ .../Admin/Debug/TaskOutputModal.vue | 29 +++ frontend/vue/components/Admin/Index.vue | 8 +- frontend/vue/components/Admin/Sidebar.vue | 34 +++ .../vue/components/Common/SidebarMenu.vue | 96 ++++++++ frontend/vue/components/PanelLayout.vue | 224 ++++++++++++++++++ frontend/vue/components/Stations/Sidebar.vue | 92 +++++++ frontend/vue/{base.js => layout.js} | 37 ++- frontend/vue/layouts/AdminPanelLayout.ts | 28 +++ frontend/vue/layouts/MinimalLayout.ts | 16 ++ frontend/vue/layouts/PanelLayout.ts | 25 ++ frontend/vue/layouts/StationPanelLayout.ts | 28 +++ frontend/vue/pages/Account.js | 6 +- frontend/vue/pages/Admin/ApiKeys.js | 5 +- frontend/vue/pages/Admin/AuditLog.js | 6 +- frontend/vue/pages/Admin/Backups.js | 6 +- frontend/vue/pages/Admin/Branding.js | 6 +- frontend/vue/pages/Admin/CustomFields.js | 6 +- frontend/vue/pages/Admin/Debug.js | 6 +- frontend/vue/pages/Admin/GeoLite.js | 6 +- frontend/vue/pages/Admin/Index.js | 6 +- frontend/vue/pages/Admin/Logs.js | 6 +- frontend/vue/pages/Admin/Permissions.js | 6 +- frontend/vue/pages/Admin/Relays.js | 6 +- frontend/vue/pages/Admin/Settings.js | 6 +- frontend/vue/pages/Admin/Shoutcast.js | 6 +- frontend/vue/pages/Admin/Stations.js | 6 +- frontend/vue/pages/Admin/StereoTool.js | 6 +- frontend/vue/pages/Admin/StorageLocations.js | 6 +- frontend/vue/pages/Admin/Updates.js | 6 +- frontend/vue/pages/Admin/Users.js | 6 +- frontend/vue/pages/Dashboard.js | 8 +- frontend/vue/pages/Public/FullPlayer.js | 6 +- frontend/vue/pages/Public/History.js | 6 +- frontend/vue/pages/Public/OnDemand.js | 6 +- frontend/vue/pages/Public/Player.js | 6 +- frontend/vue/pages/Public/Requests.js | 6 +- frontend/vue/pages/Public/Schedule.js | 6 +- frontend/vue/pages/Public/WebDJ.js | 6 +- frontend/vue/pages/Recover.js | 6 +- frontend/vue/pages/Setup/Register.js | 6 +- frontend/vue/pages/Setup/Settings.js | 6 +- frontend/vue/pages/Setup/Station.js | 6 +- frontend/vue/pages/Stations/Branding.js | 6 +- frontend/vue/pages/Stations/BulkMedia.js | 6 +- frontend/vue/pages/Stations/Fallback.js | 6 +- frontend/vue/pages/Stations/Help.js | 6 +- frontend/vue/pages/Stations/HlsStreams.js | 6 +- .../vue/pages/Stations/LiquidsoapConfig.js | 6 +- frontend/vue/pages/Stations/Media.js | 6 +- frontend/vue/pages/Stations/Mounts.js | 6 +- frontend/vue/pages/Stations/Playlists.js | 6 +- frontend/vue/pages/Stations/Podcasts.js | 6 +- frontend/vue/pages/Stations/Profile.js | 6 +- frontend/vue/pages/Stations/ProfileEdit.js | 6 +- frontend/vue/pages/Stations/Queue.js | 6 +- frontend/vue/pages/Stations/Remotes.js | 6 +- .../vue/pages/Stations/Reports/Listeners.js | 6 +- .../vue/pages/Stations/Reports/Overview.js | 6 +- .../vue/pages/Stations/Reports/Requests.js | 6 +- .../pages/Stations/Reports/SoundExchange.js | 6 +- .../vue/pages/Stations/Reports/Timeline.js | 6 +- frontend/vue/pages/Stations/Restart.js | 6 +- frontend/vue/pages/Stations/SftpUsers.js | 6 +- .../vue/pages/Stations/StereoToolConfig.js | 6 +- frontend/vue/pages/Stations/Streamers.js | 6 +- frontend/vue/pages/Stations/Webhooks.js | 6 +- frontend/vue/vendor/axios.ts | 12 +- frontend/vue/vendor/azuracast.ts | 49 ++-- frontend/vue/vendor/gettext.ts | 35 ++- frontend/vue/vendor/luxon.ts | 3 +- frontend/vue/vendor/sweetalert.ts | 30 +-- .../IndexAction.php => DebugAction.php} | 18 +- .../Admin/Debug/ClearCacheAction.php | 9 +- .../Admin/Debug/ClearQueueAction.php | 8 +- .../Admin/Debug/ClearStationQueueAction.php | 14 +- .../{ => Api}/Admin/Debug/NextSongAction.php | 14 +- .../Admin/Debug/NowPlayingAction.php | 14 +- .../{ => Api}/Admin/Debug/SyncAction.php | 14 +- .../{ => Api}/Admin/Debug/TelnetAction.php | 14 +- src/Middleware/Module/Admin.php | 23 +- src/Middleware/Module/PanelLayout.php | 58 +++++ src/Middleware/Module/Stations.php | 30 ++- src/View.php | 59 ++++- templates/admin/sidebar.phtml | 15 -- templates/main.phtml | 196 --------------- templates/minimal.phtml | 2 - templates/panel.phtml | 43 ++++ templates/partials/bodyjs.phtml | 47 ---- templates/partials/sidebar_menu.phtml | 67 ------ templates/partials/vue_body.phtml | 4 +- templates/stations/sidebar.phtml | 68 ------ templates/system/log_view.phtml | 57 ----- templates/system/vue_page.phtml | 2 +- ..._DebugCest.php => Api_Admin_DebugCest.php} | 7 +- tests/Functional/Station_ProfileCest.php | 42 ---- 103 files changed, 1279 insertions(+), 961 deletions(-) create mode 100644 frontend/vue/components/Admin/Debug/TaskOutput.vue create mode 100644 frontend/vue/components/Admin/Debug/TaskOutputModal.vue create mode 100644 frontend/vue/components/Admin/Sidebar.vue create mode 100644 frontend/vue/components/Common/SidebarMenu.vue create mode 100644 frontend/vue/components/PanelLayout.vue create mode 100644 frontend/vue/components/Stations/Sidebar.vue rename frontend/vue/{base.js => layout.js} (71%) create mode 100644 frontend/vue/layouts/AdminPanelLayout.ts create mode 100644 frontend/vue/layouts/MinimalLayout.ts create mode 100644 frontend/vue/layouts/PanelLayout.ts create mode 100644 frontend/vue/layouts/StationPanelLayout.ts rename src/Controller/Admin/{Debug/IndexAction.php => DebugAction.php} (86%) rename src/Controller/{ => Api}/Admin/Debug/ClearCacheAction.php (70%) rename src/Controller/{ => Api}/Admin/Debug/ClearQueueAction.php (76%) rename src/Controller/{ => Api}/Admin/Debug/ClearStationQueueAction.php (77%) rename src/Controller/{ => Api}/Admin/Debug/NextSongAction.php (75%) rename src/Controller/{ => Api}/Admin/Debug/NowPlayingAction.php (74%) rename src/Controller/{ => Api}/Admin/Debug/SyncAction.php (82%) rename src/Controller/{ => Api}/Admin/Debug/TelnetAction.php (79%) create mode 100644 src/Middleware/Module/PanelLayout.php delete mode 100644 templates/admin/sidebar.phtml delete mode 100644 templates/main.phtml create mode 100644 templates/panel.phtml delete mode 100644 templates/partials/bodyjs.phtml delete mode 100644 templates/partials/sidebar_menu.phtml delete mode 100644 templates/stations/sidebar.phtml delete mode 100644 templates/system/log_view.phtml rename tests/Functional/{Admin_DebugCest.php => Api_Admin_DebugCest.php} (66%) delete mode 100644 tests/Functional/Station_ProfileCest.php diff --git a/config/routes/admin.php b/config/routes/admin.php index b560f1aa7..112b120e8 100644 --- a/config/routes/admin.php +++ b/config/routes/admin.php @@ -14,50 +14,8 @@ return static function (RouteCollectorProxy $app) { $group->get('', Controller\Admin\IndexAction::class) ->setName('admin:index:index'); - $group->group( - '/debug', - function (RouteCollectorProxy $group) { - $group->get('', Controller\Admin\Debug\IndexAction::class) - ->setName('admin:debug:index'); - - $group->get('/clear-cache', Controller\Admin\Debug\ClearCacheAction::class) - ->setName('admin:debug:clear-cache'); - - $group->get( - '/clear-queue[/{queue}]', - Controller\Admin\Debug\ClearQueueAction::class - )->setName('admin:debug:clear-queue'); - - $group->get('/sync/{task}', Controller\Admin\Debug\SyncAction::class) - ->setName('admin:debug:sync'); - - $group->group( - '/station/{station_id}', - function (RouteCollectorProxy $group) { - $group->map( - ['GET', 'POST'], - '/nowplaying', - Controller\Admin\Debug\NowPlayingAction::class - )->setName('admin:debug:nowplaying'); - - $group->map( - ['GET', 'POST'], - '/nextsong', - Controller\Admin\Debug\NextSongAction::class - )->setName('admin:debug:nextsong'); - - $group->map( - ['GET', 'POST'], - '/clearqueue', - Controller\Admin\Debug\ClearStationQueueAction::class - )->setName('admin:debug:clear-station-queue'); - - $group->post('/telnet', Controller\Admin\Debug\TelnetAction::class) - ->setName('admin:debug:telnet'); - } - )->add(Middleware\GetStation::class); - } - )->add(new Middleware\Permissions(GlobalPermissions::All)); + $group->get('/debug', Controller\Admin\DebugAction::class) + ->setName('admin:debug:index'); $group->group( '/install', @@ -127,6 +85,7 @@ return static function (RouteCollectorProxy $app) { } ) ->add(Middleware\Module\Admin::class) + ->add(Middleware\Module\PanelLayout::class) ->add(Middleware\EnableView::class) ->add(new Middleware\Permissions(GlobalPermissions::View)) ->add(Middleware\RequireLogin::class); diff --git a/config/routes/api_admin.php b/config/routes/api_admin.php index 3fd2e2726..d5773efe0 100644 --- a/config/routes/api_admin.php +++ b/config/routes/api_admin.php @@ -57,6 +57,45 @@ return static function (RouteCollectorProxy $group) { } )->add(new Middleware\Permissions(GlobalPermissions::Backups)); + $group->group( + '/debug', + function (RouteCollectorProxy $group) { + $group->put('/clear-cache', Controller\Api\Admin\Debug\ClearCacheAction::class) + ->setName('api:admin:debug:clear-cache'); + + $group->put( + '/clear-queue[/{queue}]', + Controller\Api\Admin\Debug\ClearQueueAction::class + )->setName('api:admin:debug:clear-queue'); + + $group->put('/sync/{task}', Controller\Api\Admin\Debug\SyncAction::class) + ->setName('api:admin:debug:sync'); + + $group->group( + '/station/{station_id}', + function (RouteCollectorProxy $group) { + $group->put( + '/nowplaying', + Controller\Api\Admin\Debug\NowPlayingAction::class + )->setName('api:admin:debug:nowplaying'); + + $group->put( + '/nextsong', + Controller\Api\Admin\Debug\NextSongAction::class + )->setName('api:admin:debug:nextsong'); + + $group->put( + '/clearqueue', + Controller\Api\Admin\Debug\ClearStationQueueAction::class + )->setName('api:admin:debug:clear-station-queue'); + + $group->put('/telnet', Controller\Api\Admin\Debug\TelnetAction::class) + ->setName('api:admin:debug:telnet'); + } + )->add(Middleware\GetStation::class); + } + )->add(new Middleware\Permissions(GlobalPermissions::All)); + $group->get('/server/stats', Controller\Api\Admin\ServerStatsAction::class) ->setName('api:admin:server:stats') ->add(new Middleware\Permissions(GlobalPermissions::View)); diff --git a/config/routes/base.php b/config/routes/base.php index d6527436f..6dc516267 100644 --- a/config/routes/base.php +++ b/config/routes/base.php @@ -4,43 +4,38 @@ declare(strict_types=1); use App\Controller; use App\Enums\GlobalPermissions; -use App\Http\Response; -use App\Http\ServerRequest; use App\Middleware; -use Psr\Http\Message\ResponseInterface; use Slim\Routing\RouteCollectorProxy; return static function (RouteCollectorProxy $app) { $app->get('/', Controller\Frontend\IndexAction::class) ->setName('home'); + $app->get('/logout', Controller\Frontend\Account\LogoutAction::class) + ->setName('account:logout') + ->add(Middleware\RequireLogin::class); + + $app->get('/login-as/{id}/{csrf}', Controller\Frontend\Account\MasqueradeAction::class) + ->setName('account:masquerade') + ->add(new Middleware\Permissions(GlobalPermissions::All)) + ->add(Middleware\RequireLogin::class); + + $app->get('/endsession', Controller\Frontend\Account\EndMasqueradeAction::class) + ->setName('account:endmasquerade') + ->add(Middleware\RequireLogin::class); + + $app->group( '', function (RouteCollectorProxy $group) { $group->get('/dashboard', Controller\Frontend\DashboardAction::class) ->setName('dashboard'); - $group->get('/logout', Controller\Frontend\Account\LogoutAction::class) - ->setName('account:logout'); - - $group->get('/login-as/{id}/{csrf}', Controller\Frontend\Account\MasqueradeAction::class) - ->setName('account:masquerade') - ->add(new Middleware\Permissions(GlobalPermissions::All)); - - $group->get('/endsession', Controller\Frontend\Account\EndMasqueradeAction::class) - ->setName('account:endmasquerade'); - - $group->get( - '/api_keys', - function (ServerRequest $request, Response $response): ResponseInterface { - return $response->withRedirect('/profile'); - } - ); - $group->get('/profile', Controller\Frontend\Profile\IndexAction::class) ->setName('profile:index'); } - )->add(Middleware\EnableView::class) + )->add(Middleware\Module\PanelLayout::class) + ->add(Middleware\EnableView::class) ->add(Middleware\RequireLogin::class); $app->map(['GET', 'POST'], '/login', Controller\Frontend\Account\LoginAction::class) @@ -72,10 +67,12 @@ return static function (RouteCollectorProxy $app) { ->setName('setup:register'); $group->map(['GET', 'POST'], '/station', Controller\Frontend\SetupController::class . ':stationAction') - ->setName('setup:station'); + ->setName('setup:station') + ->add(Middleware\Module\PanelLayout::class); $group->map(['GET', 'POST'], '/settings', Controller\Frontend\SetupController::class . ':settingsAction') - ->setName('setup:settings'); + ->setName('setup:settings') + ->add(Middleware\Module\PanelLayout::class); } )->add(Middleware\EnableView::class); }; diff --git a/config/routes/stations.php b/config/routes/stations.php index 769b02c31..04a40e492 100644 --- a/config/routes/stations.php +++ b/config/routes/stations.php @@ -142,6 +142,7 @@ return static function (RouteCollectorProxy $app) { } ) ->add(Middleware\Module\Stations::class) + ->add(Middleware\Module\PanelLayout::class) ->add(new Middleware\Permissions(StationPermissions::View, true)) ->add(Middleware\RequireStation::class) ->add(Middleware\GetStation::class) diff --git a/frontend/js/layout.js b/frontend/js/layout.js index a9d4472eb..73fea1372 100644 --- a/frontend/js/layout.js +++ b/frontend/js/layout.js @@ -43,21 +43,24 @@ window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', () } }); +export function switchTheme() { + const currentTheme = getPreferredTheme(); + if (currentTheme === 'light') { + setStoredTheme('dark'); + setTheme('dark'); + } else { + setStoredTheme('light'); + setTheme('light'); + } +} + ready(() => { // Theme switcher document.querySelectorAll('.theme-switcher').forEach( toggle => { toggle.addEventListener('click', (e) => { e.stopPropagation(); - - const currentTheme = getPreferredTheme(); - if (currentTheme === 'light') { - setStoredTheme('dark'); - setTheme('dark'); - } else { - setStoredTheme('light'); - setTheme('light'); - } + switchTheme(); }); }); diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json index c5e9d275f..9f3d6a737 100644 --- a/frontend/tsconfig.json +++ b/frontend/tsconfig.json @@ -35,6 +35,7 @@ "vue/**/*.ts", "vue/**/*.d.ts", "vue/**/*.tsx", - "vue/**/*.vue" + "vue/**/*.vue", + "vue/**/*.js" ] } diff --git a/frontend/vue/components/Admin/Debug.vue b/frontend/vue/components/Admin/Debug.vue index 3a5d0dfde..b63bcdafe 100644 --- a/frontend/vue/components/Admin/Debug.vue +++ b/frontend/vue/components/Admin/Debug.vue @@ -16,13 +16,13 @@ @@ -38,13 +38,13 @@ @@ -66,13 +66,13 @@ {{ row.item.pattern }} @@ -103,13 +103,13 @@

- {{ $gettext('Clear Queue') }} - +
@@ -137,33 +137,33 @@
{{ $gettext('AutoDJ Queue') }}
- {{ $gettext('Clear Queue') }} - - +
{{ $gettext('Get Now Playing') }}
@@ -171,6 +171,8 @@ + + diff --git a/frontend/vue/components/Admin/Debug/TaskOutput.vue b/frontend/vue/components/Admin/Debug/TaskOutput.vue new file mode 100644 index 000000000..945404f1f --- /dev/null +++ b/frontend/vue/components/Admin/Debug/TaskOutput.vue @@ -0,0 +1,109 @@ + + + diff --git a/frontend/vue/components/Admin/Debug/TaskOutputModal.vue b/frontend/vue/components/Admin/Debug/TaskOutputModal.vue new file mode 100644 index 000000000..c7ea34987 --- /dev/null +++ b/frontend/vue/components/Admin/Debug/TaskOutputModal.vue @@ -0,0 +1,29 @@ + + diff --git a/frontend/vue/components/Admin/Index.vue b/frontend/vue/components/Admin/Index.vue index 2e4ebbb09..e063c9f94 100644 --- a/frontend/vue/components/Admin/Index.vue +++ b/frontend/vue/components/Admin/Index.vue @@ -415,12 +415,9 @@ import RunningBadge from "~/components/Common/Badges/RunningBadge.vue"; import {onMounted, ref, shallowRef} from "vue"; import {useAxios} from "~/vendor/axios"; import {useNotify} from "~/functions/useNotify"; +import {useAzuraCast} from "~/vendor/azuracast"; const props = defineProps({ - adminPanels: { - type: Object, - required: true - }, statsUrl: { type: String, required: true @@ -431,6 +428,9 @@ const props = defineProps({ } }); +const {sidebarProps} = useAzuraCast(); +const adminPanels = sidebarProps.menu; + const stats = shallowRef({ cpu: { total: { diff --git a/frontend/vue/components/Admin/Sidebar.vue b/frontend/vue/components/Admin/Sidebar.vue new file mode 100644 index 000000000..f535686fe --- /dev/null +++ b/frontend/vue/components/Admin/Sidebar.vue @@ -0,0 +1,34 @@ + + + diff --git a/frontend/vue/components/Common/SidebarMenu.vue b/frontend/vue/components/Common/SidebarMenu.vue new file mode 100644 index 000000000..438e3a0f9 --- /dev/null +++ b/frontend/vue/components/Common/SidebarMenu.vue @@ -0,0 +1,96 @@ + + + diff --git a/frontend/vue/components/PanelLayout.vue b/frontend/vue/components/PanelLayout.vue new file mode 100644 index 000000000..75cadaae2 --- /dev/null +++ b/frontend/vue/components/PanelLayout.vue @@ -0,0 +1,224 @@ + + + diff --git a/frontend/vue/components/Stations/Sidebar.vue b/frontend/vue/components/Stations/Sidebar.vue new file mode 100644 index 000000000..f2fa530e3 --- /dev/null +++ b/frontend/vue/components/Stations/Sidebar.vue @@ -0,0 +1,92 @@ + + + diff --git a/frontend/vue/base.js b/frontend/vue/layout.js similarity index 71% rename from frontend/vue/base.js rename to frontend/vue/layout.js index f02a48e95..bee3f94eb 100644 --- a/frontend/vue/base.js +++ b/frontend/vue/layout.js @@ -1,27 +1,18 @@ -import {createApp, h} from "vue"; +import {createApp} from "vue"; import installAxios from "~/vendor/axios"; import {installPinia} from '~/vendor/pinia'; import {installTranslate} from "~/vendor/gettext"; import Oruga from "@oruga-ui/oruga-next"; import {bootstrapConfig} from "@oruga-ui/theme-bootstrap"; import {installCurrentVueInstance} from "~/vendor/vueInstance"; +import {installGlobalProps} from "~/vendor/azuracast"; -export default function (component) { - const vueApp = createApp({ - render() { - return h(component, this.$appProps) - }, - }); +export default function initApp(appConfig = {}) { + const vueApp = createApp(appConfig); /* Track current instance (for programmatic use). */ installCurrentVueInstance(vueApp); - /* Gettext */ - installTranslate(vueApp); - - /* Axios */ - installAxios(vueApp); - /* Pinia */ installPinia(vueApp); @@ -52,11 +43,19 @@ export default function (component) { } }); - const vueComponent = (el, props) => { - vueApp.config.globalProperties.$appProps = props; - vueApp.mount(el); - } + window.vueComponent = (el, globalProps) => { + installGlobalProps(vueApp, globalProps); - window.vueComponent = vueComponent; - return vueComponent; + /* Gettext */ + installTranslate(vueApp, globalProps.locale ?? 'en_US'); + + /* Axios */ + installAxios(vueApp, globalProps.apiCsrf ?? null); + + vueApp.mount(el); + }; + + return { + vueApp + }; } diff --git a/frontend/vue/layouts/AdminPanelLayout.ts b/frontend/vue/layouts/AdminPanelLayout.ts new file mode 100644 index 000000000..94bd8cbc3 --- /dev/null +++ b/frontend/vue/layouts/AdminPanelLayout.ts @@ -0,0 +1,28 @@ +import {useAzuraCast} from "~/vendor/azuracast"; +import {Component, h} from "vue"; +import PanelLayoutComponent from "~/components/PanelLayout.vue"; +import Sidebar from "~/components/Admin/Sidebar.vue"; + +export default function useAdminPanelLayout(component: string | Component) { + return { + setup() { + const {panelProps, sidebarProps, componentProps} = useAzuraCast(); + + return { + panelProps, + sidebarProps, + componentProps + } + }, + render() { + return h( + PanelLayoutComponent, + this.panelProps, + { + sidebar: () => h(Sidebar, this.sidebarProps), + default: () => h(component, this.componentProps) + } + ); + } + } +} diff --git a/frontend/vue/layouts/MinimalLayout.ts b/frontend/vue/layouts/MinimalLayout.ts new file mode 100644 index 000000000..94501def8 --- /dev/null +++ b/frontend/vue/layouts/MinimalLayout.ts @@ -0,0 +1,16 @@ +import {useAzuraCast} from "~/vendor/azuracast"; +import {Component, h} from "vue"; + +export default function useMinimalLayout(component: string | Component) { + return { + setup() { + const {componentProps} = useAzuraCast(); + return { + componentProps + } + }, + render() { + return h(component, this.componentProps); + } + } +} diff --git a/frontend/vue/layouts/PanelLayout.ts b/frontend/vue/layouts/PanelLayout.ts new file mode 100644 index 000000000..cf166ef49 --- /dev/null +++ b/frontend/vue/layouts/PanelLayout.ts @@ -0,0 +1,25 @@ +import {useAzuraCast} from "~/vendor/azuracast"; +import {Component, h} from "vue"; +import PanelLayoutComponent from "~/components/PanelLayout.vue"; + +export default function usePanelLayout(component: string | Component) { + return { + setup() { + const {panelProps, componentProps} = useAzuraCast(); + + return { + panelProps, + componentProps + } + }, + render() { + return h( + PanelLayoutComponent, + this.panelProps, + { + default: () => h(component, this.componentProps) + } + ); + } + } +} diff --git a/frontend/vue/layouts/StationPanelLayout.ts b/frontend/vue/layouts/StationPanelLayout.ts new file mode 100644 index 000000000..69ef30151 --- /dev/null +++ b/frontend/vue/layouts/StationPanelLayout.ts @@ -0,0 +1,28 @@ +import {useAzuraCast} from "~/vendor/azuracast"; +import {Component, h} from "vue"; +import PanelLayoutComponent from "~/components/PanelLayout.vue"; +import Sidebar from "~/components/Stations/Sidebar.vue"; + +export default function useStationPanelLayout(component: string | Component) { + return { + setup() { + const {panelProps, sidebarProps, componentProps} = useAzuraCast(); + + return { + panelProps, + sidebarProps, + componentProps + } + }, + render() { + return h( + PanelLayoutComponent, + this.panelProps, + { + sidebar: () => h(Sidebar, this.sidebarProps), + default: () => h(component, this.componentProps) + } + ); + } + } +} diff --git a/frontend/vue/pages/Account.js b/frontend/vue/pages/Account.js index 40de66a51..d1ee2c390 100644 --- a/frontend/vue/pages/Account.js +++ b/frontend/vue/pages/Account.js @@ -1,5 +1,5 @@ -import initBase from '~/base.js'; - import Account from '~/components/Account'; +import initApp from "~/layout"; +import usePanelLayout from "~/layouts/PanelLayout"; -export default initBase(Account); +initApp(usePanelLayout(Account)); diff --git a/frontend/vue/pages/Admin/ApiKeys.js b/frontend/vue/pages/Admin/ApiKeys.js index 094820706..35f6a337c 100644 --- a/frontend/vue/pages/Admin/ApiKeys.js +++ b/frontend/vue/pages/Admin/ApiKeys.js @@ -1,4 +1,5 @@ -import initBase from '~/base.js'; import AdminApiKeys from '~/components/Admin/ApiKeys.vue'; +import initApp from "~/layout"; +import useAdminPanelLayout from "~/layouts/AdminPanelLayout"; -export default initBase(AdminApiKeys); +initApp(useAdminPanelLayout(AdminApiKeys)); diff --git a/frontend/vue/pages/Admin/AuditLog.js b/frontend/vue/pages/Admin/AuditLog.js index b047a2f60..4f815ece1 100644 --- a/frontend/vue/pages/Admin/AuditLog.js +++ b/frontend/vue/pages/Admin/AuditLog.js @@ -1,5 +1,5 @@ -import initBase from '~/base.js'; - import AuditLog from '~/components/Admin/AuditLog.vue'; +import initApp from "~/layout"; +import useAdminPanelLayout from "~/layouts/AdminPanelLayout"; -export default initBase(AuditLog); +initApp(useAdminPanelLayout(AuditLog)); diff --git a/frontend/vue/pages/Admin/Backups.js b/frontend/vue/pages/Admin/Backups.js index 48a0cb63b..30c812fe0 100644 --- a/frontend/vue/pages/Admin/Backups.js +++ b/frontend/vue/pages/Admin/Backups.js @@ -1,5 +1,5 @@ -import initBase from '~/base.js'; - import AdminBackups from '~/components/Admin/Backups.vue'; +import initApp from "~/layout"; +import useAdminPanelLayout from "~/layouts/AdminPanelLayout"; -export default initBase(AdminBackups); +initApp(useAdminPanelLayout(AdminBackups)); diff --git a/frontend/vue/pages/Admin/Branding.js b/frontend/vue/pages/Admin/Branding.js index 26719cec3..9412f85ac 100644 --- a/frontend/vue/pages/Admin/Branding.js +++ b/frontend/vue/pages/Admin/Branding.js @@ -1,5 +1,5 @@ -import initBase from '~/base.js'; - import AdminBranding from '~/components/Admin/Branding.vue'; +import initApp from "~/layout"; +import useAdminPanelLayout from "~/layouts/AdminPanelLayout"; -export default initBase(AdminBranding); +initApp(useAdminPanelLayout(AdminBranding)); diff --git a/frontend/vue/pages/Admin/CustomFields.js b/frontend/vue/pages/Admin/CustomFields.js index 70f33490f..fcd2ae8fb 100644 --- a/frontend/vue/pages/Admin/CustomFields.js +++ b/frontend/vue/pages/Admin/CustomFields.js @@ -1,5 +1,5 @@ -import initBase from '~/base.js'; - import AdminCustomFields from '~/components/Admin/CustomFields.vue'; +import initApp from "~/layout"; +import useAdminPanelLayout from "~/layouts/AdminPanelLayout"; -export default initBase(AdminCustomFields); +initApp(useAdminPanelLayout(AdminCustomFields)); diff --git a/frontend/vue/pages/Admin/Debug.js b/frontend/vue/pages/Admin/Debug.js index fe36593b0..c9909d4f1 100644 --- a/frontend/vue/pages/Admin/Debug.js +++ b/frontend/vue/pages/Admin/Debug.js @@ -1,5 +1,5 @@ -import initBase from '~/base.js'; - import AdminDebug from '~/components/Admin/Debug.vue'; +import initApp from "~/layout"; +import useAdminPanelLayout from "~/layouts/AdminPanelLayout"; -export default initBase(AdminDebug); +initApp(useAdminPanelLayout(AdminDebug)); diff --git a/frontend/vue/pages/Admin/GeoLite.js b/frontend/vue/pages/Admin/GeoLite.js index 8f3a6bff9..8f448ca4d 100644 --- a/frontend/vue/pages/Admin/GeoLite.js +++ b/frontend/vue/pages/Admin/GeoLite.js @@ -1,5 +1,5 @@ -import initBase from '~/base.js'; - import AdminGeoLite from '~/components/Admin/GeoLite.vue'; +import initApp from "~/layout"; +import useAdminPanelLayout from "~/layouts/AdminPanelLayout"; -export default initBase(AdminGeoLite); +initApp(useAdminPanelLayout(AdminGeoLite)); diff --git a/frontend/vue/pages/Admin/Index.js b/frontend/vue/pages/Admin/Index.js index 75450b70e..f8b527c13 100644 --- a/frontend/vue/pages/Admin/Index.js +++ b/frontend/vue/pages/Admin/Index.js @@ -1,5 +1,5 @@ -import initBase from '~/base.js'; - import AdminIndex from '~/components/Admin/Index.vue'; +import initApp from "~/layout"; +import usePanelLayout from "~/layouts/PanelLayout"; -export default initBase(AdminIndex); +initApp(usePanelLayout(AdminIndex)); diff --git a/frontend/vue/pages/Admin/Logs.js b/frontend/vue/pages/Admin/Logs.js index 42bff9af6..a7c6049ad 100644 --- a/frontend/vue/pages/Admin/Logs.js +++ b/frontend/vue/pages/Admin/Logs.js @@ -1,5 +1,5 @@ -import initBase from '~/base.js'; - import AdminLogs from '~/components/Admin/Logs.vue'; +import initApp from "~/layout"; +import useAdminPanelLayout from "~/layouts/AdminPanelLayout"; -export default initBase(AdminLogs); +initApp(useAdminPanelLayout(AdminLogs)); diff --git a/frontend/vue/pages/Admin/Permissions.js b/frontend/vue/pages/Admin/Permissions.js index 7bcaacf3b..ded7be1b5 100644 --- a/frontend/vue/pages/Admin/Permissions.js +++ b/frontend/vue/pages/Admin/Permissions.js @@ -1,5 +1,5 @@ -import initBase from '~/base.js'; - import AdminPermissions from '~/components/Admin/Permissions.vue'; +import initApp from "~/layout"; +import useAdminPanelLayout from "~/layouts/AdminPanelLayout"; -export default initBase(AdminPermissions); +initApp(useAdminPanelLayout(AdminPermissions)); diff --git a/frontend/vue/pages/Admin/Relays.js b/frontend/vue/pages/Admin/Relays.js index c305d6950..324050f45 100644 --- a/frontend/vue/pages/Admin/Relays.js +++ b/frontend/vue/pages/Admin/Relays.js @@ -1,5 +1,5 @@ -import initBase from '~/base.js'; - import AdminRelays from '~/components/Admin/Relays.vue'; +import initApp from "~/layout"; +import useAdminPanelLayout from "~/layouts/AdminPanelLayout"; -export default initBase(AdminRelays); +initApp(useAdminPanelLayout(AdminRelays)); diff --git a/frontend/vue/pages/Admin/Settings.js b/frontend/vue/pages/Admin/Settings.js index bd3c44aec..1d3848e28 100644 --- a/frontend/vue/pages/Admin/Settings.js +++ b/frontend/vue/pages/Admin/Settings.js @@ -1,5 +1,5 @@ -import initBase from '~/base.js'; - import AdminSettings from '~/components/Admin/Settings.vue'; +import initApp from "~/layout"; +import useAdminPanelLayout from "~/layouts/AdminPanelLayout"; -export default initBase(AdminSettings); +initApp(useAdminPanelLayout(AdminSettings)); diff --git a/frontend/vue/pages/Admin/Shoutcast.js b/frontend/vue/pages/Admin/Shoutcast.js index beaf0e9d3..b8fd666c9 100644 --- a/frontend/vue/pages/Admin/Shoutcast.js +++ b/frontend/vue/pages/Admin/Shoutcast.js @@ -1,5 +1,5 @@ -import initBase from '~/base.js'; - import AdminShoutcast from '~/components/Admin/Shoutcast.vue'; +import initApp from "~/layout"; +import useAdminPanelLayout from "~/layouts/AdminPanelLayout"; -export default initBase(AdminShoutcast); +initApp(useAdminPanelLayout(AdminShoutcast)); diff --git a/frontend/vue/pages/Admin/Stations.js b/frontend/vue/pages/Admin/Stations.js index aa80a9185..2735091e0 100644 --- a/frontend/vue/pages/Admin/Stations.js +++ b/frontend/vue/pages/Admin/Stations.js @@ -1,5 +1,5 @@ -import initBase from '~/base.js'; - import AdminStations from '~/components/Admin/Stations.vue'; +import initApp from "~/layout"; +import useAdminPanelLayout from "~/layouts/AdminPanelLayout"; -export default initBase(AdminStations); +initApp(useAdminPanelLayout(AdminStations)); diff --git a/frontend/vue/pages/Admin/StereoTool.js b/frontend/vue/pages/Admin/StereoTool.js index 675afc406..06d3cf60b 100644 --- a/frontend/vue/pages/Admin/StereoTool.js +++ b/frontend/vue/pages/Admin/StereoTool.js @@ -1,5 +1,5 @@ -import initBase from '~/base.js'; - import AdminStereoTool from '~/components/Admin/StereoTool.vue'; +import initApp from "~/layout"; +import useAdminPanelLayout from "~/layouts/AdminPanelLayout"; -export default initBase(AdminStereoTool); +initApp(useAdminPanelLayout(AdminStereoTool)); diff --git a/frontend/vue/pages/Admin/StorageLocations.js b/frontend/vue/pages/Admin/StorageLocations.js index 5330dc770..bdcef2808 100644 --- a/frontend/vue/pages/Admin/StorageLocations.js +++ b/frontend/vue/pages/Admin/StorageLocations.js @@ -1,5 +1,5 @@ -import initBase from '~/base.js'; - import StorageLocations from '~/components/Admin/StorageLocations.vue'; +import initApp from "~/layout"; +import useAdminPanelLayout from "~/layouts/AdminPanelLayout"; -export default initBase(StorageLocations); +initApp(useAdminPanelLayout(StorageLocations)); diff --git a/frontend/vue/pages/Admin/Updates.js b/frontend/vue/pages/Admin/Updates.js index e7349dd06..e7e1f0ebb 100644 --- a/frontend/vue/pages/Admin/Updates.js +++ b/frontend/vue/pages/Admin/Updates.js @@ -1,5 +1,5 @@ -import initBase from '~/base.js'; - import AdminUpdates from '~/components/Admin/Updates.vue'; +import initApp from "~/layout"; +import useAdminPanelLayout from "~/layouts/AdminPanelLayout"; -export default initBase(AdminUpdates); +initApp(useAdminPanelLayout(AdminUpdates)); diff --git a/frontend/vue/pages/Admin/Users.js b/frontend/vue/pages/Admin/Users.js index 1ee0fbf84..ec294757f 100644 --- a/frontend/vue/pages/Admin/Users.js +++ b/frontend/vue/pages/Admin/Users.js @@ -1,5 +1,5 @@ -import initBase from '~/base.js'; - import AdminUsers from '~/components/Admin/Users.vue'; +import initApp from "~/layout"; +import useAdminPanelLayout from "~/layouts/AdminPanelLayout"; -export default initBase(AdminUsers); +initApp(useAdminPanelLayout(AdminUsers)); diff --git a/frontend/vue/pages/Dashboard.js b/frontend/vue/pages/Dashboard.js index 3fc5705cb..2d5343c55 100644 --- a/frontend/vue/pages/Dashboard.js +++ b/frontend/vue/pages/Dashboard.js @@ -1,5 +1,5 @@ -import initBase from '~/base.js'; +import initApp from "~/layout"; +import usePanelLayout from "~/layouts/PanelLayout"; +import Dashboard from "~/components/Dashboard.vue"; -import Dashboard from '~/components/Dashboard'; - -export default initBase(Dashboard); +initApp(usePanelLayout(Dashboard)); diff --git a/frontend/vue/pages/Public/FullPlayer.js b/frontend/vue/pages/Public/FullPlayer.js index a4e74ba05..b9e75067f 100644 --- a/frontend/vue/pages/Public/FullPlayer.js +++ b/frontend/vue/pages/Public/FullPlayer.js @@ -1,5 +1,5 @@ -import initBase from '~/base.js'; - import FullPlayer from '~/components/Public/FullPlayer.vue'; +import initApp from "~/layout"; +import useMinimalLayout from "~/layouts/MinimalLayout"; -export default initBase(FullPlayer); +initApp(useMinimalLayout(FullPlayer)); diff --git a/frontend/vue/pages/Public/History.js b/frontend/vue/pages/Public/History.js index f9c2f5b4e..94222d3f0 100644 --- a/frontend/vue/pages/Public/History.js +++ b/frontend/vue/pages/Public/History.js @@ -1,5 +1,5 @@ -import initBase from '~/base.js'; - import History from '~/components/Public/History.vue'; +import initApp from "~/layout"; +import useMinimalLayout from "~/layouts/MinimalLayout"; -export default initBase(History); +initApp(useMinimalLayout(History)); diff --git a/frontend/vue/pages/Public/OnDemand.js b/frontend/vue/pages/Public/OnDemand.js index 285fb25dd..a16ceb312 100644 --- a/frontend/vue/pages/Public/OnDemand.js +++ b/frontend/vue/pages/Public/OnDemand.js @@ -1,5 +1,5 @@ -import initBase from '~/base.js'; - import OnDemand from '~/components/Public/OnDemand.vue'; +import initApp from "~/layout"; +import useMinimalLayout from "~/layouts/MinimalLayout"; -export default initBase(OnDemand); +initApp(useMinimalLayout(OnDemand)); diff --git a/frontend/vue/pages/Public/Player.js b/frontend/vue/pages/Public/Player.js index 30e2873ca..40d2c9609 100644 --- a/frontend/vue/pages/Public/Player.js +++ b/frontend/vue/pages/Public/Player.js @@ -1,5 +1,5 @@ -import initBase from '~/base.js'; - import Player from '~/components/Public/Player.vue'; +import initApp from "~/layout"; +import useMinimalLayout from "~/layouts/MinimalLayout"; -export default initBase(Player); +initApp(useMinimalLayout(Player)); diff --git a/frontend/vue/pages/Public/Requests.js b/frontend/vue/pages/Public/Requests.js index e262f3035..9489ba7b4 100644 --- a/frontend/vue/pages/Public/Requests.js +++ b/frontend/vue/pages/Public/Requests.js @@ -1,5 +1,5 @@ -import initBase from '~/base.js'; - import Requests from '~/components/Public/Requests.vue'; +import initApp from "~/layout"; +import useMinimalLayout from "~/layouts/MinimalLayout"; -export default initBase(Requests); +initApp(useMinimalLayout(Requests)); diff --git a/frontend/vue/pages/Public/Schedule.js b/frontend/vue/pages/Public/Schedule.js index b62d1e652..42bab7b4e 100644 --- a/frontend/vue/pages/Public/Schedule.js +++ b/frontend/vue/pages/Public/Schedule.js @@ -1,5 +1,5 @@ -import initBase from '~/base.js'; - import Schedule from '~/components/Public/Schedule.vue'; +import initApp from "~/layout"; +import useMinimalLayout from "~/layouts/MinimalLayout"; -export default initBase(Schedule); +initApp(useMinimalLayout(Schedule)); diff --git a/frontend/vue/pages/Public/WebDJ.js b/frontend/vue/pages/Public/WebDJ.js index c268414d1..be4b4b055 100644 --- a/frontend/vue/pages/Public/WebDJ.js +++ b/frontend/vue/pages/Public/WebDJ.js @@ -1,5 +1,5 @@ -import initBase from '~/base.js'; - import WebDJ from '~/components/Public/WebDJ.vue'; +import initApp from "~/layout"; +import useMinimalLayout from "~/layouts/MinimalLayout"; -export default initBase(WebDJ); +initApp(useMinimalLayout(WebDJ)); diff --git a/frontend/vue/pages/Recover.js b/frontend/vue/pages/Recover.js index 32e874217..f70db3fd1 100644 --- a/frontend/vue/pages/Recover.js +++ b/frontend/vue/pages/Recover.js @@ -1,5 +1,5 @@ -import initBase from '~/base.js'; - import Recover from '~/components/Recover.vue'; +import initApp from "~/layout"; +import useMinimalLayout from "~/layouts/MinimalLayout"; -export default initBase(Recover); +initApp(useMinimalLayout(Recover)); diff --git a/frontend/vue/pages/Setup/Register.js b/frontend/vue/pages/Setup/Register.js index 2db953324..b133c9d38 100644 --- a/frontend/vue/pages/Setup/Register.js +++ b/frontend/vue/pages/Setup/Register.js @@ -1,5 +1,5 @@ -import initBase from '~/base.js'; - import SetupRegister from '~/components/Setup/Register.vue'; +import initApp from "~/layout"; +import useMinimalLayout from "~/layouts/MinimalLayout"; -export default initBase(SetupRegister); +initApp(useMinimalLayout(SetupRegister)); diff --git a/frontend/vue/pages/Setup/Settings.js b/frontend/vue/pages/Setup/Settings.js index f4148216b..a180ec194 100644 --- a/frontend/vue/pages/Setup/Settings.js +++ b/frontend/vue/pages/Setup/Settings.js @@ -1,5 +1,5 @@ -import initBase from '~/base.js'; - import SetupSettings from '~/components/Setup/Settings.vue'; +import initApp from "~/layout"; +import usePanelLayout from "~/layouts/PanelLayout"; -export default initBase(SetupSettings); +initApp(usePanelLayout(SetupSettings)); diff --git a/frontend/vue/pages/Setup/Station.js b/frontend/vue/pages/Setup/Station.js index c16621e26..27014a695 100644 --- a/frontend/vue/pages/Setup/Station.js +++ b/frontend/vue/pages/Setup/Station.js @@ -1,5 +1,5 @@ -import initBase from '~/base.js'; - import SetupStation from '~/components/Setup/Station.vue'; +import initApp from "~/layout"; +import usePanelLayout from "~/layouts/PanelLayout"; -export default initBase(SetupStation); +initApp(usePanelLayout(SetupStation)); diff --git a/frontend/vue/pages/Stations/Branding.js b/frontend/vue/pages/Stations/Branding.js index ff32ae09f..46d358875 100644 --- a/frontend/vue/pages/Stations/Branding.js +++ b/frontend/vue/pages/Stations/Branding.js @@ -1,5 +1,5 @@ -import initBase from '~/base.js'; - import StationsBranding from '~/components/Stations/Branding.vue'; +import initApp from "~/layout"; +import useStationPanelLayout from "~/layouts/StationPanelLayout"; -export default initBase(StationsBranding); +initApp(useStationPanelLayout(StationsBranding)); diff --git a/frontend/vue/pages/Stations/BulkMedia.js b/frontend/vue/pages/Stations/BulkMedia.js index e832b65e7..3854d4a7a 100644 --- a/frontend/vue/pages/Stations/BulkMedia.js +++ b/frontend/vue/pages/Stations/BulkMedia.js @@ -1,5 +1,5 @@ -import initBase from '~/base.js'; - import BulkMedia from '~/components/Stations/BulkMedia.vue'; +import initApp from "~/layout"; +import useStationPanelLayout from "~/layouts/StationPanelLayout"; -export default initBase(BulkMedia); +initApp(useStationPanelLayout(BulkMedia)); diff --git a/frontend/vue/pages/Stations/Fallback.js b/frontend/vue/pages/Stations/Fallback.js index 7ba16ff8f..64756936e 100644 --- a/frontend/vue/pages/Stations/Fallback.js +++ b/frontend/vue/pages/Stations/Fallback.js @@ -1,5 +1,5 @@ -import initBase from '~/base.js'; - import Fallback from '~/components/Stations/Fallback.vue'; +import initApp from "~/layout"; +import useStationPanelLayout from "~/layouts/StationPanelLayout"; -export default initBase(Fallback); +initApp(useStationPanelLayout(Fallback)); diff --git a/frontend/vue/pages/Stations/Help.js b/frontend/vue/pages/Stations/Help.js index 9fbcd39b9..28cb7e9c8 100644 --- a/frontend/vue/pages/Stations/Help.js +++ b/frontend/vue/pages/Stations/Help.js @@ -1,5 +1,5 @@ -import initBase from '~/base.js'; - import Help from '~/components/Stations/Help.vue'; +import initApp from "~/layout"; +import useStationPanelLayout from "~/layouts/StationPanelLayout"; -export default initBase(Help); +initApp(useStationPanelLayout(Help)); diff --git a/frontend/vue/pages/Stations/HlsStreams.js b/frontend/vue/pages/Stations/HlsStreams.js index ad31d1cf0..ab4513f38 100644 --- a/frontend/vue/pages/Stations/HlsStreams.js +++ b/frontend/vue/pages/Stations/HlsStreams.js @@ -1,5 +1,5 @@ -import initBase from '~/base.js'; - import HlsStreams from '~/components/Stations/HlsStreams.vue'; +import initApp from "~/layout"; +import useStationPanelLayout from "~/layouts/StationPanelLayout"; -export default initBase(HlsStreams); +initApp(useStationPanelLayout(HlsStreams)); diff --git a/frontend/vue/pages/Stations/LiquidsoapConfig.js b/frontend/vue/pages/Stations/LiquidsoapConfig.js index 5a8f96d66..0b213a2f4 100644 --- a/frontend/vue/pages/Stations/LiquidsoapConfig.js +++ b/frontend/vue/pages/Stations/LiquidsoapConfig.js @@ -1,5 +1,5 @@ -import initBase from '~/base.js'; - import LiquidsoapConfig from '~/components/Stations/LiquidsoapConfig.vue'; +import initApp from "~/layout"; +import useStationPanelLayout from "~/layouts/StationPanelLayout"; -export default initBase(LiquidsoapConfig); +initApp(useStationPanelLayout(LiquidsoapConfig)); diff --git a/frontend/vue/pages/Stations/Media.js b/frontend/vue/pages/Stations/Media.js index f8578f37b..7916d26f6 100644 --- a/frontend/vue/pages/Stations/Media.js +++ b/frontend/vue/pages/Stations/Media.js @@ -1,5 +1,5 @@ -import initBase from '~/base.js'; - import Media from '~/components/Stations/Media.vue'; +import initApp from "~/layout"; +import useStationPanelLayout from "~/layouts/StationPanelLayout"; -export default initBase(Media); +initApp(useStationPanelLayout(Media)); diff --git a/frontend/vue/pages/Stations/Mounts.js b/frontend/vue/pages/Stations/Mounts.js index fcdb4fac9..139b8d407 100644 --- a/frontend/vue/pages/Stations/Mounts.js +++ b/frontend/vue/pages/Stations/Mounts.js @@ -1,5 +1,5 @@ -import initBase from '~/base.js'; - import Mounts from '~/components/Stations/Mounts.vue'; +import initApp from "~/layout"; +import useStationPanelLayout from "~/layouts/StationPanelLayout"; -export default initBase(Mounts); +initApp(useStationPanelLayout(Mounts)); diff --git a/frontend/vue/pages/Stations/Playlists.js b/frontend/vue/pages/Stations/Playlists.js index 03b14b524..31d7ee4dd 100644 --- a/frontend/vue/pages/Stations/Playlists.js +++ b/frontend/vue/pages/Stations/Playlists.js @@ -1,7 +1,7 @@ -import initBase from '~/base.js'; - import '~/store'; import Playlists from '~/components/Stations/Playlists.vue'; +import initApp from "~/layout"; +import useStationPanelLayout from "~/layouts/StationPanelLayout"; -export default initBase(Playlists); +initApp(useStationPanelLayout(Playlists)); diff --git a/frontend/vue/pages/Stations/Podcasts.js b/frontend/vue/pages/Stations/Podcasts.js index 93fbe749e..626203f4f 100644 --- a/frontend/vue/pages/Stations/Podcasts.js +++ b/frontend/vue/pages/Stations/Podcasts.js @@ -1,5 +1,5 @@ -import initBase from '~/base.js'; - import Podcasts from '~/components/Stations/Podcasts.vue'; +import initApp from "~/layout"; +import useStationPanelLayout from "~/layouts/StationPanelLayout"; -export default initBase(Podcasts); +initApp(useStationPanelLayout(Podcasts)); diff --git a/frontend/vue/pages/Stations/Profile.js b/frontend/vue/pages/Stations/Profile.js index b79ca021f..fe87c71ad 100644 --- a/frontend/vue/pages/Stations/Profile.js +++ b/frontend/vue/pages/Stations/Profile.js @@ -1,5 +1,5 @@ -import initBase from '~/base.js'; - import Profile from '~/components/Stations/Profile.vue'; +import initApp from "~/layout"; +import useStationPanelLayout from "~/layouts/StationPanelLayout"; -export default initBase(Profile); +initApp(useStationPanelLayout(Profile)); diff --git a/frontend/vue/pages/Stations/ProfileEdit.js b/frontend/vue/pages/Stations/ProfileEdit.js index a7bc93ccc..a6e5208fb 100644 --- a/frontend/vue/pages/Stations/ProfileEdit.js +++ b/frontend/vue/pages/Stations/ProfileEdit.js @@ -1,5 +1,5 @@ -import initBase from '~/base.js'; - import ProfileEdit from '~/components/Stations/ProfileEdit.vue'; +import initApp from "~/layout"; +import useStationPanelLayout from "~/layouts/StationPanelLayout"; -export default initBase(ProfileEdit); +initApp(useStationPanelLayout(ProfileEdit)); diff --git a/frontend/vue/pages/Stations/Queue.js b/frontend/vue/pages/Stations/Queue.js index 64a617b2d..6feff2351 100644 --- a/frontend/vue/pages/Stations/Queue.js +++ b/frontend/vue/pages/Stations/Queue.js @@ -1,5 +1,5 @@ -import initBase from '~/base.js'; - import Queue from '~/components/Stations/Queue.vue'; +import initApp from "~/layout"; +import useStationPanelLayout from "~/layouts/StationPanelLayout"; -export default initBase(Queue); +initApp(useStationPanelLayout(Queue)); diff --git a/frontend/vue/pages/Stations/Remotes.js b/frontend/vue/pages/Stations/Remotes.js index 8408e2e54..38fb82b19 100644 --- a/frontend/vue/pages/Stations/Remotes.js +++ b/frontend/vue/pages/Stations/Remotes.js @@ -1,5 +1,5 @@ -import initBase from '~/base.js'; - import Remotes from '~/components/Stations/Remotes.vue'; +import initApp from "~/layout"; +import useStationPanelLayout from "~/layouts/StationPanelLayout"; -export default initBase(Remotes); +initApp(useStationPanelLayout(Remotes)); diff --git a/frontend/vue/pages/Stations/Reports/Listeners.js b/frontend/vue/pages/Stations/Reports/Listeners.js index a820471a1..c642fc40a 100644 --- a/frontend/vue/pages/Stations/Reports/Listeners.js +++ b/frontend/vue/pages/Stations/Reports/Listeners.js @@ -1,5 +1,5 @@ -import initBase from '~/base.js'; - import Listeners from '~/components/Stations/Reports/Listeners.vue'; +import initApp from "~/layout"; +import useStationPanelLayout from "~/layouts/StationPanelLayout"; -export default initBase(Listeners); +initApp(useStationPanelLayout(Listeners)); diff --git a/frontend/vue/pages/Stations/Reports/Overview.js b/frontend/vue/pages/Stations/Reports/Overview.js index 1e62d396f..6a04e9b11 100644 --- a/frontend/vue/pages/Stations/Reports/Overview.js +++ b/frontend/vue/pages/Stations/Reports/Overview.js @@ -1,5 +1,5 @@ -import initBase from '~/base.js'; - import Overview from '~/components/Stations/Reports/Overview.vue'; +import initApp from "~/layout"; +import useStationPanelLayout from "~/layouts/StationPanelLayout"; -export default initBase(Overview); +initApp(useStationPanelLayout(Overview)); diff --git a/frontend/vue/pages/Stations/Reports/Requests.js b/frontend/vue/pages/Stations/Reports/Requests.js index 654bd129f..4d83e8d9c 100644 --- a/frontend/vue/pages/Stations/Reports/Requests.js +++ b/frontend/vue/pages/Stations/Reports/Requests.js @@ -1,5 +1,5 @@ -import initBase from '~/base.js'; - import Requests from '~/components/Stations/Reports/Requests.vue'; +import initApp from "~/layout"; +import useStationPanelLayout from "~/layouts/StationPanelLayout"; -export default initBase(Requests); +initApp(useStationPanelLayout(Requests)); diff --git a/frontend/vue/pages/Stations/Reports/SoundExchange.js b/frontend/vue/pages/Stations/Reports/SoundExchange.js index cd4349531..d21e8df6e 100644 --- a/frontend/vue/pages/Stations/Reports/SoundExchange.js +++ b/frontend/vue/pages/Stations/Reports/SoundExchange.js @@ -1,5 +1,5 @@ -import initBase from '~/base.js'; - import SoundExchange from '~/components/Stations/Reports/SoundExchange.vue'; +import initApp from "~/layout"; +import useStationPanelLayout from "~/layouts/StationPanelLayout"; -export default initBase(SoundExchange); +initApp(useStationPanelLayout(SoundExchange)); diff --git a/frontend/vue/pages/Stations/Reports/Timeline.js b/frontend/vue/pages/Stations/Reports/Timeline.js index de5d562b9..544b3a68a 100644 --- a/frontend/vue/pages/Stations/Reports/Timeline.js +++ b/frontend/vue/pages/Stations/Reports/Timeline.js @@ -1,5 +1,5 @@ -import initBase from '~/base.js'; - import Timeline from '~/components/Stations/Reports/Timeline.vue'; +import initApp from "~/layout"; +import useStationPanelLayout from "~/layouts/StationPanelLayout"; -export default initBase(Timeline); +initApp(useStationPanelLayout(Timeline)); diff --git a/frontend/vue/pages/Stations/Restart.js b/frontend/vue/pages/Stations/Restart.js index d52263e96..0519ab6ed 100644 --- a/frontend/vue/pages/Stations/Restart.js +++ b/frontend/vue/pages/Stations/Restart.js @@ -1,5 +1,5 @@ -import initBase from '~/base.js'; - import Restart from '~/components/Stations/Restart.vue'; +import initApp from "~/layout"; +import useStationPanelLayout from "~/layouts/StationPanelLayout"; -export default initBase(Restart); +initApp(useStationPanelLayout(Restart)); diff --git a/frontend/vue/pages/Stations/SftpUsers.js b/frontend/vue/pages/Stations/SftpUsers.js index 541300090..20feaae1a 100644 --- a/frontend/vue/pages/Stations/SftpUsers.js +++ b/frontend/vue/pages/Stations/SftpUsers.js @@ -1,5 +1,5 @@ -import initBase from '~/base.js'; - import SftpUsers from "~/components/Stations/SftpUsers"; +import initApp from "~/layout"; +import useStationPanelLayout from "~/layouts/StationPanelLayout"; -export default initBase(SftpUsers); +initApp(useStationPanelLayout(SftpUsers)); diff --git a/frontend/vue/pages/Stations/StereoToolConfig.js b/frontend/vue/pages/Stations/StereoToolConfig.js index 1214cbfea..d92f3db7d 100644 --- a/frontend/vue/pages/Stations/StereoToolConfig.js +++ b/frontend/vue/pages/Stations/StereoToolConfig.js @@ -1,5 +1,5 @@ -import initBase from '~/base.js'; - import StereoToolConfig from '~/components/Stations/StereoToolConfig.vue'; +import initApp from "~/layout"; +import useStationPanelLayout from "~/layouts/StationPanelLayout"; -export default initBase(StereoToolConfig); +initApp(useStationPanelLayout(StereoToolConfig)); diff --git a/frontend/vue/pages/Stations/Streamers.js b/frontend/vue/pages/Stations/Streamers.js index 33e125210..37a3aa2e9 100644 --- a/frontend/vue/pages/Stations/Streamers.js +++ b/frontend/vue/pages/Stations/Streamers.js @@ -1,5 +1,5 @@ -import initBase from '~/base.js'; - import Streamers from '~/components/Stations/Streamers.vue'; +import initApp from "~/layout"; +import useStationPanelLayout from "~/layouts/StationPanelLayout"; -export default initBase(Streamers); +initApp(useStationPanelLayout(Streamers)); diff --git a/frontend/vue/pages/Stations/Webhooks.js b/frontend/vue/pages/Stations/Webhooks.js index 37128e9b1..68715e9cf 100644 --- a/frontend/vue/pages/Stations/Webhooks.js +++ b/frontend/vue/pages/Stations/Webhooks.js @@ -1,5 +1,5 @@ -import initBase from '~/base.js'; - import Webhooks from '~/components/Stations/Webhooks'; +import initApp from "~/layout"; +import useStationPanelLayout from "~/layouts/StationPanelLayout"; -export default initBase(Webhooks); +initApp(useStationPanelLayout(Webhooks)); diff --git a/frontend/vue/vendor/axios.ts b/frontend/vue/vendor/axios.ts index c127d0cc3..761e687f1 100644 --- a/frontend/vue/vendor/axios.ts +++ b/frontend/vue/vendor/axios.ts @@ -1,10 +1,8 @@ -import axios, { AxiosStatic } from "axios"; +import axios, {AxiosStatic} from "axios"; import VueAxios from "vue-axios"; -import {App, inject} from "vue"; -import {useAzuraCast} from "~/vendor/azuracast"; +import {App, inject, InjectionKey} from "vue"; import {useTranslate} from "~/vendor/gettext"; import {useNotify} from "~/functions/useNotify"; -import {InjectionKey} from "vue"; const injectKey: InjectionKey = Symbol() as InjectionKey; @@ -17,11 +15,9 @@ export const useAxios = (): UseAxios => ({ axios: inject(injectKey) }); -export default function installAxios(vueApp: App) { +export default function installAxios(vueApp: App, apiCsrf: string | null) { // Configure auto-CSRF on requests - const {apiCsrf} = useAzuraCast(); - - if (typeof apiCsrf !== 'undefined') { + if (apiCsrf) { axios.defaults.headers.common['X-API-CSRF'] = apiCsrf; } diff --git a/frontend/vue/vendor/azuracast.ts b/frontend/vue/vendor/azuracast.ts index f6be4db06..a0844ef3e 100644 --- a/frontend/vue/vendor/azuracast.ts +++ b/frontend/vue/vendor/azuracast.ts @@ -1,23 +1,11 @@ /* eslint-disable no-undef */ -interface AzuraCastConstants { - locale: string, - localeShort: string, - localeWithDashes: string, - timeConfig: object, - apiCsrf: string | null, - enableAdvancedFeatures: boolean -} +import {App, inject, InjectionKey} from "vue"; -export function useAzuraCast(): AzuraCastConstants { - return { - locale: App.locale ?? 'en_US', - localeShort: App.locale_short ?? 'en', - localeWithDashes: App.locale_with_dashes ?? 'en-US', - timeConfig: App.time_config ?? {}, - apiCsrf: App.api_csrf ?? null, - enableAdvancedFeatures: App.enable_advanced_features ?? true - } +const globalPropsKey: InjectionKey = Symbol() as InjectionKey; + +export function installGlobalProps(vueApp: App, globalProps: AzuraCastConstants): void { + vueApp.provide(globalPropsKey, globalProps); } interface AzuraCastStationConstants { @@ -27,11 +15,24 @@ interface AzuraCastStationConstants { timezone: string } -export function useAzuraCastStation(): AzuraCastStationConstants { - return { - id: App.station?.id ?? null, - name: App.station?.name ?? null, - shortName: App.station?.shortName ?? null, - timezone: App.station?.timezone ?? 'UTC' - } +interface AzuraCastConstants { + locale: string, + localeShort: string, + localeWithDashes: string, + timeConfig: object, + apiCsrf: string | null, + enableAdvancedFeatures: boolean, + panelProps: object | null, + sidebarProps: object | null, + componentProps: object | null, + station: AzuraCastStationConstants | null, +} + +export function useAzuraCast(): AzuraCastConstants { + return inject(globalPropsKey); +} + +export function useAzuraCastStation(): AzuraCastStationConstants { + const {station} = useAzuraCast(); + return station; } diff --git a/frontend/vue/vendor/gettext.ts b/frontend/vue/vendor/gettext.ts index a19d8997a..6aee62ae1 100644 --- a/frontend/vue/vendor/gettext.ts +++ b/frontend/vue/vendor/gettext.ts @@ -1,28 +1,27 @@ import {createGettext, Language} from "vue3-gettext"; -import {useAzuraCast} from "~/vendor/azuracast"; import {App} from "vue"; -const {locale} = useAzuraCast(); - -const gettext = createGettext({ - defaultLanguage: locale, - translations: {}, - silent: true -}); - -const translations = import.meta.glob('../../../translations/**/translations.json', {as: 'json'}); -const localePath = '../../../translations/' + locale + '.UTF-8/translations.json'; - -if (localePath in translations) { - translations[localePath]().then((data) => { - gettext.translations = data; - }); -} +let gettext; export function useTranslate(): Language { return gettext; } -export function installTranslate(vueApp: App): void { +export function installTranslate(vueApp: App, locale: string): void { + gettext = createGettext({ + defaultLanguage: locale, + translations: {}, + silent: true + }); + + const translations = import.meta.glob('../../../translations/**/translations.json', {as: 'json'}); + const localePath = '../../../translations/' + locale + '.UTF-8/translations.json'; + + if (localePath in translations) { + translations[localePath]().then((data) => { + gettext.translations = data; + }); + } + vueApp.use(gettext); } diff --git a/frontend/vue/vendor/luxon.ts b/frontend/vue/vendor/luxon.ts index 1c3bf069b..3140e4243 100644 --- a/frontend/vue/vendor/luxon.ts +++ b/frontend/vue/vendor/luxon.ts @@ -1,8 +1,6 @@ import {DateTime, Duration, Settings} from 'luxon'; import {useAzuraCast} from "~/vendor/azuracast"; -const {localeWithDashes, timeConfig} = useAzuraCast(); - interface TimestampToRelative { (timestamp: number | null | undefined): string; } @@ -14,6 +12,7 @@ interface UseLuxon { } export function useLuxon(): UseLuxon { + const {localeWithDashes, timeConfig} = useAzuraCast(); Settings.defaultLocale = localeWithDashes; const timestampToRelative: TimestampToRelative = (timestamp: number | null | undefined): string => { diff --git a/frontend/vue/vendor/sweetalert.ts b/frontend/vue/vendor/sweetalert.ts index 56fb36ead..67d0f83f8 100644 --- a/frontend/vue/vendor/sweetalert.ts +++ b/frontend/vue/vendor/sweetalert.ts @@ -2,22 +2,22 @@ import Swal from 'sweetalert2/dist/sweetalert2'; import {useTranslate} from "~/vendor/gettext"; import {Directive} from "vue"; -const {$gettext} = useTranslate(); - -const swalCustom = Swal.mixin({ - confirmButtonText: $gettext('Confirm'), - cancelButtonText: $gettext('Cancel'), - showCancelButton: true, -}); - -const swalConfirmDelete = swalCustom.mixin({ - title: $gettext('Delete Record?'), - confirmButtonText: $gettext('Delete'), - confirmButtonColor: '#e64942', - focusCancel: true -}); - export function useSweetAlert() { + const {$gettext} = useTranslate(); + + const swalCustom = Swal.mixin({ + confirmButtonText: $gettext('Confirm'), + cancelButtonText: $gettext('Cancel'), + showCancelButton: true, + }); + + const swalConfirmDelete = swalCustom.mixin({ + title: $gettext('Delete Record?'), + confirmButtonText: $gettext('Delete'), + confirmButtonColor: '#e64942', + focusCancel: true + }); + const showAlert = (options = {}) => { return swalCustom.fire(options); } diff --git a/src/Controller/Admin/Debug/IndexAction.php b/src/Controller/Admin/DebugAction.php similarity index 86% rename from src/Controller/Admin/Debug/IndexAction.php rename to src/Controller/Admin/DebugAction.php index d21741e7f..2ccecbf34 100644 --- a/src/Controller/Admin/Debug/IndexAction.php +++ b/src/Controller/Admin/DebugAction.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace App\Controller\Admin\Debug; +namespace App\Controller\Admin; use App\Cache\DatabaseCache; use App\Console\Command\Sync\SingleTaskCommand; @@ -19,7 +19,7 @@ use DateTimeZone; use Psr\EventDispatcher\EventDispatcherInterface; use Psr\Http\Message\ResponseInterface; -final class IndexAction implements SingleActionInterface +final class DebugAction implements SingleActionInterface { public function __construct( private readonly StationRepository $stationRepo, @@ -42,7 +42,7 @@ final class IndexAction implements SingleActionInterface 'name' => $queue->value, 'count' => $this->queueManager->getQueueCount($queue), 'url' => $router->named( - 'admin:debug:clear-queue', + 'api:admin:debug:clear-queue', ['queue' => $queue->value] ), ]; @@ -65,7 +65,7 @@ final class IndexAction implements SingleActionInterface 'time' => $this->cache->getItem($cacheKey)->get() ?? 0, 'nextRun' => $cronExpression->getNextRunDate($now)->getTimestamp(), 'url' => $router->named( - 'admin:debug:sync', + 'api:admin:debug:sync', ['task' => rawurlencode($task)] ), ]; @@ -77,15 +77,15 @@ final class IndexAction implements SingleActionInterface 'id' => $station['id'], 'name' => $station['name'], 'clearQueueUrl' => $router->named( - 'admin:debug:clear-station-queue', + 'api:admin:debug:clear-station-queue', ['station_id' => $station['id']] ), 'getNextSongUrl' => $router->named( - 'admin:debug:nextsong', + 'api:admin:debug:nextsong', ['station_id' => $station['id']] ), 'getNowPlayingUrl' => $router->named( - 'admin:debug:nowplaying', + 'api:admin:debug:nowplaying', ['station_id' => $station['id']] ), ]; @@ -97,8 +97,8 @@ final class IndexAction implements SingleActionInterface id: 'admin-debug', title: __('System Debugger'), props: [ - 'clearCacheUrl' => $router->named('admin:debug:clear-cache'), - 'clearQueuesUrl' => $router->named('admin:debug:clear-queue'), + 'clearCacheUrl' => $router->named('api:admin:debug:clear-cache'), + 'clearQueuesUrl' => $router->named('api:admin:debug:clear-queue'), 'syncTasks' => $syncTasks, 'queueTotals' => $queueTotals, 'stations' => $stations, diff --git a/src/Controller/Admin/Debug/ClearCacheAction.php b/src/Controller/Api/Admin/Debug/ClearCacheAction.php similarity index 70% rename from src/Controller/Admin/Debug/ClearCacheAction.php rename to src/Controller/Api/Admin/Debug/ClearCacheAction.php index ab42404ff..cbdbe0bad 100644 --- a/src/Controller/Admin/Debug/ClearCacheAction.php +++ b/src/Controller/Api/Admin/Debug/ClearCacheAction.php @@ -2,10 +2,11 @@ declare(strict_types=1); -namespace App\Controller\Admin\Debug; +namespace App\Controller\Api\Admin\Debug; use App\Console\Application; use App\Controller\SingleActionInterface; +use App\Entity\Api\Status; use App\Http\Response; use App\Http\ServerRequest; use Psr\Http\Message\ResponseInterface; @@ -26,9 +27,9 @@ final class ClearCacheAction implements SingleActionInterface 'cache:clear' ); - // Flash an update to ensure the session is recreated. - $request->getFlash()->success($resultOutput); + // TODO Flash an update to ensure the session is recreated. + // $request->getFlash()->success($resultOutput); - return $response->withRedirect($request->getRouter()->fromHere('admin:debug:index')); + return $response->withJson(Status::updated()); } } diff --git a/src/Controller/Admin/Debug/ClearQueueAction.php b/src/Controller/Api/Admin/Debug/ClearQueueAction.php similarity index 76% rename from src/Controller/Admin/Debug/ClearQueueAction.php rename to src/Controller/Api/Admin/Debug/ClearQueueAction.php index 492810a00..1a112e8be 100644 --- a/src/Controller/Admin/Debug/ClearQueueAction.php +++ b/src/Controller/Api/Admin/Debug/ClearQueueAction.php @@ -2,9 +2,10 @@ declare(strict_types=1); -namespace App\Controller\Admin\Debug; +namespace App\Controller\Api\Admin\Debug; use App\Controller\SingleActionInterface; +use App\Entity\Api\Status; use App\Http\Response; use App\Http\ServerRequest; use App\MessageQueue\QueueManagerInterface; @@ -34,9 +35,6 @@ final class ClearQueueAction implements SingleActionInterface $this->queueManager->clearAllQueues(); } - // Flash an update to ensure the session is recreated. - $request->getFlash()->success(__('Message queue cleared.')); - - return $response->withRedirect($request->getRouter()->fromHere('admin:debug:index')); + return $response->withJson(Status::updated()); } } diff --git a/src/Controller/Admin/Debug/ClearStationQueueAction.php b/src/Controller/Api/Admin/Debug/ClearStationQueueAction.php similarity index 77% rename from src/Controller/Admin/Debug/ClearStationQueueAction.php rename to src/Controller/Api/Admin/Debug/ClearStationQueueAction.php index 24fbb6233..735c0a249 100644 --- a/src/Controller/Admin/Debug/ClearStationQueueAction.php +++ b/src/Controller/Api/Admin/Debug/ClearStationQueueAction.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace App\Controller\Admin\Debug; +namespace App\Controller\Api\Admin\Debug; use App\Container\LoggerAwareTrait; use App\Controller\SingleActionInterface; @@ -42,14 +42,8 @@ final class ClearStationQueueAction implements SingleActionInterface $this->logger->popHandler(); } - return $request->getView()->renderToResponse( - $response, - 'system/log_view', - [ - 'sidebar' => null, - 'title' => __('Debug Output'), - 'log_records' => $testHandler->getRecords(), - ] - ); + return $response->withJson([ + 'logs' => $testHandler->getRecords(), + ]); } } diff --git a/src/Controller/Admin/Debug/NextSongAction.php b/src/Controller/Api/Admin/Debug/NextSongAction.php similarity index 75% rename from src/Controller/Admin/Debug/NextSongAction.php rename to src/Controller/Api/Admin/Debug/NextSongAction.php index 886decf80..c4aa7ec70 100644 --- a/src/Controller/Admin/Debug/NextSongAction.php +++ b/src/Controller/Api/Admin/Debug/NextSongAction.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace App\Controller\Admin\Debug; +namespace App\Controller\Api\Admin\Debug; use App\Container\LoggerAwareTrait; use App\Controller\SingleActionInterface; @@ -39,14 +39,8 @@ final class NextSongAction implements SingleActionInterface ]); $this->logger->popHandler(); - return $request->getView()->renderToResponse( - $response, - 'system/log_view', - [ - 'sidebar' => null, - 'title' => __('Debug Output'), - 'log_records' => $testHandler->getRecords(), - ] - ); + return $response->withJson([ + 'logs' => $testHandler->getRecords(), + ]); } } diff --git a/src/Controller/Admin/Debug/NowPlayingAction.php b/src/Controller/Api/Admin/Debug/NowPlayingAction.php similarity index 74% rename from src/Controller/Admin/Debug/NowPlayingAction.php rename to src/Controller/Api/Admin/Debug/NowPlayingAction.php index e3d73f575..d94180e18 100644 --- a/src/Controller/Admin/Debug/NowPlayingAction.php +++ b/src/Controller/Api/Admin/Debug/NowPlayingAction.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace App\Controller\Admin\Debug; +namespace App\Controller\Api\Admin\Debug; use App\Container\LoggerAwareTrait; use App\Controller\SingleActionInterface; @@ -37,14 +37,8 @@ final class NowPlayingAction implements SingleActionInterface $this->logger->popHandler(); } - return $request->getView()->renderToResponse( - $response, - 'system/log_view', - [ - 'sidebar' => null, - 'title' => __('Debug Output'), - 'log_records' => $testHandler->getRecords(), - ] - ); + return $response->withJson([ + 'logs' => $testHandler->getRecords(), + ]); } } diff --git a/src/Controller/Admin/Debug/SyncAction.php b/src/Controller/Api/Admin/Debug/SyncAction.php similarity index 82% rename from src/Controller/Admin/Debug/SyncAction.php rename to src/Controller/Api/Admin/Debug/SyncAction.php index f603570bf..5578dff20 100644 --- a/src/Controller/Admin/Debug/SyncAction.php +++ b/src/Controller/Api/Admin/Debug/SyncAction.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace App\Controller\Admin\Debug; +namespace App\Controller\Api\Admin\Debug; use App\Console\Command\Sync\SingleTaskCommand; use App\Container\LoggerAwareTrait; @@ -51,14 +51,8 @@ final class SyncAction implements SingleActionInterface $this->logger->popHandler(); } - return $request->getView()->renderToResponse( - $response, - 'system/log_view', - [ - 'sidebar' => null, - 'title' => __('Debug Output'), - 'log_records' => $testHandler->getRecords(), - ] - ); + return $response->withJson([ + 'logs' => $testHandler->getRecords(), + ]); } } diff --git a/src/Controller/Admin/Debug/TelnetAction.php b/src/Controller/Api/Admin/Debug/TelnetAction.php similarity index 79% rename from src/Controller/Admin/Debug/TelnetAction.php rename to src/Controller/Api/Admin/Debug/TelnetAction.php index 607656365..a7c52ab65 100644 --- a/src/Controller/Admin/Debug/TelnetAction.php +++ b/src/Controller/Api/Admin/Debug/TelnetAction.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace App\Controller\Admin\Debug; +namespace App\Controller\Api\Admin\Debug; use App\Container\LoggerAwareTrait; use App\Controller\SingleActionInterface; @@ -50,14 +50,8 @@ final class TelnetAction implements SingleActionInterface $this->logger->popHandler(); - return $request->getView()->renderToResponse( - $response, - 'system/log_view', - [ - 'sidebar' => null, - 'title' => __('Debug Output'), - 'log_records' => $testHandler->getRecords(), - ] - ); + return $response->withJson([ + 'logs' => $testHandler->getRecords(), + ]); } } diff --git a/src/Middleware/Module/Admin.php b/src/Middleware/Module/Admin.php index cc74c67b2..ee02ab4ee 100644 --- a/src/Middleware/Module/Admin.php +++ b/src/Middleware/Module/Admin.php @@ -42,22 +42,15 @@ final class Admin $activeTab = $routeParts[1]; } - $view->addData( - [ - 'admin_panels' => $event->getFilteredMenu(), - ] - ); + $globalProps = $view->getGlobalProps(); - // These two intentionally separated (the sidebar needs admin_panels). - $view->getSections()->set( - 'sidebar', - $view->render( - 'admin/sidebar', - [ - 'active_tab' => $activeTab, - ] - ) - ); + $router = $request->getRouter(); + + $globalProps->set('sidebarProps', [ + 'homeUrl' => $router->named('admin:index:index'), + 'menu' => $event->getFilteredMenu(), + 'active' => $activeTab, + ]); return $handler->handle($request); } diff --git a/src/Middleware/Module/PanelLayout.php b/src/Middleware/Module/PanelLayout.php new file mode 100644 index 000000000..16b1bba69 --- /dev/null +++ b/src/Middleware/Module/PanelLayout.php @@ -0,0 +1,58 @@ +getView(); + $customization = $request->getCustomization(); + $user = $request->getUser(); + $auth = $request->getAuth(); + $acl = $request->getAcl(); + $router = $request->getRouter(); + + $globalProps = $view->getGlobalProps(); + + $csrf = $request->getCsrf(); + $globalProps->set('apiCsrf', $csrf->generate(ApiAuth::API_CSRF_NAMESPACE)); + + $globalProps->set('panelProps', [ + 'instanceName' => $customization->getInstanceName(), + 'userDisplayName' => $user->getDisplayName(), + 'homeUrl' => $router->named('dashboard'), + 'adminUrl' => $router->named('admin:index:index'), + 'profileUrl' => $router->named('profile:index'), + 'logoutUrl' => ($auth->isMasqueraded()) + ? $router->named('account:endmasquerade') + : $router->named('account:logout'), + 'showAdmin' => $acl->isAllowed(GlobalPermissions::View), + 'version' => $this->version->getVersionText(), + 'platform' => ($this->environment->isDocker() ? 'Docker' : 'Ansible') + . ' • PHP ' . PHP_MAJOR_VERSION . '.' . PHP_MINOR_VERSION, + ]); + + return $handler->handle($request); + } +} diff --git a/src/Middleware/Module/Stations.php b/src/Middleware/Module/Stations.php index 8d6128583..9219332d4 100644 --- a/src/Middleware/Module/Stations.php +++ b/src/Middleware/Module/Stations.php @@ -5,6 +5,7 @@ declare(strict_types=1); namespace App\Middleware\Module; use App\Container\SettingsAwareTrait; +use App\Enums\StationPermissions; use App\Event; use App\Http\ServerRequest; use Psr\EventDispatcher\EventDispatcherInterface; @@ -48,16 +49,25 @@ final class Stations $activeTab = $routeParts[1]; } - $view->getSections()->set( - 'sidebar', - $view->render( - 'stations/sidebar', - [ - 'menu' => $event->getFilteredMenu(), - 'active' => $activeTab, - ] - ), - ); + $globalProps = $view->getGlobalProps(); + + $globalProps->set('station', [ + 'id' => $station->getIdRequired(), + 'name' => $station->getName(), + 'shortName' => $station->getShortName(), + 'timezone' => $station->getTimezone(), + ]); + + $router = $request->getRouter(); + $acl = $request->getAcl(); + + $globalProps->set('sidebarProps', [ + 'profileUrl' => $router->fromHere('stations:profile:index'), + 'editProfileUrl' => $router->fromHere('stations:profile:edit'), + 'showEditProfile' => $acl->isAllowed(StationPermissions::Profile, $station), + 'menu' => $event->getFilteredMenu(), + 'active' => $activeTab, + ]); return $handler->handle($request); } diff --git a/src/View.php b/src/View.php index 0d85f2ac5..324cb9832 100644 --- a/src/View.php +++ b/src/View.php @@ -4,16 +4,20 @@ declare(strict_types=1); namespace App; +use App\Entity\User; +use App\Enums\SupportedLocales; use App\Http\RouterInterface; use App\Http\ServerRequest; use App\Traits\RequestAwareTrait; use App\Utilities\Json; use App\View\GlobalSections; +use Doctrine\Common\Collections\ArrayCollection; use League\Plates\Engine; use League\Plates\Template\Data; use Psr\EventDispatcher\EventDispatcherInterface; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; +use stdClass; use Symfony\Component\VarDumper\Cloner\VarCloner; use Symfony\Component\VarDumper\Dumper\CliDumper; @@ -21,7 +25,10 @@ final class View extends Engine { use RequestAwareTrait; - private readonly GlobalSections $sections; + private GlobalSections $sections; + + /** @var ArrayCollection */ + private ArrayCollection $globalProps; public function __construct( Customization $customization, @@ -33,11 +40,13 @@ final class View extends Engine parent::__construct($environment->getViewsDirectory(), 'phtml'); $this->sections = new GlobalSections(); + $this->globalProps = new ArrayCollection(); // Add non-request-dependent content. $this->addData( [ 'sections' => $this->sections, + 'globalProps' => $this->globalProps, 'customization' => $customization, 'environment' => $environment, 'version' => $version, @@ -94,13 +103,6 @@ final class View extends Engine 'prefetch' => [], ]; - $assetRoot = '/static/vite_dist'; - $includes = [ - 'js' => $assetRoot . '/' . $vueComponents[$componentPath]['file'], - 'css' => [], - 'prefetch' => [], - ]; - $visitedNodes = []; $fetchCss = function ($component) use ( $vueComponents, @@ -166,9 +168,40 @@ final class View extends Engine $customization = $request->getAttribute(ServerRequest::ATTR_CUSTOMIZATION); if (null !== $customization) { $requestData['customization'] = $customization; + + $this->globalProps->set( + 'enableAdvancedFeatures', + $customization->enableAdvancedFeatures() + ); } $this->addData($requestData); + + $localeObj = $request->getAttribute(ServerRequest::ATTR_LOCALE); + if (!($localeObj instanceof SupportedLocales)) { + $localeObj = SupportedLocales::default(); + } + + $locale = $localeObj->getLocaleWithoutEncoding(); + $localeShort = substr($locale, 0, 2); + $localeWithDashes = str_replace('_', '-', $locale); + + $this->globalProps->set('locale', $locale); + $this->globalProps->set('localeShort', $localeShort); + $this->globalProps->set('localeWithDashes', $localeWithDashes); + + // User profile-specific 24-hour display setting. + $userObj = $request->getAttribute(ServerRequest::ATTR_USER); + $show24Hours = ($userObj instanceof User) + ? $userObj->getShow24HourTime() + : null; + + $timeConfig = new stdClass(); + if (null !== $show24Hours) { + $timeConfig->hour12 = !$show24Hours; + } + + $this->globalProps->set('timeConfig', $timeConfig); } } @@ -177,8 +210,16 @@ final class View extends Engine return $this->sections; } + /** @return ArrayCollection */ + public function getGlobalProps(): ArrayCollection + { + return $this->globalProps; + } + public function reset(): void { + $this->sections = new GlobalSections(); + $this->globalProps = new ArrayCollection(); $this->data = new Data(); } @@ -213,7 +254,7 @@ final class View extends Engine ResponseInterface $response, string $component, ?string $id = null, - string $layout = 'main', + string $layout = 'panel', ?string $title = null, array $layoutParams = [], array $props = [], diff --git a/templates/admin/sidebar.phtml b/templates/admin/sidebar.phtml deleted file mode 100644 index 63b304879..000000000 --- a/templates/admin/sidebar.phtml +++ /dev/null @@ -1,15 +0,0 @@ - - - - -fetch('partials/sidebar_menu', ['menu' => $admin_panels]) ?> diff --git a/templates/main.phtml b/templates/main.phtml deleted file mode 100644 index 6c2fd0359..000000000 --- a/templates/main.phtml +++ /dev/null @@ -1,196 +0,0 @@ - - - - - - - - - <?= $this->e($customization->getPageTitle($title)) ?> - - fetch('partials/head') ?> - - get('head') ?> - - - - - -fetch('partials/bodyjs', [ - 'include_csrf' => true, -]) ?> - -get('bodyjs') ?> - - - - - -has('sidebar')): ?> - - - -
-
has('sidebar')): ?>class="content-alt"> -
- - section('content') ?> - - -
-

-
- -
-
-

e($title) ?>

-
-
- section('content')?> -
-
- -
-
-
- -
has('sidebar')): ?>class="footer-alt" role="contentinfo" aria-label=""> - ' . $environment->getAppName( - ) . ' • ' . $version->getVersionText() . ' • ' . ($environment->isDocker( - ) ? 'Docker' : 'Ansible') . ' • PHP ' . \PHP_MAJOR_VERSION . '.' . \PHP_MINOR_VERSION - ) ?> -
- -
- -
- -fetch('partials/toasts') ?> - - diff --git a/templates/minimal.phtml b/templates/minimal.phtml index 10cf26bff..f4588e2de 100644 --- a/templates/minimal.phtml +++ b/templates/minimal.phtml @@ -38,8 +38,6 @@ $hide_footer ??= false; -fetch('partials/bodyjs') ?> - get('bodyjs') ?> diff --git a/templates/partials/sidebar_menu.phtml b/templates/partials/sidebar_menu.phtml deleted file mode 100644 index ee4c123f1..000000000 --- a/templates/partials/sidebar_menu.phtml +++ /dev/null @@ -1,67 +0,0 @@ - - diff --git a/templates/partials/vue_body.phtml b/templates/partials/vue_body.phtml index f6dae17b3..ae18ddbc6 100644 --- a/templates/partials/vue_body.phtml +++ b/templates/partials/vue_body.phtml @@ -4,11 +4,13 @@ * @var ?string $id * @var array $props * @var App\View\GlobalSections $sections + * @var Doctrine\Common\Collections\ArrayCollection $globalProps */ $componentDeps = $this->getVueComponentInfo('vue/pages/' . $component . '.js'); -$propsJson = json_encode($props, JSON_THROW_ON_ERROR); +$globalProps->set('componentProps', $props); +$propsJson = json_encode($globalProps->toArray(), JSON_THROW_ON_ERROR); $headScripts = []; diff --git a/templates/stations/sidebar.phtml b/templates/stations/sidebar.phtml deleted file mode 100644 index dab2b1f06..000000000 --- a/templates/stations/sidebar.phtml +++ /dev/null @@ -1,68 +0,0 @@ -appendStart('bodyjs'); -?> - -end(); -?> - - - -fetch('partials/sidebar_menu', ['menu' => $menu, 'active' => $active]); -?> diff --git a/templates/system/log_view.phtml b/templates/system/log_view.phtml deleted file mode 100644 index cd1a19875..000000000 --- a/templates/system/log_view.phtml +++ /dev/null @@ -1,57 +0,0 @@ -layout('main', ['title' => $title, 'manual' => true]); -?> - - $row): ?> -
-
-

- - Debug - - Info - - Notice - - Warning - - Error - - Critical - - Alert - - Emergency - - - e($row['message'])?> -

- - -
- -
- -
- - -
-
-
- $context_value): ?> -
-
dump($context_value)?>
- - $context_value): ?> -
-
dump($context_value)?>
- -
-
-
- -
- diff --git a/templates/system/vue_page.phtml b/templates/system/vue_page.phtml index 7572a4b80..1b53c35b8 100644 --- a/templates/system/vue_page.phtml +++ b/templates/system/vue_page.phtml @@ -9,7 +9,7 @@ */ $this->layout( - $layout ?? 'main', + $layout ?? 'panel', array_merge( [ 'title' => $title ?? $id, diff --git a/tests/Functional/Admin_DebugCest.php b/tests/Functional/Api_Admin_DebugCest.php similarity index 66% rename from tests/Functional/Admin_DebugCest.php rename to tests/Functional/Api_Admin_DebugCest.php index f80edba35..fed92d21c 100644 --- a/tests/Functional/Admin_DebugCest.php +++ b/tests/Functional/Api_Admin_DebugCest.php @@ -6,7 +6,7 @@ namespace Functional; use FunctionalTester; -class Admin_DebugCest extends CestAbstract +class Api_Admin_DebugCest extends CestAbstract { /** * @before setupComplete @@ -15,7 +15,8 @@ class Admin_DebugCest extends CestAbstract public function syncTasks(FunctionalTester $I) { $I->wantTo('Test All Synchronized Tasks'); - $I->amOnPage('/admin/debug/sync/all'); - $I->seeResponseCodeIsSuccessful(); + + $I->sendPUT('/api/admin/debug/sync/all'); + $I->seeResponseCodeIs(200); } } diff --git a/tests/Functional/Station_ProfileCest.php b/tests/Functional/Station_ProfileCest.php deleted file mode 100644 index 1bfdcfb98..000000000 --- a/tests/Functional/Station_ProfileCest.php +++ /dev/null @@ -1,42 +0,0 @@ -wantTo('View and edit a station profile.'); - - $testStation = $this->getTestStation(); - $stationId = $testStation->getId(); - - $I->amOnPage('/station/' . $stationId . '/profile'); - - $I->see('Functional Test Radio'); - /* - * TODO: Implement acceptance testing with Vue rendering - $I->wantTo('Edit a station profile.'); - - - $I->amOnPage('/station/' . $station_id . '/profile/edit'); - - $I->submitForm('.form', [ - 'name' => 'Profile Update Test Radio', - 'description' => 'Testing a profile update.', - ]); - - $I->seeCurrentUrlEquals('/station/' . $station_id . '/profile'); - - $I->see('Profile Update Test Radio'); - */ - } -}