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 @@
-
{{ $gettext('Clear Cache') }}
-
+
@@ -38,13 +38,13 @@
-
{{ $gettext('Clear All Message Queues') }}
-
+
@@ -66,13 +66,13 @@
{{ row.item.pattern }}
-
{{ $gettext('Run Task') }}
-
+
@@ -103,13 +103,13 @@
@@ -137,33 +137,33 @@
{{ $gettext('AutoDJ 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 @@
+
+
+
+
+
+
+
+
+ - {{ context_header }}
+ - {{ dump(context_value) }}
+
+
+ - {{ context_header }}
+ - {{ dump(context_value) }}
+
+
+
+
+
+
+
+
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 @@
+
+
+ {{ $gettext('Skip to main content') }}
+
+
+
+
+
+
+
+
+
+
+
+
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 @@
-
-
-
-
-= $this->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)) ?>
-
- = $this->fetch('partials/head') ?>
-
- = $sections->get('head') ?>
-
-
-
-
-
-= $this->fetch('partials/bodyjs', [
- 'include_csrf' => true,
-]) ?>
-
-= $sections->get('bodyjs') ?>
-
-= __('Skip to main content') ?>
-
-
-
-has('sidebar')): ?>
-
-
-
-
- has('sidebar')): ?>class="content-alt">
-
-
- = $this->section('content') ?>
-
-
-
-
-
-
-
- =$this->section('content')?>
-
-
-
-
-
-
-
-
-
-
-
-= $this->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;
-= $this->fetch('partials/bodyjs') ?>
-
= $sections->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): ?>
-
-
-
-
-
-
-
- $context_value): ?>
- - =$context_header?>
- - =$this->dump($context_value)?>
-
- $context_value): ?>
- - =$context_header?>
- - =$this->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');
- */
- }
-}