Restore proxy endpoint setting

This commit is contained in:
2026-04-02 01:42:45 +03:00
parent 19ac2966c8
commit 0ae46d22d2
6 changed files with 32 additions and 7 deletions

View File

@@ -35,3 +35,4 @@ Updated: 2026-04-02
19. Added service-removal confirmation with linked-user warnings, backend cascade deletion for removed services, and migration that strips persisted legacy `admin` services from stored state. 19. Added service-removal confirmation with linked-user warnings, backend cascade deletion for removed services, and migration that strips persisted legacy `admin` services from stored state.
20. Made `npm run dev` start both the Vite client and Express backend, added a Vite API proxy for local development, and restored `system` as the default panel theme so the login screen follows OS appearance. 20. Made `npm run dev` start both the Vite client and Express backend, added a Vite API proxy for local development, and restored `system` as the default panel theme so the login screen follows OS appearance.
21. Re-separated the Settings tab into distinct panel-settings and services cards so panel preferences no longer appear inside the Services section. 21. Re-separated the Settings tab into distinct panel-settings and services cards so panel preferences no longer appear inside the Services section.
22. Restored editable proxy endpoint in panel settings so copied proxy URLs and displayed user endpoints can be corrected from the UI.

View File

@@ -24,7 +24,7 @@ Updated: 2026-04-02
- `src/main.tsx`: application bootstrap - `src/main.tsx`: application bootstrap
- `src/App.tsx`: authenticated panel shell with API-backed login, `sessionStorage` token persistence, localized labels, early theme application, and protected panel mutations - `src/App.tsx`: authenticated panel shell with API-backed login, `sessionStorage` token persistence, localized labels, early theme application, and protected panel mutations
- `src/SystemTab.tsx`: Settings tab with separate panel-settings and services cards, unified service type editing, remove confirmation, and generated config preview - `src/SystemTab.tsx`: Settings tab with separate panel-settings and services cards, editable proxy endpoint, unified service type editing, remove confirmation, and generated config preview
- `src/App.test.tsx`: login-gate, preferences persistence, modal interaction, pause/resume, delete-confirm, and settings-save UI tests - `src/App.test.tsx`: login-gate, preferences persistence, modal interaction, pause/resume, delete-confirm, and settings-save UI tests
- `src/app.css`: full panel styling - `src/app.css`: full panel styling
- `src/data/mockDashboard.ts`: default panel state and frontend fallback snapshot - `src/data/mockDashboard.ts`: default panel state and frontend fallback snapshot

View File

