+
+
navigate('/credentials') },
+ { label: credential?.name ?? `#${id}` },
+ ]}
+ />
+ {credential && (
+
+
+
+
+ )}
+
+
+ {error && (
+
+ {error.startsWith('404') ? t('errors.not_found_credential', { id }) : error}
+
+ )}
+
+ {loading &&
{t('credentials.loading')}
}
+
+ {credential && (
+ <>
+
+
+ ) : (
+ '—'
+ ),
+ },
+ {
+ term: t('credentials.field_created'),
+ detail: ,
+ },
+ {
+ term: t('credentials.field_updated'),
+ detail: ,
+ },
+ ]}
+ />
+
+
+ {credential.data && (
+ <>
+
{t('credentials.section_data')}
+
+
+ {(() => {
+ try {
+ return JSON.stringify(JSON.parse(credential.data), null, 2);
+ } catch {
+ return credential.data;
+ }
+ })()}
+
+
+ >
+ )}
+ >
+ )}
+
+ );
+}
diff --git a/client/src/pages/credential/CredentialsPage.tsx b/client/src/pages/credential/CredentialsPage.tsx
new file mode 100644
index 0000000..2277012
--- /dev/null
+++ b/client/src/pages/credential/CredentialsPage.tsx
@@ -0,0 +1,130 @@
+import { useEffect, useState } from 'react';
+import { useNavigate } from 'react-router-dom';
+import { useTranslation } from 'react-i18next';
+import { Settings, Pencil, Trash2, Plus } from 'lucide-react';
+import { credentials } from '../../api';
+import type { Credential } from '../../api';
+import { Breadcrumbs, Button, Card, ContextMenu, DescriptionList, Timestamp } from '../../ui';
+import styles from '../Page.module.css';
+
+function CredentialCard({
+ credential,
+ onDelete,
+}: {
+ credential: Credential;
+ onDelete: (id: number) => void;
+}) {
+ const { t } = useTranslation();
+ const navigate = useNavigate();
+
+ const header = (
+