// Proxies Page - Proxy nodes management with tabs and groups import { Theme, Typography, SpacingSystem } from "../theme/theme.slint"; import { Item } from "../components/item.slint"; import { Badge } from "../components/badge.slint"; import { Button } from "../components/button.slint"; import { Empty } from "../components/empty.slint"; import { Container } from "../layouts/container.slint"; import { ProxyGroup, ProxyNode } from "../types.slint"; export component ProxiesPage inherits Rectangle { in property <[ProxyGroup]> proxy-groups; in-out property proxy-mode: "rule"; // "rule" | "global" | "direct" callback load-proxy-groups(string /* mode */); callback select-proxy(string /* group-name */, string /* node-name */); callback test-proxy-delay(string /* node-name */); background: Theme.colors.background; VerticalLayout { // Fixed header with tabs Rectangle { height: 48px; background: Theme.colors.background; HorizontalLayout { padding: SpacingSystem.spacing.s4; spacing: SpacingSystem.spacing.s4; alignment: space-between; Text { text: "Proxies"; font-size: Typography.sizes.xl; font-weight: Typography.weights.bold; color: Theme.colors.foreground; vertical-alignment: center; } // Mode tabs HorizontalLayout { spacing: SpacingSystem.spacing.s1; Rectangle { width: 80px; height: 32px; background: proxy-mode == "rule" ? Theme.colors.primary : Theme.colors.muted; border-radius: SpacingSystem.radius.md; TouchArea { clicked => { proxy-mode = "rule"; root.load-proxy-groups("rule"); } } Text { text: "Rule"; font-size: Typography.sizes.sm; font-weight: Typography.weights.medium; color: proxy-mode == "rule" ? Theme.colors.primary-foreground : Theme.colors.muted-foreground; horizontal-alignment: center; vertical-alignment: center; } } Rectangle { width: 80px; height: 32px; background: proxy-mode == "global" ? Theme.colors.primary : Theme.colors.muted; border-radius: SpacingSystem.radius.md; TouchArea { clicked => { proxy-mode = "global"; root.load-proxy-groups("global"); } } Text { text: "Global"; font-size: Typography.sizes.sm; font-weight: Typography.weights.medium; color: proxy-mode == "global" ? Theme.colors.primary-foreground : Theme.colors.muted-foreground; horizontal-alignment: center; vertical-alignment: center; } } Rectangle { width: 80px; height: 32px; background: proxy-mode == "direct" ? Theme.colors.primary : Theme.colors.muted; border-radius: SpacingSystem.radius.md; TouchArea { clicked => { proxy-mode = "direct"; root.load-proxy-groups("direct"); } } Text { text: "Direct"; font-size: Typography.sizes.sm; font-weight: Typography.weights.medium; color: proxy-mode == "direct" ? Theme.colors.primary-foreground : Theme.colors.muted-foreground; horizontal-alignment: center; vertical-alignment: center; } } } } } // Scrollable content Flickable { viewport-height: content-layout.preferred-height + SpacingSystem.spacing.s4 * 2; content-layout := VerticalLayout { padding: SpacingSystem.spacing.s4; spacing: SpacingSystem.spacing.s4; // Empty state if proxy-groups.length == 0: Empty { height: 300px; icon: "🌍"; title: "No Groups"; description: "Proxy groups will appear here"; } // Proxy groups for group in proxy-groups: Container { VerticalLayout { padding: SpacingSystem.spacing.s4; spacing: SpacingSystem.spacing.s3; // Group header HorizontalLayout { spacing: SpacingSystem.spacing.s3; alignment: space-between; VerticalLayout { spacing: SpacingSystem.spacing.s1; Text { text: group.name; font-size: Typography.sizes.base; font-weight: Typography.weights.semibold; color: Theme.colors.foreground; } Text { text: "Type: " + group.type + " | Current: " + group.now; font-size: Typography.sizes.xs; color: Theme.colors.muted-foreground; } } Text { text: group.nodes.length + " nodes"; font-size: Typography.sizes.xs; color: Theme.colors.muted-foreground; vertical-alignment: center; } } // Divider Rectangle { height: 1px; background: Theme.colors.border; } // Proxy nodes VerticalLayout { spacing: SpacingSystem.spacing.s2; for node in group.nodes: Rectangle { height: 60px; background: node.name == group.now ? Theme.colors.accent : transparent; border-radius: SpacingSystem.radius.md; border-width: 1px; border-color: Theme.colors.border; HorizontalLayout { padding: SpacingSystem.spacing.s3; spacing: SpacingSystem.spacing.s3; alignment: space-between; // Node info HorizontalLayout { spacing: SpacingSystem.spacing.s3; // Status indicator Rectangle { width: 8px; height: 8px; border-radius: 4px; background: node.delay >= 0 && node.delay < 200 ? #22c55e : node.delay >= 200 && node.delay < 500 ? #f59e0b : node.delay >= 500 ? #ef4444 : Theme.colors.muted; } // Node details VerticalLayout { spacing: SpacingSystem.spacing.s1; Text { text: node.name; font-size: Typography.sizes.sm; font-weight: Typography.weights.medium; color: Theme.colors.foreground; } HorizontalLayout { spacing: SpacingSystem.spacing.s2; Badge { text: node.type; variant: "outline"; } if node.udp: Badge { text: "UDP"; variant: "outline"; } if node.delay >= 0: Text { text: node.delay + "ms"; font-size: Typography.sizes.xs; color: Theme.colors.muted-foreground; } } } } // Actions HorizontalLayout { spacing: SpacingSystem.spacing.s2; Button { text: "🔍"; variant: "ghost"; size: "sm"; clicked => { root.test-proxy-delay(node.name); } } if node.name != group.now: Button { text: "Select"; variant: "outline"; size: "sm"; clicked => { root.select-proxy(group.name, node.name); } } if node.name == group.now: Badge { text: "Active"; variant: "default"; } } } TouchArea { clicked => { if node.name != group.now { root.select-proxy(group.name, node.name); } } } } } } } } } } }