axiodb 22.3.1 → 22.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/electron/electron-builder.json +58 -0
- package/electron/main/main.cts +585 -0
- package/electron/main/preload.cts +51 -0
- package/electron/package-lock.json +8123 -0
- package/electron/package.json +52 -0
- package/electron/public/AXioDB.png +0 -0
- package/electron/resources/after-install.sh +45 -0
- package/electron/resources/after-remove.sh +29 -0
- package/electron/resources/icon.png +0 -0
- package/electron/resources/icons/128x128.png +0 -0
- package/electron/resources/icons/16x16.png +0 -0
- package/electron/resources/icons/24x24.png +0 -0
- package/electron/resources/icons/256x256.png +0 -0
- package/electron/resources/icons/32x32.png +0 -0
- package/electron/resources/icons/48x48.png +0 -0
- package/electron/resources/icons/512x512.png +0 -0
- package/electron/resources/icons/64x64.png +0 -0
- package/electron/src/App.jsx +128 -0
- package/electron/src/api/authApi.js +83 -0
- package/electron/src/api/client.js +102 -0
- package/electron/src/assets/AXioDB.png +0 -0
- package/electron/src/components/auth/CreateRoleModal.jsx +189 -0
- package/electron/src/components/auth/CreateUserModal.jsx +131 -0
- package/electron/src/components/auth/ForcePasswordChangeModal.jsx +132 -0
- package/electron/src/components/auth/ProtectedRoute.jsx +42 -0
- package/electron/src/components/auth/ResetPasswordModal.jsx +90 -0
- package/electron/src/components/auth/UserAvatarMenu.jsx +103 -0
- package/electron/src/components/collection/CreateCollectionModal.jsx +116 -0
- package/electron/src/components/collection/DeleteCollectionModal.jsx +85 -0
- package/electron/src/components/collection/SchemaViewModal.jsx +369 -0
- package/electron/src/components/dashboard/CollectionsChart.jsx +94 -0
- package/electron/src/components/dashboard/DatabaseTreeView.jsx +130 -0
- package/electron/src/components/dashboard/InMemoryCacheCard.jsx +60 -0
- package/electron/src/components/dashboard/StorageDonut.jsx +76 -0
- package/electron/src/components/dashboard/StorageUsageCard.jsx +59 -0
- package/electron/src/components/dashboard/TotalCollectionsCard.jsx +47 -0
- package/electron/src/components/dashboard/TotalDatabasesCard.jsx +43 -0
- package/electron/src/components/dashboard/TotalDocumentsCard.jsx +44 -0
- package/electron/src/components/database/CreateDatabaseModal.jsx +109 -0
- package/electron/src/components/database/DeleteDatabaseModal.jsx +97 -0
- package/electron/src/components/query/CodeEditor.jsx +343 -0
- package/electron/src/components/query/ObjectEditor.jsx +115 -0
- package/electron/src/components/query/ObjectView.jsx +44 -0
- package/electron/src/components/query/QueryEditor.jsx +32 -0
- package/electron/src/components/query/queryLanguage.js +755 -0
- package/electron/src/components/ui/Button.jsx +58 -0
- package/electron/src/components/ui/Card.jsx +29 -0
- package/electron/src/components/ui/ErrorBoundary.jsx +108 -0
- package/electron/src/components/ui/Feedback.jsx +66 -0
- package/electron/src/components/ui/Field.jsx +65 -0
- package/electron/src/components/ui/MetricCard.jsx +104 -0
- package/electron/src/components/ui/Modal.jsx +119 -0
- package/electron/src/components/ui/Page.jsx +40 -0
- package/electron/src/config/key.js +11 -0
- package/electron/src/index.css +181 -0
- package/electron/src/index.html +13 -0
- package/electron/src/layout/Sidebar.jsx +478 -0
- package/electron/src/layout/StatusBar.jsx +70 -0
- package/electron/src/layout/Titlebar.jsx +128 -0
- package/electron/src/main.jsx +42 -0
- package/electron/src/pages/ConnectionHub.jsx +464 -0
- package/electron/src/pages/Dashboard.jsx +128 -0
- package/electron/src/pages/Documents.jsx +823 -0
- package/electron/src/pages/Import.jsx +527 -0
- package/electron/src/pages/UserManagement.jsx +294 -0
- package/electron/src/pages/Welcome.jsx +157 -0
- package/electron/src/store/authStore.js +30 -0
- package/electron/src/store/connectionStore.js +103 -0
- package/electron/src/store/dbStore.js +165 -0
- package/electron/src/store/store.js +8 -0
- package/electron/src/utils/format.js +39 -0
- package/electron/vite.config.js +28 -0
- package/lib/Services/Indexation.operation.js +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { ChipIcon } from '@heroicons/react/outline'
|
|
2
|
+
import { useEffect, useState } from 'react'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Component to display the total in-memory cache size in the AxioDB system
|
|
6
|
+
*/
|
|
7
|
+
const InMemoryCacheCard = ({ CacheStorageInfo }) => {
|
|
8
|
+
const [loading, setLoading] = useState(true)
|
|
9
|
+
|
|
10
|
+
useEffect(() => {
|
|
11
|
+
// Simulate API call with dummy data
|
|
12
|
+
const fetchData = () => {
|
|
13
|
+
setTimeout(() => {
|
|
14
|
+
setLoading(false)
|
|
15
|
+
}, 800)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
fetchData()
|
|
19
|
+
}, [])
|
|
20
|
+
|
|
21
|
+
// Calculate percentage of cache used
|
|
22
|
+
const usagePercentage =
|
|
23
|
+
(CacheStorageInfo.Storage / CacheStorageInfo.Max) * 100
|
|
24
|
+
|
|
25
|
+
return (
|
|
26
|
+
<div className='bg-white rounded-lg shadow-md p-6 hover:shadow-lg transition-shadow'>
|
|
27
|
+
<div className='flex justify-between mb-3'>
|
|
28
|
+
<div>
|
|
29
|
+
<h3 className='text-lg font-medium text-ink-900'>In-Memory Cache</h3>
|
|
30
|
+
{loading
|
|
31
|
+
? (
|
|
32
|
+
<div className='h-8 mt-2 bg-ink-200 rounded animate-pulse w-24' />
|
|
33
|
+
)
|
|
34
|
+
: (
|
|
35
|
+
<p className='text-3xl font-bold text-orange-600 mt-2'>
|
|
36
|
+
{CacheStorageInfo.Storage}{' '}
|
|
37
|
+
<span className='text-lg'>{CacheStorageInfo.Unit}</span>
|
|
38
|
+
</p>
|
|
39
|
+
)}
|
|
40
|
+
<p className='text-sm text-ink-500 mt-1'>
|
|
41
|
+
of {CacheStorageInfo.Max} {CacheStorageInfo.Unit} allocated
|
|
42
|
+
</p>
|
|
43
|
+
</div>
|
|
44
|
+
<div className='p-3 bg-orange-100 rounded-full'>
|
|
45
|
+
<ChipIcon className='h-8 w-8 text-orange-600' />
|
|
46
|
+
</div>
|
|
47
|
+
</div>
|
|
48
|
+
|
|
49
|
+
{/* Progress bar */}
|
|
50
|
+
<div className='w-full bg-ink-200 rounded-full h-2.5 mt-2'>
|
|
51
|
+
<div
|
|
52
|
+
className='bg-orange-600 h-2.5 rounded-full'
|
|
53
|
+
style={{ width: `${loading ? 0 : usagePercentage}%` }}
|
|
54
|
+
/>
|
|
55
|
+
</div>
|
|
56
|
+
</div>
|
|
57
|
+
)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export default InMemoryCacheCard
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { Cell, Pie, PieChart, ResponsiveContainer } from 'recharts'
|
|
2
|
+
import Card, { CardHeader } from '../ui/Card'
|
|
3
|
+
import { Skeleton } from '../ui/Feedback'
|
|
4
|
+
import { formatStorage } from '../../utils/format'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Used-vs-free ring for a storage figure, with the percentage in the middle.
|
|
8
|
+
*
|
|
9
|
+
* A donut is the right shape here because the question is "how much of the whole is gone",
|
|
10
|
+
* not "how has it changed" - there is no time series behind these numbers.
|
|
11
|
+
*/
|
|
12
|
+
const StorageDonut = ({ title, subtitle, used = 0, total = 0, unit = 'MB', loading = false, color = 'var(--color-viz-1)' }) => {
|
|
13
|
+
const safeTotal = total > 0 ? total : 1
|
|
14
|
+
const percent = Math.min((used / safeTotal) * 100, 100)
|
|
15
|
+
const data = [
|
|
16
|
+
{ name: 'Used', value: Math.max(used, 0) },
|
|
17
|
+
{ name: 'Free', value: Math.max(safeTotal - used, 0) }
|
|
18
|
+
]
|
|
19
|
+
|
|
20
|
+
return (
|
|
21
|
+
<Card className='flex flex-col'>
|
|
22
|
+
<CardHeader title={title} subtitle={subtitle} />
|
|
23
|
+
<div className='flex flex-1 items-center gap-5 p-5'>
|
|
24
|
+
{loading
|
|
25
|
+
? <Skeleton className='h-32 w-32 rounded-full' />
|
|
26
|
+
: (
|
|
27
|
+
<div className='relative h-32 w-32 shrink-0'>
|
|
28
|
+
<ResponsiveContainer width='100%' height='100%'>
|
|
29
|
+
<PieChart>
|
|
30
|
+
<Pie
|
|
31
|
+
data={data}
|
|
32
|
+
dataKey='value'
|
|
33
|
+
innerRadius='72%'
|
|
34
|
+
outerRadius='100%'
|
|
35
|
+
startAngle={90}
|
|
36
|
+
endAngle={-270}
|
|
37
|
+
stroke='none'
|
|
38
|
+
animationDuration={800}
|
|
39
|
+
animationEasing='ease-out'
|
|
40
|
+
>
|
|
41
|
+
<Cell fill={color} />
|
|
42
|
+
<Cell fill='var(--color-ink-200)' />
|
|
43
|
+
</Pie>
|
|
44
|
+
</PieChart>
|
|
45
|
+
</ResponsiveContainer>
|
|
46
|
+
<div className='pointer-events-none absolute inset-0 flex flex-col items-center justify-center'>
|
|
47
|
+
<span className='text-xl font-bold tabular-nums text-ink-900'>
|
|
48
|
+
{percent.toFixed(percent < 10 ? 1 : 0)}%
|
|
49
|
+
</span>
|
|
50
|
+
<span className='text-[10px] uppercase tracking-wide text-ink-400'>used</span>
|
|
51
|
+
</div>
|
|
52
|
+
</div>
|
|
53
|
+
)}
|
|
54
|
+
|
|
55
|
+
<dl className='min-w-0 flex-1 space-y-3'>
|
|
56
|
+
<div>
|
|
57
|
+
<dt className='text-xs text-ink-500'>Used</dt>
|
|
58
|
+
<dd className='text-sm font-semibold tabular-nums text-ink-900'>
|
|
59
|
+
{loading ? <Skeleton className='h-4 w-20' /> : formatStorage(used, unit)}
|
|
60
|
+
</dd>
|
|
61
|
+
</div>
|
|
62
|
+
<div>
|
|
63
|
+
<dt className='text-xs text-ink-500'>Available</dt>
|
|
64
|
+
<dd className='text-sm font-semibold tabular-nums text-ink-900'>
|
|
65
|
+
{loading
|
|
66
|
+
? <Skeleton className='h-4 w-20' />
|
|
67
|
+
: formatStorage(Math.max(total - used, 0), unit)}
|
|
68
|
+
</dd>
|
|
69
|
+
</div>
|
|
70
|
+
</dl>
|
|
71
|
+
</div>
|
|
72
|
+
</Card>
|
|
73
|
+
)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export default StorageDonut
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { ServerIcon } from '@heroicons/react/outline'
|
|
2
|
+
import { useEffect, useState } from 'react'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Component to display the total storage used in the AxioDB system
|
|
6
|
+
*/
|
|
7
|
+
const StorageUsageCard = ({ storageInfo }) => {
|
|
8
|
+
const [loading, setLoading] = useState(true)
|
|
9
|
+
|
|
10
|
+
useEffect(() => {
|
|
11
|
+
// Simulate API call with dummy data
|
|
12
|
+
const fetchData = () => {
|
|
13
|
+
setTimeout(() => {
|
|
14
|
+
setLoading(false)
|
|
15
|
+
}, 800)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
fetchData()
|
|
19
|
+
}, [])
|
|
20
|
+
|
|
21
|
+
// Calculate percentage of storage used
|
|
22
|
+
const usagePercentage = (storageInfo?.total / storageInfo?.machine) * 100
|
|
23
|
+
|
|
24
|
+
return (
|
|
25
|
+
<div className='bg-white rounded-lg shadow-md p-6 hover:shadow-lg transition-shadow'>
|
|
26
|
+
<div className='flex justify-between mb-3'>
|
|
27
|
+
<div>
|
|
28
|
+
<h3 className='text-lg font-medium text-ink-900'>Storage Used</h3>
|
|
29
|
+
{loading
|
|
30
|
+
? (
|
|
31
|
+
<div className='h-8 mt-2 bg-ink-200 rounded animate-pulse w-24' />
|
|
32
|
+
)
|
|
33
|
+
: (
|
|
34
|
+
<p className='text-3xl font-bold text-brand-600 mt-2'>
|
|
35
|
+
{storageInfo?.total}{' '}
|
|
36
|
+
<span className='text-lg'>{storageInfo?.matrixUnit}</span>
|
|
37
|
+
</p>
|
|
38
|
+
)}
|
|
39
|
+
<p className='text-sm text-ink-500 mt-1'>
|
|
40
|
+
of {storageInfo?.machine} {storageInfo?.matrixUnit} available
|
|
41
|
+
</p>
|
|
42
|
+
</div>
|
|
43
|
+
<div className='p-3 bg-brand-100 rounded-full'>
|
|
44
|
+
<ServerIcon className='h-8 w-8 text-brand-600' />
|
|
45
|
+
</div>
|
|
46
|
+
</div>
|
|
47
|
+
|
|
48
|
+
{/* Progress bar */}
|
|
49
|
+
<div className='w-full bg-ink-200 rounded-full h-2.5 mt-2'>
|
|
50
|
+
<div
|
|
51
|
+
className='bg-brand-600 h-2.5 rounded-full'
|
|
52
|
+
style={{ width: `${loading ? 0 : usagePercentage}%` }}
|
|
53
|
+
/>
|
|
54
|
+
</div>
|
|
55
|
+
</div>
|
|
56
|
+
)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export default StorageUsageCard
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { CollectionIcon } from '@heroicons/react/outline'
|
|
2
|
+
import { useEffect, useState } from 'react'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Component to display the total number of collections in the AxioDB system
|
|
6
|
+
*/
|
|
7
|
+
const TotalCollectionsCard = ({ totalCollections }) => {
|
|
8
|
+
const [loading, setLoading] = useState(true)
|
|
9
|
+
|
|
10
|
+
useEffect(() => {
|
|
11
|
+
// Simulate API call with dummy data
|
|
12
|
+
const fetchData = () => {
|
|
13
|
+
setTimeout(() => {
|
|
14
|
+
setLoading(false)
|
|
15
|
+
}, 600)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
fetchData()
|
|
19
|
+
}, [])
|
|
20
|
+
|
|
21
|
+
return (
|
|
22
|
+
<div className='bg-white rounded-lg shadow-md p-6 hover:shadow-lg transition-shadow'>
|
|
23
|
+
<div className='flex justify-between'>
|
|
24
|
+
<div>
|
|
25
|
+
{/* <h3 className="text-lg font-medium text-ink-900">
|
|
26
|
+
Total Collections
|
|
27
|
+
</h3> */}
|
|
28
|
+
{loading
|
|
29
|
+
? (
|
|
30
|
+
<div className='h-8 mt-2 bg-ink-200 rounded animate-pulse w-16' />
|
|
31
|
+
)
|
|
32
|
+
: (
|
|
33
|
+
<p className='text-2xl font-bold text-indigo-600 mt-2'>
|
|
34
|
+
{totalCollections}{' '}
|
|
35
|
+
{totalCollections <= 1 ? 'Collection' : 'Collections'}
|
|
36
|
+
</p>
|
|
37
|
+
)}
|
|
38
|
+
</div>
|
|
39
|
+
<div className='p-3 bg-indigo-100 rounded-full'>
|
|
40
|
+
<CollectionIcon className='h-8 w-8 text-indigo-600' />
|
|
41
|
+
</div>
|
|
42
|
+
</div>
|
|
43
|
+
</div>
|
|
44
|
+
)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export default TotalCollectionsCard
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { DatabaseIcon } from '@heroicons/react/outline'
|
|
2
|
+
import { useEffect, useState } from 'react'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Component to display the total number of databases in the AxioDB system
|
|
6
|
+
*/
|
|
7
|
+
const TotalDatabasesCard = ({ totalDatabases }) => {
|
|
8
|
+
// In a real application, this would come from an API
|
|
9
|
+
const [loading, setLoading] = useState(true)
|
|
10
|
+
|
|
11
|
+
useEffect(() => {
|
|
12
|
+
// Simulate fetching total databases count
|
|
13
|
+
setTimeout(() => {
|
|
14
|
+
setLoading(false)
|
|
15
|
+
}, 1000) // Simulate a 1 second delay
|
|
16
|
+
}, [])
|
|
17
|
+
|
|
18
|
+
return (
|
|
19
|
+
<div className='bg-white rounded-lg shadow-md p-6 hover:shadow-lg transition-shadow'>
|
|
20
|
+
<div className='flex justify-between'>
|
|
21
|
+
<div>
|
|
22
|
+
{/* <h3 className="text-lg font-medium text-ink-900">Total Databases</h3> */}
|
|
23
|
+
{loading
|
|
24
|
+
? (
|
|
25
|
+
<div className='h-8 mt-2 bg-ink-200 rounded animate-pulse w-16 text-center' />
|
|
26
|
+
)
|
|
27
|
+
: (
|
|
28
|
+
<p className='text-2xl font-bold text-brand-700 mt-2 text-center'>
|
|
29
|
+
{totalDatabases <= 1
|
|
30
|
+
? `${totalDatabases} Database`
|
|
31
|
+
: `${totalDatabases} Databases`}
|
|
32
|
+
</p>
|
|
33
|
+
)}
|
|
34
|
+
</div>
|
|
35
|
+
<div className='p-3 bg-info-50 rounded-full'>
|
|
36
|
+
<DatabaseIcon className='h-8 w-8 text-brand-700' />
|
|
37
|
+
</div>
|
|
38
|
+
</div>
|
|
39
|
+
</div>
|
|
40
|
+
)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export default TotalDatabasesCard
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { DocumentTextIcon } from '@heroicons/react/outline'
|
|
2
|
+
import { useEffect, useState } from 'react'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Component to display the total number of documents in the AxioDB system
|
|
6
|
+
*/
|
|
7
|
+
const TotalDocumentsCard = ({ totalDocuments }) => {
|
|
8
|
+
const [loading, setLoading] = useState(true)
|
|
9
|
+
|
|
10
|
+
useEffect(() => {
|
|
11
|
+
const fetchData = () => {
|
|
12
|
+
setTimeout(() => {
|
|
13
|
+
setLoading(false)
|
|
14
|
+
}, 700)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
fetchData()
|
|
18
|
+
}, [])
|
|
19
|
+
|
|
20
|
+
return (
|
|
21
|
+
<div className='bg-white rounded-lg shadow-md p-6 hover:shadow-lg transition-shadow'>
|
|
22
|
+
<div className='flex justify-between'>
|
|
23
|
+
<div>
|
|
24
|
+
<h3 className='text-lg font-medium text-ink-900'>Total Documents</h3>
|
|
25
|
+
{loading
|
|
26
|
+
? (
|
|
27
|
+
<div className='h-8 mt-2 bg-ink-200 rounded animate-pulse w-20' />
|
|
28
|
+
)
|
|
29
|
+
: (
|
|
30
|
+
<p className='text-3xl font-bold text-purple-600 mt-2'>
|
|
31
|
+
{totalDocuments}
|
|
32
|
+
</p>
|
|
33
|
+
)}
|
|
34
|
+
<p className='text-sm text-ink-500 mt-1'>Across all collections</p>
|
|
35
|
+
</div>
|
|
36
|
+
<div className='p-3 bg-purple-100 rounded-full'>
|
|
37
|
+
<DocumentTextIcon className='h-8 w-8 text-purple-600' />
|
|
38
|
+
</div>
|
|
39
|
+
</div>
|
|
40
|
+
</div>
|
|
41
|
+
)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export default TotalDocumentsCard
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { useState } from 'react'
|
|
2
|
+
import apiClient from '../../api/client'
|
|
3
|
+
import Modal from '../ui/Modal'
|
|
4
|
+
import Button from '../ui/Button'
|
|
5
|
+
import { Input } from '../ui/Field'
|
|
6
|
+
|
|
7
|
+
const CreateDatabaseModal = ({ isOpen, onClose, onDatabaseCreated }) => {
|
|
8
|
+
const [databaseName, setDatabaseName] = useState('')
|
|
9
|
+
const [isSubmitting, setIsSubmitting] = useState(false)
|
|
10
|
+
const [error, setError] = useState('')
|
|
11
|
+
|
|
12
|
+
// Reset form state when modal is opened/closed
|
|
13
|
+
const handleClose = () => {
|
|
14
|
+
setDatabaseName('')
|
|
15
|
+
setError('')
|
|
16
|
+
setIsSubmitting(false) // Ensure submission state is reset when closing
|
|
17
|
+
onClose()
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const handleSubmit = async (e) => {
|
|
21
|
+
e.preventDefault()
|
|
22
|
+
|
|
23
|
+
// Validate input
|
|
24
|
+
if (!databaseName.trim()) {
|
|
25
|
+
setError('Database name is required')
|
|
26
|
+
return
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Alphanumeric validation (plus underscores)
|
|
30
|
+
if (!/^[a-zA-Z0-9_]+$/.test(databaseName)) {
|
|
31
|
+
setError(
|
|
32
|
+
'Database name can only contain letters, numbers, and underscores'
|
|
33
|
+
)
|
|
34
|
+
return
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
setIsSubmitting(true)
|
|
38
|
+
setError('')
|
|
39
|
+
|
|
40
|
+
try {
|
|
41
|
+
const response = await apiClient.post('/api/db/create-database', {
|
|
42
|
+
name: databaseName
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
if (
|
|
46
|
+
response.data.statusCode === 200 ||
|
|
47
|
+
response.data.statusCode === 201
|
|
48
|
+
) {
|
|
49
|
+
onDatabaseCreated(databaseName)
|
|
50
|
+
setIsSubmitting(false) // Reset submission state on success
|
|
51
|
+
handleClose()
|
|
52
|
+
} else {
|
|
53
|
+
setIsSubmitting(false) // Reset submission state on success
|
|
54
|
+
throw new Error('Failed to create database')
|
|
55
|
+
}
|
|
56
|
+
} catch (error) {
|
|
57
|
+
console.error('Error creating database:', error)
|
|
58
|
+
setError(
|
|
59
|
+
error.response?.data?.message ||
|
|
60
|
+
'Failed to create database. Please try again.'
|
|
61
|
+
)
|
|
62
|
+
setIsSubmitting(false)
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
const DatabaseIcon = (
|
|
66
|
+
<svg className='h-5 w-5' fill='none' viewBox='0 0 24 24' stroke='currentColor' strokeWidth={2}>
|
|
67
|
+
<ellipse cx='12' cy='6' rx='8' ry='3' />
|
|
68
|
+
<path d='M4 6v12c0 1.66 3.58 3 8 3s8-1.34 8-3V6' />
|
|
69
|
+
</svg>
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
return (
|
|
73
|
+
<Modal
|
|
74
|
+
isOpen={isOpen}
|
|
75
|
+
onClose={handleClose}
|
|
76
|
+
title='Create database'
|
|
77
|
+
icon={DatabaseIcon}
|
|
78
|
+
size='md'
|
|
79
|
+
footer={
|
|
80
|
+
<>
|
|
81
|
+
<Button variant='secondary' onClick={handleClose} disabled={isSubmitting}>
|
|
82
|
+
Cancel
|
|
83
|
+
</Button>
|
|
84
|
+
<Button onClick={handleSubmit} loading={isSubmitting} disabled={!databaseName.trim()}>
|
|
85
|
+
Create database
|
|
86
|
+
</Button>
|
|
87
|
+
</>
|
|
88
|
+
}
|
|
89
|
+
>
|
|
90
|
+
<form onSubmit={handleSubmit}>
|
|
91
|
+
<Input
|
|
92
|
+
label='Database name'
|
|
93
|
+
value={databaseName}
|
|
94
|
+
onChange={(event) => setDatabaseName(event.target.value)}
|
|
95
|
+
placeholder='AppDB'
|
|
96
|
+
mono
|
|
97
|
+
autoFocus
|
|
98
|
+
error={error || undefined}
|
|
99
|
+
hint='Letters, numbers and underscores only. The name "config" is reserved by AxioDB.'
|
|
100
|
+
/>
|
|
101
|
+
|
|
102
|
+
{/* Lets Enter submit the form without rendering a second visible button. */}
|
|
103
|
+
<button type='submit' className='hidden' aria-hidden='true' tabIndex={-1} />
|
|
104
|
+
</form>
|
|
105
|
+
</Modal>
|
|
106
|
+
)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export default CreateDatabaseModal
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { useState } from 'react'
|
|
2
|
+
import apiClient from '../../api/client'
|
|
3
|
+
import Modal from '../ui/Modal'
|
|
4
|
+
import Button from '../ui/Button'
|
|
5
|
+
import { Alert } from '../ui/Feedback'
|
|
6
|
+
|
|
7
|
+
const WarningIcon = (
|
|
8
|
+
<svg className='h-5 w-5' fill='none' viewBox='0 0 24 24' stroke='currentColor' strokeWidth={2}>
|
|
9
|
+
<path strokeLinecap='round' strokeLinejoin='round' d='M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126zM12 15.75h.007v.008H12v-.008z' />
|
|
10
|
+
</svg>
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Destructive confirmation. Requires the database name to be typed back before the delete
|
|
15
|
+
* button unlocks - this removes every collection and document underneath it, and there is no
|
|
16
|
+
* undo, so a single mis-aimed click should not be enough.
|
|
17
|
+
*/
|
|
18
|
+
const DeleteDatabaseModal = ({ isOpen, dbName, onClose, onConfirmDelete }) => {
|
|
19
|
+
const [isDeleting, setIsDeleting] = useState(false)
|
|
20
|
+
const [confirmation, setConfirmation] = useState('')
|
|
21
|
+
const [error, setError] = useState(null)
|
|
22
|
+
|
|
23
|
+
const handleClose = () => {
|
|
24
|
+
setConfirmation('')
|
|
25
|
+
setError(null)
|
|
26
|
+
onClose()
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const handleDelete = async () => {
|
|
30
|
+
setIsDeleting(true)
|
|
31
|
+
setError(null)
|
|
32
|
+
|
|
33
|
+
try {
|
|
34
|
+
const response = await apiClient.delete('/api/db/delete-database', {
|
|
35
|
+
params: { dbName }
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
if (response.status !== 200) throw new Error('Failed to delete database')
|
|
39
|
+
|
|
40
|
+
setConfirmation('')
|
|
41
|
+
onConfirmDelete()
|
|
42
|
+
} catch (err) {
|
|
43
|
+
console.error('Error deleting database:', err)
|
|
44
|
+
setError(err.response?.data?.message || 'Failed to delete database. Please try again.')
|
|
45
|
+
} finally {
|
|
46
|
+
setIsDeleting(false)
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return (
|
|
51
|
+
<Modal
|
|
52
|
+
isOpen={isOpen}
|
|
53
|
+
onClose={handleClose}
|
|
54
|
+
title='Delete database'
|
|
55
|
+
subtitle={dbName}
|
|
56
|
+
icon={WarningIcon}
|
|
57
|
+
tone='danger'
|
|
58
|
+
size='md'
|
|
59
|
+
footer={
|
|
60
|
+
<>
|
|
61
|
+
<Button variant='secondary' onClick={handleClose} disabled={isDeleting}>
|
|
62
|
+
Cancel
|
|
63
|
+
</Button>
|
|
64
|
+
<Button
|
|
65
|
+
variant='danger'
|
|
66
|
+
onClick={handleDelete}
|
|
67
|
+
loading={isDeleting}
|
|
68
|
+
disabled={confirmation !== dbName}
|
|
69
|
+
>
|
|
70
|
+
Delete permanently
|
|
71
|
+
</Button>
|
|
72
|
+
</>
|
|
73
|
+
}
|
|
74
|
+
>
|
|
75
|
+
<Alert tone='danger' title='This cannot be undone'>
|
|
76
|
+
Deleting <strong>{dbName}</strong> removes every collection and document inside it
|
|
77
|
+
from disk.
|
|
78
|
+
</Alert>
|
|
79
|
+
|
|
80
|
+
<label htmlFor='confirm-db-name' className='mt-5 block text-sm text-ink-700'>
|
|
81
|
+
Type <span className='font-mono font-semibold text-ink-900'>{dbName}</span> to confirm:
|
|
82
|
+
</label>
|
|
83
|
+
<input
|
|
84
|
+
id='confirm-db-name'
|
|
85
|
+
value={confirmation}
|
|
86
|
+
onChange={(event) => setConfirmation(event.target.value)}
|
|
87
|
+
autoComplete='off'
|
|
88
|
+
placeholder={dbName}
|
|
89
|
+
className='mt-2 w-full rounded-lg border border-ink-300 px-3 py-2 font-mono text-sm shadow-sm transition-all focus:border-danger-500 focus:outline-none focus:ring-2 focus:ring-danger-500/25'
|
|
90
|
+
/>
|
|
91
|
+
|
|
92
|
+
{error && <Alert tone='danger' className='mt-4'>{error}</Alert>}
|
|
93
|
+
</Modal>
|
|
94
|
+
)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export default DeleteDatabaseModal
|