diff --git a/apps/docs/content/docs/guides/diagnostic.mdx b/apps/docs/content/docs/guides/diagnostic.mdx
new file mode 100644
index 000000000..567546897
--- /dev/null
+++ b/apps/docs/content/docs/guides/diagnostic.mdx
@@ -0,0 +1,4 @@
+---
+title: Diagnostic Tools
+description: How to use diagnostic tools to debug and monitor your app.
+---
diff --git a/apps/docs/content/docs/ui/alert-dialog.mdx b/apps/docs/content/docs/ui/alert-dialog.mdx
index 931bae75b..0b53e93ce 100644
--- a/apps/docs/content/docs/ui/alert-dialog.mdx
+++ b/apps/docs/content/docs/ui/alert-dialog.mdx
@@ -129,7 +129,7 @@ import {
AlertDialogTrigger,
} from 'vitnode-frontend/components/ui/alert-dialog';
-const Content = React.lazy(() =>
+const Content = React.lazy(async () =>
import('./content').then(module => ({
default: module.ContentTest,
})),
diff --git a/apps/docs/content/docs/ui/dialog.mdx b/apps/docs/content/docs/ui/dialog.mdx
index 7740bf956..6712a63e1 100644
--- a/apps/docs/content/docs/ui/dialog.mdx
+++ b/apps/docs/content/docs/ui/dialog.mdx
@@ -119,7 +119,7 @@ import {
} from 'vitnode-frontend/components/ui/dialog';
import { Loader } from 'vitnode-frontend/components/ui/loader';
-const Content = React.lazy(() =>
+const Content = React.lazy(async () =>
import('./content').then(module => ({
default: module.ContentTest,
})),
diff --git a/apps/frontend/package.json b/apps/frontend/package.json
index 0f69b9aa2..1720f6c8b 100644
--- a/apps/frontend/package.json
+++ b/apps/frontend/package.json
@@ -5,7 +5,7 @@
"license": "GPL-3.0 license",
"scripts": {
"config:init": "vitnode-frontend init",
- "dev": "next dev --turbo",
+ "dev": "rm -rf .next/cache && next dev --turbo",
"dev:turbo": "next dev --turbo",
"build": "next build",
"analyze": "cross-env ANALYZE=true pnpm build",
diff --git a/apps/frontend/plugins/admin/langs/en.json b/apps/frontend/plugins/admin/langs/en.json
index 10b8cff22..86cea07d4 100644
--- a/apps/frontend/plugins/admin/langs/en.json
+++ b/apps/frontend/plugins/admin/langs/en.json
@@ -51,6 +51,16 @@
"submit": "Read How To Rebuild"
},
"core": {
+ "diagnostic": {
+ "title": "Diagnostic Tools",
+ "desc": "Use these tools to diagnose and fix common issues with your website.",
+ "clear_cache": {
+ "title": "Clear Cache",
+ "desc": "This action clean up the cache of your website. This can help fix issues with your website but may slow down your website temporarily.",
+ "confirm": "Yes, clear cache",
+ "success": "Cache has been cleared."
+ }
+ },
"email": {
"hello": "Hello",
"footer": "You're receiving this email because your account activity triggered this email. Please do not reply to this email. If you have any questions, please contact us at:"
diff --git a/packages/backend/scripts/update-plugins.ts b/packages/backend/scripts/update-plugins.ts
index 27ed3aba2..c00cf5044 100644
--- a/packages/backend/scripts/update-plugins.ts
+++ b/packages/backend/scripts/update-plugins.ts
@@ -3,7 +3,7 @@ import * as fs from 'fs';
import { join } from 'path';
import { NodePgDatabase } from 'drizzle-orm/node-postgres';
-import { eq } from 'drizzle-orm';
+import { eq, sql } from 'drizzle-orm';
import { ConfigPlugin } from '../src/providers/plugins.type';
import coreSchemaDatabase from '../src/database';
@@ -25,6 +25,13 @@ export const updatePlugins = async ({
.filter(plugin => !['core', 'plugins.module.ts'].includes(plugin));
await db.transaction(async tx => {
+ const pluginsFromDatabase = await tx.query.core_plugins.findMany({
+ columns: {
+ code: true,
+ id: true,
+ },
+ });
+
await Promise.all(
plugins.map(async (code, index) => {
const pluginPath = join(pluginsPath, code);
@@ -36,9 +43,9 @@ export const updatePlugins = async ({
isDefaultIndex = index;
}
- const plugin = await tx.query.core_plugins.findFirst({
- where: (table, { eq }) => eq(table.code, code),
- });
+ const plugin = pluginsFromDatabase.find(
+ plugin => plugin.code === config.code,
+ );
if (plugin) {
await tx
@@ -54,23 +61,36 @@ export const updatePlugins = async ({
version_code: config.version_code,
})
.where(eq(core_plugins.id, plugin.id));
-
- return;
+ } else {
+ await tx.insert(core_plugins).values([
+ {
+ name: config.name,
+ description: config.description,
+ code: config.code,
+ support_url: config.support_url,
+ author: config.author,
+ author_url: config.author_url,
+ allow_default: config.allow_default,
+ version: config.version,
+ version_code: config.version_code,
+ default: isDefaultIndex === index && !defaultPlugin,
+ },
+ ]);
}
- await tx.insert(core_plugins).values([
- {
- name: config.name,
- description: config.description,
- code: config.code,
- support_url: config.support_url,
- author: config.author,
- author_url: config.author_url,
- allow_default: config.allow_default,
- version: config.version,
- version_code: config.version_code,
- default: isDefaultIndex === index && !defaultPlugin,
- },
- ]);
+
+ await tx.execute(sql`commit`);
+ }),
+ );
+
+ // Remove plugins that are not in the plugins folder
+ const pluginsToDelete = pluginsFromDatabase.filter(
+ plugin => !plugins.includes(plugin.code),
+ );
+
+ await Promise.all(
+ pluginsToDelete.map(async plugin => {
+ await tx.delete(core_plugins).where(eq(core_plugins.id, plugin.id));
+ await tx.execute(sql`commit`);
}),
);
});
diff --git a/packages/backend/src/core/admin/plugins/delete/delete.service.ts b/packages/backend/src/core/admin/plugins/delete/delete.service.ts
index 32ccd4d65..b0541ac3b 100644
--- a/packages/backend/src/core/admin/plugins/delete/delete.service.ts
+++ b/packages/backend/src/core/admin/plugins/delete/delete.service.ts
@@ -9,7 +9,6 @@ import { ChangeFilesAdminPluginsService } from '../helpers/files/change/change.s
import { InternalDatabaseService } from '@/utils/database/internal_database.service';
import { CustomError, NotFoundError } from '@/errors';
-import { core_migrations } from '@/database/schema/files';
import { ABSOLUTE_PATHS_BACKEND } from '@/index';
import { core_plugins } from '@/database/schema/plugins';
import { setRebuildRequired } from '@/functions/rebuild-required';
@@ -67,10 +66,8 @@ export class DeleteAdminPluginsService {
message: `Error deleting tables for plugin ${code}`,
});
}
- // Delete migrations
- await this.databaseService.db
- .delete(core_migrations)
- .where(eq(core_migrations.plugin, code));
+ // Generate migrations
+ // TODO: Generate migrations
// Change files when delete
this.changeFilesService.changeFilesWhenDelete({ code });
diff --git a/packages/backend/src/database/schema/files.ts b/packages/backend/src/database/schema/files.ts
index dda6ec8b7..d72d2193d 100644
--- a/packages/backend/src/database/schema/files.ts
+++ b/packages/backend/src/database/schema/files.ts
@@ -1,10 +1,8 @@
import {
- bigint,
index,
integer,
pgTable,
serial,
- text,
timestamp,
varchar,
} from 'drizzle-orm/pg-core';
@@ -72,11 +70,3 @@ export const core_files_using_relations = relations(
}),
}),
);
-
-export const core_migrations = pgTable('core_migrations', {
- id: serial('id').primaryKey(),
- hash: text('hash').notNull(),
- plugin: varchar('plugin', { length: 255 }).notNull(),
- created_migration: bigint('created_migration', { mode: 'bigint' }),
- created: timestamp('created').notNull().defaultNow(),
-});
diff --git a/packages/create-vitnode-app/helpers/create-packages-json.ts b/packages/create-vitnode-app/helpers/create-packages-json.ts
index 59d0566e9..8864c4eb6 100644
--- a/packages/create-vitnode-app/helpers/create-packages-json.ts
+++ b/packages/create-vitnode-app/helpers/create-packages-json.ts
@@ -77,7 +77,7 @@ export const createPackagesJSON = ({
private: true,
scripts: {
'config:init': 'vitnode-frontend init',
- dev: 'vitnode-frontend init && next dev --turbo',
+ dev: 'rm -rf .next/cache && vitnode-frontend init && next dev --turbo',
build: 'next build',
start: 'next start',
lint: 'eslint .',
diff --git a/packages/frontend/src/views/admin/layout/auth/aside/avatar.tsx b/packages/frontend/src/views/admin/layout/auth/aside/avatar.tsx
index e2278397d..fde6ee9c2 100644
--- a/packages/frontend/src/views/admin/layout/auth/aside/avatar.tsx
+++ b/packages/frontend/src/views/admin/layout/auth/aside/avatar.tsx
@@ -3,6 +3,7 @@
import {
GitBranch,
Globe,
+ HammerIcon,
Home,
LogOut,
SquareArrowOutUpRight,
@@ -71,6 +72,12 @@ export const AvatarAsideAuthAdmin = () => {
{tCore('user-bar.my_profile')}
+
+
+
+ {t('core.diagnostic.title')}
+
+
diff --git a/packages/frontend/src/views/admin/views/core/diagnostic/actions/actions.tsx b/packages/frontend/src/views/admin/views/core/diagnostic/actions/actions.tsx
new file mode 100644
index 000000000..838f049b4
--- /dev/null
+++ b/packages/frontend/src/views/admin/views/core/diagnostic/actions/actions.tsx
@@ -0,0 +1,7 @@
+'use client';
+
+import { ClearCacheActionDiagnostic } from './clear_cache/clear_cache';
+
+export const ActionsDiagnosticTools = () => {
+ return ;
+};
diff --git a/packages/frontend/src/views/admin/views/core/diagnostic/actions/clear_cache/clear_cache.tsx b/packages/frontend/src/views/admin/views/core/diagnostic/actions/clear_cache/clear_cache.tsx
new file mode 100644
index 000000000..47f862356
--- /dev/null
+++ b/packages/frontend/src/views/admin/views/core/diagnostic/actions/clear_cache/clear_cache.tsx
@@ -0,0 +1,40 @@
+import { EraserIcon } from 'lucide-react';
+import { useTranslations } from 'next-intl';
+import React from 'react';
+
+import {
+ AlertDialog,
+ AlertDialogContent,
+ AlertDialogTrigger,
+} from '@/components/ui/alert-dialog';
+import { Button } from '@/components/ui/button';
+import { Loader } from '@/components/ui/loader';
+
+const Content = React.lazy(async () =>
+ import('./content').then(module => ({
+ default: module.ContentClearCacheActionDiagnostic,
+ })),
+);
+
+export const ClearCacheActionDiagnostic = () => {
+ const t = useTranslations('admin.core.diagnostic.clear_cache');
+
+ return (
+ <>
+
+
+
+
+
+
+ }>
+
+
+
+
+ >
+ );
+};
diff --git a/packages/frontend/src/views/admin/views/core/diagnostic/actions/clear_cache/content.tsx b/packages/frontend/src/views/admin/views/core/diagnostic/actions/clear_cache/content.tsx
new file mode 100644
index 000000000..400d511e8
--- /dev/null
+++ b/packages/frontend/src/views/admin/views/core/diagnostic/actions/clear_cache/content.tsx
@@ -0,0 +1,42 @@
+import { useTranslations } from 'next-intl';
+import { toast } from 'sonner';
+
+import {
+ AlertDialogAction,
+ AlertDialogCancel,
+ AlertDialogDescription,
+ AlertDialogFooter,
+ AlertDialogHeader,
+ AlertDialogTitle,
+} from '@/components/ui/alert-dialog';
+import { Button } from '@/components/ui/button';
+import { mutationApi } from './hooks/mutation-api';
+
+export const ContentClearCacheActionDiagnostic = () => {
+ const t = useTranslations('admin.core.diagnostic.clear_cache');
+ const tCore = useTranslations('core');
+
+ return (
+ <>
+
+ {tCore('are_you_sure')}
+ {t('desc')}
+
+
+ {tCore('cancel')}
+
+
+
+
+ >
+ );
+};
diff --git a/packages/frontend/src/views/admin/views/core/diagnostic/actions/clear_cache/hooks/mutation-api.ts b/packages/frontend/src/views/admin/views/core/diagnostic/actions/clear_cache/hooks/mutation-api.ts
new file mode 100644
index 000000000..1ec0f7cfb
--- /dev/null
+++ b/packages/frontend/src/views/admin/views/core/diagnostic/actions/clear_cache/hooks/mutation-api.ts
@@ -0,0 +1,7 @@
+'use server';
+
+import { revalidatePath } from 'next/cache';
+
+export const mutationApi = async () => {
+ revalidatePath('/', 'layout');
+};
diff --git a/packages/frontend/src/views/admin/views/core/diagnostic/diagnostic-tools-view.tsx b/packages/frontend/src/views/admin/views/core/diagnostic/diagnostic-tools-view.tsx
new file mode 100644
index 000000000..214872991
--- /dev/null
+++ b/packages/frontend/src/views/admin/views/core/diagnostic/diagnostic-tools-view.tsx
@@ -0,0 +1,24 @@
+import { Metadata } from 'next';
+import { useTranslations } from 'next-intl';
+import { getTranslations } from 'next-intl/server';
+
+import { HeaderContent } from '@/components/ui/header-content';
+import { ActionsDiagnosticTools } from './actions/actions';
+
+export const generateMetadataDiagnosticAdmin = async (): Promise => {
+ const t = await getTranslations('admin.core.diagnostic');
+
+ return {
+ title: t('title'),
+ };
+};
+
+export const DiagnosticToolsView = () => {
+ const t = useTranslations('admin.core.diagnostic');
+
+ return (
+
+
+
+ );
+};
diff --git a/packages/frontend/src/views/admin/views/slug.tsx b/packages/frontend/src/views/admin/views/slug.tsx
index 8da2843bb..ef1ad5f39 100644
--- a/packages/frontend/src/views/admin/views/slug.tsx
+++ b/packages/frontend/src/views/admin/views/slug.tsx
@@ -54,6 +54,10 @@ import {
generateMetadataPluginsAdmin,
PluginsAdminView,
} from './core/plugins/plugins-admin-view';
+import {
+ DiagnosticToolsView,
+ generateMetadataDiagnosticAdmin,
+} from './core/diagnostic/diagnostic-tools-view';
export interface SlugAdminViewProps {
params: { locale: string; slug: string[] };
@@ -66,6 +70,8 @@ export const generateMetadataSlugAdmin = async ({
switch (slug[0]) {
case 'core':
switch (slug[1]) {
+ case 'diagnostic':
+ return generateMetadataDiagnosticAdmin();
case 'advanced':
switch (slug[2]) {
case 'files':
@@ -161,6 +167,8 @@ export const SlugAdminView = (props: SlugAdminViewProps) => {
return ;
case 'plugins':
return ;
+ case 'diagnostic':
+ return ;
case 'dashboard':
return ;
}