chore(frontend): expose Routing and Deployments tabs by default (#955)

Drop the SENCHO_EXPERIMENTAL gate from the Fleet Routing and
Deployments tabs so they ship in the default UI. Both have been
verified production-ready and promoted out of experimental.

Routing trigger and content are now wrapped only by isAdmiral plus
the existing AdmiralGate. Deployments trigger and content are wrapped
by isPaid (Skipper+); Community users no longer see the tab at all,
mirroring the Routing pattern. Federation and Secrets remain inside
the experimental block as dev-only previews.

Removes the SoonBadge component and "Coming soon" pill from the
preview placeholder so the tab bar shows only ready, tier-appropriate
tabs without ambiguous SOON labels.
This commit is contained in:
Anso
2026-05-06 21:18:31 -04:00
committed by GitHub
parent bc1077c722
commit e2edc6ceb8
2 changed files with 21 additions and 43 deletions
+17 -26
View File
@@ -20,7 +20,7 @@ import { useLicense } from '@/context/LicenseContext';
import { AdmiralGate } from './AdmiralGate'; import { AdmiralGate } from './AdmiralGate';
import FleetSnapshots from './FleetSnapshots'; import FleetSnapshots from './FleetSnapshots';
import { FleetConfiguration } from './fleet/FleetConfiguration'; import { FleetConfiguration } from './fleet/FleetConfiguration';
import { FleetSoonPlaceholder, SoonBadge } from './fleet/FleetSoonPlaceholder'; import { FleetSoonPlaceholder } from './fleet/FleetSoonPlaceholder';
import { RoutingTab } from './fleet/RoutingTab'; import { RoutingTab } from './fleet/RoutingTab';
import { DeploymentsTab } from './blueprints/DeploymentsTab'; import { DeploymentsTab } from './blueprints/DeploymentsTab';
@@ -73,7 +73,7 @@ export function FleetView({ onNavigateToNode }: FleetViewProps) {
<Camera className="w-4 h-4 mr-1.5" />Snapshots <Camera className="w-4 h-4 mr-1.5" />Snapshots
</TabsTrigger> </TabsTrigger>
</TabsHighlightItem> </TabsHighlightItem>
{isAdmiral && experimental && ( {isAdmiral && (
<TabsHighlightItem value="routing"> <TabsHighlightItem value="routing">
<TabsTrigger value="routing"> <TabsTrigger value="routing">
<ArrowLeftRight className="w-4 h-4 mr-1.5" />Traffic · Routing <ArrowLeftRight className="w-4 h-4 mr-1.5" />Traffic · Routing
@@ -85,25 +85,24 @@ export function FleetView({ onNavigateToNode }: FleetViewProps) {
<SlidersHorizontal className="w-4 h-4 mr-1.5" />Status <SlidersHorizontal className="w-4 h-4 mr-1.5" />Status
</TabsTrigger> </TabsTrigger>
</TabsHighlightItem> </TabsHighlightItem>
{isPaid && (
<TabsHighlightItem value="deployments">
<TabsTrigger value="deployments">
<Send className="w-4 h-4 mr-1.5" />Deployments
</TabsTrigger>
</TabsHighlightItem>
)}
{experimental && ( {experimental && (
<> <>
<span aria-hidden className="self-center mx-1 h-4 w-px bg-border" /> <span aria-hidden className="self-center mx-1 h-4 w-px bg-border" />
<TabsHighlightItem value="deployments">
<TabsTrigger value="deployments">
<Send className="w-4 h-4 mr-1.5" />Deployments
{!isPaid && <SoonBadge />}
</TabsTrigger>
</TabsHighlightItem>
<TabsHighlightItem value="federation"> <TabsHighlightItem value="federation">
<TabsTrigger value="federation"> <TabsTrigger value="federation">
<Network className="w-4 h-4 mr-1.5" />Federation <Network className="w-4 h-4 mr-1.5" />Federation
<SoonBadge />
</TabsTrigger> </TabsTrigger>
</TabsHighlightItem> </TabsHighlightItem>
<TabsHighlightItem value="secrets"> <TabsHighlightItem value="secrets">
<TabsTrigger value="secrets"> <TabsTrigger value="secrets">
<KeyRound className="w-4 h-4 mr-1.5" />Secrets <KeyRound className="w-4 h-4 mr-1.5" />Secrets
<SoonBadge />
</TabsTrigger> </TabsTrigger>
</TabsHighlightItem> </TabsHighlightItem>
</> </>
@@ -163,7 +162,7 @@ export function FleetView({ onNavigateToNode }: FleetViewProps) {
<TabsContent value="snapshots"> <TabsContent value="snapshots">
<FleetSnapshots /> <FleetSnapshots />
</TabsContent> </TabsContent>
{isAdmiral && experimental && ( {isAdmiral && (
<TabsContent value="routing"> <TabsContent value="routing">
<AdmiralGate> <AdmiralGate>
<RoutingTab /> <RoutingTab />
@@ -173,25 +172,17 @@ export function FleetView({ onNavigateToNode }: FleetViewProps) {
<TabsContent value="configuration"> <TabsContent value="configuration">
<FleetConfiguration /> <FleetConfiguration />
</TabsContent> </TabsContent>
{isPaid && (
<TabsContent value="deployments">
<DeploymentsTab />
</TabsContent>
)}
{experimental && ( {experimental && (
<> <>
<TabsContent value="deployments">
{isPaid ? (
<DeploymentsTab />
) : (
<FleetSoonPlaceholder
icon={<Send className="h-4 w-4" />}
kicker="Deployments · Blueprints"
title="Declare once. Distribute everywhere."
description="Pick nodes by label, drop in a docker-compose, and Sencho keeps the matching nodes in sync. Drift detection always on; auto-fix optional."
plannedActions={['Author', 'Target', 'Reconcile', 'Snapshot+evict']}
/>
)}
</TabsContent>
<TabsContent value="federation"> <TabsContent value="federation">
<FleetSoonPlaceholder <FleetSoonPlaceholder
icon={<Network className="h-4 w-4" />} icon={<Network className="h-4 w-4" />}
kicker="Federation · Coming soon" kicker="Federation"
title="The fleet as one logical surface" title="The fleet as one logical surface"
description="Pin policies, drain a node for maintenance, weight-aware scheduling. This stack runs on whichever node has capacity." description="Pin policies, drain a node for maintenance, weight-aware scheduling. This stack runs on whichever node has capacity."
plannedActions={['Pin policy', 'Drain node', 'Cordon', 'Capacity plan']} plannedActions={['Pin policy', 'Drain node', 'Cordon', 'Capacity plan']}
@@ -200,7 +191,7 @@ export function FleetView({ onNavigateToNode }: FleetViewProps) {
<TabsContent value="secrets"> <TabsContent value="secrets">
<FleetSoonPlaceholder <FleetSoonPlaceholder
icon={<KeyRound className="h-4 w-4" />} icon={<KeyRound className="h-4 w-4" />}
kicker="Secrets · Coming soon" kicker="Secrets"
title="One source of truth for env, creds and certs" title="One source of truth for env, creds and certs"
description="Push to selected nodes, rotate centrally, audit who-saw-what. Solves silent drift across copies." description="Push to selected nodes, rotate centrally, audit who-saw-what. Solves silent drift across copies."
plannedActions={['Sync env', 'Rotate', 'Audit', 'Pin to nodes']} plannedActions={['Sync env', 'Rotate', 'Audit', 'Pin to nodes']}
@@ -1,13 +1,5 @@
import type { ReactNode } from 'react'; import type { ReactNode } from 'react';
export function SoonBadge() {
return (
<span className="ml-1.5 font-mono text-[9px] uppercase tracking-[0.2em] text-brand">
SOON
</span>
);
}
interface FleetSoonPlaceholderProps { interface FleetSoonPlaceholderProps {
icon: ReactNode; icon: ReactNode;
kicker: string; kicker: string;
@@ -26,16 +18,11 @@ export function FleetSoonPlaceholder({
return ( return (
<div className="mx-auto max-w-3xl rounded-xl border border-card-border border-t-card-border-top bg-card shadow-card-bevel"> <div className="mx-auto max-w-3xl rounded-xl border border-card-border border-t-card-border-top bg-card shadow-card-bevel">
<div className="flex flex-col gap-5 p-8"> <div className="flex flex-col gap-5 p-8">
<div className="flex items-center justify-between gap-3"> <div className="inline-flex items-center gap-2 text-brand">
<div className="inline-flex items-center gap-2 text-brand"> <span className="inline-flex h-4 w-4 items-center justify-center" aria-hidden>
<span className="inline-flex h-4 w-4 items-center justify-center" aria-hidden> {icon}
{icon}
</span>
<span className="font-mono text-[10px] uppercase tracking-[0.2em]">{kicker}</span>
</div>
<span className="rounded border border-brand/20 bg-brand/10 px-1.5 py-0.5 font-mono text-[9px] uppercase tracking-[0.2em] text-brand">
Coming soon
</span> </span>
<span className="font-mono text-[10px] uppercase tracking-[0.2em]">{kicker}</span>
</div> </div>
<h3 className="font-serif text-2xl italic leading-tight tracking-[-0.01em] text-stat-value"> <h3 className="font-serif text-2xl italic leading-tight tracking-[-0.01em] text-stat-value">