sidebar fix
This commit is contained in:
241
kms/web/src/components/ApplicationSidebar.tsx
Normal file
241
kms/web/src/components/ApplicationSidebar.tsx
Normal file
@ -0,0 +1,241 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import {
|
||||
Paper,
|
||||
TextInput,
|
||||
MultiSelect,
|
||||
Button,
|
||||
Group,
|
||||
Stack,
|
||||
Title,
|
||||
ActionIcon,
|
||||
ScrollArea,
|
||||
Box,
|
||||
} from '@mantine/core';
|
||||
import { IconX } from '@tabler/icons-react';
|
||||
import { useForm } from '@mantine/form';
|
||||
import { notifications } from '@mantine/notifications';
|
||||
import { apiService, Application, CreateApplicationRequest } from '../services/apiService';
|
||||
|
||||
interface ApplicationSidebarProps {
|
||||
opened: boolean;
|
||||
onClose: () => void;
|
||||
onSuccess: () => void;
|
||||
editingApp?: Application | null;
|
||||
}
|
||||
|
||||
const ApplicationSidebar: React.FC<ApplicationSidebarProps> = ({
|
||||
opened,
|
||||
onClose,
|
||||
onSuccess,
|
||||
editingApp,
|
||||
}) => {
|
||||
const isEditing = !!editingApp;
|
||||
|
||||
const appTypeOptions = [
|
||||
{ value: 'static', label: 'Static Token App' },
|
||||
{ value: 'user', label: 'User Token App' },
|
||||
];
|
||||
|
||||
const form = useForm<CreateApplicationRequest>({
|
||||
initialValues: {
|
||||
app_id: '',
|
||||
app_link: '',
|
||||
type: [],
|
||||
callback_url: '',
|
||||
token_prefix: '',
|
||||
token_renewal_duration: '24h',
|
||||
max_token_duration: '168h',
|
||||
owner: {
|
||||
type: 'individual',
|
||||
name: 'Admin User',
|
||||
owner: 'admin@example.com',
|
||||
},
|
||||
},
|
||||
validate: {
|
||||
app_id: (value) => value.length < 1 ? 'App ID is required' : null,
|
||||
app_link: (value) => value.length < 1 ? 'App Link is required' : null,
|
||||
callback_url: (value) => value.length < 1 ? 'Callback URL is required' : null,
|
||||
},
|
||||
});
|
||||
|
||||
const parseDuration = (duration: string): number => {
|
||||
// Convert duration string like "24h" to seconds
|
||||
const match = duration.match(/^(\d+)([hmd]?)$/);
|
||||
if (!match) return 86400; // Default to 24h in seconds
|
||||
|
||||
const value = parseInt(match[1]);
|
||||
const unit = match[2] || 'h';
|
||||
|
||||
switch (unit) {
|
||||
case 'm': return value * 60; // minutes to seconds
|
||||
case 'h': return value * 3600; // hours to seconds
|
||||
case 'd': return value * 86400; // days to seconds
|
||||
default: return value * 3600; // default to hours
|
||||
}
|
||||
};
|
||||
|
||||
// Update form values when editingApp changes
|
||||
useEffect(() => {
|
||||
if (editingApp) {
|
||||
form.setValues({
|
||||
app_id: editingApp.app_id || '',
|
||||
app_link: editingApp.app_link || '',
|
||||
type: editingApp.type || [],
|
||||
callback_url: editingApp.callback_url || '',
|
||||
token_prefix: editingApp.token_prefix || '',
|
||||
token_renewal_duration: editingApp.token_renewal_duration || '24h',
|
||||
max_token_duration: editingApp.max_token_duration || '168h',
|
||||
owner: {
|
||||
type: editingApp.owner?.type || 'individual',
|
||||
name: editingApp.owner?.name || 'Admin User',
|
||||
owner: editingApp.owner?.owner || 'admin@example.com',
|
||||
},
|
||||
});
|
||||
} else {
|
||||
// Reset to default values when not editing
|
||||
form.reset();
|
||||
}
|
||||
}, [editingApp, opened]);
|
||||
|
||||
const handleSubmit = async (values: typeof form.values) => {
|
||||
try {
|
||||
const submitData = {
|
||||
...values,
|
||||
token_renewal_duration_seconds: parseDuration(values.token_renewal_duration),
|
||||
max_token_duration_seconds: parseDuration(values.max_token_duration),
|
||||
};
|
||||
|
||||
if (isEditing && editingApp) {
|
||||
await apiService.updateApplication(editingApp.app_id, submitData);
|
||||
notifications.show({
|
||||
title: 'Success',
|
||||
message: 'Application updated successfully',
|
||||
color: 'green',
|
||||
});
|
||||
} else {
|
||||
await apiService.createApplication(submitData);
|
||||
notifications.show({
|
||||
title: 'Success',
|
||||
message: 'Application created successfully',
|
||||
color: 'green',
|
||||
});
|
||||
}
|
||||
|
||||
onSuccess();
|
||||
onClose();
|
||||
form.reset();
|
||||
} catch (error) {
|
||||
console.error('Error saving application:', error);
|
||||
notifications.show({
|
||||
title: 'Error',
|
||||
message: `Failed to ${isEditing ? 'update' : 'create'} application`,
|
||||
color: 'red',
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Paper
|
||||
style={{
|
||||
position: 'fixed',
|
||||
top: 60, // Below header
|
||||
right: opened ? 0 : '-450px',
|
||||
bottom: 0,
|
||||
width: '450px',
|
||||
zIndex: 1000,
|
||||
borderRadius: 0,
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
borderLeft: '1px solid var(--mantine-color-gray-3)',
|
||||
backgroundColor: 'var(--mantine-color-body)',
|
||||
transition: 'right 0.3s ease',
|
||||
}}
|
||||
>
|
||||
{/* Header */}
|
||||
<Group justify="space-between" p="md" style={{ borderBottom: '1px solid var(--mantine-color-gray-3)' }}>
|
||||
<Title order={4}>
|
||||
{isEditing ? 'Edit Application' : 'Create New Application'}
|
||||
</Title>
|
||||
<ActionIcon
|
||||
variant="subtle"
|
||||
color="gray"
|
||||
onClick={onClose}
|
||||
>
|
||||
<IconX size={18} />
|
||||
</ActionIcon>
|
||||
</Group>
|
||||
|
||||
{/* Content */}
|
||||
<ScrollArea style={{ flex: 1 }}>
|
||||
<Box p="md">
|
||||
<form onSubmit={form.onSubmit(handleSubmit)}>
|
||||
<Stack gap="md">
|
||||
<TextInput
|
||||
label="Application ID"
|
||||
placeholder="my-app-id"
|
||||
required
|
||||
{...form.getInputProps('app_id')}
|
||||
disabled={isEditing}
|
||||
/>
|
||||
|
||||
<TextInput
|
||||
label="Application Link"
|
||||
placeholder="https://myapp.example.com"
|
||||
required
|
||||
{...form.getInputProps('app_link')}
|
||||
/>
|
||||
|
||||
<MultiSelect
|
||||
label="Application Type"
|
||||
placeholder="Select application types"
|
||||
data={appTypeOptions}
|
||||
required
|
||||
{...form.getInputProps('type')}
|
||||
/>
|
||||
|
||||
<TextInput
|
||||
label="Callback URL"
|
||||
placeholder="https://myapp.example.com/callback"
|
||||
required
|
||||
{...form.getInputProps('callback_url')}
|
||||
/>
|
||||
|
||||
<TextInput
|
||||
label="Token Prefix (Optional)"
|
||||
placeholder="myapp_"
|
||||
{...form.getInputProps('token_prefix')}
|
||||
/>
|
||||
|
||||
<Group grow>
|
||||
<TextInput
|
||||
label="Token Renewal Duration"
|
||||
placeholder="24h"
|
||||
{...form.getInputProps('token_renewal_duration')}
|
||||
/>
|
||||
<TextInput
|
||||
label="Max Token Duration"
|
||||
placeholder="168h"
|
||||
{...form.getInputProps('max_token_duration')}
|
||||
/>
|
||||
</Group>
|
||||
|
||||
<Group justify="flex-end" mt="md">
|
||||
<Button
|
||||
variant="light"
|
||||
onClick={onClose}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button type="submit">
|
||||
{isEditing ? 'Update Application' : 'Create Application'}
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</form>
|
||||
</Box>
|
||||
</ScrollArea>
|
||||
</Paper>
|
||||
);
|
||||
};
|
||||
|
||||
export default ApplicationSidebar;
|
||||
@ -29,12 +29,13 @@ import {
|
||||
import { useForm } from '@mantine/form';
|
||||
import { notifications } from '@mantine/notifications';
|
||||
import { apiService, Application, CreateApplicationRequest } from '../services/apiService';
|
||||
import ApplicationSidebar from './ApplicationSidebar';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
const Applications: React.FC = () => {
|
||||
const [applications, setApplications] = useState<Application[]>([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [modalOpen, setModalOpen] = useState(false);
|
||||
const [sidebarOpen, setSidebarOpen] = useState(false);
|
||||
const [editingApp, setEditingApp] = useState<Application | null>(null);
|
||||
const [detailModalOpen, setDetailModalOpen] = useState(false);
|
||||
const [selectedApp, setSelectedApp] = useState<Application | null>(null);
|
||||
@ -122,7 +123,7 @@ const Applications: React.FC = () => {
|
||||
color: 'green',
|
||||
});
|
||||
}
|
||||
setModalOpen(false);
|
||||
setSidebarOpen(false);
|
||||
setEditingApp(null);
|
||||
form.reset();
|
||||
loadApplications();
|
||||
@ -148,7 +149,7 @@ const Applications: React.FC = () => {
|
||||
max_token_duration: `${app.max_token_duration / 3600}h`,
|
||||
owner: app.owner,
|
||||
});
|
||||
setModalOpen(true);
|
||||
setSidebarOpen(true);
|
||||
};
|
||||
|
||||
const handleDelete = async (appId: string) => {
|
||||
@ -247,7 +248,13 @@ const Applications: React.FC = () => {
|
||||
));
|
||||
|
||||
return (
|
||||
<Stack gap="lg">
|
||||
<Stack
|
||||
gap="lg"
|
||||
style={{
|
||||
transition: 'margin-right 0.3s ease',
|
||||
marginRight: sidebarOpen ? '450px' : '0',
|
||||
}}
|
||||
>
|
||||
<Group justify="space-between">
|
||||
<div>
|
||||
<Title order={2} mb="xs">
|
||||
@ -259,7 +266,7 @@ const Applications: React.FC = () => {
|
||||
onClick={() => {
|
||||
setEditingApp(null);
|
||||
form.reset();
|
||||
setModalOpen(true);
|
||||
setSidebarOpen(true);
|
||||
}}
|
||||
>
|
||||
New Application
|
||||
@ -288,7 +295,7 @@ const Applications: React.FC = () => {
|
||||
onClick={() => {
|
||||
setEditingApp(null);
|
||||
form.reset();
|
||||
setModalOpen(true);
|
||||
setSidebarOpen(true);
|
||||
}}
|
||||
>
|
||||
Create Application
|
||||
@ -312,86 +319,17 @@ const Applications: React.FC = () => {
|
||||
</Card>
|
||||
)}
|
||||
|
||||
{/* Create/Edit Modal */}
|
||||
<Modal
|
||||
opened={modalOpen}
|
||||
<ApplicationSidebar
|
||||
opened={sidebarOpen}
|
||||
onClose={() => {
|
||||
setModalOpen(false);
|
||||
setSidebarOpen(false);
|
||||
setEditingApp(null);
|
||||
form.reset();
|
||||
}}
|
||||
title={editingApp ? 'Edit Application' : 'Create New Application'}
|
||||
size="lg"
|
||||
>
|
||||
<form onSubmit={form.onSubmit(handleSubmit)}>
|
||||
<Stack gap="md">
|
||||
<TextInput
|
||||
label="Application ID"
|
||||
placeholder="my-app-id"
|
||||
required
|
||||
{...form.getInputProps('app_id')}
|
||||
disabled={!!editingApp}
|
||||
/>
|
||||
|
||||
<TextInput
|
||||
label="Application Link"
|
||||
placeholder="https://myapp.example.com"
|
||||
required
|
||||
{...form.getInputProps('app_link')}
|
||||
/>
|
||||
|
||||
<MultiSelect
|
||||
label="Application Type"
|
||||
placeholder="Select application types"
|
||||
data={appTypeOptions}
|
||||
required
|
||||
{...form.getInputProps('type')}
|
||||
/>
|
||||
|
||||
<TextInput
|
||||
label="Callback URL"
|
||||
placeholder="https://myapp.example.com/callback"
|
||||
required
|
||||
{...form.getInputProps('callback_url')}
|
||||
/>
|
||||
|
||||
<TextInput
|
||||
label="Token Prefix (Optional)"
|
||||
placeholder="myapp_"
|
||||
{...form.getInputProps('token_prefix')}
|
||||
/>
|
||||
|
||||
<Group grow>
|
||||
<TextInput
|
||||
label="Token Renewal Duration"
|
||||
placeholder="24h"
|
||||
{...form.getInputProps('token_renewal_duration')}
|
||||
/>
|
||||
<TextInput
|
||||
label="Max Token Duration"
|
||||
placeholder="168h"
|
||||
{...form.getInputProps('max_token_duration')}
|
||||
/>
|
||||
</Group>
|
||||
|
||||
<Group justify="flex-end" mt="md">
|
||||
<Button
|
||||
variant="subtle"
|
||||
onClick={() => {
|
||||
setModalOpen(false);
|
||||
setEditingApp(null);
|
||||
form.reset();
|
||||
}}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button type="submit">
|
||||
{editingApp ? 'Update Application' : 'Create Application'}
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</form>
|
||||
</Modal>
|
||||
onSuccess={() => {
|
||||
loadApplications();
|
||||
}}
|
||||
editingApp={editingApp}
|
||||
/>
|
||||
|
||||
{/* Detail Modal */}
|
||||
<Modal
|
||||
|
||||
293
kms/web/src/components/TokenSidebar.tsx
Normal file
293
kms/web/src/components/TokenSidebar.tsx
Normal file
@ -0,0 +1,293 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import {
|
||||
Paper,
|
||||
TextInput,
|
||||
Button,
|
||||
Group,
|
||||
Stack,
|
||||
Title,
|
||||
ActionIcon,
|
||||
ScrollArea,
|
||||
Box,
|
||||
Select,
|
||||
Alert,
|
||||
Code,
|
||||
Divider,
|
||||
Text,
|
||||
Modal,
|
||||
} from '@mantine/core';
|
||||
import { IconX, IconCheck, IconCopy } from '@tabler/icons-react';
|
||||
import { useForm } from '@mantine/form';
|
||||
import { notifications } from '@mantine/notifications';
|
||||
import PermissionTree from './PermissionTree';
|
||||
import {
|
||||
apiService,
|
||||
Application,
|
||||
CreateTokenRequest,
|
||||
CreateTokenResponse,
|
||||
} from '../services/apiService';
|
||||
|
||||
interface TokenSidebarProps {
|
||||
opened: boolean;
|
||||
onClose: () => void;
|
||||
onSuccess: () => void;
|
||||
applications: Application[];
|
||||
}
|
||||
|
||||
const TokenSidebar: React.FC<TokenSidebarProps> = ({
|
||||
opened,
|
||||
onClose,
|
||||
onSuccess,
|
||||
applications,
|
||||
}) => {
|
||||
const [tokenModalOpen, setTokenModalOpen] = useState(false);
|
||||
const [createdToken, setCreatedToken] = useState<CreateTokenResponse | null>(null);
|
||||
|
||||
const form = useForm<CreateTokenRequest & { app_id: string }>({
|
||||
initialValues: {
|
||||
app_id: '',
|
||||
owner: {
|
||||
type: 'individual',
|
||||
name: 'Admin User',
|
||||
owner: 'admin@example.com',
|
||||
},
|
||||
permissions: [],
|
||||
},
|
||||
validate: {
|
||||
app_id: (value) => value.length < 1 ? 'Application is required' : null,
|
||||
permissions: (value) => value.length < 1 ? 'At least one permission is required' : null,
|
||||
},
|
||||
});
|
||||
|
||||
// Reset form when sidebar opens
|
||||
useEffect(() => {
|
||||
if (opened) {
|
||||
form.reset();
|
||||
}
|
||||
}, [opened]);
|
||||
|
||||
const handleSubmit = async (values: CreateTokenRequest & { app_id: string }) => {
|
||||
try {
|
||||
const { app_id, ...tokenData } = values;
|
||||
const response = await apiService.createToken(app_id, tokenData);
|
||||
setCreatedToken(response);
|
||||
setTokenModalOpen(true);
|
||||
form.reset();
|
||||
onSuccess();
|
||||
notifications.show({
|
||||
title: 'Success',
|
||||
message: 'Token created successfully',
|
||||
color: 'green',
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Failed to create token:', error);
|
||||
notifications.show({
|
||||
title: 'Error',
|
||||
message: 'Failed to create token',
|
||||
color: 'red',
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const copyToClipboard = (text: string) => {
|
||||
navigator.clipboard.writeText(text);
|
||||
notifications.show({
|
||||
title: 'Copied',
|
||||
message: 'Copied to clipboard',
|
||||
color: 'blue',
|
||||
});
|
||||
};
|
||||
|
||||
const handleTokenModalClose = () => {
|
||||
setTokenModalOpen(false);
|
||||
setCreatedToken(null);
|
||||
onClose();
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Paper
|
||||
style={{
|
||||
position: 'fixed',
|
||||
top: 60, // Below header
|
||||
right: opened ? 0 : '-450px',
|
||||
bottom: 0,
|
||||
width: '450px',
|
||||
zIndex: 1000,
|
||||
borderRadius: 0,
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
borderLeft: '1px solid var(--mantine-color-gray-3)',
|
||||
backgroundColor: 'var(--mantine-color-body)',
|
||||
transition: 'right 0.3s ease',
|
||||
}}
|
||||
>
|
||||
{/* Header */}
|
||||
<Group justify="space-between" p="md" style={{ borderBottom: '1px solid var(--mantine-color-gray-3)' }}>
|
||||
<Title order={4}>
|
||||
Create New Token
|
||||
</Title>
|
||||
<ActionIcon
|
||||
variant="subtle"
|
||||
color="gray"
|
||||
onClick={onClose}
|
||||
>
|
||||
<IconX size={18} />
|
||||
</ActionIcon>
|
||||
</Group>
|
||||
|
||||
{/* Content */}
|
||||
<ScrollArea style={{ flex: 1 }}>
|
||||
<Box p="md">
|
||||
<form onSubmit={form.onSubmit(handleSubmit)}>
|
||||
<Stack gap="md">
|
||||
<Select
|
||||
label="Application"
|
||||
placeholder="Select an application"
|
||||
required
|
||||
data={applications.map(app => ({
|
||||
value: app.app_id,
|
||||
label: `${app.app_id} (${app.type.join(', ')})`,
|
||||
}))}
|
||||
{...form.getInputProps('app_id')}
|
||||
/>
|
||||
|
||||
<div>
|
||||
<Text size="sm" fw={500} mb="xs">
|
||||
Required Permissions
|
||||
</Text>
|
||||
<Text size="xs" c="dimmed" mb="md">
|
||||
Select the permissions this token should have
|
||||
</Text>
|
||||
<PermissionTree
|
||||
permissions={form.values.permissions}
|
||||
onChange={(permissions) => form.setFieldValue('permissions', permissions)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<TextInput
|
||||
label="Owner Name"
|
||||
placeholder="Token owner name"
|
||||
{...form.getInputProps('owner.name')}
|
||||
/>
|
||||
|
||||
<TextInput
|
||||
label="Owner Email"
|
||||
placeholder="owner@example.com"
|
||||
{...form.getInputProps('owner.owner')}
|
||||
/>
|
||||
|
||||
<Group justify="flex-end" mt="md">
|
||||
<Button
|
||||
variant="light"
|
||||
onClick={onClose}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
type="submit"
|
||||
disabled={applications.length === 0}
|
||||
>
|
||||
Create Token
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</form>
|
||||
</Box>
|
||||
</ScrollArea>
|
||||
</Paper>
|
||||
|
||||
{/* Token Created Modal */}
|
||||
<Modal
|
||||
opened={tokenModalOpen}
|
||||
onClose={handleTokenModalClose}
|
||||
title="Token Created Successfully"
|
||||
size="lg"
|
||||
closeOnEscape={false}
|
||||
closeOnClickOutside={false}
|
||||
>
|
||||
<Stack gap="md">
|
||||
<Alert
|
||||
icon={<IconCheck size={16} />}
|
||||
title="Success!"
|
||||
color="green"
|
||||
>
|
||||
Your token has been created successfully. Please copy and store it securely as you won't be able to see it again.
|
||||
</Alert>
|
||||
|
||||
<div>
|
||||
<Text size="sm" fw={500} mb="xs">
|
||||
Token:
|
||||
</Text>
|
||||
<Group gap="xs">
|
||||
<Code
|
||||
block
|
||||
style={{
|
||||
flex: 1,
|
||||
wordBreak: 'break-all',
|
||||
whiteSpace: 'pre-wrap',
|
||||
}}
|
||||
>
|
||||
{createdToken?.token}
|
||||
</Code>
|
||||
<ActionIcon
|
||||
variant="light"
|
||||
onClick={() => createdToken?.token && copyToClipboard(createdToken.token)}
|
||||
title="Copy Token"
|
||||
>
|
||||
<IconCopy size={16} />
|
||||
</ActionIcon>
|
||||
</Group>
|
||||
</div>
|
||||
|
||||
{createdToken?.prefix && (
|
||||
<div>
|
||||
<Text size="sm" fw={500} mb="xs">
|
||||
Token Prefix:
|
||||
</Text>
|
||||
<Code>{createdToken.prefix}</Code>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<Divider />
|
||||
|
||||
<div>
|
||||
<Text size="sm" fw={500} mb="xs">
|
||||
Token Details:
|
||||
</Text>
|
||||
<Stack gap="xs">
|
||||
<Group justify="space-between">
|
||||
<Text size="sm">Token ID:</Text>
|
||||
<Code>{createdToken?.id}</Code>
|
||||
</Group>
|
||||
<Group justify="space-between">
|
||||
<Text size="sm">Type:</Text>
|
||||
<Code>{createdToken?.type}</Code>
|
||||
</Group>
|
||||
{createdToken?.permissions && (
|
||||
<div>
|
||||
<Text size="sm" mb="xs">Permissions:</Text>
|
||||
<Group gap="xs">
|
||||
{createdToken.permissions.map((permission) => (
|
||||
<Code key={permission} size="xs">
|
||||
{permission}
|
||||
</Code>
|
||||
))}
|
||||
</Group>
|
||||
</div>
|
||||
)}
|
||||
</Stack>
|
||||
</div>
|
||||
|
||||
<Group justify="flex-end" mt="md">
|
||||
<Button onClick={handleTokenModalClose}>
|
||||
Done
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default TokenSidebar;
|
||||
@ -38,6 +38,7 @@ import {
|
||||
CreateTokenRequest,
|
||||
CreateTokenResponse,
|
||||
} from '../services/apiService';
|
||||
import TokenSidebar from './TokenSidebar';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
interface TokenWithApp extends StaticToken {
|
||||
@ -48,7 +49,7 @@ const Tokens: React.FC = () => {
|
||||
const [applications, setApplications] = useState<Application[]>([]);
|
||||
const [tokens, setTokens] = useState<TokenWithApp[]>([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [modalOpen, setModalOpen] = useState(false);
|
||||
const [sidebarOpen, setSidebarOpen] = useState(false);
|
||||
const [tokenModalOpen, setTokenModalOpen] = useState(false);
|
||||
const [createdToken, setCreatedToken] = useState<CreateTokenResponse | null>(null);
|
||||
|
||||
@ -134,7 +135,7 @@ const Tokens: React.FC = () => {
|
||||
const { app_id, ...tokenData } = values;
|
||||
const response = await apiService.createToken(app_id, tokenData);
|
||||
setCreatedToken(response);
|
||||
setModalOpen(false);
|
||||
setSidebarOpen(false);
|
||||
setTokenModalOpen(true);
|
||||
form.reset();
|
||||
loadAllTokens();
|
||||
@ -237,7 +238,13 @@ const Tokens: React.FC = () => {
|
||||
));
|
||||
|
||||
return (
|
||||
<Stack gap="lg">
|
||||
<Stack
|
||||
gap="lg"
|
||||
style={{
|
||||
transition: 'margin-right 0.3s ease',
|
||||
marginRight: sidebarOpen ? '450px' : '0',
|
||||
}}
|
||||
>
|
||||
<Group justify="space-between">
|
||||
<div>
|
||||
<Title order={2} mb="xs">
|
||||
@ -248,7 +255,7 @@ const Tokens: React.FC = () => {
|
||||
leftSection={<IconPlus size={16} />}
|
||||
onClick={() => {
|
||||
form.reset();
|
||||
setModalOpen(true);
|
||||
setSidebarOpen(true);
|
||||
}}
|
||||
disabled={applications.length === 0}
|
||||
>
|
||||
@ -288,7 +295,7 @@ const Tokens: React.FC = () => {
|
||||
leftSection={<IconPlus size={16} />}
|
||||
onClick={() => {
|
||||
form.reset();
|
||||
setModalOpen(true);
|
||||
setSidebarOpen(true);
|
||||
}}
|
||||
>
|
||||
Create Token
|
||||
@ -314,61 +321,17 @@ const Tokens: React.FC = () => {
|
||||
</Card>
|
||||
)}
|
||||
|
||||
{/* Create Token Modal */}
|
||||
<Modal
|
||||
opened={modalOpen}
|
||||
<TokenSidebar
|
||||
opened={sidebarOpen}
|
||||
onClose={() => {
|
||||
setModalOpen(false);
|
||||
setSidebarOpen(false);
|
||||
form.reset();
|
||||
}}
|
||||
title="Create New Token"
|
||||
size="lg"
|
||||
>
|
||||
<form onSubmit={form.onSubmit(handleSubmit)}>
|
||||
<Stack gap="md">
|
||||
<Select
|
||||
label="Application"
|
||||
placeholder="Select an application"
|
||||
required
|
||||
data={applications.map(app => ({
|
||||
value: app.app_id,
|
||||
label: `${app.app_id} (${app.type.join(', ')})`,
|
||||
}))}
|
||||
{...form.getInputProps('app_id')}
|
||||
/>
|
||||
|
||||
<PermissionTree
|
||||
permissions={form.values.permissions}
|
||||
onChange={(permissions) => form.setFieldValue('permissions', permissions)}
|
||||
/>
|
||||
|
||||
<TextInput
|
||||
label="Owner Name"
|
||||
placeholder="Token owner name"
|
||||
{...form.getInputProps('owner.name')}
|
||||
/>
|
||||
|
||||
<TextInput
|
||||
label="Owner Email"
|
||||
placeholder="owner@example.com"
|
||||
{...form.getInputProps('owner.owner')}
|
||||
/>
|
||||
|
||||
<Group justify="flex-end" mt="md">
|
||||
<Button
|
||||
variant="subtle"
|
||||
onClick={() => {
|
||||
setModalOpen(false);
|
||||
form.reset();
|
||||
}}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button type="submit">Create Token</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</form>
|
||||
</Modal>
|
||||
onSuccess={() => {
|
||||
loadAllTokens();
|
||||
}}
|
||||
applications={applications}
|
||||
/>
|
||||
|
||||
{/* Token Created Modal */}
|
||||
<Modal
|
||||
|
||||
Reference in New Issue
Block a user