milestone 32: task-shaped configuration, file mode as a rendering, config status api
Gates / frontend (push) Successful in 1m22s
Gates / test (push) Successful in 1m54s
Gates / test-aarch64 (push) Successful in 7m57s
Gates / package (push) Successful in 5m29s
Gates / container (push) Successful in 10s
CI / gates (push) Successful in 32m51s
Gates / frontend (push) Successful in 1m22s
Gates / test (push) Successful in 1m54s
Gates / test-aarch64 (push) Successful in 7m57s
Gates / package (push) Successful in 5m29s
Gates / container (push) Successful in 10s
CI / gates (push) Successful in 32m51s
This commit is contained in:
@@ -4,10 +4,12 @@ import * as stylex from "@stylexjs/stylex";
|
||||
import { clientUpdateMutation } from "@/lib/queries";
|
||||
import type { Client, Group } from "@/lib/types";
|
||||
import InlineError from "@/lib/InlineError";
|
||||
import ConfigLockIndicator from "@/features/configuration/ConfigLockIndicator";
|
||||
import { useReadOnlyConfig } from "@/features/configuration/authority";
|
||||
import Dialog from "@/ui/Dialog";
|
||||
import Select from "@/ui/Select";
|
||||
import { styles as shared } from "@/ui/styles";
|
||||
import { READ_ONLY_HINT, useReadOnlyConfig } from "@/features/settings/authority";
|
||||
import { colors } from "@/ui/tokens.stylex";
|
||||
|
||||
interface Props {
|
||||
client: Client;
|
||||
@@ -39,20 +41,30 @@ const styles = stylex.create({
|
||||
},
|
||||
actions: {
|
||||
display: "flex",
|
||||
flexWrap: "wrap",
|
||||
alignItems: "center",
|
||||
justifyContent: "flex-end",
|
||||
gap: "0.5rem",
|
||||
},
|
||||
lockNote: {
|
||||
marginRight: "auto",
|
||||
fontSize: "0.75rem",
|
||||
lineHeight: "1rem",
|
||||
color: colors.textMuted,
|
||||
},
|
||||
});
|
||||
|
||||
export default function ClientEditDialog({ client, groups, onClose }: Props) {
|
||||
const queryClient = useQueryClient();
|
||||
const mutation = useMutation(clientUpdateMutation(queryClient));
|
||||
const readOnly = useReadOnlyConfig();
|
||||
// Adopting the learned name as a typed one is the natural gesture, but only
|
||||
// where the save can land: under file authority the PUT answers 403, and the
|
||||
// file's declared name is the one that wins.
|
||||
const [name, setName] = useState(client.name === "" && !readOnly ? client.learned_name : client.name);
|
||||
// Adopting the learned name as a typed one is the natural gesture.
|
||||
const [name, setName] = useState(client.name === "" ? client.learned_name : client.name);
|
||||
const [groupId, setGroupId] = useState(client.group_id);
|
||||
// The dialog opens only where the save can land, but authority is polled and
|
||||
// can turn under an open dialog. So the save path consults it on every render
|
||||
// rather than trusting the state that was true when the dialog opened; the
|
||||
// draft stays on screen, locked, instead of vanishing mid-edit.
|
||||
const readOnly = useReadOnlyConfig();
|
||||
|
||||
return (
|
||||
<Dialog label={`Edit client ${client.ip}`} isOpen onClose={onClose}>
|
||||
@@ -61,6 +73,9 @@ export default function ClientEditDialog({ client, groups, onClose }: Props) {
|
||||
{...stylex.props(styles.form)}
|
||||
onSubmit={(event) => {
|
||||
event.preventDefault();
|
||||
// Enter in the name field submits without the Save button, so
|
||||
// the lock lives here too, not only in what is rendered.
|
||||
if (readOnly) return;
|
||||
mutation.mutate(
|
||||
{ id: client.id, edit: { name: name.trim(), group_id: groupId } },
|
||||
{ onSuccess: onClose },
|
||||
@@ -86,17 +101,24 @@ export default function ClientEditDialog({ client, groups, onClose }: Props) {
|
||||
/>
|
||||
<InlineError error={mutation.error} />
|
||||
<div {...stylex.props(styles.actions)}>
|
||||
{readOnly && (
|
||||
<span {...stylex.props(styles.lockNote)}>
|
||||
This edit can no longer be saved.
|
||||
<ConfigLockIndicator />
|
||||
</span>
|
||||
)}
|
||||
<button type="button" onClick={onClose} {...stylex.props(shared.button, shared.focusRing)}>
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
type="submit"
|
||||
disabled={mutation.isPending || readOnly}
|
||||
title={readOnly ? READ_ONLY_HINT : undefined}
|
||||
{...stylex.props(shared.primaryButton, shared.focusRing)}
|
||||
>
|
||||
Save
|
||||
</button>
|
||||
{!readOnly && (
|
||||
<button
|
||||
type="submit"
|
||||
disabled={mutation.isPending}
|
||||
{...stylex.props(shared.primaryButton, shared.focusRing)}
|
||||
>
|
||||
Save
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</form>
|
||||
</Dialog>
|
||||
|
||||
Reference in New Issue
Block a user