mirror of
https://github.com/Studio-Saelix/sencho.git
synced 2026-08-30 12:09:15 +00:00
feat(nodes): add capability-based node compatibility negotiation (#350)
* feat(nodes): add capability-based node compatibility negotiation Each Sencho instance now exposes /api/meta with its version and supported capabilities. When the user switches nodes, the frontend fetches this metadata and disables features the remote node doesn't support via a CapabilityGate overlay. Version is shown in the node switcher dropdown and connection test results. - Backend: CapabilityRegistry with static capability list and fetchRemoteMeta helper - Backend: /api/meta (public) and /api/nodes/:id/meta (auth) endpoints - Frontend: NodeContext enhanced with per-node meta caching (5min TTL) - Frontend: CapabilityGate component with typed Capability union - Frontend: 13 features wrapped with capability gates - Docs: node-compatibility.mdx + OpenAPI spec updates * fix(nodes): revert to require() for package.json version reading The static import fails in the Docker multi-stage build because the root package.json is not copied into the backend-builder stage. The require() call resolves at runtime when the file is available.
This commit is contained in:
@@ -0,0 +1,58 @@
|
||||
import axios from 'axios';
|
||||
|
||||
/**
|
||||
* Static registry of capabilities supported by THIS Sencho instance.
|
||||
* Append-only: when a new feature ships, add its capability string here.
|
||||
* The frontend uses these flags (not semver comparisons) to gate features
|
||||
* on nodes that may be running older versions.
|
||||
*/
|
||||
export const CAPABILITIES = [
|
||||
'stacks',
|
||||
'containers',
|
||||
'resources',
|
||||
'templates',
|
||||
'global-logs',
|
||||
'system-stats',
|
||||
'fleet',
|
||||
'auto-updates',
|
||||
'labels',
|
||||
'webhooks',
|
||||
'network-topology',
|
||||
'notifications',
|
||||
'notification-routing',
|
||||
'host-console',
|
||||
'audit-log',
|
||||
'scheduled-ops',
|
||||
'sso',
|
||||
'api-tokens',
|
||||
'users',
|
||||
'registries',
|
||||
] as const;
|
||||
|
||||
export type Capability = (typeof CAPABILITIES)[number];
|
||||
|
||||
export function getSenchoVersion(): string {
|
||||
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
||||
return require('../../../package.json').version;
|
||||
}
|
||||
|
||||
export interface RemoteMeta {
|
||||
version: string | null;
|
||||
capabilities: string[];
|
||||
}
|
||||
|
||||
/** Fetch /api/meta from a remote Sencho instance. Returns empty data on failure. */
|
||||
export async function fetchRemoteMeta(baseUrl: string, apiToken: string): Promise<RemoteMeta> {
|
||||
try {
|
||||
const res = await axios.get(`${baseUrl}/api/meta`, {
|
||||
headers: { Authorization: `Bearer ${apiToken}` },
|
||||
timeout: 5000,
|
||||
});
|
||||
return {
|
||||
version: res.data.version ?? null,
|
||||
capabilities: Array.isArray(res.data.capabilities) ? res.data.capabilities : [],
|
||||
};
|
||||
} catch {
|
||||
return { version: null, capabilities: [] };
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user