refactor: dynamic routes
This commit is contained in:
@@ -0,0 +1,3 @@
|
|||||||
|
// Identity hook for build-time route extensions. Tooling may overwrite
|
||||||
|
// this file to inject additional routes; the original is restored on cleanup.
|
||||||
|
export const applyRouteExtensions = <T>(base: T): T => base;
|
||||||
+4
-11
@@ -1,6 +1,7 @@
|
|||||||
import { keepAliveRoutes } from './keep-alive';
|
import { keepAliveRoutes } from './keep-alive';
|
||||||
|
import { applyRouteExtensions } from './routes.extensions';
|
||||||
|
|
||||||
export default [
|
const baseRoutes = [
|
||||||
{
|
{
|
||||||
name: 'dashboard',
|
name: 'dashboard',
|
||||||
path: '/dashboard',
|
path: '/dashboard',
|
||||||
@@ -312,16 +313,6 @@ export default [
|
|||||||
defaultIcon: 'icon-key',
|
defaultIcon: 'icon-key',
|
||||||
component: './api-keys'
|
component: './api-keys'
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: 'settings',
|
|
||||||
path: '/settings',
|
|
||||||
key: 'settings',
|
|
||||||
hideInMenu: true,
|
|
||||||
selectedIcon: 'icon-settings-filled',
|
|
||||||
icon: 'icon-settings',
|
|
||||||
defaultIcon: 'icon-settings',
|
|
||||||
component: './settings'
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
name: 'profile',
|
name: 'profile',
|
||||||
path: '/profile',
|
path: '/profile',
|
||||||
@@ -346,3 +337,5 @@ export default [
|
|||||||
component: './404'
|
component: './404'
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
export default applyRouteExtensions(baseRoutes);
|
||||||
|
|||||||
+1
-7
@@ -3,20 +3,14 @@
|
|||||||
"author": "gpustack",
|
"author": "gpustack",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "max build",
|
"build": "max build",
|
||||||
"build:enterprise": "node ./scripts/run-enterprise-build.cjs",
|
|
||||||
"check:locales": "node --import tsx ./src/locales/check.ts",
|
"check:locales": "node --import tsx ./src/locales/check.ts",
|
||||||
"dev": "max dev",
|
"dev": "max dev",
|
||||||
"dev:enterprise": "node ./scripts/run-enterprise-dev.cjs",
|
|
||||||
"format": "prettier --cache --write .",
|
"format": "prettier --cache --write .",
|
||||||
"postinstall": "max setup",
|
"postinstall": "max setup",
|
||||||
"prepare": "husky",
|
"prepare": "husky",
|
||||||
"preview": "max preview",
|
"preview": "max preview",
|
||||||
"setup": "max setup",
|
"setup": "max setup",
|
||||||
"start": "npm run dev",
|
"start": "npm run dev"
|
||||||
"sync:enterprise-config": "node ./scripts/sync-enterprise-config.cjs",
|
|
||||||
"sync:enterprise-config:clean": "node ./scripts/sync-enterprise-config.cjs clean",
|
|
||||||
"sync:enterprise-global": "npm run sync:enterprise-config",
|
|
||||||
"sync:enterprise-global:clean": "npm run sync:enterprise-config:clean"
|
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ant-design/icons": "^6.1.0",
|
"@ant-design/icons": "^6.1.0",
|
||||||
|
|||||||
@@ -1,47 +0,0 @@
|
|||||||
const { spawnSync } = require('node:child_process');
|
|
||||||
const path = require('node:path');
|
|
||||||
|
|
||||||
const syncScript = path.resolve(__dirname, './sync-enterprise-config.cjs');
|
|
||||||
|
|
||||||
const runNodeScript = (args) => {
|
|
||||||
const result = spawnSync(process.execPath, [syncScript, ...args], {
|
|
||||||
stdio: 'inherit'
|
|
||||||
});
|
|
||||||
|
|
||||||
if (result.error) {
|
|
||||||
throw result.error;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof result.status === 'number' && result.status !== 0) {
|
|
||||||
process.exit(result.status);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const runBuild = () =>
|
|
||||||
spawnSync('max', ['build'], {
|
|
||||||
env: {
|
|
||||||
...process.env,
|
|
||||||
ENABLE_ENTERPRISE: 'true'
|
|
||||||
},
|
|
||||||
shell: true,
|
|
||||||
stdio: 'inherit'
|
|
||||||
});
|
|
||||||
|
|
||||||
let buildResult;
|
|
||||||
|
|
||||||
try {
|
|
||||||
runNodeScript([]);
|
|
||||||
buildResult = runBuild();
|
|
||||||
} finally {
|
|
||||||
try {
|
|
||||||
runNodeScript(['clean']);
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Failed to clean enterprise config:', error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (buildResult?.error) {
|
|
||||||
throw buildResult.error;
|
|
||||||
}
|
|
||||||
|
|
||||||
process.exit(buildResult?.status ?? 0);
|
|
||||||
@@ -1,69 +0,0 @@
|
|||||||
const { spawn, spawnSync } = require('node:child_process');
|
|
||||||
const path = require('node:path');
|
|
||||||
|
|
||||||
const syncScript = path.resolve(__dirname, './sync-enterprise-config.cjs');
|
|
||||||
|
|
||||||
const runNodeScript = (args) => {
|
|
||||||
const result = spawnSync(process.execPath, [syncScript, ...args], {
|
|
||||||
stdio: 'inherit'
|
|
||||||
});
|
|
||||||
|
|
||||||
if (typeof result.status === 'number' && result.status !== 0) {
|
|
||||||
process.exit(result.status);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (result.error) {
|
|
||||||
throw result.error;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
runNodeScript([]);
|
|
||||||
|
|
||||||
const child = spawn('max', ['dev'], {
|
|
||||||
env: {
|
|
||||||
...process.env,
|
|
||||||
ENABLE_ENTERPRISE: 'true'
|
|
||||||
},
|
|
||||||
shell: true,
|
|
||||||
stdio: 'inherit'
|
|
||||||
});
|
|
||||||
|
|
||||||
let cleaned = false;
|
|
||||||
|
|
||||||
const clean = () => {
|
|
||||||
if (cleaned) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
cleaned = true;
|
|
||||||
try {
|
|
||||||
runNodeScript(['clean']);
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Failed to clean enterprise config:', error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const forwardSignal = (signal) => {
|
|
||||||
if (!child.killed) {
|
|
||||||
child.kill(signal);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
['SIGINT', 'SIGTERM', 'SIGHUP'].forEach((signal) => {
|
|
||||||
process.on(signal, () => {
|
|
||||||
forwardSignal(signal);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
child.on('error', (error) => {
|
|
||||||
clean();
|
|
||||||
throw error;
|
|
||||||
});
|
|
||||||
|
|
||||||
child.on('exit', (code, signal) => {
|
|
||||||
clean();
|
|
||||||
if (signal) {
|
|
||||||
process.kill(process.pid, signal);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
process.exit(code ?? 0);
|
|
||||||
});
|
|
||||||
@@ -1,96 +0,0 @@
|
|||||||
const fs = require('node:fs');
|
|
||||||
const path = require('node:path');
|
|
||||||
|
|
||||||
const workspaceGlobalPath = path.resolve(__dirname, '../../../global.tsx');
|
|
||||||
const workspaceDependenciesPath = path.resolve(__dirname, '../../../dependencies.json');
|
|
||||||
const workspaceTsconfigPath = path.resolve(__dirname, '../../../tsconfig.json');
|
|
||||||
const targetGlobalPath = path.resolve(__dirname, '../src/global.tsx');
|
|
||||||
const targetPackageJsonPath = path.resolve(__dirname, '../package.json');
|
|
||||||
const targetTsconfigPath = path.resolve(__dirname, '../tsconfig.json');
|
|
||||||
const mode = process.argv[2] || 'append';
|
|
||||||
const blockStart = '// ENTERPRISE_PLUGIN_BLOCK_START';
|
|
||||||
const blockEnd = '// ENTERPRISE_PLUGIN_BLOCK_END';
|
|
||||||
|
|
||||||
const workspaceSource = fs.readFileSync(workspaceGlobalPath, 'utf8').trim();
|
|
||||||
const blockContent = `\n${blockStart}\n${workspaceSource}\n${blockEnd}\n`;
|
|
||||||
|
|
||||||
const stripBlock = (content) => {
|
|
||||||
const pattern = new RegExp(`\\n?${blockStart}[\\s\\S]*?${blockEnd}\\n?`, 'g');
|
|
||||||
return content.replace(pattern, '\n').replace(/\n{3,}/g, '\n\n').trimEnd() + '\n';
|
|
||||||
};
|
|
||||||
|
|
||||||
const currentContent = fs.readFileSync(targetGlobalPath, 'utf8');
|
|
||||||
const strippedContent = stripBlock(currentContent);
|
|
||||||
|
|
||||||
const readJson = (filePath) => JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
||||||
const writeJson = (filePath, data) => {
|
|
||||||
fs.writeFileSync(filePath, `${JSON.stringify(data, null, 2)}\n`);
|
|
||||||
};
|
|
||||||
|
|
||||||
const syncPackageJson = () => {
|
|
||||||
const sourceDependencies =
|
|
||||||
readJson(workspaceDependenciesPath).dependencies || {};
|
|
||||||
const targetPackageJson = readJson(targetPackageJsonPath);
|
|
||||||
const currentDependencies = {
|
|
||||||
...(targetPackageJson.dependencies || {})
|
|
||||||
};
|
|
||||||
|
|
||||||
if (mode === 'clean') {
|
|
||||||
Object.keys(sourceDependencies).forEach((dependencyName) => {
|
|
||||||
delete currentDependencies[dependencyName];
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
Object.assign(currentDependencies, sourceDependencies);
|
|
||||||
}
|
|
||||||
|
|
||||||
targetPackageJson.dependencies = currentDependencies;
|
|
||||||
writeJson(targetPackageJsonPath, targetPackageJson);
|
|
||||||
console.log(
|
|
||||||
`${mode === 'clean' ? 'Cleaned' : 'Synced'} enterprise dependencies in ${targetPackageJsonPath}`,
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const syncTsconfig = () => {
|
|
||||||
const sourcePaths =
|
|
||||||
readJson(workspaceTsconfigPath).compilerOptions?.paths || {};
|
|
||||||
const targetTsconfig = readJson(targetTsconfigPath);
|
|
||||||
const currentPaths = {
|
|
||||||
...(targetTsconfig.compilerOptions?.paths || {})
|
|
||||||
};
|
|
||||||
|
|
||||||
if (mode === 'clean') {
|
|
||||||
Object.keys(sourcePaths).forEach((pathKey) => {
|
|
||||||
delete currentPaths[pathKey];
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
Object.assign(currentPaths, sourcePaths);
|
|
||||||
}
|
|
||||||
|
|
||||||
targetTsconfig.compilerOptions = targetTsconfig.compilerOptions || {};
|
|
||||||
targetTsconfig.compilerOptions.paths = currentPaths;
|
|
||||||
|
|
||||||
writeJson(targetTsconfigPath, targetTsconfig);
|
|
||||||
console.log(
|
|
||||||
`${mode === 'clean' ? 'Cleaned' : 'Synced'} enterprise tsconfig paths in ${targetTsconfigPath}`,
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
if (mode === 'clean') {
|
|
||||||
if (strippedContent !== currentContent) {
|
|
||||||
fs.writeFileSync(targetGlobalPath, strippedContent);
|
|
||||||
console.log(`Cleaned enterprise plugin block from ${targetGlobalPath}`);
|
|
||||||
} else {
|
|
||||||
console.log(`Enterprise plugin block already absent: ${targetGlobalPath}`);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
const updatedContent = `${strippedContent.trimEnd()}${blockContent}`;
|
|
||||||
if (updatedContent !== currentContent) {
|
|
||||||
fs.writeFileSync(targetGlobalPath, updatedContent);
|
|
||||||
console.log(`Appended enterprise plugin block to ${targetGlobalPath}`);
|
|
||||||
} else {
|
|
||||||
console.log(`Enterprise plugin block already up to date: ${targetGlobalPath}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
syncPackageJson();
|
|
||||||
syncTsconfig();
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
import { getGPUStackPlugin } from '@/plugins';
|
|
||||||
import { Result } from 'antd';
|
|
||||||
import React from 'react';
|
|
||||||
|
|
||||||
const SettingsPage: React.FC = () => {
|
|
||||||
const enterprisePlugin = getGPUStackPlugin();
|
|
||||||
const BrandingUI = enterprisePlugin?.components?.BrandingUI;
|
|
||||||
|
|
||||||
if (!BrandingUI) {
|
|
||||||
return (
|
|
||||||
<Result
|
|
||||||
status="404"
|
|
||||||
title="404"
|
|
||||||
subTitle="Enterprise settings page is unavailable."
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return <BrandingUI />;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default SettingsPage;
|
|
||||||
@@ -1,57 +0,0 @@
|
|||||||
import { getGPUStackPlugin } from '@/plugins';
|
|
||||||
import type { RouteConfig } from '@/plugins/types';
|
|
||||||
|
|
||||||
type ClientRoute = RouteConfig & {
|
|
||||||
id?: string;
|
|
||||||
children?: ClientRoute[];
|
|
||||||
routes?: ClientRoute[];
|
|
||||||
};
|
|
||||||
|
|
||||||
function getLayoutChildren(routes: ClientRoute[]): ClientRoute[] | null {
|
|
||||||
const rootRoute = routes.find((route) => route.path === '/');
|
|
||||||
if (!rootRoute) return null;
|
|
||||||
|
|
||||||
const rootChildren = rootRoute.routes || rootRoute.children;
|
|
||||||
if (!Array.isArray(rootChildren)) return null;
|
|
||||||
|
|
||||||
const globalLayout = rootChildren.find((route) =>
|
|
||||||
route.id?.includes('global-layout')
|
|
||||||
);
|
|
||||||
if (!globalLayout) return null;
|
|
||||||
|
|
||||||
const layoutChildren = globalLayout.children || globalLayout.routes;
|
|
||||||
return Array.isArray(layoutChildren) ? layoutChildren : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
function mergeRoute(targetRoutes: ClientRoute[], pluginRoute: ClientRoute) {
|
|
||||||
const existsIndex = targetRoutes.findIndex(
|
|
||||||
(route) =>
|
|
||||||
(pluginRoute.path && route.path === pluginRoute.path) ||
|
|
||||||
(pluginRoute.key && route.key === pluginRoute.key)
|
|
||||||
);
|
|
||||||
|
|
||||||
if (existsIndex === -1) {
|
|
||||||
targetRoutes.push(pluginRoute);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const currentRoute = targetRoutes[existsIndex];
|
|
||||||
targetRoutes[existsIndex] = {
|
|
||||||
...currentRoute,
|
|
||||||
...pluginRoute,
|
|
||||||
routes: pluginRoute.routes || currentRoute.routes,
|
|
||||||
children: pluginRoute.children || currentRoute.children
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function mergeEnterpriseRoutes(routes: ClientRoute[]) {
|
|
||||||
const enterprisePlugin = getGPUStackPlugin();
|
|
||||||
if (!enterprisePlugin?.routes?.length) return;
|
|
||||||
|
|
||||||
const layoutChildren = getLayoutChildren(routes);
|
|
||||||
if (!layoutChildren) return;
|
|
||||||
|
|
||||||
enterprisePlugin.routes.forEach((route) =>
|
|
||||||
mergeRoute(layoutChildren, route as ClientRoute)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user