feat(ui): Phase 57 - remote context UX (Option A) + network layer fixes

Task 1 - Network layer fixes:
- AppStoreView: replace raw fetch() with apiFetch() on /templates and
  /templates/deploy so x-node-id header is injected for proxy routing
- GlobalObservabilityView: replace raw fetch() with apiFetch() on
  /stacks and /logs/global
- HostConsole: append ?nodeId= to WebSocket URL so the upgrade handler
  correctly routes the PTY session to the active remote node

Task 2 - Scoped context two-tier UX (Option A / Portainer-style):
- EditorLayout: add a context pill in the top header bar showing the
  active node name (pulsing blue for remote, green for local)
- HostConsole: header now reads "Host Console - [Node Name]"
- ResourcesView: title now reads "Resources Hub - [Node Name]" for remote
- GlobalObservabilityView: floating node badge in top-left corner for remote
- AppStoreView: deploy sheet shows "Deploying to: [Node Name]" badge
- SettingsModal: remote nodes only see System Limits + Developer tabs;
  global-only tabs (Account, Appearance, Notifications, Nodes) are hidden;
  header subtitle shows remote node name
This commit is contained in:
SaelixCode
2026-03-19 20:39:57 -04:00
parent e027a94492
commit 04c770c198
7 changed files with 117 additions and 40 deletions
+12 -3
View File
@@ -8,6 +8,8 @@ import { ScrollArea } from "@/components/ui/scroll-area";
import { Button } from "@/components/ui/button";
import { Search, Rocket, Loader2, Info, ExternalLink, Github, Star } from "lucide-react";
import { toast } from "sonner";
import { apiFetch } from '@/lib/api';
import { useNodes } from '@/context/NodeContext';
export interface TemplateEnv {
name: string;
@@ -36,6 +38,7 @@ interface AppStoreViewProps {
}
export function AppStoreView({ onDeploySuccess }: AppStoreViewProps) {
const { activeNode } = useNodes();
const [templates, setTemplates] = useState<Template[]>([]);
const [searchQuery, setSearchQuery] = useState('');
const [selectedTemplate, setSelectedTemplate] = useState<Template | null>(null);
@@ -59,7 +62,7 @@ export function AppStoreView({ onDeploySuccess }: AppStoreViewProps) {
const fetchTemplates = async () => {
try {
const res = await fetch('/api/templates');
const res = await apiFetch('/templates');
if (!res.ok) throw new Error('Failed to fetch templates');
const data = await res.json();
setTemplates(data || []);
@@ -156,9 +159,8 @@ export function AppStoreView({ onDeploySuccess }: AppStoreViewProps) {
});
try {
const res = await fetch('/api/templates/deploy', {
const res = await apiFetch('/templates/deploy', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
stackName: stackName.trim(),
template: modifiedTemplate,
@@ -255,6 +257,13 @@ export function AppStoreView({ onDeploySuccess }: AppStoreViewProps) {
{selectedTemplate && (
<div className="flex flex-col h-full">
<SheetHeader className="mb-6 text-left">
{activeNode?.type === 'remote' && (
<div className="mb-2">
<Badge variant="secondary" className="text-xs font-normal">
Deploying to: {activeNode.name}
</Badge>
</div>
)}
<div className="flex items-center gap-4 mb-4">
<div className="w-16 h-16 rounded bg-white p-1 flex-shrink-0 flex items-center justify-center overflow-hidden border">
{!imgErrors[selectedTemplate.title] && selectedTemplate.logo ? (