capsapi
This commit is contained in:
@@ -27,20 +27,27 @@ const SettingsRoute = () => {
|
|||||||
creating,
|
creating,
|
||||||
deletingId,
|
deletingId,
|
||||||
regeneratingId,
|
regeneratingId,
|
||||||
updatingId,
|
|
||||||
createdSecret,
|
createdSecret,
|
||||||
refresh,
|
refresh,
|
||||||
|
refreshCapabilitySets,
|
||||||
create,
|
create,
|
||||||
revoke,
|
revoke,
|
||||||
regenerate,
|
regenerate,
|
||||||
updateCapabilities,
|
|
||||||
dismissSecret,
|
dismissSecret,
|
||||||
|
capabilitySets,
|
||||||
|
capabilitySetsLoading,
|
||||||
} = useApiTokens({ api, token, notifyApiError, setStatusMessage });
|
} = useApiTokens({ api, token, notifyApiError, setStatusMessage });
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
refresh();
|
refresh();
|
||||||
|
refreshCapabilitySets();
|
||||||
refreshPasskeys();
|
refreshPasskeys();
|
||||||
}, [refresh, refreshPasskeys]);
|
}, [refresh, refreshCapabilitySets, refreshPasskeys]);
|
||||||
|
|
||||||
|
const handleRefresh = useCallback(() => {
|
||||||
|
refresh();
|
||||||
|
refreshCapabilitySets();
|
||||||
|
}, [refresh, refreshCapabilitySets]);
|
||||||
|
|
||||||
const handleClose = useCallback(() => {
|
const handleClose = useCallback(() => {
|
||||||
dismissSecret();
|
dismissSecret();
|
||||||
@@ -71,14 +78,14 @@ const SettingsRoute = () => {
|
|||||||
creating={creating}
|
creating={creating}
|
||||||
deletingId={deletingId}
|
deletingId={deletingId}
|
||||||
regeneratingId={regeneratingId}
|
regeneratingId={regeneratingId}
|
||||||
updatingId={updatingId}
|
onRefresh={handleRefresh}
|
||||||
onRefresh={refresh}
|
|
||||||
onCreate={create}
|
onCreate={create}
|
||||||
onDelete={revoke}
|
onDelete={revoke}
|
||||||
onRegenerate={regenerate}
|
onRegenerate={regenerate}
|
||||||
onUpdateCapabilities={updateCapabilities}
|
|
||||||
createdToken={createdSecret}
|
createdToken={createdSecret}
|
||||||
onDismissCreatedToken={dismissSecret}
|
onDismissCreatedToken={dismissSecret}
|
||||||
|
capabilitySets={capabilitySets}
|
||||||
|
capabilitySetsLoading={capabilitySetsLoading}
|
||||||
passkeys={passkeys}
|
passkeys={passkeys}
|
||||||
passkeysSupported={passkeysSupported}
|
passkeysSupported={passkeysSupported}
|
||||||
passkeysLoading={passkeysLoading}
|
passkeysLoading={passkeysLoading}
|
||||||
|
|||||||
@@ -20,12 +20,12 @@ const SettingsModal = ({
|
|||||||
creating = false,
|
creating = false,
|
||||||
deletingId = null,
|
deletingId = null,
|
||||||
regeneratingId = null,
|
regeneratingId = null,
|
||||||
updatingId = null,
|
capabilitySets = [],
|
||||||
|
capabilitySetsLoading = false,
|
||||||
onRefresh,
|
onRefresh,
|
||||||
onCreate,
|
onCreate,
|
||||||
onDelete,
|
onDelete,
|
||||||
onRegenerate,
|
onRegenerate,
|
||||||
onUpdateCapabilities,
|
|
||||||
createdToken = null,
|
createdToken = null,
|
||||||
onDismissCreatedToken,
|
onDismissCreatedToken,
|
||||||
passkeys = [],
|
passkeys = [],
|
||||||
@@ -41,7 +41,7 @@ const SettingsModal = ({
|
|||||||
const [activeSection, setActiveSection] = useState(defaultSection);
|
const [activeSection, setActiveSection] = useState(defaultSection);
|
||||||
const [newTokenLabel, setNewTokenLabel] = useState('');
|
const [newTokenLabel, setNewTokenLabel] = useState('');
|
||||||
const [newTokenExpires, setNewTokenExpires] = useState('');
|
const [newTokenExpires, setNewTokenExpires] = useState('');
|
||||||
const [newTokenCapabilities, setNewTokenCapabilities] = useState([]);
|
const [newTokenCapabilitySetId, setNewTokenCapabilitySetId] = useState('');
|
||||||
const [formError, setFormError] = useState(null);
|
const [formError, setFormError] = useState(null);
|
||||||
const [newPasskeyNickname, setNewPasskeyNickname] = useState('');
|
const [newPasskeyNickname, setNewPasskeyNickname] = useState('');
|
||||||
|
|
||||||
@@ -58,7 +58,7 @@ const SettingsModal = ({
|
|||||||
setActiveSection(defaultSection);
|
setActiveSection(defaultSection);
|
||||||
setNewTokenLabel('');
|
setNewTokenLabel('');
|
||||||
setNewTokenExpires('');
|
setNewTokenExpires('');
|
||||||
setNewTokenCapabilities([]);
|
setNewTokenCapabilitySetId('');
|
||||||
setFormError(null);
|
setFormError(null);
|
||||||
setNewPasskeyNickname('');
|
setNewPasskeyNickname('');
|
||||||
}
|
}
|
||||||
@@ -120,57 +120,36 @@ const SettingsModal = ({
|
|||||||
[onRevokePasskey],
|
[onRevokePasskey],
|
||||||
);
|
);
|
||||||
|
|
||||||
const capabilityOptions = useMemo(
|
const capabilitySetOptions = useMemo(
|
||||||
() => [
|
() => capabilitySets.map((set) => ({
|
||||||
{ value: 'webdav', label: 'WebDAV access' },
|
value: set.id,
|
||||||
{ value: 'api', label: 'REST API access' },
|
label: set.label || set.slug || set.id,
|
||||||
],
|
capabilities: Array.isArray(set.capabilities) ? set.capabilities : [],
|
||||||
[],
|
})),
|
||||||
|
[capabilitySets],
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleNewCapabilityChange = useCallback((capability, enabled) => {
|
const capabilitySetMap = useMemo(
|
||||||
|
() => Object.fromEntries(capabilitySetOptions.map((option) => [option.value, option])),
|
||||||
|
[capabilitySetOptions],
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!capabilitySetOptions.length) {
|
||||||
|
setNewTokenCapabilitySetId('');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!newTokenCapabilitySetId
|
||||||
|
|| !capabilitySetOptions.some((option) => option.value === newTokenCapabilitySetId)) {
|
||||||
|
setNewTokenCapabilitySetId(capabilitySetOptions[0].value);
|
||||||
|
}
|
||||||
|
}, [capabilitySetOptions, newTokenCapabilitySetId]);
|
||||||
|
|
||||||
|
const handleNewCapabilitySetChange = useCallback((event) => {
|
||||||
setFormError(null);
|
setFormError(null);
|
||||||
setNewTokenCapabilities((previous) => {
|
setNewTokenCapabilitySetId(event.target.value);
|
||||||
if (enabled) {
|
|
||||||
if (previous.includes(capability)) {
|
|
||||||
return previous;
|
|
||||||
}
|
|
||||||
return [...previous, capability];
|
|
||||||
}
|
|
||||||
return previous.filter((value) => value !== capability);
|
|
||||||
});
|
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handleToggleTokenCapability = useCallback(
|
|
||||||
async (token, capability, enabled) => {
|
|
||||||
if (!token?.id || !onUpdateCapabilities) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const existing = Array.isArray(token.capabilities) ? [...token.capabilities] : [];
|
|
||||||
let next;
|
|
||||||
if (enabled) {
|
|
||||||
if (existing.includes(capability)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
next = [...existing, capability];
|
|
||||||
} else {
|
|
||||||
next = existing.filter((value) => value !== capability);
|
|
||||||
if (next.length === 0) {
|
|
||||||
setFormError('Tokens must have at least one capability.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
setFormError(null);
|
|
||||||
const result = await onUpdateCapabilities(token.id, next);
|
|
||||||
if (result === false) {
|
|
||||||
setFormError('Failed to update token capabilities.');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[onUpdateCapabilities],
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleCreateToken = useCallback(
|
const handleCreateToken = useCallback(
|
||||||
async (event) => {
|
async (event) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
@@ -190,25 +169,38 @@ const SettingsModal = ({
|
|||||||
normalizedExpires = parsed.toISOString();
|
normalizedExpires = parsed.toISOString();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!newTokenCapabilities.length) {
|
if (!capabilitySetOptions.length) {
|
||||||
setFormError('Select at least one capability.');
|
setFormError('Capability sets are still loading.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const selectedCapabilitySetId = newTokenCapabilitySetId
|
||||||
|
|| capabilitySetOptions[0]?.value;
|
||||||
|
if (!selectedCapabilitySetId) {
|
||||||
|
setFormError('Select a capability set.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = await onCreate?.({
|
const result = await onCreate?.({
|
||||||
label: normalizedLabel,
|
label: normalizedLabel,
|
||||||
expires_at: normalizedExpires,
|
expires_at: normalizedExpires,
|
||||||
capabilities: newTokenCapabilities,
|
capability_set_id: selectedCapabilitySetId,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (result !== false) {
|
if (result !== false) {
|
||||||
setNewTokenLabel('');
|
setNewTokenLabel('');
|
||||||
setNewTokenExpires('');
|
setNewTokenExpires('');
|
||||||
setNewTokenCapabilities([]);
|
setNewTokenCapabilitySetId(capabilitySetOptions[0]?.value || '');
|
||||||
setFormError(null);
|
setFormError(null);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[newTokenExpires, newTokenLabel, newTokenCapabilities, onCreate],
|
[
|
||||||
|
capabilitySetOptions,
|
||||||
|
newTokenCapabilitySetId,
|
||||||
|
newTokenExpires,
|
||||||
|
newTokenLabel,
|
||||||
|
onCreate,
|
||||||
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleRegenerateToken = useCallback(
|
const handleRegenerateToken = useCallback(
|
||||||
@@ -231,15 +223,15 @@ const SettingsModal = ({
|
|||||||
type="button"
|
type="button"
|
||||||
className="secondary"
|
className="secondary"
|
||||||
onClick={handleRefresh}
|
onClick={handleRefresh}
|
||||||
disabled={loading}
|
disabled={loading || capabilitySetsLoading}
|
||||||
>
|
>
|
||||||
{loading ? 'Refreshing…' : 'Refresh'}
|
{loading || capabilitySetsLoading ? 'Refreshing…' : 'Refresh'}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
API tokens can grant access to the REST API, WebDAV, or both. Select at least one
|
API tokens use predefined capability sets. Choose the set that matches the access you
|
||||||
capability for each token. You can adjust capabilities for existing tokens at any time.
|
need when creating or updating a token.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
{createdToken ? (
|
{createdToken ? (
|
||||||
@@ -279,28 +271,49 @@ const SettingsModal = ({
|
|||||||
onChange={(event) => setNewTokenExpires(event.target.value)}
|
onChange={(event) => setNewTokenExpires(event.target.value)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<fieldset className="settings-form__field">
|
<div className="settings-form__field">
|
||||||
<legend>Capabilities</legend>
|
<label htmlFor="api-token-capability-set">Capability set</label>
|
||||||
<div className="settings-form__choices">
|
<select
|
||||||
{capabilityOptions.map((option) => {
|
id="api-token-capability-set"
|
||||||
const checked = newTokenCapabilities.includes(option.value);
|
value={newTokenCapabilitySetId}
|
||||||
return (
|
onChange={handleNewCapabilitySetChange}
|
||||||
<label key={option.value} className="settings-choice">
|
disabled={
|
||||||
<input
|
creating
|
||||||
type="checkbox"
|
|| capabilitySetsLoading
|
||||||
checked={checked}
|
|| !capabilitySetOptions.length
|
||||||
onChange={(event) =>
|
|
||||||
handleNewCapabilityChange(option.value, event.target.checked)
|
|
||||||
}
|
}
|
||||||
/>
|
>
|
||||||
<span>{option.label}</span>
|
{!capabilitySetOptions.length ? (
|
||||||
</label>
|
<option value="">No capability sets available</option>
|
||||||
);
|
) : null}
|
||||||
})}
|
{capabilitySetOptions.map((option) => (
|
||||||
|
<option key={option.value} value={option.value}>
|
||||||
|
{option.label}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
{capabilitySetsLoading ? (
|
||||||
|
<small>Loading capability sets…</small>
|
||||||
|
) : null}
|
||||||
|
{!capabilitySetsLoading && !capabilitySetOptions.length ? (
|
||||||
|
<small>No capability sets available.</small>
|
||||||
|
) : null}
|
||||||
|
{!capabilitySetsLoading
|
||||||
|
&& capabilitySetMap[newTokenCapabilitySetId]?.capabilities?.length ? (
|
||||||
|
<small>
|
||||||
|
Includes: {capabilitySetMap[newTokenCapabilitySetId].capabilities.join(', ')}
|
||||||
|
</small>
|
||||||
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
</fieldset>
|
|
||||||
<div className="settings-form__actions">
|
<div className="settings-form__actions">
|
||||||
<button type="submit" disabled={creating}>
|
<button
|
||||||
|
type="submit"
|
||||||
|
disabled={
|
||||||
|
creating
|
||||||
|
|| capabilitySetsLoading
|
||||||
|
|| !capabilitySetOptions.length
|
||||||
|
}
|
||||||
|
>
|
||||||
{creating ? 'Creating…' : 'Create token'}
|
{creating ? 'Creating…' : 'Create token'}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -323,16 +336,23 @@ const SettingsModal = ({
|
|||||||
<th scope="col">Created</th>
|
<th scope="col">Created</th>
|
||||||
<th scope="col">Last used</th>
|
<th scope="col">Last used</th>
|
||||||
<th scope="col">Expires</th>
|
<th scope="col">Expires</th>
|
||||||
<th scope="col">Capabilities</th>
|
<th scope="col">Capability set</th>
|
||||||
<th scope="col">Actions</th>
|
<th scope="col">Actions</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{tokens.map((token) => {
|
{tokens.map((token) => {
|
||||||
const isRevoked = Boolean(token?.revoked_at);
|
const isRevoked = Boolean(token?.revoked_at);
|
||||||
const capabilitySet = Array.isArray(token?.capabilities)
|
const selectedSet = token?.capability_set_id
|
||||||
|
? capabilitySetMap[token.capability_set_id]
|
||||||
|
: null;
|
||||||
|
const capabilityList = Array.isArray(token?.capabilities) && token.capabilities.length
|
||||||
? token.capabilities
|
? token.capabilities
|
||||||
: [];
|
: selectedSet?.capabilities || [];
|
||||||
|
const capabilitySetLabel = selectedSet?.label
|
||||||
|
|| selectedSet?.slug
|
||||||
|
|| token.capability_set_id
|
||||||
|
|| '—';
|
||||||
return (
|
return (
|
||||||
<tr key={token.id} className={isRevoked ? 'is-revoked' : undefined}>
|
<tr key={token.id} className={isRevoked ? 'is-revoked' : undefined}>
|
||||||
<td>{token.label || '—'}</td>
|
<td>{token.label || '—'}</td>
|
||||||
@@ -340,36 +360,17 @@ const SettingsModal = ({
|
|||||||
<td>{formatDateTime(token.last_used_at)}</td>
|
<td>{formatDateTime(token.last_used_at)}</td>
|
||||||
<td>{formatDateTime(token.expires_at)}</td>
|
<td>{formatDateTime(token.expires_at)}</td>
|
||||||
<td>
|
<td>
|
||||||
<div className="settings-form__choices">
|
<div className="settings-capability-set">
|
||||||
{capabilityOptions.map((option) => {
|
<span className="settings-capability-set__label">{capabilitySetLabel}</span>
|
||||||
const checked = capabilitySet.includes(option.value);
|
|
||||||
return (
|
|
||||||
<label key={option.value} className="settings-choice">
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
checked={checked}
|
|
||||||
disabled={
|
|
||||||
isRevoked
|
|
||||||
|| deletingId === token.id
|
|
||||||
|| regeneratingId === token.id
|
|
||||||
|| updatingId === token.id
|
|
||||||
}
|
|
||||||
onChange={(event) =>
|
|
||||||
handleToggleTokenCapability(
|
|
||||||
token,
|
|
||||||
option.value,
|
|
||||||
event.target.checked,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<span>{option.label}</span>
|
|
||||||
</label>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
{updatingId === token.id ? (
|
|
||||||
<span className="settings-status">Saving…</span>
|
|
||||||
) : null}
|
|
||||||
</div>
|
</div>
|
||||||
|
{capabilitySetsLoading ? (
|
||||||
|
<small>Loading capability sets…</small>
|
||||||
|
) : null}
|
||||||
|
{!capabilitySetsLoading && capabilityList.length ? (
|
||||||
|
<div className="settings-capabilities">
|
||||||
|
<small>Includes: {capabilityList.join(', ')}</small>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
</td>
|
</td>
|
||||||
<td className="settings-table__actions">
|
<td className="settings-table__actions">
|
||||||
{isRevoked ? (
|
{isRevoked ? (
|
||||||
@@ -414,12 +415,13 @@ const SettingsModal = ({
|
|||||||
creating,
|
creating,
|
||||||
deletingId,
|
deletingId,
|
||||||
regeneratingId,
|
regeneratingId,
|
||||||
updatingId,
|
|
||||||
newTokenLabel,
|
newTokenLabel,
|
||||||
newTokenExpires,
|
newTokenExpires,
|
||||||
newTokenCapabilities,
|
newTokenCapabilitySetId,
|
||||||
formError,
|
formError,
|
||||||
capabilityOptions,
|
capabilitySetOptions,
|
||||||
|
capabilitySetMap,
|
||||||
|
capabilitySetsLoading,
|
||||||
formatDateTime,
|
formatDateTime,
|
||||||
handleCopyToken,
|
handleCopyToken,
|
||||||
handleCreateToken,
|
handleCreateToken,
|
||||||
@@ -427,8 +429,7 @@ const SettingsModal = ({
|
|||||||
handleRefresh,
|
handleRefresh,
|
||||||
onDelete,
|
onDelete,
|
||||||
handleDismissSecret,
|
handleDismissSecret,
|
||||||
handleNewCapabilityChange,
|
handleNewCapabilitySetChange,
|
||||||
handleToggleTokenCapability,
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const renderPasskeysSection = useMemo(() => {
|
const renderPasskeysSection = useMemo(() => {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useCallback, useState } from 'react';
|
import { useCallback, useEffect, useState } from 'react';
|
||||||
|
|
||||||
const useApiTokens = ({ api, notifyApiError, setStatusMessage, token }) => {
|
const useApiTokens = ({ api, notifyApiError, setStatusMessage, token }) => {
|
||||||
const [tokens, setTokens] = useState([]);
|
const [tokens, setTokens] = useState([]);
|
||||||
@@ -6,8 +6,25 @@ const useApiTokens = ({ api, notifyApiError, setStatusMessage, token }) => {
|
|||||||
const [creating, setCreating] = useState(false);
|
const [creating, setCreating] = useState(false);
|
||||||
const [deletingId, setDeletingId] = useState(null);
|
const [deletingId, setDeletingId] = useState(null);
|
||||||
const [regeneratingId, setRegeneratingId] = useState(null);
|
const [regeneratingId, setRegeneratingId] = useState(null);
|
||||||
const [updatingId, setUpdatingId] = useState(null);
|
|
||||||
const [createdSecret, setCreatedSecret] = useState(null);
|
const [createdSecret, setCreatedSecret] = useState(null);
|
||||||
|
const [capabilitySets, setCapabilitySets] = useState([]);
|
||||||
|
const [capabilitySetsLoading, setCapabilitySetsLoading] = useState(false);
|
||||||
|
|
||||||
|
const refreshCapabilitySets = useCallback(async () => {
|
||||||
|
if (!token) {
|
||||||
|
setCapabilitySets([]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setCapabilitySetsLoading(true);
|
||||||
|
try {
|
||||||
|
const { data } = await api.get('/capability-sets');
|
||||||
|
setCapabilitySets(Array.isArray(data) ? data : []);
|
||||||
|
} catch (error) {
|
||||||
|
notifyApiError?.(error, 'Failed to load capability sets.');
|
||||||
|
} finally {
|
||||||
|
setCapabilitySetsLoading(false);
|
||||||
|
}
|
||||||
|
}, [api, notifyApiError, token]);
|
||||||
|
|
||||||
const refresh = useCallback(async () => {
|
const refresh = useCallback(async () => {
|
||||||
if (!token) {
|
if (!token) {
|
||||||
@@ -25,10 +42,13 @@ const useApiTokens = ({ api, notifyApiError, setStatusMessage, token }) => {
|
|||||||
}, [api, notifyApiError, token]);
|
}, [api, notifyApiError, token]);
|
||||||
|
|
||||||
const create = useCallback(
|
const create = useCallback(
|
||||||
async ({ label, expires_at, capabilities } = {}) => {
|
async ({ label, expires_at, capability_set_id } = {}) => {
|
||||||
if (creating) {
|
if (creating) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
if (!capability_set_id) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
setCreating(true);
|
setCreating(true);
|
||||||
try {
|
try {
|
||||||
const payload = {};
|
const payload = {};
|
||||||
@@ -38,9 +58,7 @@ const useApiTokens = ({ api, notifyApiError, setStatusMessage, token }) => {
|
|||||||
if (expires_at) {
|
if (expires_at) {
|
||||||
payload.expires_at = expires_at;
|
payload.expires_at = expires_at;
|
||||||
}
|
}
|
||||||
if (Array.isArray(capabilities) && capabilities.length > 0) {
|
payload.capability_set_id = capability_set_id;
|
||||||
payload.capabilities = capabilities;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { data } = await api.post('/profile/api-tokens', payload);
|
const { data } = await api.post('/profile/api-tokens', payload);
|
||||||
if (data?.token_info) {
|
if (data?.token_info) {
|
||||||
@@ -132,45 +150,13 @@ const useApiTokens = ({ api, notifyApiError, setStatusMessage, token }) => {
|
|||||||
[api, notifyApiError, refresh, setStatusMessage],
|
[api, notifyApiError, refresh, setStatusMessage],
|
||||||
);
|
);
|
||||||
|
|
||||||
const updateCapabilities = useCallback(
|
useEffect(() => {
|
||||||
async (tokenId, capabilities) => {
|
if (token) {
|
||||||
if (!tokenId) {
|
refreshCapabilitySets();
|
||||||
return false;
|
|
||||||
}
|
|
||||||
setUpdatingId(tokenId);
|
|
||||||
try {
|
|
||||||
const { data } = await api.patch(`/profile/api-tokens/${tokenId}`, {
|
|
||||||
capabilities,
|
|
||||||
});
|
|
||||||
if (data) {
|
|
||||||
setTokens((previous) => {
|
|
||||||
let found = false;
|
|
||||||
const next = previous.map((entry) => {
|
|
||||||
if (entry.id === data.id) {
|
|
||||||
found = true;
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
return entry;
|
|
||||||
});
|
|
||||||
if (!found) {
|
|
||||||
return [data, ...previous];
|
|
||||||
}
|
|
||||||
return next;
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
await refresh();
|
setCapabilitySets([]);
|
||||||
}
|
}
|
||||||
setStatusMessage?.('API token updated.', 'success');
|
}, [token, refreshCapabilitySets]);
|
||||||
return true;
|
|
||||||
} catch (error) {
|
|
||||||
notifyApiError?.(error, 'Failed to update API token.');
|
|
||||||
return false;
|
|
||||||
} finally {
|
|
||||||
setUpdatingId(null);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[api, notifyApiError, refresh, setStatusMessage],
|
|
||||||
);
|
|
||||||
|
|
||||||
const dismissSecret = useCallback(() => {
|
const dismissSecret = useCallback(() => {
|
||||||
setCreatedSecret(null);
|
setCreatedSecret(null);
|
||||||
@@ -182,14 +168,15 @@ const useApiTokens = ({ api, notifyApiError, setStatusMessage, token }) => {
|
|||||||
creating,
|
creating,
|
||||||
deletingId,
|
deletingId,
|
||||||
regeneratingId,
|
regeneratingId,
|
||||||
updatingId,
|
|
||||||
createdSecret,
|
createdSecret,
|
||||||
refresh,
|
refresh,
|
||||||
create,
|
create,
|
||||||
revoke,
|
revoke,
|
||||||
regenerate,
|
regenerate,
|
||||||
updateCapabilities,
|
|
||||||
dismissSecret,
|
dismissSecret,
|
||||||
|
capabilitySets,
|
||||||
|
capabilitySetsLoading,
|
||||||
|
refreshCapabilitySets,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user