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 ; }