This commit is contained in:
2025-11-14 02:35:17 +01:00
parent 6f4ff68062
commit a3c5494bf7
53 changed files with 269 additions and 340 deletions
+1 -1
View File
@@ -71,7 +71,7 @@ const SettingsModal: React.FC<SettingsModalProps> = ({
if (activeSectionConfig.component) {
const SectionComponent = activeSectionConfig.component;
sectionContent = <SectionComponent {...sectionProps} />;
} else if (typeof activeSectionConfig.render === 'function') {
} else if (activeSectionConfig.render) {
sectionContent = activeSectionConfig.render(sectionProps);
}
}
@@ -7,6 +7,7 @@ import {
} from 'react';
import type { JSX } from 'react';
import { CheckIcon, ChevronDownIcon } from '../../ui/icons';
import { isPlainObject } from '../../utils/typeGuards';
type CapabilityValue = string | number;
@@ -28,14 +29,18 @@ interface CapabilityDropdownProps {
summaryLabel?: string;
}
const isCapabilityOption = (
option: CapabilityDropdownOption | CapabilityValue | null | undefined,
): option is CapabilityDropdownOption => isPlainObject(option);
const resolveCapabilityValue = (
option: CapabilityDropdownOption | CapabilityValue | null | undefined,
): CapabilityValue | null => {
if (option == null) {
return null;
}
if (typeof option === 'string' || typeof option === 'number') {
return option;
if (!isCapabilityOption(option)) {
return option as CapabilityValue;
}
if (option.value != null) {
return option.value;
@@ -166,9 +171,9 @@ const CapabilityDropdown = ({
if (value == null) {
return null;
}
const label = typeof formatLabel === 'function'
const label = formatLabel
? formatLabel(value)
: (typeof option === 'object' && option?.label) || String(value);
: (isCapabilityOption(option) && option?.label) || String(value);
const selected = selectedValues.includes(value);
return (
<button
@@ -96,7 +96,7 @@ const ApiTokensSection = ({
const [newTokenExpires, setNewTokenExpires] = useState('');
const [newTokenCapabilitySetId, setNewTokenCapabilitySetId] = useState<string>('');
const [formError, setFormError] = useState<string | null>(null);
const supportsClipboardWrite = typeof navigator !== 'undefined' && Boolean(navigator?.clipboard?.writeText);
const supportsClipboardWrite = Boolean(navigator.clipboard?.writeText);
const [canCopyToken, setCanCopyToken] = useState<boolean>(supportsClipboardWrite);
const [copyFeedback, setCopyFeedback] = useState<CopyFeedbackState | null>(null);
@@ -125,12 +125,13 @@ const ApiTokensSection = ({
const capabilitySelectionOptions = useMemo<CapabilitySelectionOption[]>(() => (
Array.isArray(capabilities)
? capabilities.map((capability) => {
if (typeof capability !== 'string') {
return { value: String(capability ?? ''), label: String(capability ?? '') };
const capabilityText = `${capability ?? ''}`;
if (!capabilityText.includes(':')) {
return { value: capability, label: capabilityText };
}
const [namespace, action] = capability.split(':');
const [namespace, action] = capabilityText.split(':');
if (!namespace || !action) {
return { value: capability, label: capability };
return { value: capability, label: capabilityText };
}
const formattedNamespace = `${namespace.charAt(0).toUpperCase()}${namespace.slice(1)}`;
const formattedAction = action.replace(/_/g, ' ');
@@ -193,9 +194,6 @@ const ApiTokensSection = ({
});
try {
if (typeof navigator === 'undefined') {
throw new Error('Clipboard API unavailable');
}
await navigator.clipboard.writeText(createdToken);
showSuccess();
return;
@@ -214,8 +212,7 @@ const ApiTokensSection = ({
useEffect(() => {
setCopyFeedback(null);
const hasClipboard = typeof navigator !== 'undefined' && Boolean(navigator?.clipboard?.writeText);
setCanCopyToken(hasClipboard);
setCanCopyToken(Boolean(navigator.clipboard?.writeText));
}, [createdToken]);
const handleNewCapabilitySetChange = useCallback((event: ChangeEvent<HTMLSelectElement>) => {
@@ -8,6 +8,7 @@ import React, {
import type { SettingsSectionConfig } from '../SettingsModal';
import { IconX } from '../../ui/icons';
import CapabilityDropdown, { CapabilityDropdownOption } from '../components/CapabilityDropdown';
import { isPlainObject } from '../../utils/typeGuards';
type CapabilityValue = string | number;
type CapabilitySetId = string | number;
@@ -54,12 +55,15 @@ interface CapabilitySetsSectionProps {
type CapabilityOptionInput = CapabilityDropdownOption | CapabilityValue | null | undefined;
const isCapabilityOption = (option: CapabilityOptionInput): option is CapabilityDropdownOption =>
isPlainObject(option);
const resolveCapabilityValue = (option: CapabilityOptionInput): CapabilityValue | null => {
if (option == null) {
return null;
}
if (typeof option === 'string' || typeof option === 'number') {
return option;
if (!isCapabilityOption(option)) {
return option as CapabilityValue;
}
if (option.value != null) {
return option.value as CapabilityValue;
@@ -99,12 +103,13 @@ const CapabilitySetsSection: React.FC<CapabilitySetsSectionProps> = ({
const capabilitySelectionOptions = useMemo<CapabilityDropdownOption[]>(() => (
(capabilities ?? []).map((capability) => {
if (typeof capability !== 'string') {
return { value: capability, label: String(capability) };
const capabilityLabel = `${capability ?? ''}`;
if (!capabilityLabel.includes(':')) {
return { value: capability, label: capabilityLabel };
}
const [namespace, action] = capability.split(':');
const [namespace, action] = capabilityLabel.split(':');
if (!namespace || !action) {
return { value: capability, label: capability };
return { value: capability, label: capabilityLabel };
}
const formattedNamespace = `${namespace.charAt(0).toUpperCase()}${namespace.slice(1)}`;
const formattedAction = action.replace(/_/g, ' ');
@@ -161,7 +166,7 @@ const CapabilitySetsSection: React.FC<CapabilitySetsSectionProps> = ({
label: set.label || set.slug || set.id,
capabilities: Array.isArray(set.capabilities) ? set.capabilities : [],
isSystem: Boolean(set?.is_system),
version: typeof set?.cap_version === 'number' ? set.cap_version : null,
version: Number.isFinite(set?.cap_version) ? Number(set.cap_version) : null,
})),
[capabilitySets],
);
@@ -492,7 +497,7 @@ const CapabilitySetsSection: React.FC<CapabilitySetsSectionProps> = ({
: '—'}
</td>
<td>{isSystem ? 'Yes' : 'No'}</td>
<td>{typeof set.cap_version === 'number' ? set.cap_version : '—'}</td>
<td>{Number.isFinite(set.cap_version) ? Number(set.cap_version) : '—'}</td>
<td className="settings-table__actions">
{isSystem ? (
<span className="settings-status">System set</span>
+3 -5
View File
@@ -35,11 +35,9 @@ const useCapabilitySets = ({ api, notifyApiError, setStatusMessage, token }: Use
const applyCapabilitySets = useCallback((updater: CapabilitySet[] | ((prev: CapabilitySet[]) => CapabilitySet[])) => {
setCapabilitySets((previous) => {
const base = Array.isArray(previous) ? [...previous] : [];
const next = typeof updater === 'function'
? updater(base)
: Array.isArray(updater)
? [...updater]
: base;
const next = Array.isArray(updater)
? [...updater]
: updater(base);
const supportsLabels = next.some((item) => Object.prototype.hasOwnProperty.call(item || {}, 'label'));
setSupportsCapabilitySetLabels(supportsLabels);
return next;