282 lines
12 KiB
Plaintext
282 lines
12 KiB
Plaintext
// 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 <string> 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);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|