change UI set Endpoint
This commit is contained in:
@@ -22,6 +22,7 @@ import {
|
||||
ShieldCheck,
|
||||
Trash2,
|
||||
WifiOff,
|
||||
X,
|
||||
XCircle
|
||||
} from 'lucide-react';
|
||||
import {
|
||||
@@ -159,6 +160,7 @@ function App() {
|
||||
const [toast, setToast] = useState(null);
|
||||
const [busyAction, setBusyAction] = useState('');
|
||||
const [activeTask, setActiveTask] = useState(null);
|
||||
const [endpointDialogOpen, setEndpointDialogOpen] = useState(false);
|
||||
|
||||
const packageBaseUrl = settings.packageBaseUrl;
|
||||
const agentBaseUrl = settings.agentBaseUrl;
|
||||
@@ -425,13 +427,25 @@ function App() {
|
||||
}
|
||||
}, [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 nextSettings = {
|
||||
packageBaseUrl: normalizeUrl(draftSettings.packageBaseUrl || DEFAULT_PACKAGE_BASE_URL),
|
||||
agentBaseUrl: normalizeUrl(draftSettings.agentBaseUrl || DEFAULT_AGENT_BASE_URL)
|
||||
};
|
||||
setSettings(nextSettings);
|
||||
setDraftSettings(nextSettings);
|
||||
saveSettings(nextSettings);
|
||||
setEndpointDialogOpen(false);
|
||||
notify('info', 'Đã cập nhật endpoint test');
|
||||
}, [draftSettings, notify]);
|
||||
|
||||
@@ -507,6 +521,19 @@ function App() {
|
||||
return () => window.clearTimeout(timer);
|
||||
}, [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 (
|
||||
<div className="app-shell">
|
||||
<aside className="sidebar">
|
||||
@@ -536,29 +563,19 @@ function App() {
|
||||
/>
|
||||
|
||||
<div className="nav-label">Endpoint</div>
|
||||
<label className="settings-field">
|
||||
<span>Package server</span>
|
||||
<input
|
||||
value={draftSettings.packageBaseUrl}
|
||||
onChange={(event) => setDraftSettings((current) => ({
|
||||
...current,
|
||||
packageBaseUrl: event.target.value
|
||||
}))}
|
||||
/>
|
||||
</label>
|
||||
<label className="settings-field">
|
||||
<span>Local Agent</span>
|
||||
<input
|
||||
value={draftSettings.agentBaseUrl}
|
||||
onChange={(event) => setDraftSettings((current) => ({
|
||||
...current,
|
||||
agentBaseUrl: event.target.value
|
||||
}))}
|
||||
/>
|
||||
</label>
|
||||
<button className="btn btn-secondary full" type="button" onClick={applySettings}>
|
||||
<div className="endpoint-summary" aria-label="Current endpoints">
|
||||
<div className="endpoint-summary-row">
|
||||
<span>Package server</span>
|
||||
<strong title={packageBaseUrl}>{packageBaseUrl}</strong>
|
||||
</div>
|
||||
<div className="endpoint-summary-row">
|
||||
<span>Local Agent</span>
|
||||
<strong title={agentBaseUrl}>{agentBaseUrl}</strong>
|
||||
</div>
|
||||
</div>
|
||||
<button className="btn btn-secondary full" type="button" onClick={openEndpointDialog}>
|
||||
<Settings size={15} aria-hidden="true" />
|
||||
Apply
|
||||
Endpoint settings
|
||||
</button>
|
||||
</div>
|
||||
</aside>
|
||||
@@ -814,6 +831,78 @@ function App() {
|
||||
</main>
|
||||
|
||||
{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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -144,6 +144,7 @@ code {
|
||||
}
|
||||
|
||||
.brand-copy span,
|
||||
.endpoint-summary-row span,
|
||||
.settings-field span,
|
||||
.nav-label {
|
||||
color: var(--on-surface-variant);
|
||||
@@ -237,6 +238,35 @@ code {
|
||||
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,
|
||||
.filter-field input,
|
||||
.filter-field select {
|
||||
@@ -998,6 +1028,71 @@ tbody tr.selected-row td.action-col {
|
||||
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 {
|
||||
align-items: center;
|
||||
background: #111827;
|
||||
@@ -1143,4 +1238,18 @@ tbody tr.selected-row td.action-col {
|
||||
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