Restore proxy endpoint setting
This commit is contained in:
@@ -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.
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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 () => {
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
11
src/app.css
11
src/app.css
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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: 'Сервис',
|
||||||
|
|||||||
Reference in New Issue
Block a user