@v1ct0rbr/minivault-web 0.4.3 → 0.5.0
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/README.md +74 -74
- package/dist/api/MinivaultApi.d.ts +2 -1
- package/dist/components/DatabaseBanner.d.ts +3 -1
- package/dist/components/DiagnosticsTab.d.ts +1 -0
- package/dist/hooks/useBackups.d.ts +2 -0
- package/dist/index.d.ts +3 -2
- package/dist/minivault-web.js +295 -92
- package/dist/types/minivault.d.ts +9 -0
- package/package.json +72 -72
package/README.md
CHANGED
|
@@ -1,75 +1,75 @@
|
|
|
1
|
-
# minivault-web
|
|
2
|
-
|
|
3
|
-
Módulo independente de **gestão de backups** (cliente do serviço
|
|
4
|
-
[minivault](../minivault)). Consumível como pacote npm em qualquer app React,
|
|
5
|
-
estilizado com Bootstrap. Comunica-se com a API REST do minivault — normalmente
|
|
6
|
-
através de um **proxy autenticado** (ex.: `/minivault` no backend do Siga+), de
|
|
7
|
-
modo que a `X-API-Key` do minivault jamais trafega no browser.
|
|
8
|
-
|
|
9
|
-
## Recursos
|
|
10
|
-
|
|
11
|
-
- Lista de backups com paginação, status, tamanho e data.
|
|
12
|
-
- Criação de backup (com origem de arquivos LOCAL ou S3, opcional).
|
|
13
|
-
- Restauração de backup em um banco informado.
|
|
14
|
-
- Importação de dump SQL em texto (`psql`/`mysql`) via upload.
|
|
15
|
-
- Download e exclusão de backups.
|
|
16
|
-
- Tema claro/escuro e auto-refresh.
|
|
17
|
-
|
|
18
|
-
## Instalação
|
|
19
|
-
|
|
20
|
-
```bash
|
|
21
|
-
npm install @v1ct0rbr/minivault-web
|
|
22
|
-
```
|
|
23
|
-
|
|
24
|
-
Como dependência local durante o desenvolvimento:
|
|
25
|
-
|
|
26
|
-
```bash
|
|
27
|
-
npm install file:../minivault-web
|
|
28
|
-
```
|
|
29
|
-
|
|
30
|
-
## Uso no Siga+ (proxy autenticado)
|
|
31
|
-
|
|
32
|
-
```tsx
|
|
33
|
-
import { MinivaultModule } from '@v1ct0rbr/minivault-web'
|
|
34
|
-
import '@v1ct0rbr/minivault-web/style.css'
|
|
35
|
-
import { useAuthStore } from '@/store/useAuthStore'
|
|
36
|
-
|
|
37
|
-
const apiBase = import.meta.env.VITE_API_BASE_URL || 'http://localhost:8080'
|
|
38
|
-
|
|
39
|
-
export function MinivaultPage() {
|
|
40
|
-
const getToken = () => useAuthStore.getState().token
|
|
41
|
-
return <MinivaultModule baseUrl={apiBase + '/minivault'} getToken={getToken} />
|
|
42
|
-
}
|
|
43
|
-
```
|
|
44
|
-
|
|
45
|
-
## Uso standalone (desenvolvimento)
|
|
46
|
-
|
|
47
|
-
```tsx
|
|
48
|
-
<MinivaultModule baseUrl="http://localhost:8081" token="api-key-do-minivault" />
|
|
49
|
-
```
|
|
50
|
-
|
|
51
|
-
> Em produção o token do minivault deve ficar apenas no proxy do backend, nunca
|
|
52
|
-
> no bundle do frontend.
|
|
53
|
-
|
|
54
|
-
## Desenvolvimento
|
|
55
|
-
|
|
56
|
-
```bash
|
|
57
|
-
npm install
|
|
58
|
-
npm run dev # playground standalone (porta 5176)
|
|
59
|
-
npm run build:lib # gera dist/ (lib ES + types)
|
|
60
|
-
npm test # vitest
|
|
61
|
-
npm run typecheck # tsc --noEmit
|
|
62
|
-
```
|
|
63
|
-
|
|
64
|
-
### Playground standalone
|
|
65
|
-
|
|
66
|
-
```bash
|
|
67
|
-
VITE_MINIVAULT_API_URL=http://localhost:8081 VITE_MINIVAULT_API_TOKEN=chave npm run dev
|
|
68
|
-
```
|
|
69
|
-
|
|
70
|
-
## Publicação
|
|
71
|
-
|
|
72
|
-
```bash
|
|
73
|
-
npm run build:lib && npm test
|
|
74
|
-
npm publish --access public
|
|
1
|
+
# minivault-web
|
|
2
|
+
|
|
3
|
+
Módulo independente de **gestão de backups** (cliente do serviço
|
|
4
|
+
[minivault](../minivault)). Consumível como pacote npm em qualquer app React,
|
|
5
|
+
estilizado com Bootstrap. Comunica-se com a API REST do minivault — normalmente
|
|
6
|
+
através de um **proxy autenticado** (ex.: `/minivault` no backend do Siga+), de
|
|
7
|
+
modo que a `X-API-Key` do minivault jamais trafega no browser.
|
|
8
|
+
|
|
9
|
+
## Recursos
|
|
10
|
+
|
|
11
|
+
- Lista de backups com paginação, status, tamanho e data.
|
|
12
|
+
- Criação de backup (com origem de arquivos LOCAL ou S3, opcional).
|
|
13
|
+
- Restauração de backup em um banco informado.
|
|
14
|
+
- Importação de dump SQL em texto (`psql`/`mysql`) via upload.
|
|
15
|
+
- Download e exclusão de backups.
|
|
16
|
+
- Tema claro/escuro e auto-refresh.
|
|
17
|
+
|
|
18
|
+
## Instalação
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npm install @v1ct0rbr/minivault-web
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Como dependência local durante o desenvolvimento:
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
npm install file:../minivault-web
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Uso no Siga+ (proxy autenticado)
|
|
31
|
+
|
|
32
|
+
```tsx
|
|
33
|
+
import { MinivaultModule } from '@v1ct0rbr/minivault-web'
|
|
34
|
+
import '@v1ct0rbr/minivault-web/style.css'
|
|
35
|
+
import { useAuthStore } from '@/store/useAuthStore'
|
|
36
|
+
|
|
37
|
+
const apiBase = import.meta.env.VITE_API_BASE_URL || 'http://localhost:8080'
|
|
38
|
+
|
|
39
|
+
export function MinivaultPage() {
|
|
40
|
+
const getToken = () => useAuthStore.getState().token
|
|
41
|
+
return <MinivaultModule baseUrl={apiBase + '/minivault'} getToken={getToken} />
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Uso standalone (desenvolvimento)
|
|
46
|
+
|
|
47
|
+
```tsx
|
|
48
|
+
<MinivaultModule baseUrl="http://localhost:8081" token="api-key-do-minivault" />
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
> Em produção o token do minivault deve ficar apenas no proxy do backend, nunca
|
|
52
|
+
> no bundle do frontend.
|
|
53
|
+
|
|
54
|
+
## Desenvolvimento
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
npm install
|
|
58
|
+
npm run dev # playground standalone (porta 5176)
|
|
59
|
+
npm run build:lib # gera dist/ (lib ES + types)
|
|
60
|
+
npm test # vitest
|
|
61
|
+
npm run typecheck # tsc --noEmit
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### Playground standalone
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
VITE_MINIVAULT_API_URL=http://localhost:8081 VITE_MINIVAULT_API_TOKEN=chave npm run dev
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Publicação
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
npm run build:lib && npm test
|
|
74
|
+
npm publish --access public
|
|
75
75
|
```
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Backup, DatabaseCredentials, DbInfo, DumpOptions, ImportResult, OriginStorageConfig, PageResponse, RelationshipInfo, StorageFileItem, TableDetail, TableInfo, VerifyResult } from '../types/minivault';
|
|
1
|
+
import { Backup, ConnectionCheck, DatabaseCredentials, DbInfo, DumpOptions, ImportResult, OriginStorageConfig, PageResponse, RelationshipInfo, StorageFileItem, TableDetail, TableInfo, VerifyResult } from '../types/minivault';
|
|
2
2
|
export interface MinivaultApiOptions {
|
|
3
3
|
/**
|
|
4
4
|
* URL base da API. Exemplos:
|
|
@@ -44,6 +44,7 @@ export declare class MinivaultApi {
|
|
|
44
44
|
listDbTables(): Promise<TableInfo[]>;
|
|
45
45
|
getDbTable(ref: string): Promise<TableDetail>;
|
|
46
46
|
listDbRelationships(): Promise<RelationshipInfo[]>;
|
|
47
|
+
getDiagnostics(): Promise<ConnectionCheck[]>;
|
|
47
48
|
listStorageFiles(type: string, prefix?: string, storageConfig?: OriginStorageConfig): Promise<StorageFileItem[]>;
|
|
48
49
|
downloadStorageFile(key: string, type: string, storageConfig?: OriginStorageConfig): Promise<Blob>;
|
|
49
50
|
deleteStorageFile(key: string, type: string, storageConfig?: OriginStorageConfig): Promise<void>;
|
|
@@ -5,5 +5,7 @@ export interface DatabaseBannerProps {
|
|
|
5
5
|
error?: unknown;
|
|
6
6
|
/** Rótulo contextual, ex.: 'banco de origem' / 'banco de destino'. */
|
|
7
7
|
label: string;
|
|
8
|
+
/** Modo compacto: não expõe host/porta/nome do banco nem detalhes de configuração. */
|
|
9
|
+
compact?: boolean;
|
|
8
10
|
}
|
|
9
|
-
export declare function DatabaseBanner({ dbInfo, isLoading, error, label }: DatabaseBannerProps): import("react").JSX.Element;
|
|
11
|
+
export declare function DatabaseBanner({ dbInfo, isLoading, error, label, compact }: DatabaseBannerProps): import("react").JSX.Element;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function DiagnosticsTab(): import("react").JSX.Element;
|
|
@@ -2,7 +2,9 @@ import { DatabaseCredentials, DumpOptions, OriginStorageConfig } from '../types/
|
|
|
2
2
|
export declare const backupsKeys: {
|
|
3
3
|
all: readonly ["minivault", "backups"];
|
|
4
4
|
db: readonly ["minivault", "db"];
|
|
5
|
+
diagnostics: readonly ["minivault", "diagnostics"];
|
|
5
6
|
};
|
|
7
|
+
export declare function useDiagnostics(enabled?: boolean): import('@tanstack/react-query').UseQueryResult<import('../types/minivault').ConnectionCheck[], Error>;
|
|
6
8
|
export declare function useBackups(page?: number, size?: number, enabled?: boolean): import('@tanstack/react-query').UseQueryResult<import('../types/minivault').PageResponse<import('../types/minivault').Backup>, Error>;
|
|
7
9
|
export declare function useDbInfo(enabled?: boolean): import('@tanstack/react-query').UseQueryResult<import('../types/minivault').DbInfo, Error>;
|
|
8
10
|
export declare function useDbTables(enabled?: boolean): import('@tanstack/react-query').UseQueryResult<import('../types/minivault').TableInfo[], Error>;
|
package/dist/index.d.ts
CHANGED
|
@@ -5,12 +5,13 @@ export { MinivaultApi, MinivaultApiError } from './api/MinivaultApi';
|
|
|
5
5
|
export type { MinivaultApiOptions } from './api/MinivaultApi';
|
|
6
6
|
export { BackupsTab } from './components/BackupsTab';
|
|
7
7
|
export { CreateBackupTab } from './components/CreateBackupTab';
|
|
8
|
+
export { DiagnosticsTab } from './components/DiagnosticsTab';
|
|
8
9
|
export { ImportDumpTab } from './components/ImportDumpTab';
|
|
9
10
|
export { StorageExplorerTab } from './components/StorageExplorerTab';
|
|
10
11
|
export { RestoreModal } from './components/RestoreModal';
|
|
11
12
|
export { CredentialsFields } from './components/CredentialsFields';
|
|
12
13
|
export { DatabaseBanner } from './components/DatabaseBanner';
|
|
13
14
|
export { StatusBadge } from './components/StatusBadge';
|
|
14
|
-
export { useBackups, useCreateBackup, useRestoreBackup, useDeleteBackup, useDownloadBackup, useImportDump, useImportFromStorage, useDbInfo, useDbTables, useDbTableDetail, useDbRelationships, } from './hooks/useBackups';
|
|
15
|
+
export { useBackups, useCreateBackup, useRestoreBackup, useDeleteBackup, useDownloadBackup, useImportDump, useImportFromStorage, useDbInfo, useDbTables, useDbTableDetail, useDbRelationships, useDiagnostics, } from './hooks/useBackups';
|
|
15
16
|
export { formatFileSize, formatDate, STATUS_LABELS, STATUS_BADGE, DATABASE_TYPE_LABELS } from './utils/format';
|
|
16
|
-
export type { Backup, BackupRequest, BackupStatus, ColumnInfo, DatabaseCredentials, DatabaseType, DbInfo, DumpOptions, DumpFormat, ImportResult, ImportFromStorageRequest, OriginStorageConfig, PageResponse, RelationshipInfo, RestoreRequest, StorageFileItem, TableDetail, TableInfo, } from './types/minivault';
|
|
17
|
+
export type { Backup, BackupRequest, BackupStatus, ColumnInfo, ConnectionCheck, DatabaseCredentials, DatabaseType, DbInfo, DumpOptions, DumpFormat, ImportResult, ImportFromStorageRequest, OriginStorageConfig, PageResponse, RelationshipInfo, RestoreRequest, StorageFileItem, TableDetail, TableInfo, VerifyResult, } from './types/minivault';
|
package/dist/minivault-web.js
CHANGED
|
@@ -135,6 +135,9 @@ var h = class extends Error {
|
|
|
135
135
|
listDbRelationships() {
|
|
136
136
|
return this.request("/api/db/relationships");
|
|
137
137
|
}
|
|
138
|
+
getDiagnostics() {
|
|
139
|
+
return this.request("/api/diagnostics");
|
|
140
|
+
}
|
|
138
141
|
listStorageFiles(e, t, n) {
|
|
139
142
|
let r = new URLSearchParams({ type: e });
|
|
140
143
|
return t && r.set("prefix", t), n && (n.endpoint && r.set("endpoint", n.endpoint), n.bucketName && r.set("bucketName", n.bucketName), n.accessKey && r.set("accessKey", n.accessKey), n.secretKey && r.set("secretKey", n.secretKey), n.region && r.set("region", n.region), n.pathStyleEnabled && r.set("pathStyleEnabled", "true"), n.localPath && r.set("localPath", n.localPath)), this.request(`/api/storage/files?${r.toString()}`);
|
|
@@ -202,9 +205,18 @@ function x() {
|
|
|
202
205
|
//#region src/hooks/useBackups.ts
|
|
203
206
|
var S = {
|
|
204
207
|
all: ["minivault", "backups"],
|
|
205
|
-
db: ["minivault", "db"]
|
|
208
|
+
db: ["minivault", "db"],
|
|
209
|
+
diagnostics: ["minivault", "diagnostics"]
|
|
206
210
|
};
|
|
207
|
-
function C(e =
|
|
211
|
+
function C(e = !0) {
|
|
212
|
+
let t = x();
|
|
213
|
+
return u({
|
|
214
|
+
queryKey: S.diagnostics,
|
|
215
|
+
queryFn: () => t.getDiagnostics(),
|
|
216
|
+
enabled: e && !!t
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
function w(e = 0, t = 10, n = !0) {
|
|
208
220
|
let r = x();
|
|
209
221
|
return u({
|
|
210
222
|
queryKey: [
|
|
@@ -216,7 +228,7 @@ function C(e = 0, t = 10, n = !0) {
|
|
|
216
228
|
enabled: n && !!r
|
|
217
229
|
});
|
|
218
230
|
}
|
|
219
|
-
function
|
|
231
|
+
function T(e = !0) {
|
|
220
232
|
let t = x();
|
|
221
233
|
return u({
|
|
222
234
|
queryKey: [...S.db, "info"],
|
|
@@ -224,7 +236,7 @@ function w(e = !0) {
|
|
|
224
236
|
enabled: e && !!t
|
|
225
237
|
});
|
|
226
238
|
}
|
|
227
|
-
function
|
|
239
|
+
function E(e = !0) {
|
|
228
240
|
let t = x();
|
|
229
241
|
return u({
|
|
230
242
|
queryKey: [...S.db, "tables"],
|
|
@@ -232,7 +244,7 @@ function T(e = !0) {
|
|
|
232
244
|
enabled: e && !!t
|
|
233
245
|
});
|
|
234
246
|
}
|
|
235
|
-
function
|
|
247
|
+
function D(e, t = !0) {
|
|
236
248
|
let n = x();
|
|
237
249
|
return u({
|
|
238
250
|
queryKey: [
|
|
@@ -244,7 +256,7 @@ function E(e, t = !0) {
|
|
|
244
256
|
enabled: t && !!n && !!e
|
|
245
257
|
});
|
|
246
258
|
}
|
|
247
|
-
function
|
|
259
|
+
function O(e = !0) {
|
|
248
260
|
let t = x();
|
|
249
261
|
return u({
|
|
250
262
|
queryKey: [...S.db, "relationships"],
|
|
@@ -252,74 +264,74 @@ function D(e = !0) {
|
|
|
252
264
|
enabled: e && !!t
|
|
253
265
|
});
|
|
254
266
|
}
|
|
255
|
-
function
|
|
267
|
+
function k() {
|
|
256
268
|
let e = b(), t = d();
|
|
257
269
|
return l({
|
|
258
270
|
mutationFn: (t) => e.createBackup(t.database, t.originStorage, t.dumpOptions),
|
|
259
271
|
onSuccess: () => t.invalidateQueries({ queryKey: S.all })
|
|
260
272
|
});
|
|
261
273
|
}
|
|
262
|
-
function
|
|
274
|
+
function A() {
|
|
263
275
|
let e = b(), t = d();
|
|
264
276
|
return l({
|
|
265
277
|
mutationFn: (t) => e.restoreBackup(t.id, t.database, t.originStorage),
|
|
266
278
|
onSuccess: () => t.invalidateQueries({ queryKey: S.all })
|
|
267
279
|
});
|
|
268
280
|
}
|
|
269
|
-
function
|
|
281
|
+
function j() {
|
|
270
282
|
let e = b(), t = d();
|
|
271
283
|
return l({
|
|
272
284
|
mutationFn: (t) => e.deleteBackup(t),
|
|
273
285
|
onSuccess: () => t.invalidateQueries({ queryKey: S.all })
|
|
274
286
|
});
|
|
275
287
|
}
|
|
276
|
-
function
|
|
288
|
+
function M() {
|
|
277
289
|
let e = b(), t = d();
|
|
278
290
|
return l({
|
|
279
291
|
mutationFn: (t) => e.verifyBackup(t),
|
|
280
292
|
onSuccess: () => t.invalidateQueries({ queryKey: S.all })
|
|
281
293
|
});
|
|
282
294
|
}
|
|
283
|
-
function
|
|
295
|
+
function N() {
|
|
284
296
|
let e = b();
|
|
285
297
|
return l({ mutationFn: (t) => e.downloadBackup(t.id) });
|
|
286
298
|
}
|
|
287
|
-
function
|
|
299
|
+
function P() {
|
|
288
300
|
let e = b();
|
|
289
301
|
return l({ mutationFn: (t) => e.importDump(t.file, t.database) });
|
|
290
302
|
}
|
|
291
|
-
function
|
|
303
|
+
function F() {
|
|
292
304
|
let e = b();
|
|
293
305
|
return l({ mutationFn: (t) => e.importFromStorage(t.storageType, t.storagePath, t.database, t.storageConfig) });
|
|
294
306
|
}
|
|
295
307
|
//#endregion
|
|
296
308
|
//#region src/utils/format.ts
|
|
297
|
-
var
|
|
309
|
+
var I = new Intl.DateTimeFormat("pt-BR", {
|
|
298
310
|
day: "2-digit",
|
|
299
311
|
month: "2-digit",
|
|
300
312
|
year: "numeric",
|
|
301
313
|
hour: "2-digit",
|
|
302
314
|
minute: "2-digit"
|
|
303
|
-
}),
|
|
315
|
+
}), L = [
|
|
304
316
|
"B",
|
|
305
317
|
"KB",
|
|
306
318
|
"MB",
|
|
307
319
|
"GB",
|
|
308
320
|
"TB"
|
|
309
321
|
];
|
|
310
|
-
function
|
|
322
|
+
function R(e) {
|
|
311
323
|
if (e == null || e < 0) return "—";
|
|
312
324
|
if (e < 1024) return `${e} B`;
|
|
313
325
|
let t = e, n = 0;
|
|
314
|
-
for (; t >= 1024 && n <
|
|
315
|
-
return `${Number.isInteger(t) ? t : Number(t.toFixed(1))} ${
|
|
326
|
+
for (; t >= 1024 && n < L.length - 1;) t /= 1024, n += 1;
|
|
327
|
+
return `${Number.isInteger(t) ? t : Number(t.toFixed(1))} ${L[n]}`;
|
|
316
328
|
}
|
|
317
|
-
function
|
|
329
|
+
function z(e) {
|
|
318
330
|
if (!e) return "—";
|
|
319
331
|
let t = new Date(e);
|
|
320
|
-
return Number.isNaN(t.getTime()) ? "—" :
|
|
332
|
+
return Number.isNaN(t.getTime()) ? "—" : I.format(t);
|
|
321
333
|
}
|
|
322
|
-
var
|
|
334
|
+
var B = {
|
|
323
335
|
PENDING: "Pendente",
|
|
324
336
|
IN_PROGRESS: "Em andamento",
|
|
325
337
|
COMPLETED: "Concluído",
|
|
@@ -327,7 +339,7 @@ var z = {
|
|
|
327
339
|
RESTORING: "Restaurando",
|
|
328
340
|
RESTORED: "Restaurado",
|
|
329
341
|
RESTORE_FAILED: "Falha na restauração"
|
|
330
|
-
},
|
|
342
|
+
}, V = {
|
|
331
343
|
PENDING: "text-bg-secondary",
|
|
332
344
|
IN_PROGRESS: "text-bg-primary",
|
|
333
345
|
COMPLETED: "text-bg-success",
|
|
@@ -335,7 +347,7 @@ var z = {
|
|
|
335
347
|
RESTORING: "text-bg-warning",
|
|
336
348
|
RESTORED: "text-bg-info",
|
|
337
349
|
RESTORE_FAILED: "text-bg-danger"
|
|
338
|
-
},
|
|
350
|
+
}, H = {
|
|
339
351
|
postgresql: "PostgreSQL",
|
|
340
352
|
postgres: "PostgreSQL",
|
|
341
353
|
mysql: "MySQL",
|
|
@@ -344,8 +356,8 @@ var z = {
|
|
|
344
356
|
};
|
|
345
357
|
//#endregion
|
|
346
358
|
//#region src/components/StatusBadge.tsx
|
|
347
|
-
function
|
|
348
|
-
let t =
|
|
359
|
+
function U({ status: e }) {
|
|
360
|
+
let t = V[e] ?? "text-bg-secondary", n = B[e] ?? e;
|
|
349
361
|
return /* @__PURE__ */ p("span", {
|
|
350
362
|
className: `badge rounded-pill ${t}`,
|
|
351
363
|
children: n
|
|
@@ -353,7 +365,7 @@ function H({ status: e }) {
|
|
|
353
365
|
}
|
|
354
366
|
//#endregion
|
|
355
367
|
//#region src/components/DatabaseBanner.tsx
|
|
356
|
-
function
|
|
368
|
+
function W({ dbInfo: e, isLoading: t, error: n, label: r, compact: i = !1 }) {
|
|
357
369
|
if (t) return /* @__PURE__ */ p("div", {
|
|
358
370
|
className: "mb-3",
|
|
359
371
|
children: /* @__PURE__ */ m("small", {
|
|
@@ -372,10 +384,16 @@ function U({ dbInfo: e, isLoading: t, error: n, label: r }) {
|
|
|
372
384
|
"aria-hidden": !0
|
|
373
385
|
}),
|
|
374
386
|
"Não foi possível consultar o banco configurado: ",
|
|
375
|
-
|
|
387
|
+
K(n)
|
|
376
388
|
]
|
|
377
389
|
});
|
|
378
|
-
if (!e || !e.configured) return /* @__PURE__ */ m("div", {
|
|
390
|
+
if (!e || !e.configured) return i ? /* @__PURE__ */ m("div", {
|
|
391
|
+
className: "alert alert-danger mb-3 py-2 small",
|
|
392
|
+
children: [/* @__PURE__ */ p("i", {
|
|
393
|
+
className: "bi bi-x-octagon me-2",
|
|
394
|
+
"aria-hidden": !0
|
|
395
|
+
}), "Banco do servidor não configurado. Não é possível criar backups."]
|
|
396
|
+
}) : /* @__PURE__ */ m("div", {
|
|
379
397
|
className: "alert alert-danger mb-3 py-2 small",
|
|
380
398
|
children: [
|
|
381
399
|
/* @__PURE__ */ p("i", {
|
|
@@ -388,8 +406,57 @@ function U({ dbInfo: e, isLoading: t, error: n, label: r }) {
|
|
|
388
406
|
"). A criação e a importação usam esse banco."
|
|
389
407
|
]
|
|
390
408
|
});
|
|
391
|
-
let
|
|
392
|
-
return /* @__PURE__ */ m("div", {
|
|
409
|
+
let a = H[e.databaseType ?? ""] ?? e.databaseType ?? "postgresql";
|
|
410
|
+
return i ? /* @__PURE__ */ m("div", {
|
|
411
|
+
className: "mb-3",
|
|
412
|
+
children: [
|
|
413
|
+
/* @__PURE__ */ m("div", {
|
|
414
|
+
className: "d-flex flex-wrap align-items-center gap-2 small text-body-secondary",
|
|
415
|
+
children: [/* @__PURE__ */ p("i", {
|
|
416
|
+
className: "bi bi-database-check text-success",
|
|
417
|
+
"aria-hidden": !0
|
|
418
|
+
}), /* @__PURE__ */ m("span", { children: [
|
|
419
|
+
r,
|
|
420
|
+
": ",
|
|
421
|
+
/* @__PURE__ */ p("strong", { children: "banco do servidor disponível" })
|
|
422
|
+
] })]
|
|
423
|
+
}),
|
|
424
|
+
e.exportExcludedTables.length > 0 && /* @__PURE__ */ m("div", {
|
|
425
|
+
className: "mt-1 small",
|
|
426
|
+
children: [
|
|
427
|
+
/* @__PURE__ */ p("i", {
|
|
428
|
+
className: "bi bi-shield-lock me-1",
|
|
429
|
+
"aria-hidden": !0
|
|
430
|
+
}),
|
|
431
|
+
/* @__PURE__ */ p("span", {
|
|
432
|
+
className: "text-body-secondary",
|
|
433
|
+
children: "Exportação sempre ignora: "
|
|
434
|
+
}),
|
|
435
|
+
e.exportExcludedTables.map((e) => /* @__PURE__ */ p("span", {
|
|
436
|
+
className: "badge text-bg-secondary me-1",
|
|
437
|
+
children: e
|
|
438
|
+
}, `ex-${e}`))
|
|
439
|
+
]
|
|
440
|
+
}),
|
|
441
|
+
e.importExcludedTables.length > 0 && /* @__PURE__ */ m("div", {
|
|
442
|
+
className: "mt-1 small",
|
|
443
|
+
children: [
|
|
444
|
+
/* @__PURE__ */ p("i", {
|
|
445
|
+
className: "bi bi-shield-lock me-1",
|
|
446
|
+
"aria-hidden": !0
|
|
447
|
+
}),
|
|
448
|
+
/* @__PURE__ */ p("span", {
|
|
449
|
+
className: "text-body-secondary",
|
|
450
|
+
children: "Importação sempre ignora: "
|
|
451
|
+
}),
|
|
452
|
+
e.importExcludedTables.map((e) => /* @__PURE__ */ p("span", {
|
|
453
|
+
className: "badge text-bg-secondary me-1",
|
|
454
|
+
children: e
|
|
455
|
+
}, `im-${e}`))
|
|
456
|
+
]
|
|
457
|
+
})
|
|
458
|
+
]
|
|
459
|
+
}) : /* @__PURE__ */ m("div", {
|
|
393
460
|
className: "mb-3",
|
|
394
461
|
children: [
|
|
395
462
|
/* @__PURE__ */ m("div", {
|
|
@@ -402,7 +469,7 @@ function U({ dbInfo: e, isLoading: t, error: n, label: r }) {
|
|
|
402
469
|
/* @__PURE__ */ m("span", { children: [
|
|
403
470
|
r,
|
|
404
471
|
": ",
|
|
405
|
-
/* @__PURE__ */ p("strong", { children:
|
|
472
|
+
/* @__PURE__ */ p("strong", { children: a }),
|
|
406
473
|
" em ",
|
|
407
474
|
/* @__PURE__ */ m("code", { children: [
|
|
408
475
|
e.host,
|
|
@@ -457,8 +524,8 @@ function U({ dbInfo: e, isLoading: t, error: n, label: r }) {
|
|
|
457
524
|
}
|
|
458
525
|
//#endregion
|
|
459
526
|
//#region src/components/RestoreModal.tsx
|
|
460
|
-
function
|
|
461
|
-
let n =
|
|
527
|
+
function G({ backup: e, onClose: t }) {
|
|
528
|
+
let n = A(), r = T(), i = () => {
|
|
462
529
|
n.mutate({ id: e.id });
|
|
463
530
|
}, a = n.isPending;
|
|
464
531
|
return /* @__PURE__ */ m(f, { children: [/* @__PURE__ */ p("div", { className: "modal-backdrop show" }), /* @__PURE__ */ p("div", {
|
|
@@ -504,7 +571,7 @@ function W({ backup: e, onClose: t }) {
|
|
|
504
571
|
" será aplicado no banco configurado abaixo."
|
|
505
572
|
]
|
|
506
573
|
}),
|
|
507
|
-
/* @__PURE__ */ p(
|
|
574
|
+
/* @__PURE__ */ p(W, {
|
|
508
575
|
label: "Banco de destino",
|
|
509
576
|
dbInfo: r.data,
|
|
510
577
|
isLoading: r.isLoading,
|
|
@@ -515,7 +582,7 @@ function W({ backup: e, onClose: t }) {
|
|
|
515
582
|
children: [/* @__PURE__ */ p("i", {
|
|
516
583
|
className: "bi bi-x-circle me-2",
|
|
517
584
|
"aria-hidden": !0
|
|
518
|
-
}),
|
|
585
|
+
}), K(n.error)]
|
|
519
586
|
}),
|
|
520
587
|
n.isSuccess && /* @__PURE__ */ m("div", {
|
|
521
588
|
className: "alert alert-success mt-3 mb-0",
|
|
@@ -550,19 +617,19 @@ function W({ backup: e, onClose: t }) {
|
|
|
550
617
|
})
|
|
551
618
|
})] });
|
|
552
619
|
}
|
|
553
|
-
function
|
|
620
|
+
function K(e) {
|
|
554
621
|
return e instanceof Error ? e.message : String(e);
|
|
555
622
|
}
|
|
556
623
|
//#endregion
|
|
557
624
|
//#region src/components/BackupsTab.tsx
|
|
558
|
-
function
|
|
559
|
-
let [e, t] = o(0), [n, a] = o(null), [s, c] = o(null), [l, u] = o({}), { data: d, isLoading: f, isError: h, error: g, refetch: _, isFetching: v } =
|
|
625
|
+
function q() {
|
|
626
|
+
let [e, t] = o(0), [n, a] = o(null), [s, c] = o(null), [l, u] = o({}), { data: d, isLoading: f, isError: h, error: g, refetch: _, isFetching: v } = w(e, 10, !0), y = N(), b = j(), x = M();
|
|
560
627
|
r(() => {
|
|
561
628
|
if (!y.data || !y.variables) return;
|
|
562
629
|
let e = URL.createObjectURL(y.data), t = document.createElement("a");
|
|
563
630
|
t.href = e, t.download = y.variables.filename, document.body.appendChild(t), t.click(), t.remove(), setTimeout(() => URL.revokeObjectURL(e), 1e4);
|
|
564
631
|
}, [y.data, y.variables]);
|
|
565
|
-
let S = i(() => d?.content ?? [], [d]),
|
|
632
|
+
let S = i(() => d?.content ?? [], [d]), C = d?.totalPages ?? 0;
|
|
566
633
|
return /* @__PURE__ */ m("div", { children: [
|
|
567
634
|
/* @__PURE__ */ m("div", {
|
|
568
635
|
className: "d-flex align-items-center justify-content-between flex-wrap gap-2 mb-3",
|
|
@@ -719,12 +786,12 @@ function K() {
|
|
|
719
786
|
"aria-hidden": !0
|
|
720
787
|
}), e.filename]
|
|
721
788
|
}),
|
|
722
|
-
/* @__PURE__ */ p("td", { children:
|
|
789
|
+
/* @__PURE__ */ p("td", { children: H[e.databaseType] ?? e.databaseType }),
|
|
723
790
|
/* @__PURE__ */ p("td", {
|
|
724
791
|
className: "text-end",
|
|
725
|
-
children:
|
|
792
|
+
children: R(e.fileSize)
|
|
726
793
|
}),
|
|
727
|
-
/* @__PURE__ */ p("td", { children: /* @__PURE__ */ p(
|
|
794
|
+
/* @__PURE__ */ p("td", { children: /* @__PURE__ */ p(U, { status: e.status }) }),
|
|
728
795
|
/* @__PURE__ */ p("td", { children: e.sha256 ? /* @__PURE__ */ m("code", {
|
|
729
796
|
title: e.sha256,
|
|
730
797
|
style: { fontSize: "0.8em" },
|
|
@@ -747,7 +814,7 @@ function K() {
|
|
|
747
814
|
className: "text-muted",
|
|
748
815
|
children: "—"
|
|
749
816
|
}) }),
|
|
750
|
-
/* @__PURE__ */ p("td", { children:
|
|
817
|
+
/* @__PURE__ */ p("td", { children: z(e.createdAt) }),
|
|
751
818
|
/* @__PURE__ */ m("td", {
|
|
752
819
|
className: "text-end text-nowrap",
|
|
753
820
|
children: [
|
|
@@ -815,7 +882,7 @@ function K() {
|
|
|
815
882
|
"Página ",
|
|
816
883
|
e + 1,
|
|
817
884
|
" de ",
|
|
818
|
-
Math.max(
|
|
885
|
+
Math.max(C, 1)
|
|
819
886
|
]
|
|
820
887
|
}), /* @__PURE__ */ m("div", {
|
|
821
888
|
className: "btn-group",
|
|
@@ -831,7 +898,7 @@ function K() {
|
|
|
831
898
|
}), /* @__PURE__ */ p("button", {
|
|
832
899
|
type: "button",
|
|
833
900
|
className: "btn btn-outline-secondary btn-sm",
|
|
834
|
-
disabled: e + 1 >=
|
|
901
|
+
disabled: e + 1 >= C,
|
|
835
902
|
onClick: () => t((e) => e + 1),
|
|
836
903
|
children: /* @__PURE__ */ p("i", {
|
|
837
904
|
className: "bi bi-chevron-right",
|
|
@@ -840,11 +907,11 @@ function K() {
|
|
|
840
907
|
})]
|
|
841
908
|
})]
|
|
842
909
|
}),
|
|
843
|
-
n && /* @__PURE__ */ p(
|
|
910
|
+
n && /* @__PURE__ */ p(G, {
|
|
844
911
|
backup: n,
|
|
845
912
|
onClose: () => a(null)
|
|
846
913
|
}),
|
|
847
|
-
s && /* @__PURE__ */ p(
|
|
914
|
+
s && /* @__PURE__ */ p(ee, {
|
|
848
915
|
backup: s,
|
|
849
916
|
busy: b.isPending,
|
|
850
917
|
onCancel: () => c(null),
|
|
@@ -854,7 +921,7 @@ function K() {
|
|
|
854
921
|
})
|
|
855
922
|
] });
|
|
856
923
|
}
|
|
857
|
-
function
|
|
924
|
+
function ee(e) {
|
|
858
925
|
let { backup: t, busy: n, onCancel: r, onConfirm: i } = e;
|
|
859
926
|
return /* @__PURE__ */ m(f, { children: [/* @__PURE__ */ p("div", { className: "modal-backdrop show" }), /* @__PURE__ */ p("div", {
|
|
860
927
|
className: "modal show d-block",
|
|
@@ -926,7 +993,7 @@ function J(e) {
|
|
|
926
993
|
//#endregion
|
|
927
994
|
//#region src/components/CreateBackupTab.tsx
|
|
928
995
|
function Y() {
|
|
929
|
-
let e =
|
|
996
|
+
let e = k(), t = T(), n = E(t.data?.configured), [r, a] = o(!1), [s, c] = o(() => ({
|
|
930
997
|
type: "LOCAL",
|
|
931
998
|
localPath: ""
|
|
932
999
|
})), [l, u] = o(!0), [d, h] = o({
|
|
@@ -937,7 +1004,7 @@ function Y() {
|
|
|
937
1004
|
includeIndexes: !0,
|
|
938
1005
|
includeConstraints: !0,
|
|
939
1006
|
includeRelatedTables: !1
|
|
940
|
-
}), [g, _] = o(() => /* @__PURE__ */ new Set()), [v, y] = o(null), [b, x] = o({}), [S, C] = o(""),
|
|
1007
|
+
}), [g, _] = o(() => /* @__PURE__ */ new Set()), [v, y] = o(null), [b, x] = o({}), [S, C] = o(""), w = D(v, t.data?.configured), O = i(() => new Set((t.data?.exportExcludedTables ?? []).map(ne)), [t.data?.exportExcludedTables]), A = i(() => {
|
|
941
1008
|
let e = n.data ?? [], t = S.trim().toLowerCase();
|
|
942
1009
|
return t ? e.filter((e) => e.fullName.toLowerCase().includes(t)) : e;
|
|
943
1010
|
}, [n.data, S]), j = i(() => Object.values(b).reduce((e, t) => e + t.length, 0), [b]), M = (e) => {
|
|
@@ -975,14 +1042,15 @@ function Y() {
|
|
|
975
1042
|
children: "Criar novo backup"
|
|
976
1043
|
}), /* @__PURE__ */ p("p", {
|
|
977
1044
|
className: "text-muted small mb-0",
|
|
978
|
-
children: "
|
|
1045
|
+
children: "Selecione as tabelas no grid e expanda um card para marcar colunas cujo conteúdo será nulificado na exportação."
|
|
979
1046
|
})]
|
|
980
1047
|
}),
|
|
981
|
-
/* @__PURE__ */ p(
|
|
1048
|
+
/* @__PURE__ */ p(W, {
|
|
982
1049
|
label: "Banco de origem",
|
|
983
1050
|
dbInfo: t.data,
|
|
984
1051
|
isLoading: t.isLoading,
|
|
985
|
-
error: t.error
|
|
1052
|
+
error: t.error,
|
|
1053
|
+
compact: !0
|
|
986
1054
|
}),
|
|
987
1055
|
/* @__PURE__ */ m("div", {
|
|
988
1056
|
className: "card mb-3",
|
|
@@ -996,7 +1064,7 @@ function Y() {
|
|
|
996
1064
|
"aria-hidden": !0
|
|
997
1065
|
}),
|
|
998
1066
|
"Tabelas do banco (",
|
|
999
|
-
n.data?.filter((e) => !
|
|
1067
|
+
n.data?.filter((e) => !O.has(e.fullName)).length ?? 0,
|
|
1000
1068
|
" disponíveis)"
|
|
1001
1069
|
]
|
|
1002
1070
|
}), /* @__PURE__ */ m("div", {
|
|
@@ -1036,7 +1104,7 @@ function Y() {
|
|
|
1036
1104
|
"aria-hidden": !0
|
|
1037
1105
|
}),
|
|
1038
1106
|
"Falha ao listar tabelas: ",
|
|
1039
|
-
|
|
1107
|
+
K(n.error)
|
|
1040
1108
|
]
|
|
1041
1109
|
}),
|
|
1042
1110
|
A.length === 0 && !n.isLoading && /* @__PURE__ */ p("div", {
|
|
@@ -1052,16 +1120,16 @@ function Y() {
|
|
|
1052
1120
|
maxHeight: 420,
|
|
1053
1121
|
overflow: "auto"
|
|
1054
1122
|
},
|
|
1055
|
-
children: A.map((e) => /* @__PURE__ */ p(
|
|
1123
|
+
children: A.map((e) => /* @__PURE__ */ p(te, {
|
|
1056
1124
|
table: e,
|
|
1057
|
-
disabled:
|
|
1125
|
+
disabled: O.has(e.fullName),
|
|
1058
1126
|
selected: g.has(e.fullName),
|
|
1059
1127
|
expanded: v === e.fullName,
|
|
1060
1128
|
excluded: b[e.fullName] ?? [],
|
|
1061
1129
|
onToggleTable: () => M(e.fullName),
|
|
1062
1130
|
onToggleExpand: () => y(v === e.fullName ? null : e.fullName),
|
|
1063
1131
|
onToggleColumn: (t) => N(e.fullName, t),
|
|
1064
|
-
detail: v === e.fullName ?
|
|
1132
|
+
detail: v === e.fullName ? w : void 0
|
|
1065
1133
|
}, e.fullName))
|
|
1066
1134
|
}),
|
|
1067
1135
|
/* @__PURE__ */ m("div", {
|
|
@@ -1374,7 +1442,7 @@ function Y() {
|
|
|
1374
1442
|
"aria-hidden": !0
|
|
1375
1443
|
}),
|
|
1376
1444
|
"Falha ao criar backup: ",
|
|
1377
|
-
|
|
1445
|
+
K(e.error)
|
|
1378
1446
|
]
|
|
1379
1447
|
}),
|
|
1380
1448
|
e.isSuccess && /* @__PURE__ */ m("div", {
|
|
@@ -1413,7 +1481,7 @@ function Y() {
|
|
|
1413
1481
|
})
|
|
1414
1482
|
] });
|
|
1415
1483
|
}
|
|
1416
|
-
function
|
|
1484
|
+
function te({ table: e, disabled: t, selected: n, expanded: r, excluded: i, detail: a, onToggleTable: o, onToggleExpand: s, onToggleColumn: c }) {
|
|
1417
1485
|
return /* @__PURE__ */ p("div", {
|
|
1418
1486
|
className: `card h-100 ${n ? "border-primary" : ""}`,
|
|
1419
1487
|
style: { minWidth: 0 },
|
|
@@ -1477,7 +1545,7 @@ function ee({ table: e, disabled: t, selected: n, expanded: r, excluded: i, deta
|
|
|
1477
1545
|
}),
|
|
1478
1546
|
a?.isError && /* @__PURE__ */ m("small", {
|
|
1479
1547
|
className: "text-danger",
|
|
1480
|
-
children: ["Erro: ",
|
|
1548
|
+
children: ["Erro: ", K(a.error)]
|
|
1481
1549
|
}),
|
|
1482
1550
|
a?.data && /* @__PURE__ */ m(f, { children: [a.data.columns.map((e) => /* @__PURE__ */ m("div", {
|
|
1483
1551
|
className: "d-flex align-items-center gap-2 py-1",
|
|
@@ -1528,13 +1596,135 @@ function ee({ table: e, disabled: t, selected: n, expanded: r, excluded: i, deta
|
|
|
1528
1596
|
})
|
|
1529
1597
|
});
|
|
1530
1598
|
}
|
|
1531
|
-
function
|
|
1599
|
+
function ne(e) {
|
|
1532
1600
|
let t = e.trim();
|
|
1533
1601
|
return t.includes(".") ? t : `public.${t}`;
|
|
1534
1602
|
}
|
|
1535
1603
|
//#endregion
|
|
1604
|
+
//#region src/components/DiagnosticsTab.tsx
|
|
1605
|
+
function X() {
|
|
1606
|
+
let { data: e, isLoading: t, isError: n, error: r, refetch: a, isFetching: o } = C(), s = i(() => e ? {
|
|
1607
|
+
total: e.length,
|
|
1608
|
+
okCount: e.filter((e) => e.ok).length,
|
|
1609
|
+
notConfigured: e.filter((e) => !e.configured).length
|
|
1610
|
+
} : null, [e]);
|
|
1611
|
+
return /* @__PURE__ */ m("div", { children: [
|
|
1612
|
+
/* @__PURE__ */ m("div", {
|
|
1613
|
+
className: "d-flex align-items-center justify-content-between flex-wrap gap-2 mb-3",
|
|
1614
|
+
children: [/* @__PURE__ */ m("div", { children: [/* @__PURE__ */ p("h5", {
|
|
1615
|
+
className: "mb-0",
|
|
1616
|
+
children: "Diagnóstico do servidor"
|
|
1617
|
+
}), /* @__PURE__ */ p("span", {
|
|
1618
|
+
className: "text-muted small",
|
|
1619
|
+
children: "Verifica conexões de banco, storages e servidor de e-mail configurados."
|
|
1620
|
+
})] }), /* @__PURE__ */ m("button", {
|
|
1621
|
+
type: "button",
|
|
1622
|
+
className: "btn btn-outline-secondary btn-sm",
|
|
1623
|
+
onClick: () => a(),
|
|
1624
|
+
disabled: o,
|
|
1625
|
+
children: [
|
|
1626
|
+
o && /* @__PURE__ */ p("span", {
|
|
1627
|
+
className: "spinner-border spinner-border-sm me-2",
|
|
1628
|
+
"aria-hidden": !0
|
|
1629
|
+
}),
|
|
1630
|
+
/* @__PURE__ */ p("i", {
|
|
1631
|
+
className: "bi bi-arrow-repeat me-1",
|
|
1632
|
+
"aria-hidden": !0
|
|
1633
|
+
}),
|
|
1634
|
+
"Executar testes novamente"
|
|
1635
|
+
]
|
|
1636
|
+
})]
|
|
1637
|
+
}),
|
|
1638
|
+
n && /* @__PURE__ */ m("div", {
|
|
1639
|
+
className: "alert alert-danger",
|
|
1640
|
+
children: [
|
|
1641
|
+
/* @__PURE__ */ p("i", {
|
|
1642
|
+
className: "bi bi-exclamation-triangle me-2",
|
|
1643
|
+
"aria-hidden": !0
|
|
1644
|
+
}),
|
|
1645
|
+
"Não foi possível executar as checagens: ",
|
|
1646
|
+
K(r)
|
|
1647
|
+
]
|
|
1648
|
+
}),
|
|
1649
|
+
s && /* @__PURE__ */ m("div", {
|
|
1650
|
+
className: "d-flex flex-wrap gap-2 mb-3",
|
|
1651
|
+
children: [
|
|
1652
|
+
/* @__PURE__ */ m("span", {
|
|
1653
|
+
className: "badge text-bg-success",
|
|
1654
|
+
children: [
|
|
1655
|
+
/* @__PURE__ */ p("i", {
|
|
1656
|
+
className: "bi bi-check-circle me-1",
|
|
1657
|
+
"aria-hidden": !0
|
|
1658
|
+
}),
|
|
1659
|
+
s.okCount,
|
|
1660
|
+
" OK"
|
|
1661
|
+
]
|
|
1662
|
+
}),
|
|
1663
|
+
/* @__PURE__ */ m("span", {
|
|
1664
|
+
className: "badge text-bg-warning",
|
|
1665
|
+
children: [s.total - s.okCount - s.notConfigured, " com falha"]
|
|
1666
|
+
}),
|
|
1667
|
+
/* @__PURE__ */ m("span", {
|
|
1668
|
+
className: "badge text-bg-secondary",
|
|
1669
|
+
children: [s.notConfigured, " não configurado(s)"]
|
|
1670
|
+
})
|
|
1671
|
+
]
|
|
1672
|
+
}),
|
|
1673
|
+
t && !e ? /* @__PURE__ */ m("div", {
|
|
1674
|
+
className: "text-center py-5 text-muted",
|
|
1675
|
+
children: [/* @__PURE__ */ p("div", {
|
|
1676
|
+
className: "spinner-border mb-2",
|
|
1677
|
+
"aria-hidden": !0
|
|
1678
|
+
}), /* @__PURE__ */ p("div", { children: "Executando checagens…" })]
|
|
1679
|
+
}) : /* @__PURE__ */ p("div", {
|
|
1680
|
+
className: "card",
|
|
1681
|
+
children: /* @__PURE__ */ m("ul", {
|
|
1682
|
+
className: "list-group list-group-flush",
|
|
1683
|
+
children: [(e ?? []).map((e) => /* @__PURE__ */ p(re, { check: e }, e.name)), e && e.length === 0 && /* @__PURE__ */ p("li", {
|
|
1684
|
+
className: "list-group-item text-center text-muted py-4",
|
|
1685
|
+
children: "Nenhuma checagem retornada."
|
|
1686
|
+
})]
|
|
1687
|
+
})
|
|
1688
|
+
})
|
|
1689
|
+
] });
|
|
1690
|
+
}
|
|
1691
|
+
function re({ check: e }) {
|
|
1692
|
+
let t = e.configured ? e.ok ? "bi-check-circle-fill text-success" : "bi-x-octagon-fill text-danger" : "bi-dash-circle text-secondary", n = e.configured ? e.ok ? "text-bg-success" : "text-bg-danger" : "text-bg-secondary", r = e.configured ? e.ok ? "OK" : "Falha" : "Não configurado";
|
|
1693
|
+
return /* @__PURE__ */ m("li", {
|
|
1694
|
+
className: "list-group-item d-flex align-items-start gap-3",
|
|
1695
|
+
children: [/* @__PURE__ */ p("i", {
|
|
1696
|
+
className: `bi ${t} fs-4 mt-1`,
|
|
1697
|
+
"aria-hidden": !0
|
|
1698
|
+
}), /* @__PURE__ */ m("div", {
|
|
1699
|
+
className: "flex-grow-1",
|
|
1700
|
+
style: { minWidth: 0 },
|
|
1701
|
+
children: [/* @__PURE__ */ m("div", {
|
|
1702
|
+
className: "d-flex align-items-center flex-wrap gap-2",
|
|
1703
|
+
children: [
|
|
1704
|
+
/* @__PURE__ */ p("strong", { children: e.label }),
|
|
1705
|
+
/* @__PURE__ */ p("span", {
|
|
1706
|
+
className: "badge text-bg-light border",
|
|
1707
|
+
children: e.type
|
|
1708
|
+
}),
|
|
1709
|
+
/* @__PURE__ */ p("span", {
|
|
1710
|
+
className: `badge ${n}`,
|
|
1711
|
+
children: r
|
|
1712
|
+
}),
|
|
1713
|
+
/* @__PURE__ */ m("small", {
|
|
1714
|
+
className: "text-muted",
|
|
1715
|
+
children: [e.latencyMs, " ms"]
|
|
1716
|
+
})
|
|
1717
|
+
]
|
|
1718
|
+
}), /* @__PURE__ */ p("div", {
|
|
1719
|
+
className: "small text-muted mt-1",
|
|
1720
|
+
children: e.message
|
|
1721
|
+
})]
|
|
1722
|
+
})]
|
|
1723
|
+
});
|
|
1724
|
+
}
|
|
1725
|
+
//#endregion
|
|
1536
1726
|
//#region src/components/StorageExplorerTab.tsx
|
|
1537
|
-
function
|
|
1727
|
+
function Z({ storageType: e = "LOCAL", storageConfig: n }) {
|
|
1538
1728
|
let i = b(), s = d(), [c, h] = o(""), [g, _] = o([]), [v, y] = o(/* @__PURE__ */ new Set()), [x, S] = o(null), C = n?.type ?? e, { data: w, isLoading: T } = u({
|
|
1539
1729
|
queryKey: [
|
|
1540
1730
|
"storage-files",
|
|
@@ -1571,7 +1761,7 @@ function X({ storageType: e = "LOCAL", storageConfig: n }) {
|
|
|
1571
1761
|
} catch {}
|
|
1572
1762
|
}, N = async () => {
|
|
1573
1763
|
for (let e of v) await M(e);
|
|
1574
|
-
}, P = (e) => e ?
|
|
1764
|
+
}, P = (e) => e ? z(e) : "-";
|
|
1575
1765
|
return /* @__PURE__ */ m("div", {
|
|
1576
1766
|
className: "space-y-3",
|
|
1577
1767
|
children: [
|
|
@@ -1701,7 +1891,7 @@ function X({ storageType: e = "LOCAL", storageConfig: n }) {
|
|
|
1701
1891
|
}) }),
|
|
1702
1892
|
/* @__PURE__ */ p("td", {
|
|
1703
1893
|
className: "text-muted small",
|
|
1704
|
-
children: e.directory ? "-" :
|
|
1894
|
+
children: e.directory ? "-" : R(e.size)
|
|
1705
1895
|
}),
|
|
1706
1896
|
/* @__PURE__ */ p("td", {
|
|
1707
1897
|
className: "text-muted small",
|
|
@@ -1806,8 +1996,8 @@ function X({ storageType: e = "LOCAL", storageConfig: n }) {
|
|
|
1806
1996
|
}
|
|
1807
1997
|
//#endregion
|
|
1808
1998
|
//#region src/components/ImportDumpTab.tsx
|
|
1809
|
-
function
|
|
1810
|
-
let e =
|
|
1999
|
+
function Q() {
|
|
2000
|
+
let e = P(), t = F(), n = T(), r = a(null), [i, s] = o("upload"), [c, l] = o(null), [u, d] = o(""), [f, h] = o(!1), g = () => {
|
|
1811
2001
|
i === "upload" && c ? e.mutate({ file: c }) : i === "storage" && u && t.mutate({
|
|
1812
2002
|
storageType: "LOCAL",
|
|
1813
2003
|
storagePath: u
|
|
@@ -1929,7 +2119,7 @@ function Z() {
|
|
|
1929
2119
|
maxHeight: 350,
|
|
1930
2120
|
overflow: "auto"
|
|
1931
2121
|
},
|
|
1932
|
-
children: /* @__PURE__ */ p(
|
|
2122
|
+
children: /* @__PURE__ */ p(Z, {
|
|
1933
2123
|
storageType: "LOCAL",
|
|
1934
2124
|
storageConfig: {
|
|
1935
2125
|
type: "LOCAL",
|
|
@@ -1952,7 +2142,7 @@ function Z() {
|
|
|
1952
2142
|
children: [/* @__PURE__ */ p("p", {
|
|
1953
2143
|
className: "small text-body-secondary mb-3",
|
|
1954
2144
|
children: "O dump será aplicado no banco configurado no servidor. Tabelas bloqueadas por segurança são ignoradas automaticamente na importação."
|
|
1955
|
-
}), /* @__PURE__ */ p(
|
|
2145
|
+
}), /* @__PURE__ */ p(W, {
|
|
1956
2146
|
label: "Banco de destino",
|
|
1957
2147
|
dbInfo: n.data,
|
|
1958
2148
|
isLoading: n.isLoading,
|
|
@@ -1968,7 +2158,7 @@ function Z() {
|
|
|
1968
2158
|
"aria-hidden": !0
|
|
1969
2159
|
}),
|
|
1970
2160
|
"Falha na importacao: ",
|
|
1971
|
-
|
|
2161
|
+
K(y)
|
|
1972
2162
|
]
|
|
1973
2163
|
}),
|
|
1974
2164
|
v && /* @__PURE__ */ m("div", {
|
|
@@ -2010,13 +2200,13 @@ function Z() {
|
|
|
2010
2200
|
}
|
|
2011
2201
|
//#endregion
|
|
2012
2202
|
//#region src/hooks/useAutoTheme.ts
|
|
2013
|
-
function
|
|
2203
|
+
function $() {
|
|
2014
2204
|
return typeof document < "u" && document.documentElement.classList.contains("dark");
|
|
2015
2205
|
}
|
|
2016
|
-
function
|
|
2017
|
-
let [t, n] = o(
|
|
2206
|
+
function ie(e) {
|
|
2207
|
+
let [t, n] = o($), [i, a] = o(() => typeof window < "u" && window.matchMedia?.("(prefers-color-scheme: dark)")?.matches === !0);
|
|
2018
2208
|
return r(() => {
|
|
2019
|
-
let e = document.documentElement, t = new MutationObserver(() => n(
|
|
2209
|
+
let e = document.documentElement, t = new MutationObserver(() => n($()));
|
|
2020
2210
|
return t.observe(e, {
|
|
2021
2211
|
attributes: !0,
|
|
2022
2212
|
attributeFilter: ["class"]
|
|
@@ -2029,8 +2219,8 @@ function ne(e) {
|
|
|
2029
2219
|
}
|
|
2030
2220
|
//#endregion
|
|
2031
2221
|
//#region src/components/MinivaultModule.tsx
|
|
2032
|
-
function
|
|
2033
|
-
let { companyAcronym: t = "Minivault", theme: n, refetchIntervalMs: r = 15e3, storageType: i = "LOCAL", ...a } = e, l =
|
|
2222
|
+
function ae(e) {
|
|
2223
|
+
let { companyAcronym: t = "Minivault", theme: n, refetchIntervalMs: r = 15e3, storageType: i = "LOCAL", ...a } = e, l = ie(n), [u] = o(() => new s({ defaultOptions: { queries: {
|
|
2034
2224
|
refetchInterval: r,
|
|
2035
2225
|
retry: 1
|
|
2036
2226
|
} } }));
|
|
@@ -2041,15 +2231,15 @@ function re(e) {
|
|
|
2041
2231
|
children: /* @__PURE__ */ m("div", {
|
|
2042
2232
|
className: "minivault-module h-full w-full overflow-auto",
|
|
2043
2233
|
"data-minivault-theme": l,
|
|
2044
|
-
children: [/* @__PURE__ */ p(
|
|
2234
|
+
children: [/* @__PURE__ */ p(oe, {
|
|
2045
2235
|
companyAcronym: t,
|
|
2046
2236
|
theme: l
|
|
2047
|
-
}), /* @__PURE__ */ p(
|
|
2237
|
+
}), /* @__PURE__ */ p(se, { storageType: i })]
|
|
2048
2238
|
})
|
|
2049
2239
|
})
|
|
2050
2240
|
});
|
|
2051
2241
|
}
|
|
2052
|
-
function
|
|
2242
|
+
function oe({ companyAcronym: e, theme: t }) {
|
|
2053
2243
|
return /* @__PURE__ */ m("div", {
|
|
2054
2244
|
className: "d-flex align-items-center justify-content-between flex-wrap gap-2 px-3 py-2 border-bottom",
|
|
2055
2245
|
style: {
|
|
@@ -2078,7 +2268,7 @@ function ie({ companyAcronym: e, theme: t }) {
|
|
|
2078
2268
|
})]
|
|
2079
2269
|
});
|
|
2080
2270
|
}
|
|
2081
|
-
function
|
|
2271
|
+
function se({ storageType: e }) {
|
|
2082
2272
|
let [t, n] = o("backups");
|
|
2083
2273
|
return /* @__PURE__ */ m("div", {
|
|
2084
2274
|
className: "p-3",
|
|
@@ -2133,24 +2323,37 @@ function ae({ storageType: e }) {
|
|
|
2133
2323
|
"aria-hidden": !0
|
|
2134
2324
|
}), "Storage"]
|
|
2135
2325
|
})
|
|
2326
|
+
}),
|
|
2327
|
+
/* @__PURE__ */ p("li", {
|
|
2328
|
+
className: "nav-item",
|
|
2329
|
+
children: /* @__PURE__ */ m("button", {
|
|
2330
|
+
type: "button",
|
|
2331
|
+
className: `nav-link ${t === "diagnostics" ? "active" : ""}`,
|
|
2332
|
+
onClick: () => n("diagnostics"),
|
|
2333
|
+
children: [/* @__PURE__ */ p("i", {
|
|
2334
|
+
className: "bi bi-heart-pulse me-1",
|
|
2335
|
+
"aria-hidden": !0
|
|
2336
|
+
}), "Diagnóstico"]
|
|
2337
|
+
})
|
|
2136
2338
|
})
|
|
2137
2339
|
]
|
|
2138
2340
|
}),
|
|
2139
|
-
t === "backups" && /* @__PURE__ */ p(
|
|
2341
|
+
t === "backups" && /* @__PURE__ */ p(q, {}),
|
|
2140
2342
|
t === "create" && /* @__PURE__ */ p(Y, {}),
|
|
2141
|
-
t === "import" && /* @__PURE__ */ p(
|
|
2142
|
-
t === "storage" && /* @__PURE__ */ p(
|
|
2343
|
+
t === "import" && /* @__PURE__ */ p(Q, {}),
|
|
2344
|
+
t === "storage" && /* @__PURE__ */ p(Z, { storageType: e }),
|
|
2345
|
+
t === "diagnostics" && /* @__PURE__ */ p(X, {})
|
|
2143
2346
|
]
|
|
2144
2347
|
});
|
|
2145
2348
|
}
|
|
2146
2349
|
//#endregion
|
|
2147
2350
|
//#region src/components/CredentialsFields.tsx
|
|
2148
|
-
var
|
|
2351
|
+
var ce = [
|
|
2149
2352
|
"postgresql",
|
|
2150
2353
|
"mysql",
|
|
2151
2354
|
"sqlserver"
|
|
2152
2355
|
];
|
|
2153
|
-
function
|
|
2356
|
+
function le() {
|
|
2154
2357
|
return {
|
|
2155
2358
|
host: "localhost",
|
|
2156
2359
|
port: 5432,
|
|
@@ -2160,19 +2363,19 @@ function se() {
|
|
|
2160
2363
|
databaseType: "postgresql"
|
|
2161
2364
|
};
|
|
2162
2365
|
}
|
|
2163
|
-
function
|
|
2366
|
+
function ue(e) {
|
|
2164
2367
|
return e === "postgresql" || e === "postgres" ? 5432 : e === "mysql" ? 3306 : 1433;
|
|
2165
2368
|
}
|
|
2166
|
-
function
|
|
2369
|
+
function de({ value: e, onChange: t, onValidatedChange: n }) {
|
|
2167
2370
|
let r = {
|
|
2168
|
-
...
|
|
2371
|
+
...le(),
|
|
2169
2372
|
...e
|
|
2170
2373
|
}, i = (e) => {
|
|
2171
2374
|
let n = {
|
|
2172
2375
|
...r,
|
|
2173
2376
|
databaseType: e
|
|
2174
2377
|
};
|
|
2175
|
-
(r.port ===
|
|
2378
|
+
(r.port === ue(r.databaseType) || !r.port) && (n.port = ue(e)), t(n);
|
|
2176
2379
|
}, a = (e) => {
|
|
2177
2380
|
let i = {
|
|
2178
2381
|
...r,
|
|
@@ -2194,9 +2397,9 @@ function ce({ value: e, onChange: t, onValidatedChange: n }) {
|
|
|
2194
2397
|
className: "form-select",
|
|
2195
2398
|
value: r.databaseType,
|
|
2196
2399
|
onChange: (e) => i(e.target.value),
|
|
2197
|
-
children:
|
|
2400
|
+
children: ce.map((e) => /* @__PURE__ */ p("option", {
|
|
2198
2401
|
value: e,
|
|
2199
|
-
children:
|
|
2402
|
+
children: H[e] ?? e
|
|
2200
2403
|
}, e))
|
|
2201
2404
|
})]
|
|
2202
2405
|
}),
|
|
@@ -2268,4 +2471,4 @@ function ce({ value: e, onChange: t, onValidatedChange: n }) {
|
|
|
2268
2471
|
});
|
|
2269
2472
|
}
|
|
2270
2473
|
//#endregion
|
|
2271
|
-
export {
|
|
2474
|
+
export { q as BackupsTab, Y as CreateBackupTab, de as CredentialsFields, H as DATABASE_TYPE_LABELS, W as DatabaseBanner, X as DiagnosticsTab, Q as ImportDumpTab, g as MinivaultApi, h as MinivaultApiError, y as MinivaultApiProvider, ae as MinivaultModule, G as RestoreModal, V as STATUS_BADGE, B as STATUS_LABELS, U as StatusBadge, Z as StorageExplorerTab, z as formatDate, R as formatFileSize, w as useBackups, k as useCreateBackup, T as useDbInfo, O as useDbRelationships, D as useDbTableDetail, E as useDbTables, j as useDeleteBackup, C as useDiagnostics, N as useDownloadBackup, P as useImportDump, F as useImportFromStorage, b as useMinivaultApi, A as useRestoreBackup };
|
|
@@ -19,6 +19,15 @@ export interface VerifyResult {
|
|
|
19
19
|
dumpReadable: boolean;
|
|
20
20
|
message: string;
|
|
21
21
|
}
|
|
22
|
+
export interface ConnectionCheck {
|
|
23
|
+
name: string;
|
|
24
|
+
label: string;
|
|
25
|
+
type: string;
|
|
26
|
+
configured: boolean;
|
|
27
|
+
ok: boolean;
|
|
28
|
+
message: string;
|
|
29
|
+
latencyMs: number;
|
|
30
|
+
}
|
|
22
31
|
export interface PageResponse<T> {
|
|
23
32
|
content: T[];
|
|
24
33
|
page: number;
|
package/package.json
CHANGED
|
@@ -1,73 +1,73 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@v1ct0rbr/minivault-web",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Módulo independente de gerenciamento de backups (minivault). Consumível como pacote npm em qualquer app React. Cliente do minivault REST API (acesso tipicamente via proxy autenticado).",
|
|
5
|
-
"author": "Victor Queiroga (https://www.linkedin.com/in/victor-queiroga)",
|
|
6
|
-
"license": "MIT",
|
|
7
|
-
"type": "module",
|
|
8
|
-
"publishConfig": {
|
|
9
|
-
"access": "public",
|
|
10
|
-
"registry": "https://registry.npmjs.org/"
|
|
11
|
-
},
|
|
12
|
-
"files": [
|
|
13
|
-
"dist",
|
|
14
|
-
"README.md"
|
|
15
|
-
],
|
|
16
|
-
"keywords": [
|
|
17
|
-
"backup",
|
|
18
|
-
"minivault",
|
|
19
|
-
"der",
|
|
20
|
-
"react",
|
|
21
|
-
"bootstrap",
|
|
22
|
-
"postgresql"
|
|
23
|
-
],
|
|
24
|
-
"repository": {
|
|
25
|
-
"type": "git",
|
|
26
|
-
"url": "https://github.com/v1ct0rbr/minivault-web.git"
|
|
27
|
-
},
|
|
28
|
-
"main": "./dist/minivault-web.js",
|
|
29
|
-
"module": "./dist/minivault-web.js",
|
|
30
|
-
"types": "./dist/index.d.ts",
|
|
31
|
-
"exports": {
|
|
32
|
-
".": {
|
|
33
|
-
"types": "./dist/index.d.ts",
|
|
34
|
-
"import": "./dist/minivault-web.js"
|
|
35
|
-
},
|
|
36
|
-
"./style.css": "./dist/minivault-web.css"
|
|
37
|
-
},
|
|
38
|
-
"scripts": {
|
|
39
|
-
"dev": "vite --port 5176",
|
|
40
|
-
"build": "vite build && tsc --noEmit",
|
|
41
|
-
"build:lib": "vite build --config vite.lib.config.ts && tsc --noEmit",
|
|
42
|
-
"prepublishOnly": "npm run build:lib && npm test",
|
|
43
|
-
"prepack": "npm run build:lib",
|
|
44
|
-
"preview": "vite preview",
|
|
45
|
-
"test": "vitest run",
|
|
46
|
-
"test:watch": "vitest",
|
|
47
|
-
"typecheck": "tsc --noEmit",
|
|
48
|
-
"lint": "eslint ."
|
|
49
|
-
},
|
|
50
|
-
"dependencies": {
|
|
51
|
-
"bootstrap": "^5.3.3",
|
|
52
|
-
"bootstrap-icons": "^1.11.3"
|
|
53
|
-
},
|
|
54
|
-
"peerDependencies": {
|
|
55
|
-
"@tanstack/react-query": "^5.0.0",
|
|
56
|
-
"react": "^19.0.0",
|
|
57
|
-
"react-dom": "^19.0.0"
|
|
58
|
-
},
|
|
59
|
-
"devDependencies": {
|
|
60
|
-
"@tanstack/react-query": "^5.66.5",
|
|
61
|
-
"@types/node": "^22.10.2",
|
|
62
|
-
"@types/react": "^19.2.0",
|
|
63
|
-
"@types/react-dom": "^19.2.0",
|
|
64
|
-
"@vitejs/plugin-react": "^5.0.4",
|
|
65
|
-
"postcss-prefix-selector": "^2.1.1",
|
|
66
|
-
"react": "^19.2.0",
|
|
67
|
-
"react-dom": "^19.2.0",
|
|
68
|
-
"typescript": "^5.7.2",
|
|
69
|
-
"vite": "^8.0.16",
|
|
70
|
-
"vite-plugin-dts": "^4.5.0",
|
|
71
|
-
"vitest": "^4.1.8"
|
|
72
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "@v1ct0rbr/minivault-web",
|
|
3
|
+
"version": "0.5.0",
|
|
4
|
+
"description": "Módulo independente de gerenciamento de backups (minivault). Consumível como pacote npm em qualquer app React. Cliente do minivault REST API (acesso tipicamente via proxy autenticado).",
|
|
5
|
+
"author": "Victor Queiroga (https://www.linkedin.com/in/victor-queiroga)",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"publishConfig": {
|
|
9
|
+
"access": "public",
|
|
10
|
+
"registry": "https://registry.npmjs.org/"
|
|
11
|
+
},
|
|
12
|
+
"files": [
|
|
13
|
+
"dist",
|
|
14
|
+
"README.md"
|
|
15
|
+
],
|
|
16
|
+
"keywords": [
|
|
17
|
+
"backup",
|
|
18
|
+
"minivault",
|
|
19
|
+
"der",
|
|
20
|
+
"react",
|
|
21
|
+
"bootstrap",
|
|
22
|
+
"postgresql"
|
|
23
|
+
],
|
|
24
|
+
"repository": {
|
|
25
|
+
"type": "git",
|
|
26
|
+
"url": "https://github.com/v1ct0rbr/minivault-web.git"
|
|
27
|
+
},
|
|
28
|
+
"main": "./dist/minivault-web.js",
|
|
29
|
+
"module": "./dist/minivault-web.js",
|
|
30
|
+
"types": "./dist/index.d.ts",
|
|
31
|
+
"exports": {
|
|
32
|
+
".": {
|
|
33
|
+
"types": "./dist/index.d.ts",
|
|
34
|
+
"import": "./dist/minivault-web.js"
|
|
35
|
+
},
|
|
36
|
+
"./style.css": "./dist/minivault-web.css"
|
|
37
|
+
},
|
|
38
|
+
"scripts": {
|
|
39
|
+
"dev": "vite --port 5176",
|
|
40
|
+
"build": "vite build && tsc --noEmit",
|
|
41
|
+
"build:lib": "vite build --config vite.lib.config.ts && tsc --noEmit",
|
|
42
|
+
"prepublishOnly": "npm run build:lib && npm test",
|
|
43
|
+
"prepack": "npm run build:lib",
|
|
44
|
+
"preview": "vite preview",
|
|
45
|
+
"test": "vitest run",
|
|
46
|
+
"test:watch": "vitest",
|
|
47
|
+
"typecheck": "tsc --noEmit",
|
|
48
|
+
"lint": "eslint ."
|
|
49
|
+
},
|
|
50
|
+
"dependencies": {
|
|
51
|
+
"bootstrap": "^5.3.3",
|
|
52
|
+
"bootstrap-icons": "^1.11.3"
|
|
53
|
+
},
|
|
54
|
+
"peerDependencies": {
|
|
55
|
+
"@tanstack/react-query": "^5.0.0",
|
|
56
|
+
"react": "^19.0.0",
|
|
57
|
+
"react-dom": "^19.0.0"
|
|
58
|
+
},
|
|
59
|
+
"devDependencies": {
|
|
60
|
+
"@tanstack/react-query": "^5.66.5",
|
|
61
|
+
"@types/node": "^22.10.2",
|
|
62
|
+
"@types/react": "^19.2.0",
|
|
63
|
+
"@types/react-dom": "^19.2.0",
|
|
64
|
+
"@vitejs/plugin-react": "^5.0.4",
|
|
65
|
+
"postcss-prefix-selector": "^2.1.1",
|
|
66
|
+
"react": "^19.2.0",
|
|
67
|
+
"react-dom": "^19.2.0",
|
|
68
|
+
"typescript": "^5.7.2",
|
|
69
|
+
"vite": "^8.0.16",
|
|
70
|
+
"vite-plugin-dts": "^4.5.0",
|
|
71
|
+
"vitest": "^4.1.8"
|
|
72
|
+
}
|
|
73
73
|
}
|