change UI set Endpoint
This commit is contained in:
@@ -22,6 +22,7 @@ import {
|
|||||||
ShieldCheck,
|
ShieldCheck,
|
||||||
Trash2,
|
Trash2,
|
||||||
WifiOff,
|
WifiOff,
|
||||||
|
X,
|
||||||
XCircle
|
XCircle
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import {
|
import {
|
||||||
@@ -159,6 +160,7 @@ function App() {
|
|||||||
const [toast, setToast] = useState(null);
|
const [toast, setToast] = useState(null);
|
||||||
const [busyAction, setBusyAction] = useState('');
|
const [busyAction, setBusyAction] = useState('');
|
||||||
const [activeTask, setActiveTask] = useState(null);
|
const [activeTask, setActiveTask] = useState(null);
|
||||||
|
const [endpointDialogOpen, setEndpointDialogOpen] = useState(false);
|
||||||
|
|
||||||
const packageBaseUrl = settings.packageBaseUrl;
|
const packageBaseUrl = settings.packageBaseUrl;
|
||||||
const agentBaseUrl = settings.agentBaseUrl;
|
const agentBaseUrl = settings.agentBaseUrl;
|
||||||
@@ -425,13 +427,25 @@ function App() {
|
|||||||
}
|
}
|
||||||
}, [agentBaseUrl, agentHealth, notify, packageBaseUrl, startPreflightFailedTask, startTask]);
|
}, [agentBaseUrl, agentHealth, notify, packageBaseUrl, startPreflightFailedTask, startTask]);
|
||||||
|
|
||||||
|
const openEndpointDialog = useCallback(() => {
|
||||||
|
setDraftSettings(settings);
|
||||||
|
setEndpointDialogOpen(true);
|
||||||
|
}, [settings]);
|
||||||
|
|
||||||
|
const closeEndpointDialog = useCallback(() => {
|
||||||
|
setDraftSettings(settings);
|
||||||
|
setEndpointDialogOpen(false);
|
||||||
|
}, [settings]);
|
||||||
|
|
||||||
const applySettings = useCallback(() => {
|
const applySettings = useCallback(() => {
|
||||||
const nextSettings = {
|
const nextSettings = {
|
||||||
packageBaseUrl: normalizeUrl(draftSettings.packageBaseUrl || DEFAULT_PACKAGE_BASE_URL),
|
packageBaseUrl: normalizeUrl(draftSettings.packageBaseUrl || DEFAULT_PACKAGE_BASE_URL),
|
||||||
agentBaseUrl: normalizeUrl(draftSettings.agentBaseUrl || DEFAULT_AGENT_BASE_URL)
|
agentBaseUrl: normalizeUrl(draftSettings.agentBaseUrl || DEFAULT_AGENT_BASE_URL)
|
||||||
};
|
};
|
||||||
setSettings(nextSettings);
|
setSettings(nextSettings);
|
||||||
|
setDraftSettings(nextSettings);
|
||||||
saveSettings(nextSettings);
|
saveSettings(nextSettings);
|
||||||
|
setEndpointDialogOpen(false);
|
||||||
notify('info', 'Đã cập nhật endpoint test');
|
notify('info', 'Đã cập nhật endpoint test');
|
||||||
}, [draftSettings, notify]);
|
}, [draftSettings, notify]);
|
||||||
|
|
||||||
@@ -507,6 +521,19 @@ function App() {
|
|||||||
return () => window.clearTimeout(timer);
|
return () => window.clearTimeout(timer);
|
||||||
}, [toast]);
|
}, [toast]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!endpointDialogOpen) return undefined;
|
||||||
|
|
||||||
|
function onKeyDown(event) {
|
||||||
|
if (event.key === 'Escape') {
|
||||||
|
closeEndpointDialog();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener('keydown', onKeyDown);
|
||||||
|
return () => window.removeEventListener('keydown', onKeyDown);
|
||||||
|
}, [closeEndpointDialog, endpointDialogOpen]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="app-shell">
|
<div className="app-shell">
|
||||||
<aside className="sidebar">
|
<aside className="sidebar">
|
||||||
@@ -536,29 +563,19 @@ function App() {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<div className="nav-label">Endpoint</div>
|
<div className="nav-label">Endpoint</div>
|
||||||
<label className="settings-field">
|
<div className="endpoint-summary" aria-label="Current endpoints">
|
||||||
|
<div className="endpoint-summary-row">
|
||||||
<span>Package server</span>
|
<span>Package server</span>
|
||||||
<input
|
<strong title={packageBaseUrl}>{packageBaseUrl}</strong>
|
||||||
value={draftSettings.packageBaseUrl}
|
</div>
|
||||||
onChange={(event) => setDraftSettings((current) => ({
|
<div className="endpoint-summary-row">
|
||||||
...current,
|
|
||||||
packageBaseUrl: event.target.value
|
|
||||||
}))}
|
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
<label className="settings-field">
|
|
||||||
<span>Local Agent</span>
|
<span>Local Agent</span>
|
||||||
<input
|
<strong title={agentBaseUrl}>{agentBaseUrl}</strong>
|
||||||
value={draftSettings.agentBaseUrl}
|
</div>
|
||||||
onChange={(event) => setDraftSettings((current) => ({
|
</div>
|
||||||
...current,
|
<button className="btn btn-secondary full" type="button" onClick={openEndpointDialog}>
|
||||||
agentBaseUrl: event.target.value
|
|
||||||
}))}
|
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
<button className="btn btn-secondary full" type="button" onClick={applySettings}>
|
|
||||||
<Settings size={15} aria-hidden="true" />
|
<Settings size={15} aria-hidden="true" />
|
||||||
Apply
|
Endpoint settings
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</aside>
|
</aside>
|
||||||
@@ -814,6 +831,78 @@ function App() {
|
|||||||
</main>
|
</main>
|
||||||
|
|
||||||
{toast && <Toast toast={toast} />}
|
{toast && <Toast toast={toast} />}
|
||||||
|
{endpointDialogOpen && (
|
||||||
|
<EndpointDialog
|
||||||
|
draftSettings={draftSettings}
|
||||||
|
onApply={applySettings}
|
||||||
|
onCancel={closeEndpointDialog}
|
||||||
|
onChange={setDraftSettings}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function EndpointDialog({ draftSettings, onApply, onCancel, onChange }) {
|
||||||
|
return (
|
||||||
|
<div className="dialog-backdrop">
|
||||||
|
<section
|
||||||
|
aria-labelledby="endpoint-dialog-title"
|
||||||
|
aria-modal="true"
|
||||||
|
className="dialog-panel"
|
||||||
|
role="dialog"
|
||||||
|
>
|
||||||
|
<form
|
||||||
|
onSubmit={(event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
onApply();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className="dialog-header">
|
||||||
|
<div>
|
||||||
|
<h2 id="endpoint-dialog-title">Endpoint settings</h2>
|
||||||
|
<p>Changes only apply after you press Apply.</p>
|
||||||
|
</div>
|
||||||
|
<button className="icon-button subtle" type="button" title="Close" onClick={onCancel}>
|
||||||
|
<X size={16} aria-hidden="true" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="dialog-body">
|
||||||
|
<label className="settings-field">
|
||||||
|
<span>Package server</span>
|
||||||
|
<input
|
||||||
|
autoFocus
|
||||||
|
value={draftSettings.packageBaseUrl}
|
||||||
|
onChange={(event) => onChange((current) => ({
|
||||||
|
...current,
|
||||||
|
packageBaseUrl: event.target.value
|
||||||
|
}))}
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
<label className="settings-field">
|
||||||
|
<span>Local Agent</span>
|
||||||
|
<input
|
||||||
|
value={draftSettings.agentBaseUrl}
|
||||||
|
onChange={(event) => onChange((current) => ({
|
||||||
|
...current,
|
||||||
|
agentBaseUrl: event.target.value
|
||||||
|
}))}
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="dialog-actions">
|
||||||
|
<button className="btn btn-secondary" type="button" onClick={onCancel}>
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
<button className="btn btn-primary" type="submit">
|
||||||
|
<Settings size={15} aria-hidden="true" />
|
||||||
|
Apply endpoints
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</section>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -144,6 +144,7 @@ code {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.brand-copy span,
|
.brand-copy span,
|
||||||
|
.endpoint-summary-row span,
|
||||||
.settings-field span,
|
.settings-field span,
|
||||||
.nav-label {
|
.nav-label {
|
||||||
color: var(--on-surface-variant);
|
color: var(--on-surface-variant);
|
||||||
@@ -237,6 +238,35 @@ code {
|
|||||||
gap: 5px;
|
gap: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.endpoint-summary {
|
||||||
|
background: rgba(255, 255, 255, 0.68);
|
||||||
|
border: 1px solid rgba(169, 180, 185, 0.35);
|
||||||
|
border-radius: var(--radius);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 9px;
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.endpoint-summary-row {
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.endpoint-summary-row span,
|
||||||
|
.endpoint-summary-row strong {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.endpoint-summary-row strong {
|
||||||
|
color: #172033;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 800;
|
||||||
|
margin-top: 4px;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
.settings-field input,
|
.settings-field input,
|
||||||
.filter-field input,
|
.filter-field input,
|
||||||
.filter-field select {
|
.filter-field select {
|
||||||
@@ -998,6 +1028,71 @@ tbody tr.selected-row td.action-col {
|
|||||||
overflow-wrap: anywhere;
|
overflow-wrap: anywhere;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dialog-backdrop {
|
||||||
|
align-items: center;
|
||||||
|
background: rgba(15, 23, 42, 0.36);
|
||||||
|
display: flex;
|
||||||
|
inset: 0;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 24px;
|
||||||
|
position: fixed;
|
||||||
|
z-index: 110;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-panel {
|
||||||
|
background: var(--surface-lowest);
|
||||||
|
border: 1px solid rgba(169, 180, 185, 0.5);
|
||||||
|
border-radius: var(--radius);
|
||||||
|
box-shadow: var(--shadow-lg);
|
||||||
|
max-height: calc(100vh - 48px);
|
||||||
|
overflow: auto;
|
||||||
|
width: min(480px, 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-panel form {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-header {
|
||||||
|
align-items: flex-start;
|
||||||
|
border-bottom: 1px solid #eef2f7;
|
||||||
|
display: flex;
|
||||||
|
gap: 14px;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-header h2 {
|
||||||
|
color: #111827;
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 800;
|
||||||
|
line-height: 1.2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-header p {
|
||||||
|
color: var(--on-surface-variant);
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 1.45;
|
||||||
|
margin-top: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-body {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 12px;
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-actions {
|
||||||
|
align-items: center;
|
||||||
|
border-top: 1px solid #eef2f7;
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
justify-content: flex-end;
|
||||||
|
padding: 12px 16px 16px;
|
||||||
|
}
|
||||||
|
|
||||||
.toast {
|
.toast {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
background: #111827;
|
background: #111827;
|
||||||
@@ -1143,4 +1238,18 @@ tbody tr.selected-row td.action-col {
|
|||||||
gap: 6px;
|
gap: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dialog-backdrop {
|
||||||
|
align-items: flex-start;
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-actions {
|
||||||
|
align-items: stretch;
|
||||||
|
flex-direction: column-reverse;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-actions .btn {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user