This commit is contained in:
2026-01-30 12:56:00 +08:00
parent 91f24cb462
commit dcbbda951e
89 changed files with 13486 additions and 72 deletions

226
ui/pages/proxies.slint Normal file
View File

@@ -0,0 +1,226 @@
import { Theme, SpacingSystem, Typography } from "../theme/theme.slint";
import { Tabs, TabItem, TabContent } from "../components/tabs.slint";
import { Accordion, AccordionItemData, AccordionContent } from "../components/accordion.slint";
import { Item } from "../components/item.slint";
export struct ProxyNode {
name: string,
type: string,
latency: string,
}
export struct ProxyGroup {
name: string,
nodes: [ProxyNode],
expanded: bool,
}
export component ProxiesPage {
in-out property <[ProxyGroup]> proxy-groups: [];
in-out property <int> current-mode: 0; // 0=Rule, 1=Global, 2=Direct
in property <string> selected-proxy: "";
callback mode-changed(int);
callback group-toggled(int, bool);
callback proxy-selected(string);
VerticalLayout {
padding: SpacingSystem.spacing.s4;
spacing: SpacingSystem.spacing.s4;
// Header with Tabs
HorizontalLayout {
spacing: SpacingSystem.spacing.s3;
alignment: space-between;
Text {
text: "Proxies";
color: Theme.colors.foreground;
font-size: Typography.sizes.xl;
font-weight: Typography.weights.bold;
vertical-alignment: center;
}
Rectangle {
horizontal-stretch: 1;
}
}
// Tabs
Tabs {
tabs: [
{ title: "Rule" },
{ title: "Global" },
{ title: "Direct" },
];
current-index: root.current-mode;
tab-changed(index) => {
root.current-mode = index;
root.mode-changed(index);
}
// Tab Content
TabContent {
index: 0;
current-index: root.current-mode;
ScrollView {
VerticalLayout {
spacing: SpacingSystem.spacing.s3;
Accordion {
items: root.proxy-groups.map(|g| {
return { title: g.name, expanded: g.expanded };
});
item-toggled(index, expanded) => {
root.group-toggled(index, expanded);
}
for group[group-index] in root.proxy-groups: AccordionContent {
ProxyGroupContent {
nodes: group.nodes;
selected-proxy: root.selected-proxy;
node-selected(name) => {
root.proxy-selected(name);
}
}
}
}
}
}
}
TabContent {
index: 1;
current-index: root.current-mode;
Text {
text: "Global mode - All traffic goes through selected proxy";
color: Theme.colors.muted-foreground;
horizontal-alignment: center;
vertical-alignment: center;
}
}
TabContent {
index: 2;
current-index: root.current-mode;
Text {
text: "Direct mode - All traffic goes direct";
color: Theme.colors.muted-foreground;
horizontal-alignment: center;
vertical-alignment: center;
}
}
}
}
}
component ProxyGroupContent {
in property <[ProxyNode]> nodes: [];
in property <string> selected-proxy: "";
callback node-selected(string);
GridLayout {
spacing: SpacingSystem.spacing.s3;
for node[index] in root.nodes: ProxyNodeItem {
node-data: node;
selected: node.name == root.selected-proxy;
clicked => {
root.node-selected(node.name);
}
}
}
}
component ProxyNodeItem {
in property <ProxyNode> node-data;
in property <bool> selected: false;
callback clicked();
private property <bool> hovered: false;
min-width: 200px;
height: 60px;
states [
selected when root.selected: {
container.border-color: Theme.coloimary;
container.background: Theme.colors.primary.transparentize(0.9);
}
hovered when root.hovered && !root.selected: {
container.background: Theme.colors.muted;
}
]
container := Rectangle {
background: transparent;
border-radius: SpacingSystem.radius.md;
border-width: 1px;
border-color: Theme.colors.border;
animate background { duration: 200ms; }
animate border-color { duration: 200ms; }
HorizontalLayout {
padding: SpacingSystem.spacing.s3;
spacing: SpacingSystem.spacing.s3;
alignment: space-between;
VerticalLayout {
spacing: SpacingSystem.spacing.s1;
HorizontalLayout {
spacing: SpacingSystem.spacing.s2;
Text {
text: "✓";
color: root.selected ? Theme.colors.primary : Theme.colors.muted-foreground;
font-size: Typography.sizes.sm;
vertical-alignment: center;
}
Text {
text: root.node-data.name;
color: Theme.colors.foreground;
font-size: Typography.sizes.sm;
font-weight: Typography.weights.medium;
vertical-alignment: center;
}
}
Text {
text: root.node-data.type;
color: Theme.colors.muted-foreground;
font-size: Typography.sizes.xs;
}
}
Text {
text: root.node-data.latency;
color: Theme.colors.primary;
font-size: Typography.sizes.sm;
font-weight: Typography.weights.semibold;
vertical-alignment: center;
}
}
touch := TouchArea {
clicked => {
root.clicked();
}
moved => {
root.hovered = self.has-hover;
}
}
}
}