Refactor proxy routing and installer flows

This commit is contained in:
2026-07-08 11:05:52 +03:00
parent e745633d91
commit 88d5b94133
14 changed files with 1899 additions and 487 deletions

View File

@@ -122,7 +122,11 @@ button:disabled {
.ui-button:focus-visible,
.ui-icon-button:focus-visible,
.ui-details-popover-trigger:focus-visible,
.ui-details-popover-close:focus-visible,
.ui-hover-details:focus-visible,
.ui-tab:focus-visible,
.server-select-button:focus-visible,
.ui-action-menu-popover button:focus-visible {
outline: 0;
box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.72);
@@ -196,6 +200,111 @@ button:disabled {
padding: 0;
}
.ui-hover-details {
position: relative;
outline: none;
cursor: help;
}
.ui-details-popover-trigger {
appearance: none;
font: inherit;
outline: none;
text-align: left;
}
.ui-details-popover {
position: fixed;
z-index: 90;
display: grid;
gap: 8px;
border: 1px solid #334155;
border-radius: 5px;
background: #0d1118;
box-shadow: 0 18px 42px rgba(0, 0, 0, 0.46);
color: #e5e7eb;
padding: 10px;
animation: ui-popover-in 130ms var(--ease-out);
}
.ui-details-popover::before {
position: absolute;
top: -5px;
left: var(--details-popover-arrow-left);
width: 8px;
height: 8px;
border-top: 1px solid #334155;
border-left: 1px solid #334155;
background: #0d1118;
content: "";
transform: translateX(-50%) rotate(45deg);
}
.ui-details-popover[data-placement="top"]::before {
top: auto;
bottom: -5px;
border: 0;
border-right: 1px solid #334155;
border-bottom: 1px solid #334155;
}
.ui-details-popover-head {
display: flex;
align-items: center;
justify-content: space-between;
gap: 10px;
min-width: 0;
}
.ui-details-popover-head strong {
min-width: 0;
color: #f8fafc;
font-size: 13px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.ui-details-popover-close {
display: grid;
place-items: center;
width: 26px;
min-width: 26px;
height: 26px;
border: 1px solid #263040;
border-radius: 4px;
background: #131923;
color: #cbd5e1;
cursor: pointer;
font-size: 16px;
line-height: 1;
padding: 0;
}
.ui-details-popover-close:hover {
border-color: #3b82f6;
background: #182033;
color: #f8fafc;
}
.ui-details-popover-body {
display: grid;
gap: 5px;
color: #a9b4c5;
font-size: 12px;
font-weight: 650;
line-height: 1.35;
}
.ui-details-popover-body p {
margin: 0;
overflow-wrap: anywhere;
}
.ui-details-popover-body p:first-child {
color: #e5e7eb;
}
.ui-button-icon,
.ui-button-label {
display: inline-flex;
@@ -546,10 +655,7 @@ button:disabled {
gap: 6px;
}
.ui-service-row > .setup-details,
.ui-service-row > .singbox-info-popover,
.ui-service-row > .singbox-workspace,
.ui-service-row > .singbox-install-note {
.ui-service-row > .singbox-workspace {
grid-column: 1 / -1;
}
@@ -600,7 +706,7 @@ button:disabled {
.process-add-line,
.app-row,
.route-switch,
.singbox-info-actions,
.singbox-workspace-actions,
.panel-tabs,
.server-row {
display: flex;
@@ -766,6 +872,60 @@ button:disabled {
text-align: right;
}
.proxy-overview {
display: grid;
gap: 10px;
border: 1px solid #2b3342;
border-radius: 4px;
background: #111720;
padding: 10px;
}
.proxy-overview.ok {
border-color: rgba(34, 197, 94, 0.36);
}
.proxy-overview.warning {
border-color: rgba(245, 158, 11, 0.42);
}
.proxy-overview.error {
border-color: rgba(239, 68, 68, 0.42);
}
.proxy-overview.checking {
border-color: rgba(59, 130, 246, 0.42);
}
.proxy-overview-head {
display: flex;
align-items: end;
justify-content: space-between;
gap: 12px;
min-width: 0;
}
.proxy-overview-head span {
display: block;
color: #8d99ae;
font-size: 12px;
}
.proxy-overview-head h2 {
margin: 2px 0 0;
color: #eef2ff;
font-size: 17px;
letter-spacing: 0;
}
.proxy-overview-head > strong {
min-width: 0;
color: #bfdbfe;
font-size: 13px;
overflow-wrap: anywhere;
text-align: right;
}
.summary-panel {
gap: 12px;
}
@@ -1045,7 +1205,7 @@ button.summary-card:hover {
.service-menu-button,
.subscription-line button,
.route-switch button,
.singbox-info-actions button,
.singbox-workspace-actions button,
.server-row {
min-height: 36px;
border: 1px solid #343b49;
@@ -1065,7 +1225,7 @@ button.summary-card:hover {
.service-menu-button:hover,
.subscription-line button:hover,
.route-switch button:hover,
.singbox-info-actions button:hover,
.singbox-workspace-actions button:hover,
.server-row:hover {
background: #2d3543;
}
@@ -1221,35 +1381,6 @@ button.summary-card:hover {
animation: spin 0.75s linear infinite;
}
.setup-toggle {
display: inline-flex;
gap: 7px;
align-items: center;
width: fit-content;
min-height: 0;
border: 0;
background: transparent;
color: #93c5fd;
padding: 4px 0 0;
text-align: left;
cursor: pointer;
}
.setup-toggle:hover {
color: #bfdbfe;
}
.setup-toggle .ui-button-label > span {
display: inline-block;
border: 1px solid #343b49;
border-radius: 4px;
background: #1b202b;
color: #cbd5e1;
padding: 1px 6px;
font-size: 11px;
font-weight: 700;
}
.setup-strip {
display: grid;
grid-template-columns: auto minmax(0, 1fr);
@@ -1313,52 +1444,6 @@ button.summary-card:hover {
white-space: nowrap;
}
.setup-details {
display: grid;
grid-column: 2 / -1;
gap: 6px;
border-top: 1px solid #2b3342;
margin-top: 2px;
padding-top: 10px;
}
.setup-item {
display: grid;
grid-template-columns: auto minmax(0, 1fr);
gap: 9px;
align-items: start;
min-height: 34px;
border: 1px solid #263040;
border-radius: 4px;
background: #111720;
padding: 8px 9px;
}
.setup-state-dot {
width: 9px;
height: 9px;
border-radius: 999px;
background: #f59e0b;
box-shadow: 0 0 0 3px rgba(245, 158, 11, 0.12);
margin-top: 5px;
}
.setup-item.installed .setup-state-dot {
background: #22c55e;
box-shadow: 0 0 0 3px rgba(34, 197, 94, 0.12);
}
.setup-item strong,
.setup-item span {
display: block;
overflow-wrap: anywhere;
}
.setup-item span {
color: #9aa8bd;
font-size: 13px;
}
.service-actions {
position: relative;
display: flex;
@@ -1434,62 +1519,71 @@ button.summary-card:hover {
.route-panel {
display: grid;
gap: 8px;
margin-top: 10px;
margin-top: 8px;
border: 1px solid #2b3342;
border-radius: 4px;
background: #151923;
padding: 10px;
}
.external-proxy-card {
.connection-check {
display: grid;
gap: 9px;
}
.proxy-check-line {
display: grid;
grid-template-columns: minmax(0, 1fr) 132px;
grid-template-columns: minmax(0, 1fr) minmax(180px, 260px) 132px;
gap: 8px;
align-items: stretch;
}
.proxy-check-state,
.route-preview {
.connection-check-main,
.connection-endpoint {
display: grid;
align-content: center;
gap: 3px;
min-width: 0;
min-height: 46px;
border: 1px solid #2b3342;
border: 1px solid #263040;
border-radius: 4px;
background: #111720;
padding: 9px 11px;
background: #0d1016;
padding: 8px 10px;
}
.proxy-check-state.ok {
border-color: rgba(34, 197, 94, 0.42);
.connection-endpoint {
cursor: pointer;
}
.proxy-check-state.warning {
border-color: rgba(245, 158, 11, 0.42);
.connection-endpoint:hover {
border-color: #3b82f6;
background: #101827;
}
.proxy-check-state.error {
border-color: rgba(239, 68, 68, 0.42);
}
.proxy-check-state strong,
.route-preview strong {
color: #eef2ff;
overflow-wrap: anywhere;
}
.proxy-check-state span,
.route-preview span {
.connection-check-main > span,
.connection-endpoint > span {
color: #8d99ae;
font-size: 12px;
font-weight: 700;
}
.connection-check-main strong,
.connection-endpoint strong {
min-width: 0;
color: #eef2ff;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.connection-check-main p {
color: #9aa8bd;
margin: 0;
overflow-wrap: anywhere;
}
.route-preview {
margin-top: 2px;
.connection-check .ui-button {
width: 100%;
}
.external-proxy-card {
display: grid;
gap: 9px;
}
.simple-field {
@@ -1502,6 +1596,12 @@ button.summary-card:hover {
margin: 0;
}
.field-error {
color: #fcd34d;
font-size: 12px;
overflow-wrap: anywhere;
}
.simple-field input,
.process-add-line input,
.subscription-line input {
@@ -1531,82 +1631,30 @@ button.summary-card:hover {
padding-top: 10px;
}
.singbox-inline-actions {
display: flex;
align-items: center;
flex-wrap: wrap;
.setup-summary {
display: inline-grid;
grid-template-columns: auto auto;
gap: 6px;
}
.info-toggle {
display: grid;
place-items: center;
width: 28px;
height: 24px;
align-items: center;
width: fit-content;
border: 1px solid #263040;
border-radius: 4px;
background: #111720;
color: #bfdbfe;
cursor: pointer;
}
.info-toggle:hover {
background: #1d2735;
}
.info-toggle svg {
display: block;
}
.singbox-info-popover {
position: relative;
z-index: 8;
display: grid;
grid-column: 1 / -1;
gap: 10px;
width: 100%;
border: 1px solid #343b49;
border-radius: 4px;
background: #111720;
box-shadow: none;
padding: 12px;
}
.singbox-info-grid {
display: grid;
grid-template-columns: 86px minmax(0, 1fr);
gap: 7px 10px;
align-items: start;
}
.singbox-info-grid span {
color: #8d99ae;
padding: 4px 7px;
font-size: 12px;
}
.singbox-info-grid strong {
min-width: 0;
color: #dbeafe;
font-size: 12px;
overflow-wrap: anywhere;
}
.singbox-info-actions {
justify-content: flex-start;
gap: 7px;
}
.singbox-info-actions button {
display: inline-flex;
align-items: center;
gap: 7px;
min-height: 32px;
color: #dbeafe;
font-weight: 700;
}
.singbox-info-actions button svg {
display: block;
.setup-summary:hover {
border-color: #3b82f6;
background: #162033;
}
.setup-summary strong {
color: #dbeafe;
font-size: 11px;
}
.route-switch {
@@ -1669,6 +1717,30 @@ button.summary-card:hover {
color: #fecaca;
}
.singbox-workspace-head {
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
min-width: 0;
}
.singbox-workspace-head > span {
color: #8d99ae;
font-size: 12px;
font-weight: 800;
}
.singbox-workspace-actions {
justify-content: flex-end;
gap: 7px;
}
.singbox-workspace-actions .ui-button {
min-height: 32px;
color: #dbeafe;
}
.server-list {
display: grid;
grid-template-columns: repeat(5, minmax(0, 1fr));
@@ -1711,6 +1783,7 @@ button.summary-card:hover {
}
.server-select-button {
position: relative;
display: flex;
align-items: center;
gap: 7px;
@@ -1765,18 +1838,90 @@ button.summary-card:hover {
font-weight: 800;
}
.singbox-install-note {
.route-chain {
display: grid;
grid-column: 1 / -1;
gap: 3px;
border-top: 1px solid #2b3342;
color: #9aa8bd;
margin-top: 2px;
padding-top: 10px;
grid-template-columns: repeat(4, minmax(0, 1fr));
gap: 7px;
margin-top: 8px;
}
.singbox-install-note strong {
color: #e5e7eb;
.route-chain-segment {
display: grid;
grid-template-columns: auto minmax(0, 1fr) auto;
grid-template-areas:
"dot label arrow"
"dot value arrow";
gap: 2px 8px;
align-items: center;
min-width: 0;
min-height: 48px;
border: 1px solid #2b3342;
border-radius: 4px;
background: #111720;
color: #dbeafe;
cursor: pointer;
padding: 8px 9px;
width: 100%;
}
.route-chain-segment:hover {
border-color: #3b82f6;
background: #141d2b;
}
.route-chain-segment > span:not(.route-chain-dot):not(.route-chain-arrow) {
grid-area: label;
color: #8d99ae;
font-size: 11px;
font-weight: 800;
}
.route-chain-segment strong {
grid-area: value;
min-width: 0;
overflow: hidden;
color: #eef2ff;
font-size: 13px;
text-overflow: ellipsis;
white-space: nowrap;
}
.route-chain-dot {
grid-area: dot;
width: 9px;
height: 9px;
border-radius: 999px;
background: #64748b;
box-shadow: 0 0 0 3px rgba(100, 116, 139, 0.12);
}
.route-chain-arrow {
grid-area: arrow;
color: #64748b;
font-weight: 800;
}
.route-chain-segment.ok .route-chain-dot {
background: #22c55e;
box-shadow: 0 0 0 3px rgba(34, 197, 94, 0.12);
}
.route-chain-segment.warning .route-chain-dot {
background: #f59e0b;
box-shadow: 0 0 0 3px rgba(245, 158, 11, 0.12);
}
.route-chain-segment.error .route-chain-dot {
background: #ef4444;
box-shadow: 0 0 0 3px rgba(239, 68, 68, 0.12);
}
.route-chain-segment.checking .route-chain-dot {
border: 2px solid #3b82f6;
border-top-color: transparent;
background: transparent;
box-shadow: none;
animation: spin 0.75s linear infinite;
}
.apply-state {
@@ -1893,6 +2038,10 @@ button.summary-card:hover {
.ui-icon-button[data-tooltip]::before,
.ui-icon-button[data-tooltip]::after,
.ui-hover-details[data-tooltip]::before,
.ui-hover-details[data-tooltip]::after,
.server-select-button[data-tooltip]::before,
.server-select-button[data-tooltip]::after,
.add-tile[data-tooltip]::before,
.add-tile[data-tooltip]::after {
position: absolute;
@@ -1907,6 +2056,8 @@ button.summary-card:hover {
}
.ui-icon-button[data-tooltip]::before,
.ui-hover-details[data-tooltip]::before,
.server-select-button[data-tooltip]::before,
.add-tile[data-tooltip]::before {
bottom: calc(100% + 3px);
width: 8px;
@@ -1919,8 +2070,12 @@ button.summary-card:hover {
}
.ui-icon-button[data-tooltip]::after,
.ui-hover-details[data-tooltip]::after,
.server-select-button[data-tooltip]::after,
.add-tile[data-tooltip]::after {
bottom: calc(100% + 8px);
width: max-content;
max-width: min(360px, calc(100vw - 32px));
border: 1px solid #334155;
border-radius: 4px;
background: #0d1118;
@@ -1929,16 +2084,25 @@ button.summary-card:hover {
content: attr(data-tooltip);
font-size: 12px;
font-weight: 750;
line-height: 1;
line-height: 1.25;
overflow-wrap: break-word;
padding: 7px 8px;
transform: translate(-50%, 4px);
white-space: nowrap;
white-space: pre-line;
}
.ui-icon-button[data-tooltip]:hover::before,
.ui-icon-button[data-tooltip]:hover::after,
.ui-icon-button[data-tooltip]:focus-visible::before,
.ui-icon-button[data-tooltip]:focus-visible::after,
.ui-hover-details[data-tooltip]:hover::before,
.ui-hover-details[data-tooltip]:hover::after,
.ui-hover-details[data-tooltip]:focus-visible::before,
.ui-hover-details[data-tooltip]:focus-visible::after,
.server-select-button[data-tooltip]:hover::before,
.server-select-button[data-tooltip]:hover::after,
.server-select-button[data-tooltip]:focus-visible::before,
.server-select-button[data-tooltip]:focus-visible::after,
.add-tile[data-tooltip]:hover::before,
.add-tile[data-tooltip]:hover::after,
.add-tile[data-tooltip]:focus-visible::before,
@@ -1948,6 +2112,10 @@ button.summary-card:hover {
.ui-icon-button[data-tooltip]:hover::before,
.ui-icon-button[data-tooltip]:focus-visible::before,
.ui-hover-details[data-tooltip]:hover::before,
.ui-hover-details[data-tooltip]:focus-visible::before,
.server-select-button[data-tooltip]:hover::before,
.server-select-button[data-tooltip]:focus-visible::before,
.add-tile[data-tooltip]:hover::before,
.add-tile[data-tooltip]:focus-visible::before {
transform: translate(-50%, 0) rotate(45deg);
@@ -1955,6 +2123,10 @@ button.summary-card:hover {
.ui-icon-button[data-tooltip]:hover::after,
.ui-icon-button[data-tooltip]:focus-visible::after,
.ui-hover-details[data-tooltip]:hover::after,
.ui-hover-details[data-tooltip]:focus-visible::after,
.server-select-button[data-tooltip]:hover::after,
.server-select-button[data-tooltip]:focus-visible::after,
.add-tile[data-tooltip]:hover::after,
.add-tile[data-tooltip]:focus-visible::after {
transform: translate(-50%, 0);
@@ -2115,7 +2287,6 @@ button.summary-card:hover {
}
.command-row .ui-button,
.proxy-check-line .ui-button,
.subscription-line .ui-button {
width: 100%;
}
@@ -2509,7 +2680,13 @@ button.summary-card:hover {
flex-direction: column;
}
.panel-section-head > strong {
.proxy-overview-head {
align-items: stretch;
flex-direction: column;
}
.panel-section-head > strong,
.proxy-overview-head > strong {
text-align: left;
}
@@ -2558,10 +2735,14 @@ button.summary-card:hover {
grid-template-columns: 1fr;
}
.proxy-check-line {
.connection-check {
grid-template-columns: 1fr;
}
.route-chain {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
.add-toolbar {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
@@ -2625,20 +2806,21 @@ button.summary-card:hover {
overflow: visible;
}
.setup-details {
grid-column: 1 / -1;
}
.service-button {
flex: 1;
}
.singbox-info-popover {
width: auto;
.singbox-workspace-head {
align-items: stretch;
flex-direction: column;
}
.singbox-info-grid {
grid-template-columns: 70px minmax(0, 1fr);
.singbox-workspace-actions {
width: 100%;
}
.singbox-workspace-actions .ui-button {
flex: 1;
}
.server-list {