@@ -142,7 +142,7 @@ describe('App login gate', () => {
expect(screen.queryByRole('dialog', { name: /delete user/i })).not.toBeInTheDocument(); expect(screen.queryByRole('dialog', { name: /delete user/i })).not.toBeInTheDocument();
}); });
it('uses a combined service type field in settings and applies saved ports to the local fallback state', async () => { it('uses a combined service type field in settings and applies saved proxy endpoint values to the local fallback state', async () => {
const user = userEvent.setup(); const user = userEvent.setup();
render(<App />); render(<App />);
@@ -151,12 +151,15 @@ describe('App login gate', () => {
expect(screen.getByRole('heading', { name: /panel settings/i })).toBeInTheDocument(); expect(screen.getByRole('heading', { name: /panel settings/i })).toBeInTheDocument();
expect(screen.getByRole('heading', { name: /^services$/i })).toBeInTheDocument(); expect(screen.getByRole('heading', { name: /^services$/i })).toBeInTheDocument();
expect(screen.queryByLabelText(/public host/i)).not.toBeInTheDocument(); expect(screen.getByLabelText(/proxy endpoint/i)).toBeInTheDocument();
expect(screen.queryByLabelText(/command/i)).not.toBeInTheDocument(); expect(screen.queryByLabelText(/command/i)).not.toBeInTheDocument();
expect(screen.queryByLabelText(/protocol/i)).not.toBeInTheDocument(); expect(screen.queryByLabelText(/protocol/i)).not.toBeInTheDocument();
expect(screen.getAllByLabelText(/type/i)).toHaveLength(3); expect(screen.getAllByLabelText(/type/i)).toHaveLength(3);
expect(screen.queryByRole('option', { name: /admin/i })).not.toBeInTheDocument(); expect(screen.queryByRole('option', { name: /admin/i })).not.toBeInTheDocument();
await user.clear(screen.getByLabelText(/proxy endpoint/i));
await user.type(screen.getByLabelText(/proxy endpoint/i), 'gw.example.net');
const firstPortInput = screen.getAllByLabelText(/port/i)[0]; const firstPortInput = screen.getAllByLabelText(/port/i)[0];
await user.clear(firstPortInput); await user.clear(firstPortInput);
await user.type(firstPortInput, '1180'); await user.type(firstPortInput, '1180');
@@ -164,7 +167,7 @@ describe('App login gate', () => {
await user.click(screen.getByRole('button', { name: /save settings/i })); await user.click(screen.getByRole('button', { name: /save settings/i }));
await user.click(screen.getByRole('button', { name: /users/i })); await user.click(screen.getByRole('button', { name: /users/i }));
expect(screen.getAllByText(/edge\.example\.net:1180/i).length).toBeGreaterThan(0); expect(screen.getAllByText(/gw\.example\.net:1180/i).length).toBeGreaterThan(0);
}); });
it('warns before deleting a service and removes linked users after confirmation', async () => { it('warns before deleting a service and removes linked users after confirmation', async () => {

View File

@@ -75,7 +75,19 @@ export default function SystemTab({
<div className="card-header"> <div className="card-header">
<h2>{text.settings.panelTitle}</h2> <h2>{text.settings.panelTitle}</h2>
</div> </div>
<div className="settings-toolbar"> <div className="panel-settings-grid">
<label className="field-group panel-settings-wide">
<span>{text.settings.proxyHost}</span>
<input
value={draft.publicHost}
onChange={(event) =>
setDraft((current) => ({
...current,
publicHost: event.target.value,
}))
}
/>
</label>
<label className="field-group compact-field"> <label className="field-group compact-field">
<span>{text.common.language}</span> <span>{text.common.language}</span>
<select <select

View File

@@ -450,13 +450,19 @@ button,
color: var(--muted); color: var(--muted);
} }
.settings-toolbar { .settings-toolbar,
.panel-settings-grid {
display: flex; display: flex;
align-items: end; align-items: end;
gap: 12px; gap: 12px;
flex-wrap: wrap; flex-wrap: wrap;
} }
.panel-settings-wide {
min-width: 280px;
flex: 1 1 320px;
}
.compact-field { .compact-field {
min-width: 220px; min-width: 220px;
flex: 0 1 240px; flex: 0 1 240px;
@@ -708,7 +714,8 @@ pre {
.shell-header, .shell-header,
.table-toolbar, .table-toolbar,
.settings-toolbar { .settings-toolbar,
.panel-settings-grid {
flex-direction: column; flex-direction: column;
align-items: flex-start; align-items: flex-start;
} }

View File

@@ -93,6 +93,7 @@ const text = {
}, },
settings: { settings: {
panelTitle: 'Panel settings', panelTitle: 'Panel settings',
proxyHost: 'Proxy endpoint',
title: 'Services', title: 'Services',
generatedConfig: 'Generated config', generatedConfig: 'Generated config',
serviceLabel: 'Service', serviceLabel: 'Service',
@@ -209,6 +210,7 @@ const text = {
}, },
settings: { settings: {
panelTitle: 'Настройки панели', panelTitle: 'Настройки панели',
proxyHost: 'Точка входа для прокси',
title: 'Сервисы', title: 'Сервисы',
generatedConfig: 'Сгенерированный конфиг', generatedConfig: 'Сгенерированный конфиг',
serviceLabel: 'Сервис', serviceLabel: 'Сервис',