diff --git a/app/(customer)/dashboard/(admin)/admin/settings/_email/page.tsx b/app/(customer)/dashboard/(admin)/admin/settings/_email/page.tsx deleted file mode 100644 index a714d815..00000000 --- a/app/(customer)/dashboard/(admin)/admin/settings/_email/page.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import {PageParams} from "@/types/next"; -import {Page, PageContent, PageHeader, PageTitle} from "@/features/layout/page"; -import {db} from "@/db"; -import {SettingsEmailSection} from "@/components/wrappers/dashboard/admin/settings/email/settings-email-section"; -import {notFound} from "next/navigation"; - -export default async function RoutePage(props: PageParams<{}>) { - - const settings = await db.query.setting.findFirst({ - where: (fields, {eq}) => eq(fields.name, "system"), - }); - - if (!settings) { - notFound() - } - - - return ( - - -
- System email -
-
- - - -
- ); -} diff --git a/app/(customer)/dashboard/(admin)/admin/settings/_storage/page.tsx b/app/(customer)/dashboard/(admin)/admin/settings/_storage/page.tsx deleted file mode 100644 index 9ab5f5d5..00000000 --- a/app/(customer)/dashboard/(admin)/admin/settings/_storage/page.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import {PageParams} from "@/types/next"; -import {Page, PageContent, PageHeader, PageTitle} from "@/features/layout/page"; -import {SettingsStorageSection} from "@/components/wrappers/dashboard/admin/settings/storage/settings-storage-section"; -import {db} from "@/db"; -import {notFound} from "next/navigation"; - -export default async function RoutePage(props: PageParams<{}>) { - - const settings = await db.query.setting.findFirst({ - where: (fields, {eq}) => eq(fields.name, "system"), - }); - - if (!settings) { - notFound() - } - - return ( - - -
- System storage -
-
- - - -
- ); -} diff --git a/src/components/wrappers/dashboard/admin/settings/storage/settings-storage-section.tsx b/src/components/wrappers/dashboard/admin/settings/storage/settings-storage-section.tsx index c480cdd5..eed23989 100644 --- a/src/components/wrappers/dashboard/admin/settings/storage/settings-storage-section.tsx +++ b/src/components/wrappers/dashboard/admin/settings/storage/settings-storage-section.tsx @@ -4,7 +4,15 @@ import {Info} from "lucide-react"; import {ButtonWithLoading} from "@/components/wrappers/common/button/button-with-loading"; import {useRouter} from "next/navigation"; import {Setting} from "@/db/schema/01_setting"; -import {Form, FormControl, FormField, FormItem, FormLabel, FormMessage, useZodForm} from "@/components/ui/form"; +import { + Form, + FormControl, + FormField, + FormItem, + FormLabel, + FormMessage, + useZodForm +} from "@/components/ui/form"; import {StorageChannelWith} from "@/db/schema/12_storage-channel"; import {Select, SelectContent, SelectItem, SelectTrigger, SelectValue} from "@/components/ui/select"; import {useMutation} from "@tanstack/react-query"; @@ -18,6 +26,7 @@ import { } from "@/components/wrappers/dashboard/admin/settings/storage/settings-storage.action"; import {toast} from "sonner"; import {Switch} from "@/components/ui/switch"; +import {downloadMasterKeyAction} from "@/features/keys/keys.action"; export type SettingsStorageSectionProps = { settings: Setting; @@ -50,6 +59,34 @@ export const SettingsStorageSection = ({settings, storageChannels}: SettingsStor } }); + + const handleDownload = async () => { + const result = await downloadMasterKeyAction(); + + if (!result?.success) { + toast.error(result?.message ?? "Download failed"); + return; + } + + const byteCharacters = atob(result.data as string); + const byteNumbers = new Array(byteCharacters.length) + .fill(null) + .map((_, i) => byteCharacters.charCodeAt(i)); + + const blob = new Blob([new Uint8Array(byteNumbers)], { + type: "application/octet-stream", + }); + + const url = window.URL.createObjectURL(blob); + const a = document.createElement("a"); + a.href = url; + a.download = "master_key.bin"; + document.body.appendChild(a); + a.click(); + a.remove(); + window.URL.revokeObjectURL(url); + }; + return (
@@ -59,7 +96,7 @@ export const SettingsStorageSection = ({settings, storageChannels}: SettingsStor The default storage channel will be used by default to store your backups if no storage policy is configured at the database level. -
+
)} /> - - + +
+ + Download instance + +
); diff --git a/src/env.mjs b/src/env.mjs index 2c00598b..18ee380d 100644 --- a/src/env.mjs +++ b/src/env.mjs @@ -35,7 +35,7 @@ export const env = createEnv({ .string() .default(process.env.NODE_ENV === "production" ? "0 7 * * *" : "* * * * *"), - PRIVATE_PATH: z.string().optional(), + PRIVATE_PATH: z.string(), }, client: { diff --git a/src/features/keys/keys.action.ts b/src/features/keys/keys.action.ts index 5f935554..d7c047a5 100644 --- a/src/features/keys/keys.action.ts +++ b/src/features/keys/keys.action.ts @@ -1,3 +1,4 @@ +"use server" import fs from "node:fs"; import {env} from "@/env.mjs"; import path from "path"; @@ -6,7 +7,7 @@ import path from "path"; /** * Get Public server key content */ -export function getPublicServerKeyContent() { +export async function getPublicServerKeyContent() { try { const keyPath = path.join(env.PRIVATE_PATH, '/keys/server_public.pem') return fs.readFileSync(keyPath, "utf8"); @@ -23,7 +24,7 @@ export function getPublicServerKeyContent() { /** * Get Master server key */ -export function getMasterServerKeyContent() { +export async function getMasterServerKeyContent() { try { const keyPath = path.join(env.PRIVATE_PATH, '/keys/master_key.bin') return fs.readFileSync(keyPath); @@ -35,3 +36,21 @@ export function getMasterServerKeyContent() { }; } } + + +export async function downloadMasterKeyAction() { + try { + const keyPath = path.join(env.PRIVATE_PATH, "keys/master_key.bin"); + const fileBuffer = fs.readFileSync(keyPath); + + return { + success: true, + data: fileBuffer.toString("base64"), + }; + } catch (error) { + return { + success: false, + message: "Unable to download master key", + }; + } +} \ No newline at end of file diff --git a/src/utils/edge_key.ts b/src/utils/edge_key.ts index fee5ef21..2d9944d6 100644 --- a/src/utils/edge_key.ts +++ b/src/utils/edge_key.ts @@ -2,7 +2,7 @@ import {getMasterServerKeyContent} from "@/features/keys/keys.action"; export async function generateEdgeKey(serverUrl: string, agentId: string): Promise { - const masterKey = getMasterServerKeyContent() + const masterKey = await getMasterServerKeyContent() const edgeKeyData = { serverUrl, agentId,