mirror of
https://github.com/Studio-Saelix/sencho.git
synced 2026-08-20 15:22:59 +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:
@@ -22,6 +22,7 @@ import { VisuallyHidden } from '@radix-ui/react-visually-hidden';
|
||||
import { apiFetch } from '@/lib/api';
|
||||
import { toast } from '@/components/ui/toast-store';
|
||||
import { ProGate } from '../ProGate';
|
||||
import { CapabilityGate } from '../CapabilityGate';
|
||||
import { LabelDot, type Label, type LabelColor } from '../LabelPill';
|
||||
|
||||
const LABEL_COLORS: LabelColor[] = ['teal', 'blue', 'purple', 'rose', 'amber', 'green', 'orange', 'pink', 'cyan', 'slate'];
|
||||
@@ -123,6 +124,7 @@ export function LabelsSection() {
|
||||
|
||||
return (
|
||||
<ProGate featureName="Stack Labels">
|
||||
<CapabilityGate capability="labels" featureName="Stack Labels">
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center justify-between pr-8">
|
||||
<div>
|
||||
@@ -233,6 +235,7 @@ export function LabelsSection() {
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
</CapabilityGate>
|
||||
</ProGate>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ import {
|
||||
import { toast } from '@/components/ui/toast-store';
|
||||
import { apiFetch } from '@/lib/api';
|
||||
import { AdmiralGate } from '@/components/AdmiralGate';
|
||||
import { CapabilityGate } from '@/components/CapabilityGate';
|
||||
import { TierBadge } from '@/components/TierBadge';
|
||||
import { Plus, Trash2, Pencil, RefreshCw, Zap, X, GitBranch } from 'lucide-react';
|
||||
|
||||
@@ -230,6 +231,7 @@ export function NotificationRoutingSection() {
|
||||
|
||||
return (
|
||||
<AdmiralGate featureName="Notification Routing">
|
||||
<CapabilityGate capability="notification-routing" featureName="Notification Routing">
|
||||
<div className="space-y-6">
|
||||
<div className="flex items-start justify-between pr-8">
|
||||
<div>
|
||||
@@ -452,6 +454,7 @@ export function NotificationRoutingSection() {
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</CapabilityGate>
|
||||
</AdmiralGate>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ import { apiFetch } from '@/lib/api';
|
||||
import { useAuth, type UserRole } from '@/context/AuthContext';
|
||||
import { useLicense } from '@/context/LicenseContext';
|
||||
import { ProGate } from '@/components/ProGate';
|
||||
import { CapabilityGate } from '@/components/CapabilityGate';
|
||||
import { RefreshCw, Trash2, Plus, Pencil } from 'lucide-react';
|
||||
|
||||
interface UserItem {
|
||||
@@ -230,6 +231,7 @@ export function UsersSection() {
|
||||
|
||||
return (
|
||||
<ProGate featureName="User management">
|
||||
<CapabilityGate capability="users" featureName="User Management">
|
||||
<div className="space-y-6">
|
||||
<div className="flex items-start justify-between pr-8">
|
||||
<div>
|
||||
@@ -452,6 +454,7 @@ export function UsersSection() {
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</CapabilityGate>
|
||||
</ProGate>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@
|
||||
import { toast } from '@/components/ui/toast-store';
|
||||
import { apiFetch } from '@/lib/api';
|
||||
import { ProGate } from '@/components/ProGate';
|
||||
import { CapabilityGate } from '@/components/CapabilityGate';
|
||||
import { TierBadge } from '@/components/TierBadge';
|
||||
import {
|
||||
RefreshCw, CheckCircle, XCircle, Webhook, Copy, Trash2,
|
||||
@@ -141,10 +142,12 @@ export function WebhooksSection({ isPro }: { isPro: boolean }) {
|
||||
<p className="text-sm text-muted-foreground">Trigger stack actions from CI/CD pipelines via HTTP.</p>
|
||||
</div>
|
||||
<ProGate featureName="Webhooks">
|
||||
<CapabilityGate capability="webhooks" featureName="Webhooks">
|
||||
<div className="space-y-3">
|
||||
<div className="h-16 rounded-lg border bg-card" />
|
||||
<div className="h-16 rounded-lg border bg-card" />
|
||||
</div>
|
||||
</CapabilityGate>
|
||||
</ProGate>
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user