mirror of
https://github.com/Lissy93/dashy.git
synced 2026-06-06 08:44:23 -04:00
181 lines
6.5 KiB
JavaScript
181 lines
6.5 KiB
JavaScript
/**
|
|
* This is the router config, which defined the location for
|
|
* each page within the app, and how they should be loaded
|
|
* Note that the page paths are defined in @/utils/config/defaults.js
|
|
*/
|
|
|
|
// Import vue router
|
|
import { createRouter, createWebHistory, createWebHashHistory } from 'vue-router';
|
|
import { Progress } from 'rsup-progress';
|
|
|
|
// Import views, that are not lazy-loaded
|
|
import Home from '@/views/Home.vue';
|
|
|
|
// Import helper functions, config data and defaults
|
|
import store from '@/store';
|
|
import i18n from '@/utils/i18n';
|
|
import Keys from '@/utils/StoreMutations';
|
|
import { isAuthEnabled, isLoggedIn, isGuestAccessEnabled } from '@/utils/auth/Auth';
|
|
import { isOidcEnabled } from '@/utils/auth/OidcAuth';
|
|
import { isKeycloakEnabled } from '@/utils/auth/KeycloakAuth';
|
|
import { isHeaderAuthEnabled } from '@/utils/auth/HeaderAuth';
|
|
import { startingView as defaultStartingView, routePaths } from '@/utils/config/defaults';
|
|
import { VIEW_META } from '@/utils/config/ConfigHelpers';
|
|
import ErrorHandler from '@/utils/logging/ErrorHandler';
|
|
|
|
const progress = new Progress({ color: 'var(--progress-bar)' });
|
|
|
|
/* True for ANY auth (OIDC, KC, HeaderAuth, etc) */
|
|
const isAnyAuthConfigured = () =>
|
|
isAuthEnabled() || isOidcEnabled() || isKeycloakEnabled() || isHeaderAuthEnabled();
|
|
|
|
/* Returns true if user is already authenticated, or if auth is not enabled */
|
|
const isAuthenticated = () => {
|
|
if (store.state.criticalError) return false;
|
|
const authEnabled = isAnyAuthConfigured();
|
|
const userLoggedIn = isLoggedIn();
|
|
const guestEnabled = isGuestAccessEnabled();
|
|
return (!authEnabled || userLoggedIn || guestEnabled);
|
|
};
|
|
|
|
/* Determines if the page URL is an OAuth2 redirect-back from OIDC or Keycloak
|
|
* Passes through the auth guard, so the callback ?code reaches the handler */
|
|
const isOauthCallback = () =>
|
|
new URLSearchParams(window.location.search).has('code')
|
|
&& (isOidcEnabled() || isKeycloakEnabled());
|
|
|
|
/* Resolve landing view from appConfig.startingView at runtime if set */
|
|
const resolveStartingView = () => {
|
|
const raw = store.state.config?.appConfig?.startingView || defaultStartingView;
|
|
const view = raw === 'default' ? 'home' : raw;
|
|
return VIEW_META[view] ? view : 'home';
|
|
};
|
|
|
|
/* Build the canonical /<view>/:page?/:section? routes for a given view + component.
|
|
* withSection=false for workspace (no single-section view yet). Page meta is
|
|
* owned by App.vue's watcher via PageMeta.js — routes don't carry titles. */
|
|
const makeViewRoutes = (basePath, viewName, component, withSection = true) => {
|
|
const routes = [
|
|
{ path: basePath, name: viewName, component },
|
|
{ path: `${basePath}/:page`, name: `${viewName}-page`, component },
|
|
];
|
|
if (withSection) {
|
|
routes.push({
|
|
path: `${basePath}/:page/:section`,
|
|
name: `${viewName}-section`,
|
|
component,
|
|
});
|
|
}
|
|
return routes;
|
|
};
|
|
|
|
/* Routing mode, can be either 'hash', 'history' or 'abstract' */
|
|
const mode = import.meta.env.VITE_APP_ROUTING_MODE || 'history';
|
|
|
|
/* Map mode string to Vue Router 4 history function */
|
|
const history = mode === 'hash'
|
|
? createWebHashHistory(import.meta.env.BASE_URL)
|
|
: createWebHistory(import.meta.env.BASE_URL);
|
|
|
|
/* List of all routes, props, components and metadata */
|
|
const router = createRouter({
|
|
history,
|
|
routes: [
|
|
{
|
|
path: '/',
|
|
name: 'landing',
|
|
component: Home,
|
|
beforeEnter: (to, from, next) => {
|
|
const view = resolveStartingView();
|
|
if (!view || view === 'home') next();
|
|
// If user set `startingView`, we redirect to canonical /<view> URL
|
|
else next(`/${view}`);
|
|
},
|
|
},
|
|
// Canonical /<view>/:page?/:section? routes for each view
|
|
...makeViewRoutes(routePaths.home, 'home', Home),
|
|
...makeViewRoutes(routePaths.minimal, 'minimal', () => import('./views/Minimal.vue')),
|
|
...makeViewRoutes(routePaths.workspace, 'workspace', () => import('./views/Workspace.vue'), false),
|
|
{ // The login page
|
|
path: routePaths.login,
|
|
name: 'login',
|
|
component: () => import('./views/Login.vue'),
|
|
beforeEnter: (to, from, next) => {
|
|
// If the user already logged in + guest mode not enabled, then redirect home
|
|
if (isAuthenticated() && !isGuestAccessEnabled()) router.push({ path: '/' });
|
|
next();
|
|
},
|
|
},
|
|
{ // The export config page
|
|
path: routePaths.download,
|
|
name: 'download',
|
|
component: () => import('./views/DownloadConfig.vue'),
|
|
},
|
|
{ // Page not found, any non-defined routes will land here
|
|
path: routePaths.notFound,
|
|
name: '404',
|
|
component: () => import('./views/404.vue'),
|
|
meta: { theme: 'dashy-docs' },
|
|
beforeEnter: (to, from, next) => {
|
|
if (to.redirectedFrom) { // Log error, if redirected here from another route
|
|
ErrorHandler(`Route not found: '${to.redirectedFrom.fullPath}'`);
|
|
}
|
|
next();
|
|
},
|
|
},
|
|
{ // Redirect any not-found routed to the 404 view
|
|
path: '/:pathMatch(.*)*',
|
|
redirect: '/404',
|
|
},
|
|
],
|
|
});
|
|
|
|
/**
|
|
* On first page load, initialize and wait for the the config loading.
|
|
* Also guards against silently leaving an active edit session for a
|
|
* different config (unsaved edits would otherwise become untethered
|
|
* from whatever page the user switched to).
|
|
*/
|
|
router.beforeEach(async (to, from, next) => {
|
|
progress.start();
|
|
try {
|
|
if (!store.state.rootConfig && !store.state.criticalError) {
|
|
await store.dispatch(Keys.INITIALIZE_CONFIG);
|
|
}
|
|
// If in edit mode and navigating to a DIFFERENT page, confirm + cancel edit.
|
|
const pageChanged = from.params?.page !== to.params?.page;
|
|
if (store.state.editMode && pageChanged) {
|
|
|
|
const ok = confirm(i18n.global.t('interactive-editor.menu.leave-while-editing-confirm'));
|
|
if (!ok) {
|
|
progress.end();
|
|
next(false);
|
|
return;
|
|
}
|
|
// Discard edits for the page we're leaving and exit edit mode.
|
|
await store.dispatch(Keys.INITIALIZE_CONFIG, store.state.currentConfigInfo.confId);
|
|
store.commit(Keys.SET_EDIT_MODE, false);
|
|
}
|
|
if (to.name !== 'login' && !isAuthenticated() && !isOauthCallback()) {
|
|
next({ name: 'login' });
|
|
} else next();
|
|
} catch (e) {
|
|
ErrorHandler('Navigation guard failed', e);
|
|
next();
|
|
}
|
|
});
|
|
|
|
/* Stop the loading progress bar once navigation settles */
|
|
router.afterEach(() => {
|
|
progress.end();
|
|
});
|
|
|
|
/* Catch navigation + lazy-import failures */
|
|
router.onError((err) => {
|
|
progress.end();
|
|
ErrorHandler('Navigation failed. Try hard-reload (Shift + F5)', err);
|
|
});
|
|
|
|
// All done - export the now configured router
|
|
export default router;
|