Implement Plugin Manager: Add new pages for managing plugins, including store, installed plugins, and upload functionality. Update navigation to include Plugin Manager section. Enhance RsTab component to handle empty tab states. Remove unused ticket model from Prisma schema.
This commit is contained in:
parent
989b757a9f
commit
2fe91054e3
@ -25,11 +25,11 @@ const props = defineProps({
|
||||
// Slots
|
||||
const slots = useSlots();
|
||||
|
||||
const tabs = ref(slots.default().map((tab) => tab.props));
|
||||
const selectedTitle = ref(tabs.value[0]["title"]);
|
||||
const tabs = ref(slots.default().map((tab) => tab.props).filter(Boolean));
|
||||
const selectedTitle = ref(tabs.value.length > 0 && tabs.value[0] ? tabs.value[0]["title"] : "");
|
||||
|
||||
tabs.value.forEach((tab) => {
|
||||
if (typeof tab.active !== "undefined") {
|
||||
if (tab && typeof tab.active !== "undefined") {
|
||||
selectedTitle.value = tab.title;
|
||||
}
|
||||
});
|
||||
|
@ -75,6 +75,28 @@ export default [
|
||||
"path": "/devtool/api-editor",
|
||||
"icon": "material-symbols:api-rounded",
|
||||
"child": []
|
||||
},
|
||||
{
|
||||
"title": "Plugin Manager",
|
||||
"icon": "mdi:puzzle",
|
||||
"child": [
|
||||
{
|
||||
"title": "Dashboard",
|
||||
"path": "/devtool/plugin-manager"
|
||||
},
|
||||
{
|
||||
"title": "Plugin Store",
|
||||
"path": "/devtool/plugin-manager/store"
|
||||
},
|
||||
{
|
||||
"title": "My Plugins",
|
||||
"path": "/devtool/plugin-manager/installed"
|
||||
},
|
||||
{
|
||||
"title": "Upload Plugin",
|
||||
"path": "/devtool/plugin-manager/upload"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"meta": {
|
||||
|
396
pages/devtool/plugin-manager/index.vue
Normal file
396
pages/devtool/plugin-manager/index.vue
Normal file
@ -0,0 +1,396 @@
|
||||
<script setup>
|
||||
definePageMeta({
|
||||
title: "Plugin Manager",
|
||||
middleware: ["auth"],
|
||||
requiresAuth: true,
|
||||
});
|
||||
|
||||
// Quick stats for dashboard
|
||||
const pluginStats = ref({
|
||||
installed: 3,
|
||||
active: 3,
|
||||
available: 4,
|
||||
updates: 2
|
||||
});
|
||||
|
||||
// Quick actions
|
||||
const quickActions = ref([
|
||||
{
|
||||
title: "Browse Store",
|
||||
description: "Discover new plugins and applications",
|
||||
icon: "mdi:store",
|
||||
color: "blue",
|
||||
path: "/devtool/plugin-manager/store"
|
||||
},
|
||||
{
|
||||
title: "My Plugins",
|
||||
description: "Manage installed plugins",
|
||||
icon: "mdi:puzzle",
|
||||
color: "green",
|
||||
path: "/devtool/plugin-manager/installed"
|
||||
},
|
||||
{
|
||||
title: "Upload Plugin",
|
||||
description: "Install custom plugin packages",
|
||||
icon: "mdi:cloud-upload",
|
||||
color: "purple",
|
||||
path: "/devtool/plugin-manager/upload"
|
||||
},
|
||||
{
|
||||
title: "Plugin Settings",
|
||||
description: "Configure plugin system settings",
|
||||
icon: "mdi:cog",
|
||||
color: "gray",
|
||||
path: "/devtool/plugin-manager/settings"
|
||||
}
|
||||
]);
|
||||
|
||||
// Recently installed plugins
|
||||
const recentPlugins = ref([
|
||||
{
|
||||
id: 1,
|
||||
name: "notification-management",
|
||||
displayName: "Notification Management System",
|
||||
version: "1.2.0",
|
||||
description: "Complete notification system with templates, scheduling, and analytics",
|
||||
status: "active",
|
||||
author: "CORRAD Team",
|
||||
installDate: "2024-01-15",
|
||||
icon: "mdi:bell-ring",
|
||||
color: "orange",
|
||||
authentik: {
|
||||
applicationSlug: "notification-management",
|
||||
requiredScopes: ["notifications:read", "notifications:write", "notifications:admin"],
|
||||
groups: ["notification-users", "notification-admins"]
|
||||
},
|
||||
menuItems: 6,
|
||||
apiEndpoints: 2,
|
||||
migrations: 3
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: "report-management",
|
||||
displayName: "Report Management System",
|
||||
version: "1.0.0",
|
||||
description: "Advanced reporting system integrated with Metabase for analytics and dashboards",
|
||||
status: "active",
|
||||
author: "CORRAD Team",
|
||||
installDate: "2024-01-12",
|
||||
icon: "mdi:chart-line",
|
||||
color: "blue",
|
||||
authentik: {
|
||||
applicationSlug: "report-management",
|
||||
requiredScopes: ["reports:read", "reports:write", "reports:admin"],
|
||||
groups: ["report-users", "report-admins"]
|
||||
},
|
||||
menuItems: 5,
|
||||
apiEndpoints: 3,
|
||||
migrations: 2
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: "rbac",
|
||||
displayName: "RBAC System",
|
||||
version: "1.0.0",
|
||||
description: "Role-Based Access Control system integrated with Authentik",
|
||||
status: "active",
|
||||
author: "CORRAD Team",
|
||||
installDate: "2024-01-10",
|
||||
icon: "mdi:shield-account",
|
||||
color: "green",
|
||||
authentik: {
|
||||
applicationSlug: "rbac-system",
|
||||
requiredScopes: ["rbac:read", "rbac:write", "rbac:admin"],
|
||||
groups: ["rbac-users", "rbac-admins"]
|
||||
},
|
||||
menuItems: 4,
|
||||
apiEndpoints: 4,
|
||||
migrations: 1
|
||||
}
|
||||
]);
|
||||
|
||||
// Plugin updates available
|
||||
const availableUpdates = ref([
|
||||
{
|
||||
id: 4,
|
||||
name: "audit-trail",
|
||||
displayName: "Audit Trail System",
|
||||
currentVersion: "1.0.0",
|
||||
newVersion: "1.1.0",
|
||||
updateSize: "2.1 MB",
|
||||
icon: "mdi:file-document-alert",
|
||||
color: "purple"
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
name: "queue-management",
|
||||
displayName: "Queue Management System",
|
||||
currentVersion: "1.0.0",
|
||||
newVersion: "1.0.1",
|
||||
updateSize: "800 KB",
|
||||
icon: "mdi:format-list-numbered",
|
||||
color: "indigo"
|
||||
}
|
||||
]);
|
||||
|
||||
const getColorClasses = (color, type = 'bg') => {
|
||||
const colorMap = {
|
||||
bg: {
|
||||
blue: 'bg-blue-100 text-blue-600',
|
||||
green: 'bg-green-100 text-green-600',
|
||||
purple: 'bg-purple-100 text-purple-600',
|
||||
orange: 'bg-orange-100 text-orange-600',
|
||||
indigo: 'bg-indigo-100 text-indigo-600',
|
||||
red: 'bg-red-100 text-red-600',
|
||||
gray: 'bg-gray-100 text-gray-600'
|
||||
},
|
||||
hover: {
|
||||
blue: 'hover:bg-blue-200',
|
||||
green: 'hover:bg-green-200',
|
||||
purple: 'hover:bg-purple-200',
|
||||
orange: 'hover:bg-orange-200',
|
||||
indigo: 'hover:bg-indigo-200',
|
||||
red: 'hover:bg-red-200',
|
||||
gray: 'hover:bg-gray-200'
|
||||
}
|
||||
};
|
||||
return colorMap[type][color] || colorMap[type]['gray'];
|
||||
};
|
||||
|
||||
const navigateToAction = (path) => {
|
||||
navigateTo(path);
|
||||
};
|
||||
|
||||
const getStatusBadgeVariant = (status) => {
|
||||
return status === 'active' ? 'success' : 'secondary';
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<LayoutsBreadcrumb />
|
||||
|
||||
<!-- Welcome Header -->
|
||||
<rs-card class="mb-6">
|
||||
<template #body>
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<h1 class="text-2xl font-bold text-gray-900 mb-2">Plugin Manager</h1>
|
||||
<p class="text-gray-600">
|
||||
Manage your application ecosystem. Install, configure, and organize plugins to extend functionality.
|
||||
</p>
|
||||
</div>
|
||||
<div class="hidden md:block">
|
||||
<Icon name="mdi:puzzle" size="64" class="text-primary/20" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</rs-card>
|
||||
|
||||
<!-- Stats Overview -->
|
||||
<div class="grid grid-cols-2 md:grid-cols-4 gap-4 mb-6">
|
||||
<rs-card class="text-center">
|
||||
<template #body>
|
||||
<div class="p-4">
|
||||
<div class="flex items-center justify-center mb-2">
|
||||
<div class="bg-blue-100 text-blue-600 p-3 rounded-full">
|
||||
<Icon name="mdi:puzzle" size="24" />
|
||||
</div>
|
||||
</div>
|
||||
<h3 class="text-2xl font-bold text-gray-900">{{ pluginStats.installed }}</h3>
|
||||
<p class="text-sm text-gray-600">Installed</p>
|
||||
</div>
|
||||
</template>
|
||||
</rs-card>
|
||||
|
||||
<rs-card class="text-center">
|
||||
<template #body>
|
||||
<div class="p-4">
|
||||
<div class="flex items-center justify-center mb-2">
|
||||
<div class="bg-green-100 text-green-600 p-3 rounded-full">
|
||||
<Icon name="mdi:check-circle" size="24" />
|
||||
</div>
|
||||
</div>
|
||||
<h3 class="text-2xl font-bold text-gray-900">{{ pluginStats.active }}</h3>
|
||||
<p class="text-sm text-gray-600">Active</p>
|
||||
</div>
|
||||
</template>
|
||||
</rs-card>
|
||||
|
||||
<rs-card class="text-center">
|
||||
<template #body>
|
||||
<div class="p-4">
|
||||
<div class="flex items-center justify-center mb-2">
|
||||
<div class="bg-purple-100 text-purple-600 p-3 rounded-full">
|
||||
<Icon name="mdi:store" size="24" />
|
||||
</div>
|
||||
</div>
|
||||
<h3 class="text-2xl font-bold text-gray-900">{{ pluginStats.available }}</h3>
|
||||
<p class="text-sm text-gray-600">Available</p>
|
||||
</div>
|
||||
</template>
|
||||
</rs-card>
|
||||
|
||||
<rs-card class="text-center">
|
||||
<template #body>
|
||||
<div class="p-4">
|
||||
<div class="flex items-center justify-center mb-2">
|
||||
<div class="bg-orange-100 text-orange-600 p-3 rounded-full">
|
||||
<Icon name="mdi:update" size="24" />
|
||||
</div>
|
||||
</div>
|
||||
<h3 class="text-2xl font-bold text-gray-900">{{ pluginStats.updates }}</h3>
|
||||
<p class="text-sm text-gray-600">Updates</p>
|
||||
</div>
|
||||
</template>
|
||||
</rs-card>
|
||||
</div>
|
||||
|
||||
<!-- Quick Actions -->
|
||||
<rs-card class="mb-6">
|
||||
<template #header>
|
||||
<div class="flex items-center">
|
||||
<Icon name="mdi:lightning-bolt" class="mr-2" />
|
||||
Quick Actions
|
||||
</div>
|
||||
</template>
|
||||
<template #body>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
|
||||
<div
|
||||
v-for="action in quickActions"
|
||||
:key="action.title"
|
||||
class="group p-6 rounded-lg border-2 border-gray-200 hover:border-primary cursor-pointer transition-all duration-200 hover:shadow-md"
|
||||
@click="navigateToAction(action.path)"
|
||||
>
|
||||
<div class="text-center">
|
||||
<div
|
||||
:class="[getColorClasses(action.color), getColorClasses(action.color, 'hover')]"
|
||||
class="w-16 h-16 mx-auto rounded-full flex items-center justify-center mb-4 transition-colors"
|
||||
>
|
||||
<Icon :name="action.icon" size="32" />
|
||||
</div>
|
||||
<h3 class="font-semibold text-lg text-gray-900 mb-2 group-hover:text-primary transition-colors">
|
||||
{{ action.title }}
|
||||
</h3>
|
||||
<p class="text-gray-600 text-sm">{{ action.description }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</rs-card>
|
||||
|
||||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||
<!-- Recently Installed -->
|
||||
<rs-card>
|
||||
<template #header>
|
||||
<div class="flex items-center justify-between w-full">
|
||||
<div class="flex items-center">
|
||||
<Icon name="mdi:history" class="mr-2" />
|
||||
Recently Installed
|
||||
</div>
|
||||
<NuxtLink
|
||||
to="/devtool/plugin-manager/installed"
|
||||
class="text-primary hover:text-primary/80 text-sm font-medium"
|
||||
>
|
||||
View All
|
||||
</NuxtLink>
|
||||
</div>
|
||||
</template>
|
||||
<template #body>
|
||||
<div class="space-y-4">
|
||||
<div
|
||||
v-for="plugin in recentPlugins"
|
||||
:key="plugin.id"
|
||||
class="flex items-center p-4 rounded-lg border border-gray-200 hover:shadow-sm transition-shadow"
|
||||
>
|
||||
<div
|
||||
:class="getColorClasses(plugin.color)"
|
||||
class="w-12 h-12 rounded-lg flex items-center justify-center mr-4"
|
||||
>
|
||||
<Icon :name="plugin.icon" size="24" />
|
||||
</div>
|
||||
<div class="flex-1 min-w-0">
|
||||
<div class="flex items-center justify-between">
|
||||
<h4 class="font-medium text-gray-900 truncate">{{ plugin.displayName }}</h4>
|
||||
<rs-badge :variant="getStatusBadgeVariant(plugin.status)" size="sm">
|
||||
{{ plugin.status }}
|
||||
</rs-badge>
|
||||
</div>
|
||||
<p class="text-sm text-gray-600 mt-1">v{{ plugin.version }} • {{ plugin.author }}</p>
|
||||
<div class="flex items-center space-x-3 mt-2 text-xs text-gray-500">
|
||||
<span class="flex items-center">
|
||||
<Icon name="mdi:menu" size="12" class="mr-1" />
|
||||
{{ plugin.menuItems }} menus
|
||||
</span>
|
||||
<span class="flex items-center">
|
||||
<Icon name="mdi:api" size="12" class="mr-1" />
|
||||
{{ plugin.apiEndpoints }} APIs
|
||||
</span>
|
||||
<span class="flex items-center">
|
||||
<Icon name="mdi:database" size="12" class="mr-1" />
|
||||
{{ plugin.migrations }} migrations
|
||||
</span>
|
||||
</div>
|
||||
<p class="text-xs text-gray-500 mt-1">Installed {{ plugin.installDate }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="recentPlugins.length === 0" class="text-center py-8">
|
||||
<Icon name="mdi:puzzle-outline" size="48" class="mx-auto text-gray-400 mb-2" />
|
||||
<p class="text-gray-500">No plugins installed yet</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</rs-card>
|
||||
|
||||
<!-- Available Updates -->
|
||||
<rs-card>
|
||||
<template #header>
|
||||
<div class="flex items-center justify-between w-full">
|
||||
<div class="flex items-center">
|
||||
<Icon name="mdi:update" class="mr-2" />
|
||||
Available Updates
|
||||
<rs-badge v-if="availableUpdates.length > 0" variant="warning" size="sm" class="ml-2">
|
||||
{{ availableUpdates.length }}
|
||||
</rs-badge>
|
||||
</div>
|
||||
<rs-button size="sm" variant="outline" v-if="availableUpdates.length > 0">
|
||||
Update All
|
||||
</rs-button>
|
||||
</div>
|
||||
</template>
|
||||
<template #body>
|
||||
<div class="space-y-4">
|
||||
<div
|
||||
v-for="update in availableUpdates"
|
||||
:key="update.id"
|
||||
class="flex items-center p-4 rounded-lg border border-gray-200 hover:shadow-sm transition-shadow"
|
||||
>
|
||||
<div
|
||||
:class="getColorClasses(update.color)"
|
||||
class="w-12 h-12 rounded-lg flex items-center justify-center mr-4"
|
||||
>
|
||||
<Icon :name="update.icon" size="24" />
|
||||
</div>
|
||||
<div class="flex-1 min-w-0">
|
||||
<h4 class="font-medium text-gray-900">{{ update.displayName }}</h4>
|
||||
<p class="text-sm text-gray-600 mt-1">
|
||||
{{ update.currentVersion }} → {{ update.newVersion }}
|
||||
</p>
|
||||
<p class="text-xs text-gray-500 mt-1">{{ update.updateSize }}</p>
|
||||
</div>
|
||||
<rs-button size="sm" variant="primary">
|
||||
Update
|
||||
</rs-button>
|
||||
</div>
|
||||
|
||||
<div v-if="availableUpdates.length === 0" class="text-center py-8">
|
||||
<Icon name="mdi:check-circle" size="48" class="mx-auto text-green-400 mb-2" />
|
||||
<p class="text-gray-500">All plugins are up to date</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</rs-card>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
593
pages/devtool/plugin-manager/installed/index.vue
Normal file
593
pages/devtool/plugin-manager/installed/index.vue
Normal file
@ -0,0 +1,593 @@
|
||||
<script setup>
|
||||
definePageMeta({
|
||||
title: "My Plugins",
|
||||
middleware: ["auth"],
|
||||
requiresAuth: true,
|
||||
});
|
||||
|
||||
const nuxtApp = useNuxtApp();
|
||||
|
||||
// Filters and search
|
||||
const searchQuery = ref('');
|
||||
const statusFilter = ref('all');
|
||||
const sortBy = ref('recent');
|
||||
|
||||
// Modal states
|
||||
const showUninstallModal = ref(false);
|
||||
const showConfigModal = ref(false);
|
||||
const selectedPlugin = ref(null);
|
||||
|
||||
// Installed plugins
|
||||
const installedPlugins = ref([
|
||||
{
|
||||
id: 1,
|
||||
name: "rbac",
|
||||
displayName: "RBAC System",
|
||||
version: "1.0.0",
|
||||
description: "Role-Based Access Control system integrated with Authentik for user management",
|
||||
status: "active",
|
||||
author: "CORRAD Team",
|
||||
installDate: "2024-01-10",
|
||||
lastUsed: "2024-01-23",
|
||||
icon: "mdi:shield-account",
|
||||
color: "green",
|
||||
size: "3.2 MB",
|
||||
menuItems: [
|
||||
{ title: "User Management", path: "/rbac/users" },
|
||||
{ title: "Role Management", path: "/rbac/roles" },
|
||||
{ title: "Permissions", path: "/rbac/permissions" }
|
||||
],
|
||||
permissions: ["rbac.manage", "user.assign", "role.create"],
|
||||
settings: {
|
||||
authentikEnabled: true,
|
||||
ssoEnabled: true,
|
||||
autoSync: true
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: "notification-management",
|
||||
displayName: "Notification Management System",
|
||||
version: "1.0.0",
|
||||
description: "Complete notification system with email, SMS, push notifications and templates",
|
||||
status: "active",
|
||||
author: "CORRAD Team",
|
||||
installDate: "2024-01-15",
|
||||
lastUsed: "2024-01-23",
|
||||
icon: "mdi:bell-ring",
|
||||
color: "orange",
|
||||
size: "2.8 MB",
|
||||
menuItems: [
|
||||
{ title: "Dashboard", path: "/notifications/dashboard" },
|
||||
{ title: "Templates", path: "/notifications/templates" },
|
||||
{ title: "Settings", path: "/notifications/settings" },
|
||||
{ title: "History", path: "/notifications/history" }
|
||||
],
|
||||
permissions: ["notification.send", "template.manage", "delivery.track"],
|
||||
settings: {
|
||||
emailEnabled: true,
|
||||
smsEnabled: true,
|
||||
pushEnabled: true
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: "report-management",
|
||||
displayName: "Report Management System",
|
||||
version: "1.0.0",
|
||||
description: "Advanced reporting system integrated with Metabase for analytics and dashboards",
|
||||
status: "active",
|
||||
author: "CORRAD Team",
|
||||
installDate: "2024-01-12",
|
||||
lastUsed: "2024-01-22",
|
||||
icon: "mdi:chart-line",
|
||||
color: "blue",
|
||||
size: "3.8 MB",
|
||||
menuItems: [
|
||||
{ title: "Dashboard", path: "/reports/dashboard" },
|
||||
{ title: "Report Builder", path: "/reports/builder" },
|
||||
{ title: "Analytics", path: "/reports/analytics" },
|
||||
{ title: "Scheduled Reports", path: "/reports/schedule" }
|
||||
],
|
||||
permissions: ["report.create", "report.view", "dashboard.manage"],
|
||||
settings: {
|
||||
metabaseEnabled: true,
|
||||
autoGenerate: false,
|
||||
exportFormats: ["PDF", "Excel", "CSV"]
|
||||
}
|
||||
}
|
||||
]);
|
||||
|
||||
// Computed properties
|
||||
const filteredPlugins = computed(() => {
|
||||
let plugins = installedPlugins.value;
|
||||
|
||||
// Filter by status
|
||||
if (statusFilter.value !== 'all') {
|
||||
plugins = plugins.filter(plugin => plugin.status === statusFilter.value);
|
||||
}
|
||||
|
||||
// Filter by search
|
||||
if (searchQuery.value) {
|
||||
const query = searchQuery.value.toLowerCase();
|
||||
plugins = plugins.filter(plugin =>
|
||||
plugin.displayName.toLowerCase().includes(query) ||
|
||||
plugin.description.toLowerCase().includes(query) ||
|
||||
plugin.author.toLowerCase().includes(query)
|
||||
);
|
||||
}
|
||||
|
||||
// Sort plugins
|
||||
switch (sortBy.value) {
|
||||
case 'recent':
|
||||
plugins.sort((a, b) => new Date(b.installDate) - new Date(a.installDate));
|
||||
break;
|
||||
case 'name':
|
||||
plugins.sort((a, b) => a.displayName.localeCompare(b.displayName));
|
||||
break;
|
||||
case 'status':
|
||||
plugins.sort((a, b) => b.status.localeCompare(a.status));
|
||||
break;
|
||||
case 'lastUsed':
|
||||
plugins.sort((a, b) => new Date(b.lastUsed) - new Date(a.lastUsed));
|
||||
break;
|
||||
}
|
||||
|
||||
return plugins;
|
||||
});
|
||||
|
||||
const pluginStats = computed(() => {
|
||||
const total = installedPlugins.value.length;
|
||||
const active = installedPlugins.value.filter(p => p.status === 'active').length;
|
||||
const inactive = total - active;
|
||||
return { total, active, inactive };
|
||||
});
|
||||
|
||||
const getColorClasses = (color) => {
|
||||
const colorMap = {
|
||||
blue: 'bg-blue-100 text-blue-600',
|
||||
green: 'bg-green-100 text-green-600',
|
||||
purple: 'bg-purple-100 text-purple-600',
|
||||
orange: 'bg-orange-100 text-orange-600',
|
||||
indigo: 'bg-indigo-100 text-indigo-600',
|
||||
red: 'bg-red-100 text-red-600'
|
||||
};
|
||||
return colorMap[color] || 'bg-gray-100 text-gray-600';
|
||||
};
|
||||
|
||||
const getStatusBadgeVariant = (status) => {
|
||||
return status === 'active' ? 'success' : 'secondary';
|
||||
};
|
||||
|
||||
const togglePluginStatus = async (plugin) => {
|
||||
try {
|
||||
plugin.status = plugin.status === 'active' ? 'inactive' : 'active';
|
||||
|
||||
nuxtApp.$swal.fire({
|
||||
title: "Success",
|
||||
text: `Plugin ${plugin.status === 'active' ? 'activated' : 'deactivated'} successfully`,
|
||||
icon: "success",
|
||||
timer: 2000,
|
||||
showConfirmButton: false,
|
||||
});
|
||||
} catch (error) {
|
||||
nuxtApp.$swal.fire({
|
||||
title: "Error",
|
||||
text: "Failed to toggle plugin status",
|
||||
icon: "error",
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const openConfigModal = (plugin) => {
|
||||
selectedPlugin.value = plugin;
|
||||
showConfigModal.value = true;
|
||||
};
|
||||
|
||||
const openUninstallModal = (plugin) => {
|
||||
selectedPlugin.value = plugin;
|
||||
showUninstallModal.value = true;
|
||||
};
|
||||
|
||||
const confirmUninstall = async () => {
|
||||
try {
|
||||
const plugin = selectedPlugin.value;
|
||||
installedPlugins.value = installedPlugins.value.filter(p => p.id !== plugin.id);
|
||||
|
||||
showUninstallModal.value = false;
|
||||
selectedPlugin.value = null;
|
||||
|
||||
nuxtApp.$swal.fire({
|
||||
title: "Success",
|
||||
text: "Plugin uninstalled successfully",
|
||||
icon: "success",
|
||||
timer: 2000,
|
||||
showConfirmButton: false,
|
||||
});
|
||||
} catch (error) {
|
||||
nuxtApp.$swal.fire({
|
||||
title: "Error",
|
||||
text: "Failed to uninstall plugin",
|
||||
icon: "error",
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const updatePluginSettings = async () => {
|
||||
try {
|
||||
showConfigModal.value = false;
|
||||
nuxtApp.$swal.fire({
|
||||
title: "Success",
|
||||
text: "Plugin settings updated successfully",
|
||||
icon: "success",
|
||||
timer: 2000,
|
||||
showConfirmButton: false,
|
||||
});
|
||||
} catch (error) {
|
||||
nuxtApp.$swal.fire({
|
||||
title: "Error",
|
||||
text: "Failed to update plugin settings",
|
||||
icon: "error",
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const formatDate = (dateString) => {
|
||||
return new Date(dateString).toLocaleDateString();
|
||||
};
|
||||
|
||||
const viewPluginDetails = (plugin) => {
|
||||
// Navigate to plugin details or open a detailed modal
|
||||
navigateTo(`/devtool/plugin-manager/installed/${plugin.name}`);
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<LayoutsBreadcrumb />
|
||||
|
||||
<!-- Header -->
|
||||
<rs-card class="mb-6">
|
||||
<template #body>
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<h1 class="text-2xl font-bold text-gray-900 mb-2">My Plugins</h1>
|
||||
<p class="text-gray-600">
|
||||
Manage your installed plugins, configure settings, and control access
|
||||
</p>
|
||||
</div>
|
||||
<div class="hidden md:block">
|
||||
<Icon name="mdi:puzzle" size="64" class="text-primary/20" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</rs-card>
|
||||
|
||||
<!-- Stats Overview -->
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-4 mb-6">
|
||||
<rs-card class="text-center">
|
||||
<template #body>
|
||||
<div class="p-4">
|
||||
<div class="flex items-center justify-center mb-2">
|
||||
<div class="bg-blue-100 text-blue-600 p-3 rounded-full">
|
||||
<Icon name="mdi:puzzle" size="24" />
|
||||
</div>
|
||||
</div>
|
||||
<h3 class="text-2xl font-bold text-gray-900">{{ pluginStats.total }}</h3>
|
||||
<p class="text-sm text-gray-600">Total Installed</p>
|
||||
</div>
|
||||
</template>
|
||||
</rs-card>
|
||||
|
||||
<rs-card class="text-center">
|
||||
<template #body>
|
||||
<div class="p-4">
|
||||
<div class="flex items-center justify-center mb-2">
|
||||
<div class="bg-green-100 text-green-600 p-3 rounded-full">
|
||||
<Icon name="mdi:check-circle" size="24" />
|
||||
</div>
|
||||
</div>
|
||||
<h3 class="text-2xl font-bold text-gray-900">{{ pluginStats.active }}</h3>
|
||||
<p class="text-sm text-gray-600">Active</p>
|
||||
</div>
|
||||
</template>
|
||||
</rs-card>
|
||||
|
||||
<rs-card class="text-center">
|
||||
<template #body>
|
||||
<div class="p-4">
|
||||
<div class="flex items-center justify-center mb-2">
|
||||
<div class="bg-gray-100 text-gray-600 p-3 rounded-full">
|
||||
<Icon name="mdi:pause-circle" size="24" />
|
||||
</div>
|
||||
</div>
|
||||
<h3 class="text-2xl font-bold text-gray-900">{{ pluginStats.inactive }}</h3>
|
||||
<p class="text-sm text-gray-600">Inactive</p>
|
||||
</div>
|
||||
</template>
|
||||
</rs-card>
|
||||
</div>
|
||||
|
||||
<!-- Filters and Search -->
|
||||
<rs-card class="mb-6">
|
||||
<template #body>
|
||||
<div class="flex flex-col md:flex-row gap-4">
|
||||
<div class="flex-1">
|
||||
<FormKit
|
||||
type="search"
|
||||
placeholder="Search plugins..."
|
||||
v-model="searchQuery"
|
||||
outer-class="mb-0"
|
||||
>
|
||||
<template #prefix>
|
||||
<Icon name="mdi:magnify" class="text-gray-400" />
|
||||
</template>
|
||||
</FormKit>
|
||||
</div>
|
||||
<div class="w-full md:w-40">
|
||||
<FormKit
|
||||
type="select"
|
||||
v-model="statusFilter"
|
||||
:options="[
|
||||
{ label: 'All Status', value: 'all' },
|
||||
{ label: 'Active', value: 'active' },
|
||||
{ label: 'Inactive', value: 'inactive' }
|
||||
]"
|
||||
outer-class="mb-0"
|
||||
/>
|
||||
</div>
|
||||
<div class="w-full md:w-48">
|
||||
<FormKit
|
||||
type="select"
|
||||
v-model="sortBy"
|
||||
:options="[
|
||||
{ label: 'Recently Installed', value: 'recent' },
|
||||
{ label: 'Name A-Z', value: 'name' },
|
||||
{ label: 'Status', value: 'status' },
|
||||
{ label: 'Last Used', value: 'lastUsed' }
|
||||
]"
|
||||
outer-class="mb-0"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</rs-card>
|
||||
|
||||
<!-- Results Info -->
|
||||
<div class="flex items-center justify-between mb-6">
|
||||
<p class="text-gray-600">
|
||||
{{ filteredPlugins.length }} plugin(s) found
|
||||
</p>
|
||||
<NuxtLink
|
||||
to="/devtool/plugin-manager/store"
|
||||
class="text-primary hover:text-primary/80 text-sm font-medium"
|
||||
>
|
||||
Browse Store →
|
||||
</NuxtLink>
|
||||
</div>
|
||||
|
||||
<!-- Plugin Grid -->
|
||||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-6">
|
||||
<rs-card
|
||||
v-for="plugin in filteredPlugins"
|
||||
:key="plugin.id"
|
||||
class="hover:shadow-lg transition-shadow"
|
||||
>
|
||||
<template #body>
|
||||
<div class="space-y-4">
|
||||
<!-- Plugin Header -->
|
||||
<div class="flex items-start justify-between">
|
||||
<div class="flex items-center space-x-4">
|
||||
<div
|
||||
:class="getColorClasses(plugin.color)"
|
||||
class="w-14 h-14 rounded-xl flex items-center justify-center"
|
||||
>
|
||||
<Icon :name="plugin.icon" size="28" />
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="font-semibold text-lg text-gray-900">
|
||||
{{ plugin.displayName }}
|
||||
</h3>
|
||||
<p class="text-sm text-gray-500">v{{ plugin.version }} • {{ plugin.author }}</p>
|
||||
<p class="text-xs text-gray-400 mt-1">{{ plugin.size }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<rs-badge :variant="getStatusBadgeVariant(plugin.status)" size="sm">
|
||||
{{ plugin.status }}
|
||||
</rs-badge>
|
||||
</div>
|
||||
|
||||
<!-- Plugin Description -->
|
||||
<p class="text-gray-600 text-sm">
|
||||
{{ plugin.description }}
|
||||
</p>
|
||||
|
||||
<!-- Plugin Info -->
|
||||
<div class="grid grid-cols-2 gap-4 text-sm">
|
||||
<div>
|
||||
<span class="text-gray-500">Installed:</span>
|
||||
<div class="font-medium">{{ formatDate(plugin.installDate) }}</div>
|
||||
</div>
|
||||
<div>
|
||||
<span class="text-gray-500">Last Used:</span>
|
||||
<div class="font-medium">{{ formatDate(plugin.lastUsed) }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Menu Items -->
|
||||
<div v-if="plugin.menuItems.length > 0">
|
||||
<span class="text-sm text-gray-500">Menu Items:</span>
|
||||
<div class="flex flex-wrap gap-2 mt-1">
|
||||
<NuxtLink
|
||||
v-for="item in plugin.menuItems"
|
||||
:key="item.path"
|
||||
:to="item.path"
|
||||
class="px-2 py-1 bg-gray-100 text-gray-700 text-xs rounded-full hover:bg-gray-200 transition-colors"
|
||||
>
|
||||
{{ item.title }}
|
||||
</NuxtLink>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Actions -->
|
||||
<div class="flex gap-2 pt-2 border-t">
|
||||
<rs-button
|
||||
size="sm"
|
||||
:variant="plugin.status === 'active' ? 'outline' : 'primary'"
|
||||
@click="togglePluginStatus(plugin)"
|
||||
>
|
||||
{{ plugin.status === 'active' ? 'Deactivate' : 'Activate' }}
|
||||
</rs-button>
|
||||
|
||||
<rs-button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
@click="openConfigModal(plugin)"
|
||||
>
|
||||
<Icon name="mdi:cog" class="mr-1" size="14" />
|
||||
Settings
|
||||
</rs-button>
|
||||
|
||||
<rs-button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
@click="viewPluginDetails(plugin)"
|
||||
>
|
||||
<Icon name="mdi:information-outline" class="mr-1" size="14" />
|
||||
Details
|
||||
</rs-button>
|
||||
|
||||
<rs-button
|
||||
size="sm"
|
||||
variant="danger"
|
||||
@click="openUninstallModal(plugin)"
|
||||
>
|
||||
<Icon name="mdi:delete-outline" size="14" />
|
||||
</rs-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</rs-card>
|
||||
</div>
|
||||
|
||||
<!-- Empty State -->
|
||||
<div v-if="filteredPlugins.length === 0" class="text-center py-12">
|
||||
<rs-card>
|
||||
<template #body>
|
||||
<div class="py-8">
|
||||
<Icon name="mdi:puzzle-outline" size="64" class="mx-auto text-gray-400 mb-4" />
|
||||
<h3 class="text-lg font-medium text-gray-900 mb-2">No plugins found</h3>
|
||||
<p class="text-gray-500 mb-4">
|
||||
Try adjusting your search terms or install plugins from the store
|
||||
</p>
|
||||
<rs-button variant="outline" @click="searchQuery = ''; statusFilter = 'all'">
|
||||
Clear Filters
|
||||
</rs-button>
|
||||
</div>
|
||||
</template>
|
||||
</rs-card>
|
||||
</div>
|
||||
|
||||
<!-- Uninstall Confirmation Modal -->
|
||||
<rs-modal
|
||||
title="Uninstall Plugin"
|
||||
v-model="showUninstallModal"
|
||||
ok-title="Uninstall"
|
||||
ok-variant="danger"
|
||||
:ok-callback="confirmUninstall"
|
||||
>
|
||||
<div v-if="selectedPlugin">
|
||||
<p class="mb-4">
|
||||
Are you sure you want to uninstall <strong>{{ selectedPlugin.displayName }}</strong>?
|
||||
</p>
|
||||
<div class="bg-red-50 border border-red-200 p-4 rounded-lg">
|
||||
<p class="text-red-800 text-sm">
|
||||
<Icon name="mdi:alert" class="mr-2" size="16" />
|
||||
This action cannot be undone. All plugin data and configurations will be permanently removed.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</rs-modal>
|
||||
|
||||
<!-- Plugin Configuration Modal -->
|
||||
<rs-modal
|
||||
title="Plugin Settings"
|
||||
v-model="showConfigModal"
|
||||
ok-title="Save Settings"
|
||||
:ok-callback="updatePluginSettings"
|
||||
size="lg"
|
||||
>
|
||||
<div v-if="selectedPlugin">
|
||||
<div class="space-y-6">
|
||||
<!-- Plugin Info -->
|
||||
<div class="flex items-center space-x-4 pb-4 border-b">
|
||||
<div
|
||||
:class="getColorClasses(selectedPlugin.color)"
|
||||
class="w-12 h-12 rounded-lg flex items-center justify-center"
|
||||
>
|
||||
<Icon :name="selectedPlugin.icon" size="24" />
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="font-semibold text-lg">{{ selectedPlugin.displayName }}</h3>
|
||||
<p class="text-sm text-gray-500">v{{ selectedPlugin.version }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Settings Form -->
|
||||
<FormKit type="form" :actions="false">
|
||||
<div v-if="selectedPlugin.name === 'notification-system'">
|
||||
<FormKit
|
||||
type="checkbox"
|
||||
label="Enable Email Notifications"
|
||||
v-model="selectedPlugin.settings.emailEnabled"
|
||||
/>
|
||||
<FormKit
|
||||
type="checkbox"
|
||||
label="Enable SMS Notifications"
|
||||
v-model="selectedPlugin.settings.smsEnabled"
|
||||
/>
|
||||
<FormKit
|
||||
type="checkbox"
|
||||
label="Enable Push Notifications"
|
||||
v-model="selectedPlugin.settings.pushEnabled"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div v-else-if="selectedPlugin.name === 'backup-system'">
|
||||
<FormKit
|
||||
type="checkbox"
|
||||
label="Enable Automatic Backup"
|
||||
v-model="selectedPlugin.settings.autoBackup"
|
||||
/>
|
||||
<FormKit
|
||||
type="select"
|
||||
label="Backup Frequency"
|
||||
v-model="selectedPlugin.settings.frequency"
|
||||
:options="[
|
||||
{ label: 'Daily', value: 'daily' },
|
||||
{ label: 'Weekly', value: 'weekly' },
|
||||
{ label: 'Monthly', value: 'monthly' }
|
||||
]"
|
||||
/>
|
||||
<FormKit
|
||||
type="select"
|
||||
label="Cloud Storage"
|
||||
v-model="selectedPlugin.settings.cloudStorage"
|
||||
:options="[
|
||||
{ label: 'Enabled', value: 'enabled' },
|
||||
{ label: 'Disabled', value: 'disabled' }
|
||||
]"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div v-else>
|
||||
<p class="text-gray-500 text-center py-4">
|
||||
No configurable settings available for this plugin.
|
||||
</p>
|
||||
</div>
|
||||
</FormKit>
|
||||
</div>
|
||||
</div>
|
||||
</rs-modal>
|
||||
</div>
|
||||
</template>
|
879
pages/devtool/plugin-manager/store/[slug].vue
Normal file
879
pages/devtool/plugin-manager/store/[slug].vue
Normal file
@ -0,0 +1,879 @@
|
||||
<script setup>
|
||||
definePageMeta({
|
||||
title: "Plugin Details",
|
||||
middleware: ["auth"],
|
||||
requiresAuth: true,
|
||||
});
|
||||
|
||||
const route = useRoute();
|
||||
const nuxtApp = useNuxtApp();
|
||||
const pluginSlug = route.params.slug;
|
||||
|
||||
// Plugin installation state
|
||||
const isInstalling = ref(false);
|
||||
const installProgress = ref(0);
|
||||
const installationStep = ref('');
|
||||
const showInstallModal = ref(false);
|
||||
const installationLog = ref([]);
|
||||
|
||||
// Mock plugin data - in real app, this would be fetched based on slug
|
||||
const pluginData = ref({
|
||||
id: 1,
|
||||
name: "notification-management",
|
||||
displayName: "Notification Management System",
|
||||
version: "1.2.0",
|
||||
description: "Complete notification system with templates, scheduling, and analytics for enterprise-grade communication management.",
|
||||
author: "CORRAD Team",
|
||||
authorAvatar: "/img/avatar/1.svg",
|
||||
category: "communication",
|
||||
size: "2.8 MB",
|
||||
rating: 4.8,
|
||||
downloads: 1890,
|
||||
price: "Free",
|
||||
icon: "mdi:bell-ring",
|
||||
color: "orange",
|
||||
lastUpdated: "2024-01-18",
|
||||
license: "MIT",
|
||||
homepage: "https://corrad.dev/plugins/notification-management",
|
||||
repository: "https://github.com/corrad/notification-management",
|
||||
tags: ["Notifications", "Email", "SMS", "Templates", "Scheduling"],
|
||||
isInstalled: false,
|
||||
screenshots: [
|
||||
"/img/template/form1.jpg",
|
||||
"/img/template/form1.jpg",
|
||||
"/img/template/form1.jpg"
|
||||
],
|
||||
|
||||
// Technical details
|
||||
menuStructure: [
|
||||
{ title: "Dashboard", route: "/notifications/dashboard", icon: "view-dashboard", permission: "notifications:read" },
|
||||
{ title: "Send Notification", route: "/notifications/send", icon: "send", permission: "notifications:write" },
|
||||
{ title: "Templates", route: "/notifications/templates", icon: "file-document-multiple", permission: "notifications:write" },
|
||||
{ title: "History", route: "/notifications/history", icon: "history", permission: "notifications:read" },
|
||||
{ title: "Settings", route: "/notifications/settings", icon: "cog", permission: "notifications:admin" },
|
||||
{ title: "Reports", route: "/notifications/reports", icon: "chart-line", permission: "notifications:admin" }
|
||||
],
|
||||
|
||||
apiEndpoints: [
|
||||
{ path: "/api/notifications", methods: ["GET", "POST", "PUT", "DELETE"] },
|
||||
{ path: "/api/notifications/templates", methods: ["GET", "POST", "PUT", "DELETE"] },
|
||||
{ path: "/api/notifications/send", methods: ["POST"] },
|
||||
{ path: "/api/notifications/schedule", methods: ["GET", "POST", "PUT", "DELETE"] }
|
||||
],
|
||||
|
||||
folderStructure: [
|
||||
{ type: "folder", name: "pages/notifications/", description: "All notification-related pages" },
|
||||
{ type: "folder", name: "components/notification/", description: "Notification Vue components" },
|
||||
{ type: "folder", name: "server/api/notifications/", description: "API endpoints for notifications" },
|
||||
{ type: "folder", name: "composables/", description: "Notification composables and utilities" },
|
||||
{ type: "file", name: "migrations/001_create_notifications.sql", description: "Creates notifications table" },
|
||||
{ type: "file", name: "migrations/002_create_templates.sql", description: "Creates notification templates table" },
|
||||
{ type: "file", name: "migrations/003_create_schedules.sql", description: "Creates notification schedules table" }
|
||||
],
|
||||
|
||||
authentikIntegration: {
|
||||
applicationSlug: "notification-management",
|
||||
requiredScopes: ["notifications:read", "notifications:write", "notifications:admin"],
|
||||
groups: ["notification-users", "notification-admins"],
|
||||
permissions: [
|
||||
{ scope: "notifications:read", description: "View notifications and templates" },
|
||||
{ scope: "notifications:write", description: "Create and send notifications" },
|
||||
{ scope: "notifications:admin", description: "Manage settings and view reports" }
|
||||
]
|
||||
},
|
||||
|
||||
dependencies: {
|
||||
corrad: "^2.0.0",
|
||||
authentik: "^2023.10.0",
|
||||
nodemailer: "^6.9.0",
|
||||
twilio: "^4.19.0"
|
||||
},
|
||||
|
||||
features: [
|
||||
"Multi-channel notifications (Email, SMS, Push)",
|
||||
"Template management with variables",
|
||||
"Scheduled and recurring notifications",
|
||||
"Delivery tracking and analytics",
|
||||
"Authentik integration for permissions",
|
||||
"REST API with full CRUD operations",
|
||||
"Real-time dashboard with statistics",
|
||||
"Export reports in multiple formats"
|
||||
],
|
||||
|
||||
// Mock markdown content
|
||||
markdownContent: `# Notification Management System
|
||||
|
||||
## Overview
|
||||
The Notification Management System is a comprehensive solution for managing all types of notifications in your CORRAD+ application. It provides a unified interface for sending emails, SMS, and push notifications with advanced templating and scheduling capabilities.
|
||||
|
||||
## Key Features
|
||||
|
||||
### 🔔 Multi-Channel Support
|
||||
- **Email Notifications**: Rich HTML templates with attachment support
|
||||
- **SMS Notifications**: Integrated with Twilio for reliable delivery
|
||||
- **Push Notifications**: Web and mobile push notification support
|
||||
|
||||
### 📝 Template Management
|
||||
- Visual template editor with drag-and-drop interface
|
||||
- Variable substitution and dynamic content
|
||||
- Template versioning and approval workflow
|
||||
- Multi-language template support
|
||||
|
||||
### ⏰ Advanced Scheduling
|
||||
- One-time and recurring notifications
|
||||
- Time-zone aware scheduling
|
||||
- Bulk notification processing
|
||||
- Queue management with priority levels
|
||||
|
||||
### 📊 Analytics & Reporting
|
||||
- Real-time delivery tracking
|
||||
- Open and click-through rates
|
||||
- Bounce and unsubscribe management
|
||||
- Comprehensive analytics dashboard
|
||||
|
||||
## Technical Specifications
|
||||
|
||||
### Database Schema
|
||||
The plugin creates three main tables:
|
||||
- \`notifications\`: Stores all notification records
|
||||
- \`notification_templates\`: Template definitions and content
|
||||
- \`notification_schedules\`: Scheduling information
|
||||
|
||||
### API Endpoints
|
||||
All endpoints are secured with Authentik permissions:
|
||||
- \`GET /api/notifications\`: List notifications
|
||||
- \`POST /api/notifications\`: Send new notification
|
||||
- \`PUT /api/notifications/{id}\`: Update notification
|
||||
- \`DELETE /api/notifications/{id}\`: Delete notification
|
||||
|
||||
### Menu Integration
|
||||
The plugin adds a "Notifications" menu with the following items:
|
||||
- Dashboard (notifications:read)
|
||||
- Send Notification (notifications:write)
|
||||
- Templates (notifications:write)
|
||||
- History (notifications:read)
|
||||
- Settings (notifications:admin)
|
||||
- Reports (notifications:admin)
|
||||
|
||||
## Installation Requirements
|
||||
|
||||
### System Requirements
|
||||
- CORRAD Framework v2.0.0 or higher
|
||||
- Authentik v2023.10.0 or higher
|
||||
- Node.js v18+ for email processing
|
||||
- Database: PostgreSQL 12+ recommended
|
||||
|
||||
### External Services
|
||||
- **Email**: SMTP server or service (SendGrid, Amazon SES, etc.)
|
||||
- **SMS**: Twilio account for SMS notifications
|
||||
- **Push**: Firebase Cloud Messaging for push notifications
|
||||
|
||||
## Configuration
|
||||
|
||||
After installation, configure the following in your environment:
|
||||
|
||||
\`\`\`env
|
||||
# Email Configuration
|
||||
SMTP_HOST=your-smtp-host
|
||||
SMTP_PORT=587
|
||||
SMTP_USER=your-username
|
||||
SMTP_PASS=your-password
|
||||
|
||||
# SMS Configuration (Twilio)
|
||||
TWILIO_ACCOUNT_SID=your-account-sid
|
||||
TWILIO_AUTH_TOKEN=your-auth-token
|
||||
TWILIO_PHONE_NUMBER=your-twilio-number
|
||||
|
||||
# Push Notifications
|
||||
FCM_SERVER_KEY=your-fcm-server-key
|
||||
\`\`\`
|
||||
|
||||
## Post-Installation Steps
|
||||
|
||||
1. **Configure Authentik Groups**: Create notification-users and notification-admins groups
|
||||
2. **Set Up SMTP**: Configure email settings in the admin panel
|
||||
3. **SMS Setup**: Add Twilio credentials for SMS functionality
|
||||
4. **Template Import**: Import default notification templates
|
||||
5. **User Training**: Provide access to users based on their roles
|
||||
|
||||
## Support & Documentation
|
||||
|
||||
For detailed documentation and support:
|
||||
- Plugin Documentation: [Link to docs]
|
||||
- API Reference: [Link to API docs]
|
||||
- Community Forum: [Link to forum]
|
||||
- Bug Reports: [Link to issues]
|
||||
`
|
||||
});
|
||||
|
||||
// Mock related plugins
|
||||
const relatedPlugins = ref([
|
||||
{
|
||||
id: 2,
|
||||
name: "report-management",
|
||||
displayName: "Report Management System",
|
||||
icon: "mdi:chart-line",
|
||||
color: "blue",
|
||||
rating: 4.8,
|
||||
price: "Free"
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: "rbac",
|
||||
displayName: "RBAC System",
|
||||
icon: "mdi:shield-account",
|
||||
color: "green",
|
||||
rating: 4.9,
|
||||
price: "Free"
|
||||
}
|
||||
]);
|
||||
|
||||
// Remove activeTab since RsTab handles this internally
|
||||
|
||||
// Simple markdown to HTML converter
|
||||
const markdownToHtml = (markdown) => {
|
||||
let html = markdown
|
||||
// Headers
|
||||
.replace(/^### (.*$)/gim, '<h3 class="text-lg font-semibold mt-3 mb-2">$1</h3>')
|
||||
.replace(/^## (.*$)/gim, '<h2 class="text-xl font-semibold mt-4 mb-2">$1</h2>')
|
||||
.replace(/^# (.*$)/gim, '<h1 class="text-2xl font-bold mt-4 mb-3">$1</h1>')
|
||||
// Bold text
|
||||
.replace(/\*\*(.*?)\*\*/gim, '<strong>$1</strong>')
|
||||
// Italic text
|
||||
.replace(/\*(.*?)\*/gim, '<em>$1</em>')
|
||||
// Code blocks
|
||||
.replace(/```([^`]+?)```/gims, '<pre class="bg-gray-100 p-3 rounded-lg mt-2 mb-2 overflow-x-auto"><code>$1</code></pre>')
|
||||
// Inline code
|
||||
.replace(/`([^`]+?)`/gim, '<code class="bg-gray-100 px-1.5 py-0.5 rounded text-sm">$1</code>')
|
||||
// Links
|
||||
.replace(/\[([^\]]+)\]\(([^\)]+)\)/gim, '<a href="$2" class="text-blue-600 hover:underline">$1</a>')
|
||||
// Line breaks
|
||||
.replace(/\n/gim, '<br>');
|
||||
|
||||
// Handle bullet points as lists
|
||||
const lines = html.split('<br>');
|
||||
let inList = false;
|
||||
let result = [];
|
||||
|
||||
for (let line of lines) {
|
||||
if (line.match(/^- /)) {
|
||||
if (!inList) {
|
||||
result.push('<ul class="list-disc pl-4 space-y-0.5 my-2">');
|
||||
inList = true;
|
||||
}
|
||||
result.push('<li>' + line.replace(/^- /, '') + '</li>');
|
||||
} else {
|
||||
if (inList) {
|
||||
result.push('</ul>');
|
||||
inList = false;
|
||||
}
|
||||
if (line.trim()) {
|
||||
result.push(line);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (inList) {
|
||||
result.push('</ul>');
|
||||
}
|
||||
|
||||
return result.join('<br>').replace(/<br><br>/gim, '<br>');
|
||||
};
|
||||
|
||||
const getColorClasses = (color) => {
|
||||
const colorMap = {
|
||||
blue: 'bg-blue-100 text-blue-600',
|
||||
green: 'bg-green-100 text-green-600',
|
||||
purple: 'bg-purple-100 text-purple-600',
|
||||
orange: 'bg-orange-100 text-orange-600',
|
||||
indigo: 'bg-indigo-100 text-indigo-600',
|
||||
red: 'bg-red-100 text-red-600'
|
||||
};
|
||||
return colorMap[color] || 'bg-gray-100 text-gray-600';
|
||||
};
|
||||
|
||||
const handleInstall = () => {
|
||||
showInstallModal.value = true;
|
||||
};
|
||||
|
||||
const confirmInstall = async () => {
|
||||
showInstallModal.value = false;
|
||||
isInstalling.value = true;
|
||||
installationLog.value = [];
|
||||
installProgress.value = 0;
|
||||
|
||||
const steps = [
|
||||
{ progress: 10, step: 'Validating plugin package...', message: 'Checking plugin structure and dependencies' },
|
||||
{ progress: 25, step: 'Setting up Authentik integration...', message: 'Creating application and configuring scopes' },
|
||||
{ progress: 40, step: 'Running database migrations...', message: 'Creating notification tables and indexes' },
|
||||
{ progress: 60, step: 'Installing menu structure...', message: 'Registering navigation menu items' },
|
||||
{ progress: 75, step: 'Configuring API routes...', message: 'Setting up API endpoints and permissions' },
|
||||
{ progress: 90, step: 'Running post-install hooks...', message: 'Executing plugin initialization scripts' },
|
||||
{ progress: 100, step: 'Installation complete!', message: 'Plugin installed successfully and ready to use' }
|
||||
];
|
||||
|
||||
for (const step of steps) {
|
||||
await new Promise(resolve => setTimeout(resolve, 1500));
|
||||
installProgress.value = step.progress;
|
||||
installationStep.value = step.step;
|
||||
installationLog.value.push({
|
||||
message: step.message,
|
||||
timestamp: new Date().toLocaleTimeString(),
|
||||
type: step.progress === 100 ? 'success' : 'info'
|
||||
});
|
||||
}
|
||||
|
||||
isInstalling.value = false;
|
||||
pluginData.value.isInstalled = true;
|
||||
|
||||
nuxtApp.$swal.fire({
|
||||
title: "Success",
|
||||
text: "Plugin installed successfully! You can now access it from the navigation menu.",
|
||||
icon: "success",
|
||||
timer: 3000,
|
||||
showConfirmButton: false,
|
||||
});
|
||||
};
|
||||
|
||||
const goBack = () => {
|
||||
navigateTo('/devtool/plugin-manager/store');
|
||||
};
|
||||
|
||||
// In real app, you would fetch plugin data based on slug
|
||||
onMounted(async () => {
|
||||
// Simulate API call
|
||||
// const plugin = await $fetch(`/api/plugins/${pluginSlug}`);
|
||||
// pluginData.value = plugin;
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<LayoutsBreadcrumb />
|
||||
|
||||
<!-- Header with plugin info -->
|
||||
<rs-card class="mb-6">
|
||||
<template #body>
|
||||
<div class="flex items-start space-x-6">
|
||||
<!-- Plugin Icon -->
|
||||
<div
|
||||
:class="getColorClasses(pluginData.color)"
|
||||
class="w-20 h-20 rounded-xl flex items-center justify-center flex-shrink-0"
|
||||
>
|
||||
<Icon :name="pluginData.icon" size="40" />
|
||||
</div>
|
||||
|
||||
<!-- Plugin Details -->
|
||||
<div class="flex-1 min-w-0">
|
||||
<div class="flex items-start justify-between">
|
||||
<div>
|
||||
<h1 class="text-3xl font-bold text-gray-900 mb-2">{{ pluginData.displayName }}</h1>
|
||||
<p class="text-gray-600 mb-3">{{ pluginData.description }}</p>
|
||||
|
||||
<div class="flex items-center space-x-6 text-sm">
|
||||
<div class="flex items-center space-x-1">
|
||||
<Icon name="mdi:account" size="16" class="text-gray-400" />
|
||||
<span>{{ pluginData.author }}</span>
|
||||
</div>
|
||||
<div class="flex items-center space-x-1">
|
||||
<Icon name="mdi:star" size="16" class="text-yellow-500" />
|
||||
<span>{{ pluginData.rating }}</span>
|
||||
</div>
|
||||
<div class="flex items-center space-x-1">
|
||||
<Icon name="mdi:download" size="16" class="text-gray-400" />
|
||||
<span>{{ pluginData.downloads.toLocaleString() }} downloads</span>
|
||||
</div>
|
||||
<div class="flex items-center space-x-1">
|
||||
<Icon name="mdi:update" size="16" class="text-gray-400" />
|
||||
<span>Updated {{ pluginData.lastUpdated }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Tags -->
|
||||
<div class="flex flex-wrap gap-2 mt-3">
|
||||
<rs-badge
|
||||
v-for="tag in pluginData.tags"
|
||||
:key="tag"
|
||||
variant="secondary"
|
||||
size="sm"
|
||||
>
|
||||
{{ tag }}
|
||||
</rs-badge>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Action buttons -->
|
||||
<div class="flex flex-col space-y-3">
|
||||
<rs-button
|
||||
v-if="!pluginData.isInstalled"
|
||||
size="lg"
|
||||
@click="handleInstall"
|
||||
:disabled="isInstalling"
|
||||
class="min-w-[120px]"
|
||||
>
|
||||
<Icon v-if="!isInstalling" name="mdi:download" class="mr-2" />
|
||||
<Icon v-else name="mdi:loading" class="mr-2 animate-spin" />
|
||||
{{ isInstalling ? 'Installing...' : 'Install' }}
|
||||
</rs-button>
|
||||
<rs-badge v-else variant="success" size="lg" class="px-4 py-2">
|
||||
<Icon name="mdi:check" class="mr-2" />
|
||||
Installed
|
||||
</rs-badge>
|
||||
|
||||
<div class="text-center">
|
||||
<span class="text-2xl font-bold text-primary">{{ pluginData.price }}</span>
|
||||
<p class="text-xs text-gray-500">{{ pluginData.size }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</rs-card>
|
||||
|
||||
<!-- Installation Progress -->
|
||||
<rs-card v-if="isInstalling" class="mb-6">
|
||||
<template #header>
|
||||
<div class="flex items-center">
|
||||
<Icon name="mdi:progress-upload" class="mr-2" />
|
||||
Installation Progress
|
||||
</div>
|
||||
</template>
|
||||
<template #body>
|
||||
<div class="space-y-4">
|
||||
<!-- Progress Bar -->
|
||||
<div>
|
||||
<div class="flex justify-between text-sm mb-2">
|
||||
<span>{{ installationStep }}</span>
|
||||
<span>{{ installProgress }}%</span>
|
||||
</div>
|
||||
<div class="w-full bg-gray-200 rounded-full h-3">
|
||||
<div
|
||||
class="bg-primary h-3 rounded-full transition-all duration-500"
|
||||
:style="{ width: installProgress + '%' }"
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Installation Log -->
|
||||
<div class="bg-gray-50 rounded-lg p-4 max-h-40 overflow-y-auto">
|
||||
<div
|
||||
v-for="(log, index) in installationLog"
|
||||
:key="index"
|
||||
class="flex items-start space-x-2 text-sm mb-2 last:mb-0"
|
||||
>
|
||||
<Icon
|
||||
:name="log.type === 'success' ? 'mdi:check-circle' : 'mdi:information'"
|
||||
:class="log.type === 'success' ? 'text-green-500' : 'text-blue-500'"
|
||||
size="16"
|
||||
class="mt-0.5"
|
||||
/>
|
||||
<div class="flex-1">
|
||||
<span class="text-gray-900">{{ log.message }}</span>
|
||||
<span class="text-gray-500 ml-2">{{ log.timestamp }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</rs-card>
|
||||
|
||||
<!-- Tabs with Content -->
|
||||
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
||||
<!-- Main Content -->
|
||||
<div class="lg:col-span-2">
|
||||
<rs-tab class="mb-6">
|
||||
<!-- Overview Tab -->
|
||||
<rs-tab-item title="Overview" :active="true">
|
||||
<rs-card class="mb-6">
|
||||
<template #header>
|
||||
<Icon name="mdi:information-outline" class="mr-2" />
|
||||
Plugin Overview
|
||||
</template>
|
||||
<template #body>
|
||||
<div class="space-y-4">
|
||||
<div>
|
||||
<h3 class="font-semibold text-lg mb-3">What's Included</h3>
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
<div class="text-center p-4 bg-blue-50 rounded-lg">
|
||||
<Icon name="mdi:menu" size="32" class="text-blue-600 mx-auto mb-2" />
|
||||
<p class="font-medium">{{ pluginData.menuStructure.length }} Menu Items</p>
|
||||
<p class="text-sm text-gray-600">Navigation pages</p>
|
||||
</div>
|
||||
<div class="text-center p-4 bg-green-50 rounded-lg">
|
||||
<Icon name="mdi:api" size="32" class="text-green-600 mx-auto mb-2" />
|
||||
<p class="font-medium">{{ pluginData.apiEndpoints.length }} API Endpoints</p>
|
||||
<p class="text-sm text-gray-600">REST API routes</p>
|
||||
</div>
|
||||
<div class="text-center p-4 bg-purple-50 rounded-lg">
|
||||
<Icon name="mdi:database" size="32" class="text-purple-600 mx-auto mb-2" />
|
||||
<p class="font-medium">3 Database Tables</p>
|
||||
<p class="text-sm text-gray-600">SQL migrations</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3 class="font-semibold text-lg mb-3">Screenshots</h3>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div
|
||||
v-for="(screenshot, index) in pluginData.screenshots"
|
||||
:key="index"
|
||||
class="relative rounded-lg overflow-hidden border"
|
||||
>
|
||||
<img
|
||||
:src="screenshot"
|
||||
:alt="`Screenshot ${index + 1}`"
|
||||
class="w-full h-48 object-cover"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</rs-card>
|
||||
</rs-tab-item>
|
||||
|
||||
<!-- Features Tab -->
|
||||
<rs-tab-item title="Features">
|
||||
<rs-card>
|
||||
<template #header>
|
||||
<Icon name="mdi:star" class="mr-2" />
|
||||
Key Features
|
||||
</template>
|
||||
<template #body>
|
||||
<div class="space-y-4">
|
||||
<div
|
||||
v-for="(feature, index) in pluginData.features"
|
||||
:key="index"
|
||||
class="flex items-start space-x-3 p-3 bg-gray-50 rounded-lg"
|
||||
>
|
||||
<Icon name="mdi:check-circle" class="text-green-500 mt-0.5" size="20" />
|
||||
<span class="text-gray-900">{{ feature }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</rs-card>
|
||||
</rs-tab-item>
|
||||
|
||||
<!-- Technical Details Tab -->
|
||||
<rs-tab-item title="Technical Details">
|
||||
<div class="space-y-6">
|
||||
<!-- Menu Structure -->
|
||||
<rs-card>
|
||||
<template #header>
|
||||
<Icon name="mdi:menu" class="mr-2" />
|
||||
Menu Structure
|
||||
</template>
|
||||
<template #body>
|
||||
<div class="space-y-3">
|
||||
<div
|
||||
v-for="(menu, index) in pluginData.menuStructure"
|
||||
:key="index"
|
||||
class="flex items-center justify-between p-3 border rounded-lg"
|
||||
>
|
||||
<div class="flex items-center space-x-3">
|
||||
<Icon :name="`mdi:${menu.icon}`" size="20" class="text-gray-600" />
|
||||
<div>
|
||||
<p class="font-medium">{{ menu.title }}</p>
|
||||
<p class="text-sm text-gray-600">{{ menu.route }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<rs-badge variant="secondary" size="sm">{{ menu.permission }}</rs-badge>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</rs-card>
|
||||
|
||||
<!-- API Endpoints -->
|
||||
<rs-card>
|
||||
<template #header>
|
||||
<Icon name="mdi:api" class="mr-2" />
|
||||
API Endpoints
|
||||
</template>
|
||||
<template #body>
|
||||
<div class="space-y-3">
|
||||
<div
|
||||
v-for="(endpoint, index) in pluginData.apiEndpoints"
|
||||
:key="index"
|
||||
class="p-3 border rounded-lg"
|
||||
>
|
||||
<div class="flex items-center justify-between mb-2">
|
||||
<code class="text-sm bg-gray-100 px-2 py-1 rounded">{{ endpoint.path }}</code>
|
||||
</div>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<rs-badge
|
||||
v-for="method in endpoint.methods"
|
||||
:key="method"
|
||||
:variant="method === 'GET' ? 'success' : method === 'POST' ? 'primary' : method === 'PUT' ? 'warning' : 'danger'"
|
||||
size="sm"
|
||||
>
|
||||
{{ method }}
|
||||
</rs-badge>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</rs-card>
|
||||
|
||||
<!-- Folder Structure -->
|
||||
<rs-card>
|
||||
<template #header>
|
||||
<Icon name="mdi:folder" class="mr-2" />
|
||||
Folder Structure Changes
|
||||
</template>
|
||||
<template #body>
|
||||
<div class="space-y-2">
|
||||
<div
|
||||
v-for="(item, index) in pluginData.folderStructure"
|
||||
:key="index"
|
||||
class="flex items-start space-x-3 p-2"
|
||||
>
|
||||
<Icon
|
||||
:name="item.type === 'folder' ? 'mdi:folder' : 'mdi:file'"
|
||||
:class="item.type === 'folder' ? 'text-blue-500' : 'text-gray-500'"
|
||||
size="16"
|
||||
class="mt-1"
|
||||
/>
|
||||
<div class="flex-1">
|
||||
<code class="text-sm">{{ item.name }}</code>
|
||||
<p class="text-xs text-gray-600 mt-1">{{ item.description }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</rs-card>
|
||||
</div>
|
||||
</rs-tab-item>
|
||||
|
||||
<!-- Documentation Tab -->
|
||||
<rs-tab-item title="Documentation">
|
||||
<rs-card>
|
||||
<template #header>
|
||||
<Icon name="mdi:book-open" class="mr-2" />
|
||||
Documentation
|
||||
</template>
|
||||
<template #body>
|
||||
<div class="prose max-w-none">
|
||||
<div class="markdown-content" v-html="markdownToHtml(pluginData.markdownContent)"></div>
|
||||
</div>
|
||||
</template>
|
||||
</rs-card>
|
||||
</rs-tab-item>
|
||||
</rs-tab>
|
||||
</div>
|
||||
|
||||
<!-- Sidebar -->
|
||||
<div class="space-y-6">
|
||||
<!-- Plugin Info -->
|
||||
<rs-card>
|
||||
<template #header>
|
||||
<Icon name="mdi:information" class="mr-2" />
|
||||
Plugin Information
|
||||
</template>
|
||||
<template #body>
|
||||
<div class="space-y-3 text-sm">
|
||||
<div class="flex justify-between">
|
||||
<span class="text-gray-600">Version:</span>
|
||||
<span class="font-medium">{{ pluginData.version }}</span>
|
||||
</div>
|
||||
<div class="flex justify-between">
|
||||
<span class="text-gray-600">Size:</span>
|
||||
<span class="font-medium">{{ pluginData.size }}</span>
|
||||
</div>
|
||||
<div class="flex justify-between">
|
||||
<span class="text-gray-600">License:</span>
|
||||
<span class="font-medium">{{ pluginData.license }}</span>
|
||||
</div>
|
||||
<div class="flex justify-between">
|
||||
<span class="text-gray-600">Category:</span>
|
||||
<span class="font-medium capitalize">{{ pluginData.category }}</span>
|
||||
</div>
|
||||
<div class="flex justify-between items-center">
|
||||
<span class="text-gray-600">Homepage:</span>
|
||||
<a :href="pluginData.homepage" target="_blank" class="text-primary hover:text-primary/80 text-xs">
|
||||
<Icon name="mdi:open-in-new" size="14" />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</rs-card>
|
||||
|
||||
<!-- Authentik Integration -->
|
||||
<rs-card>
|
||||
<template #header>
|
||||
<Icon name="mdi:shield-account" class="mr-2" />
|
||||
Authentik Integration
|
||||
</template>
|
||||
<template #body>
|
||||
<div class="space-y-3 text-sm">
|
||||
<div>
|
||||
<p class="text-gray-600 mb-2">Required Scopes:</p>
|
||||
<div class="space-y-1">
|
||||
<rs-badge
|
||||
v-for="scope in pluginData.authentikIntegration.requiredScopes"
|
||||
:key="scope"
|
||||
variant="secondary"
|
||||
size="sm"
|
||||
class="block w-fit"
|
||||
>
|
||||
{{ scope }}
|
||||
</rs-badge>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-gray-600 mb-2">User Groups:</p>
|
||||
<div class="space-y-1">
|
||||
<rs-badge
|
||||
v-for="group in pluginData.authentikIntegration.groups"
|
||||
:key="group"
|
||||
variant="primary"
|
||||
size="sm"
|
||||
class="block w-fit"
|
||||
>
|
||||
{{ group }}
|
||||
</rs-badge>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</rs-card>
|
||||
|
||||
<!-- Dependencies -->
|
||||
<rs-card>
|
||||
<template #header>
|
||||
<Icon name="mdi:package" class="mr-2" />
|
||||
Dependencies
|
||||
</template>
|
||||
<template #body>
|
||||
<div class="space-y-2 text-sm">
|
||||
<div
|
||||
v-for="(version, name) in pluginData.dependencies"
|
||||
:key="name"
|
||||
class="flex justify-between items-center"
|
||||
>
|
||||
<span class="text-gray-900">{{ name }}</span>
|
||||
<code class="text-xs bg-gray-100 px-2 py-1 rounded">{{ version }}</code>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</rs-card>
|
||||
|
||||
<!-- Related Plugins -->
|
||||
<rs-card>
|
||||
<template #header>
|
||||
<Icon name="mdi:puzzle" class="mr-2" />
|
||||
Related Plugins
|
||||
</template>
|
||||
<template #body>
|
||||
<div class="space-y-3">
|
||||
<div
|
||||
v-for="plugin in relatedPlugins"
|
||||
:key="plugin.id"
|
||||
class="flex items-center space-x-3 p-2 hover:bg-gray-50 rounded-lg cursor-pointer"
|
||||
@click="navigateTo(`/devtool/plugin-manager/store/${plugin.name}`)"
|
||||
>
|
||||
<div
|
||||
:class="getColorClasses(plugin.color)"
|
||||
class="w-10 h-10 rounded-lg flex items-center justify-center"
|
||||
>
|
||||
<Icon :name="plugin.icon" size="20" />
|
||||
</div>
|
||||
<div class="flex-1 min-w-0">
|
||||
<p class="font-medium text-sm truncate">{{ plugin.displayName }}</p>
|
||||
<div class="flex items-center space-x-2">
|
||||
<div class="flex items-center">
|
||||
<Icon name="mdi:star" size="12" class="text-yellow-500 mr-1" />
|
||||
<span class="text-xs">{{ plugin.rating }}</span>
|
||||
</div>
|
||||
<span class="text-xs text-gray-600">{{ plugin.price }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</rs-card>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Back Button -->
|
||||
<div class="mt-6">
|
||||
<rs-button variant="outline" @click="goBack">
|
||||
<Icon name="mdi:arrow-left" class="mr-2" />
|
||||
Back to Store
|
||||
</rs-button>
|
||||
</div>
|
||||
|
||||
<!-- Install Confirmation Modal -->
|
||||
<rs-modal v-model="showInstallModal" title="Confirm Installation">
|
||||
<template #body>
|
||||
<div class="space-y-4">
|
||||
<div class="flex items-start space-x-3">
|
||||
<Icon name="mdi:alert-circle" class="text-orange-500 mt-1" size="20" />
|
||||
<div>
|
||||
<h4 class="font-medium text-gray-900 mb-2">Install {{ pluginData.displayName }}?</h4>
|
||||
<p class="text-gray-600 text-sm mb-3">
|
||||
This will install the plugin and make the following changes to your system:
|
||||
</p>
|
||||
<ul class="text-sm text-gray-600 space-y-1">
|
||||
<li>• Create {{ pluginData.menuStructure.length }} new menu items</li>
|
||||
<li>• Add {{ pluginData.apiEndpoints.length }} API endpoints</li>
|
||||
<li>• Run 3 database migrations</li>
|
||||
<li>• Configure Authentik integration</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template #footer>
|
||||
<div class="flex justify-end space-x-3">
|
||||
<rs-button variant="outline" @click="showInstallModal = false">
|
||||
Cancel
|
||||
</rs-button>
|
||||
<rs-button @click="confirmInstall">
|
||||
Install Plugin
|
||||
</rs-button>
|
||||
</div>
|
||||
</template>
|
||||
</rs-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.markdown-content {
|
||||
line-height: 1.6;
|
||||
}
|
||||
.markdown-content h1 {
|
||||
font-size: 1.75rem;
|
||||
font-weight: bold;
|
||||
margin: 1.5rem 0 1rem 0;
|
||||
}
|
||||
.markdown-content h2 {
|
||||
font-size: 1.5rem;
|
||||
font-weight: bold;
|
||||
margin: 1.25rem 0 0.75rem 0;
|
||||
}
|
||||
.markdown-content h3 {
|
||||
font-size: 1.25rem;
|
||||
font-weight: bold;
|
||||
margin: 1rem 0 0.5rem 0;
|
||||
}
|
||||
.markdown-content p {
|
||||
margin: 0.75rem 0;
|
||||
}
|
||||
.markdown-content ul {
|
||||
margin: 0.5rem 0;
|
||||
padding-left: 1.5rem;
|
||||
list-style-type: disc;
|
||||
}
|
||||
.markdown-content li {
|
||||
margin: 0.25rem 0;
|
||||
}
|
||||
.markdown-content code {
|
||||
background-color: #f3f4f6;
|
||||
padding: 0.125rem 0.25rem;
|
||||
border-radius: 0.25rem;
|
||||
font-family: monospace;
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
</style>
|
485
pages/devtool/plugin-manager/store/index.vue
Normal file
485
pages/devtool/plugin-manager/store/index.vue
Normal file
@ -0,0 +1,485 @@
|
||||
<script setup>
|
||||
definePageMeta({
|
||||
title: "Plugin Store",
|
||||
middleware: ["auth"],
|
||||
requiresAuth: true,
|
||||
});
|
||||
|
||||
const nuxtApp = useNuxtApp();
|
||||
|
||||
// Categories
|
||||
const categories = ref([
|
||||
{ id: 'all', name: 'All', icon: 'mdi:apps', count: 7 },
|
||||
{ id: 'business', name: 'Business', icon: 'mdi:briefcase', count: 2 },
|
||||
{ id: 'communication', name: 'Communication', icon: 'mdi:message', count: 1 },
|
||||
{ id: 'productivity', name: 'Productivity', icon: 'mdi:chart-line', count: 2 },
|
||||
{ id: 'security', name: 'Security', icon: 'mdi:shield', count: 2 }
|
||||
]);
|
||||
|
||||
const selectedCategory = ref('all');
|
||||
const searchQuery = ref('');
|
||||
const sortBy = ref('popularity');
|
||||
|
||||
// Available plugins in store
|
||||
const storePlugins = ref([
|
||||
{
|
||||
id: 1,
|
||||
name: "rbac",
|
||||
displayName: "RBAC System",
|
||||
version: "1.0.0",
|
||||
description: "Role-Based Access Control system integrated with Authentik for user management and permissions",
|
||||
author: "CORRAD Team",
|
||||
category: "security",
|
||||
size: "3.2 MB",
|
||||
rating: 4.9,
|
||||
downloads: 2450,
|
||||
price: "Free",
|
||||
icon: "mdi:shield-account",
|
||||
color: "green",
|
||||
images: [],
|
||||
tags: ["RBAC", "Authentication", "Permissions", "Authentik"],
|
||||
features: [
|
||||
"User Management",
|
||||
"Role Assignment",
|
||||
"Permission Control",
|
||||
"Authentik Integration",
|
||||
"SSO Support"
|
||||
],
|
||||
isInstalled: true,
|
||||
lastUpdated: "2024-01-20"
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: "notification-management",
|
||||
displayName: "Notification Management System",
|
||||
version: "1.0.0",
|
||||
description: "Complete notification system with email, SMS, push notifications, and template management",
|
||||
author: "CORRAD Team",
|
||||
category: "communication",
|
||||
size: "2.8 MB",
|
||||
rating: 4.8,
|
||||
downloads: 1890,
|
||||
price: "Free",
|
||||
icon: "mdi:bell-ring",
|
||||
color: "orange",
|
||||
images: [],
|
||||
tags: ["Notifications", "Email", "SMS", "Templates"],
|
||||
features: [
|
||||
"Multi-channel Notifications",
|
||||
"Template Management",
|
||||
"Delivery Tracking",
|
||||
"Scheduling System",
|
||||
"Analytics Dashboard"
|
||||
],
|
||||
isInstalled: true,
|
||||
lastUpdated: "2024-01-18"
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: "business-process-maker",
|
||||
displayName: "Business Process Maker",
|
||||
version: "1.0.0",
|
||||
description: "Visual business process designer and workflow automation system like ProcessMaker",
|
||||
author: "CORRAD Team",
|
||||
category: "productivity",
|
||||
size: "4.5 MB",
|
||||
rating: 4.7,
|
||||
downloads: 1320,
|
||||
price: "Free",
|
||||
icon: "mdi:flowchart",
|
||||
color: "blue",
|
||||
images: [],
|
||||
tags: ["BPM", "Workflow", "Automation", "Process"],
|
||||
features: [
|
||||
"Visual Process Designer",
|
||||
"Workflow Automation",
|
||||
"Task Assignment",
|
||||
"Progress Tracking",
|
||||
"Integration APIs"
|
||||
],
|
||||
isInstalled: false,
|
||||
lastUpdated: "2024-01-22"
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
name: "queue-management",
|
||||
displayName: "Queue Management System",
|
||||
version: "1.0.0",
|
||||
description: "Complete queue management system for organizing and processing tasks efficiently",
|
||||
author: "CORRAD Team",
|
||||
category: "productivity",
|
||||
size: "2.1 MB",
|
||||
rating: 4.6,
|
||||
downloads: 987,
|
||||
price: "Free",
|
||||
icon: "mdi:format-list-numbered",
|
||||
color: "indigo",
|
||||
images: [],
|
||||
tags: ["Queue", "Tasks", "Processing", "Management"],
|
||||
features: [
|
||||
"Queue Organization",
|
||||
"Task Processing",
|
||||
"Priority Management",
|
||||
"Real-time Monitoring",
|
||||
"Performance Analytics"
|
||||
],
|
||||
isInstalled: false,
|
||||
lastUpdated: "2024-01-15"
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
name: "report-management",
|
||||
displayName: "Report Management System",
|
||||
version: "1.0.0",
|
||||
description: "Advanced reporting system integrated with Metabase for analytics and dashboard creation",
|
||||
author: "CORRAD Team",
|
||||
category: "business",
|
||||
size: "3.8 MB",
|
||||
rating: 4.8,
|
||||
downloads: 1567,
|
||||
price: "Free",
|
||||
icon: "mdi:chart-line",
|
||||
color: "blue",
|
||||
images: [],
|
||||
tags: ["Reports", "Analytics", "Metabase", "Dashboard"],
|
||||
features: [
|
||||
"Custom Reports",
|
||||
"Metabase Integration",
|
||||
"Interactive Dashboards",
|
||||
"Data Visualization",
|
||||
"Scheduled Reports"
|
||||
],
|
||||
isInstalled: true,
|
||||
lastUpdated: "2024-01-19"
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
name: "audit-trail",
|
||||
displayName: "Audit Trail System",
|
||||
version: "1.0.0",
|
||||
description: "Comprehensive audit logging system using Loki and Grafana with integrated Nuxt pages",
|
||||
author: "CORRAD Team",
|
||||
category: "security",
|
||||
size: "3.5 MB",
|
||||
rating: 4.7,
|
||||
downloads: 1234,
|
||||
price: "Free",
|
||||
icon: "mdi:file-document-alert",
|
||||
color: "purple",
|
||||
images: [],
|
||||
tags: ["Audit", "Logging", "Loki", "Grafana"],
|
||||
features: [
|
||||
"Activity Logging",
|
||||
"Loki Integration",
|
||||
"Grafana Dashboards",
|
||||
"Search & Filter",
|
||||
"Compliance Reports"
|
||||
],
|
||||
isInstalled: false,
|
||||
lastUpdated: "2024-01-16"
|
||||
},
|
||||
{
|
||||
id: 7,
|
||||
name: "edms",
|
||||
displayName: "Electronic Document Management System",
|
||||
version: "1.0.0",
|
||||
description: "Complete document management system like Paperless-ngx for organizing and managing documents",
|
||||
author: "CORRAD Team",
|
||||
category: "business",
|
||||
size: "4.2 MB",
|
||||
rating: 4.6,
|
||||
downloads: 892,
|
||||
price: "Free",
|
||||
icon: "mdi:file-document-multiple",
|
||||
color: "red",
|
||||
images: [],
|
||||
tags: ["Documents", "Management", "OCR", "Archive"],
|
||||
features: [
|
||||
"Document Storage",
|
||||
"OCR Processing",
|
||||
"Search & Index",
|
||||
"Version Control",
|
||||
"Access Management"
|
||||
],
|
||||
isInstalled: false,
|
||||
lastUpdated: "2024-01-14"
|
||||
}
|
||||
]);
|
||||
|
||||
// Computed properties
|
||||
const filteredPlugins = computed(() => {
|
||||
let plugins = storePlugins.value;
|
||||
|
||||
// Filter by category
|
||||
if (selectedCategory.value !== 'all') {
|
||||
plugins = plugins.filter(plugin => plugin.category === selectedCategory.value);
|
||||
}
|
||||
|
||||
// Filter by search
|
||||
if (searchQuery.value) {
|
||||
const query = searchQuery.value.toLowerCase();
|
||||
plugins = plugins.filter(plugin =>
|
||||
plugin.displayName.toLowerCase().includes(query) ||
|
||||
plugin.description.toLowerCase().includes(query) ||
|
||||
plugin.tags.some(tag => tag.toLowerCase().includes(query))
|
||||
);
|
||||
}
|
||||
|
||||
// Sort plugins
|
||||
switch (sortBy.value) {
|
||||
case 'popularity':
|
||||
plugins.sort((a, b) => b.downloads - a.downloads);
|
||||
break;
|
||||
case 'rating':
|
||||
plugins.sort((a, b) => b.rating - a.rating);
|
||||
break;
|
||||
case 'name':
|
||||
plugins.sort((a, b) => a.displayName.localeCompare(b.displayName));
|
||||
break;
|
||||
case 'recent':
|
||||
plugins.sort((a, b) => new Date(b.lastUpdated) - new Date(a.lastUpdated));
|
||||
break;
|
||||
}
|
||||
|
||||
return plugins;
|
||||
});
|
||||
|
||||
const getColorClasses = (color) => {
|
||||
const colorMap = {
|
||||
blue: 'bg-blue-100 text-blue-600',
|
||||
green: 'bg-green-100 text-green-600',
|
||||
purple: 'bg-purple-100 text-purple-600',
|
||||
orange: 'bg-orange-100 text-orange-600',
|
||||
indigo: 'bg-indigo-100 text-indigo-600',
|
||||
red: 'bg-red-100 text-red-600'
|
||||
};
|
||||
return colorMap[color] || 'bg-gray-100 text-gray-600';
|
||||
};
|
||||
|
||||
const installPlugin = async (plugin) => {
|
||||
if (plugin.isInstalled) return;
|
||||
|
||||
try {
|
||||
// Simulate installation
|
||||
plugin.isInstalled = true;
|
||||
plugin.downloads += 1;
|
||||
|
||||
nuxtApp.$swal.fire({
|
||||
title: "Success",
|
||||
text: `${plugin.displayName} installed successfully`,
|
||||
icon: "success",
|
||||
timer: 2000,
|
||||
showConfirmButton: false,
|
||||
});
|
||||
} catch (error) {
|
||||
nuxtApp.$swal.fire({
|
||||
title: "Error",
|
||||
text: "Failed to install plugin",
|
||||
icon: "error",
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const viewPluginDetails = (plugin) => {
|
||||
navigateTo(`/devtool/plugin-manager/store/${plugin.name}`);
|
||||
};
|
||||
|
||||
const formatNumber = (num) => {
|
||||
if (num >= 1000) {
|
||||
return (num / 1000).toFixed(1) + 'k';
|
||||
}
|
||||
return num.toString();
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<LayoutsBreadcrumb />
|
||||
|
||||
<!-- Header -->
|
||||
<rs-card class="mb-6">
|
||||
<template #body>
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<h1 class="text-2xl font-bold text-gray-900 mb-2">Plugin Store</h1>
|
||||
<p class="text-gray-600">
|
||||
Discover and install new plugins to extend your application's functionality
|
||||
</p>
|
||||
</div>
|
||||
<div class="hidden md:block">
|
||||
<Icon name="mdi:store" size="64" class="text-primary/20" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</rs-card>
|
||||
|
||||
<!-- Search and Filters -->
|
||||
<rs-card class="mb-6">
|
||||
<template #body>
|
||||
<div class="space-y-4">
|
||||
<!-- Search Bar -->
|
||||
<div class="flex flex-col md:flex-row gap-4">
|
||||
<div class="flex-1">
|
||||
<FormKit
|
||||
type="search"
|
||||
placeholder="Search plugins, features, or categories..."
|
||||
v-model="searchQuery"
|
||||
outer-class="mb-0"
|
||||
>
|
||||
<template #prefix>
|
||||
<Icon name="mdi:magnify" class="text-gray-400" />
|
||||
</template>
|
||||
</FormKit>
|
||||
</div>
|
||||
<div class="w-full md:w-48">
|
||||
<FormKit
|
||||
type="select"
|
||||
v-model="sortBy"
|
||||
:options="[
|
||||
{ label: 'Most Popular', value: 'popularity' },
|
||||
{ label: 'Highest Rated', value: 'rating' },
|
||||
{ label: 'Name A-Z', value: 'name' },
|
||||
{ label: 'Recently Updated', value: 'recent' }
|
||||
]"
|
||||
outer-class="mb-0"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Categories -->
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<button
|
||||
v-for="category in categories"
|
||||
:key="category.id"
|
||||
@click="selectedCategory = category.id"
|
||||
:class="[
|
||||
'flex items-center px-4 py-2 rounded-full text-sm font-medium transition-colors',
|
||||
selectedCategory === category.id
|
||||
? 'bg-primary text-white'
|
||||
: 'bg-gray-100 text-gray-700 hover:bg-gray-200'
|
||||
]"
|
||||
>
|
||||
<Icon :name="category.icon" class="mr-2" size="16" />
|
||||
{{ category.name }}
|
||||
<span class="ml-2 px-2 py-0.5 bg-white/20 rounded-full text-xs">
|
||||
{{ category.count }}
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</rs-card>
|
||||
|
||||
<!-- Results Info -->
|
||||
<div class="flex items-center justify-between mb-6">
|
||||
<p class="text-gray-600">
|
||||
{{ filteredPlugins.length }} plugin(s) found
|
||||
</p>
|
||||
<div class="flex items-center gap-2 text-sm text-gray-500">
|
||||
<Icon name="mdi:information-outline" size="16" />
|
||||
Click on a plugin to view details
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Plugin Grid -->
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 mb-6">
|
||||
<rs-card
|
||||
v-for="plugin in filteredPlugins"
|
||||
:key="plugin.id"
|
||||
class="group hover:shadow-lg transition-all duration-300 cursor-pointer relative"
|
||||
@click="viewPluginDetails(plugin)"
|
||||
>
|
||||
<template #body>
|
||||
<div class="space-y-4">
|
||||
<!-- Plugin Header -->
|
||||
<div class="flex items-start justify-between">
|
||||
<div class="flex items-center space-x-3">
|
||||
<div
|
||||
:class="getColorClasses(plugin.color)"
|
||||
class="w-12 h-12 rounded-xl flex items-center justify-center"
|
||||
>
|
||||
<Icon :name="plugin.icon" size="24" />
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="font-semibold text-lg text-gray-900 group-hover:text-primary transition-colors">
|
||||
{{ plugin.displayName }}
|
||||
</h3>
|
||||
<p class="text-sm text-gray-500">v{{ plugin.version }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<rs-badge v-if="plugin.isInstalled" variant="success" size="sm">
|
||||
Installed
|
||||
</rs-badge>
|
||||
</div>
|
||||
|
||||
<!-- Plugin Description -->
|
||||
<p class="text-gray-600 text-sm line-clamp-2">
|
||||
{{ plugin.description }}
|
||||
</p>
|
||||
|
||||
<!-- Plugin Stats -->
|
||||
<div class="flex items-center justify-between text-sm text-gray-500">
|
||||
<div class="flex items-center space-x-4">
|
||||
<div class="flex items-center">
|
||||
<Icon name="mdi:star" class="text-yellow-500 mr-1" size="16" />
|
||||
{{ plugin.rating }}
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
<Icon name="mdi:download" class="mr-1" size="16" />
|
||||
{{ formatNumber(plugin.downloads) }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-right">
|
||||
<div class="font-medium text-gray-900">{{ plugin.price }}</div>
|
||||
<div class="text-xs">{{ plugin.size }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Tags -->
|
||||
<div class="flex flex-wrap gap-1">
|
||||
<span
|
||||
v-for="tag in plugin.tags.slice(0, 3)"
|
||||
:key="tag"
|
||||
class="px-2 py-1 bg-gray-100 text-gray-600 text-xs rounded-full"
|
||||
>
|
||||
{{ tag }}
|
||||
</span>
|
||||
<span v-if="plugin.tags.length > 3" class="text-xs text-gray-400">
|
||||
+{{ plugin.tags.length - 3 }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- Action Button -->
|
||||
<div class="pt-2 border-t">
|
||||
<rs-button
|
||||
class="w-full"
|
||||
@click.stop="viewPluginDetails(plugin)"
|
||||
>
|
||||
<Icon name="mdi:eye" class="mr-2" size="16" />
|
||||
View Details
|
||||
</rs-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</rs-card>
|
||||
</div>
|
||||
|
||||
<!-- Empty State -->
|
||||
<div v-if="filteredPlugins.length === 0" class="text-center py-12">
|
||||
<rs-card>
|
||||
<template #body>
|
||||
<div class="py-8">
|
||||
<Icon name="mdi:store-remove" size="64" class="mx-auto text-gray-400 mb-4" />
|
||||
<h3 class="text-lg font-medium text-gray-900 mb-2">No plugins found</h3>
|
||||
<p class="text-gray-500 mb-4">
|
||||
Try adjusting your search terms or browse a different category
|
||||
</p>
|
||||
<rs-button variant="outline" @click="searchQuery = ''; selectedCategory = 'all'">
|
||||
Clear Filters
|
||||
</rs-button>
|
||||
</div>
|
||||
</template>
|
||||
</rs-card>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
516
pages/devtool/plugin-manager/upload/index.vue
Normal file
516
pages/devtool/plugin-manager/upload/index.vue
Normal file
@ -0,0 +1,516 @@
|
||||
<script setup>
|
||||
definePageMeta({
|
||||
title: "Upload Plugin",
|
||||
middleware: ["auth"],
|
||||
requiresAuth: true,
|
||||
});
|
||||
|
||||
const nuxtApp = useNuxtApp();
|
||||
|
||||
// Upload states
|
||||
const isUploading = ref(false);
|
||||
const uploadProgress = ref(0);
|
||||
const uploadedFile = ref(null);
|
||||
const installationLog = ref([]);
|
||||
|
||||
// Form data
|
||||
const uploadForm = ref({
|
||||
file: null,
|
||||
agreeToTerms: false,
|
||||
overwriteExisting: false
|
||||
});
|
||||
|
||||
// Upload methods
|
||||
const handleFileUpload = (files) => {
|
||||
if (files && files.length > 0) {
|
||||
uploadedFile.value = files[0];
|
||||
}
|
||||
};
|
||||
|
||||
const simulateUpload = async () => {
|
||||
if (!uploadedFile.value || !uploadForm.value.agreeToTerms) {
|
||||
nuxtApp.$swal.fire({
|
||||
title: "Error",
|
||||
text: "Please select a file and agree to terms",
|
||||
icon: "error",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
isUploading.value = true;
|
||||
uploadProgress.value = 0;
|
||||
installationLog.value = [];
|
||||
|
||||
// Simulate upload progress
|
||||
const steps = [
|
||||
{ progress: 20, message: "Uploading plugin package..." },
|
||||
{ progress: 40, message: "Validating plugin manifest..." },
|
||||
{ progress: 60, message: "Checking dependencies..." },
|
||||
{ progress: 80, message: "Installing plugin files..." },
|
||||
{ progress: 100, message: "Plugin installed successfully!" }
|
||||
];
|
||||
|
||||
for (const step of steps) {
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
uploadProgress.value = step.progress;
|
||||
installationLog.value.push({
|
||||
message: step.message,
|
||||
timestamp: new Date().toLocaleTimeString(),
|
||||
type: step.progress === 100 ? 'success' : 'info'
|
||||
});
|
||||
}
|
||||
|
||||
isUploading.value = false;
|
||||
|
||||
nuxtApp.$swal.fire({
|
||||
title: "Success",
|
||||
text: "Plugin uploaded and installed successfully!",
|
||||
icon: "success",
|
||||
timer: 3000,
|
||||
showConfirmButton: false,
|
||||
}).then(() => {
|
||||
navigateTo('/devtool/plugin-manager/installed');
|
||||
});
|
||||
};
|
||||
|
||||
const resetUpload = () => {
|
||||
uploadedFile.value = null;
|
||||
uploadProgress.value = 0;
|
||||
isUploading.value = false;
|
||||
installationLog.value = [];
|
||||
uploadForm.value = {
|
||||
file: null,
|
||||
agreeToTerms: false,
|
||||
overwriteExisting: false
|
||||
};
|
||||
};
|
||||
|
||||
const formatFileSize = (bytes) => {
|
||||
if (bytes === 0) return '0 Bytes';
|
||||
const k = 1024;
|
||||
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
||||
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
||||
};
|
||||
|
||||
// app.config.js example code
|
||||
const appConfigExample = ref(`export default {
|
||||
// Application Identity
|
||||
name: 'notification-management',
|
||||
version: '1.2.0',
|
||||
displayName: 'Notification Management System',
|
||||
description: 'Complete notification system with templates, scheduling, and analytics',
|
||||
|
||||
// Authentik Integration
|
||||
authentik: {
|
||||
applicationSlug: 'notification-management',
|
||||
requiredScopes: ['notifications:read', 'notifications:write', 'notifications:admin'],
|
||||
groups: ['notification-users', 'notification-admins']
|
||||
},
|
||||
|
||||
// CORRAD Menu Integration
|
||||
menuStructure: {
|
||||
title: 'Notifications',
|
||||
icon: 'bell',
|
||||
permission: 'notifications:read',
|
||||
order: 100,
|
||||
children: [
|
||||
{
|
||||
title: 'Dashboard',
|
||||
route: '/notifications/dashboard',
|
||||
icon: 'dashboard',
|
||||
permission: 'notifications:read'
|
||||
},
|
||||
{
|
||||
title: 'Send Notification',
|
||||
route: '/notifications/send',
|
||||
icon: 'send',
|
||||
permission: 'notifications:write'
|
||||
},
|
||||
{
|
||||
title: 'Templates',
|
||||
route: '/notifications/templates',
|
||||
icon: 'template',
|
||||
permission: 'notifications:write'
|
||||
},
|
||||
{
|
||||
title: 'History',
|
||||
route: '/notifications/history',
|
||||
icon: 'history',
|
||||
permission: 'notifications:read'
|
||||
},
|
||||
{
|
||||
title: 'Settings',
|
||||
route: '/notifications/settings',
|
||||
icon: 'settings',
|
||||
permission: 'notifications:admin'
|
||||
},
|
||||
{
|
||||
title: 'Reports',
|
||||
route: '/notifications/reports',
|
||||
icon: 'chart',
|
||||
permission: 'notifications:admin'
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
// Database Schema
|
||||
migrations: [
|
||||
'migrations/001_create_notifications.sql',
|
||||
'migrations/002_create_templates.sql',
|
||||
'migrations/003_create_schedules.sql'
|
||||
],
|
||||
|
||||
// API Endpoints
|
||||
apiRoutes: [
|
||||
{
|
||||
path: '/api/notifications',
|
||||
methods: ['GET', 'POST', 'PUT', 'DELETE'],
|
||||
permissions: {
|
||||
'GET': 'notifications:read',
|
||||
'POST': 'notifications:write',
|
||||
'PUT': 'notifications:write',
|
||||
'DELETE': 'notifications:admin'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/api/notifications/templates',
|
||||
methods: ['GET', 'POST', 'PUT', 'DELETE'],
|
||||
permissions: {
|
||||
'GET': 'notifications:read',
|
||||
'POST': 'notifications:write',
|
||||
'PUT': 'notifications:write',
|
||||
'DELETE': 'notifications:admin'
|
||||
}
|
||||
}
|
||||
],
|
||||
|
||||
// Dependencies
|
||||
dependencies: {
|
||||
corrad: '^2.0.0',
|
||||
authentik: '^2023.10.0'
|
||||
},
|
||||
|
||||
// Installation hooks
|
||||
hooks: {
|
||||
preInstall: 'scripts/pre-install.js',
|
||||
postInstall: 'scripts/post-install.js',
|
||||
preUninstall: 'scripts/pre-uninstall.js'
|
||||
}
|
||||
}`);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<LayoutsBreadcrumb />
|
||||
|
||||
<!-- Header -->
|
||||
<rs-card class="mb-6">
|
||||
<template #body>
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<h1 class="text-2xl font-bold text-gray-900 mb-2">Upload Plugin</h1>
|
||||
<p class="text-gray-600">
|
||||
Install custom plugin packages to extend your application
|
||||
</p>
|
||||
</div>
|
||||
<div class="hidden md:block">
|
||||
<Icon name="mdi:cloud-upload" size="64" class="text-primary/20" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</rs-card>
|
||||
|
||||
<!-- Security Warning -->
|
||||
<rs-card class="mb-6">
|
||||
<template #body>
|
||||
<div class="flex items-start space-x-3">
|
||||
<Icon name="mdi:security" class="text-orange-500 mt-1" size="20" />
|
||||
<div>
|
||||
<h3 class="font-medium text-orange-800 mb-2">Plugin Installation Guidelines</h3>
|
||||
<p class="text-orange-700 text-sm mb-3">
|
||||
Upload plugin packages for CORRAD+ applications. Each plugin should include app.config.js and proper folder structure.
|
||||
Supported plugins: RBAC, Notification Management, Business Process Maker, Queue Management, Report Management, Audit Trail, and EDMS.
|
||||
</p>
|
||||
<div class="grid grid-cols-3 md:grid-cols-7 gap-2">
|
||||
<div class="bg-green-100 rounded px-2 py-1 text-xs text-center">
|
||||
<Icon name="mdi:shield-account" class="text-green-600 mb-1" size="12" />
|
||||
<div class="text-green-800">RBAC</div>
|
||||
</div>
|
||||
<div class="bg-orange-100 rounded px-2 py-1 text-xs text-center">
|
||||
<Icon name="mdi:bell-ring" class="text-orange-600 mb-1" size="12" />
|
||||
<div class="text-orange-800">Notify</div>
|
||||
</div>
|
||||
<div class="bg-blue-100 rounded px-2 py-1 text-xs text-center">
|
||||
<Icon name="mdi:flowchart" class="text-blue-600 mb-1" size="12" />
|
||||
<div class="text-blue-800">BPM</div>
|
||||
</div>
|
||||
<div class="bg-indigo-100 rounded px-2 py-1 text-xs text-center">
|
||||
<Icon name="mdi:format-list-numbered" class="text-indigo-600 mb-1" size="12" />
|
||||
<div class="text-indigo-800">Queue</div>
|
||||
</div>
|
||||
<div class="bg-blue-100 rounded px-2 py-1 text-xs text-center">
|
||||
<Icon name="mdi:chart-line" class="text-blue-600 mb-1" size="12" />
|
||||
<div class="text-blue-800">Reports</div>
|
||||
</div>
|
||||
<div class="bg-purple-100 rounded px-2 py-1 text-xs text-center">
|
||||
<Icon name="mdi:file-document-alert" class="text-purple-600 mb-1" size="12" />
|
||||
<div class="text-purple-800">Audit</div>
|
||||
</div>
|
||||
<div class="bg-red-100 rounded px-2 py-1 text-xs text-center">
|
||||
<Icon name="mdi:file-document-multiple" class="text-red-600 mb-1" size="12" />
|
||||
<div class="text-red-800">EDMS</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</rs-card>
|
||||
|
||||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||
<!-- Upload Form -->
|
||||
<rs-card>
|
||||
<template #header>
|
||||
<div class="flex items-center">
|
||||
<Icon name="mdi:upload" class="mr-2" />
|
||||
Upload Plugin Package
|
||||
</div>
|
||||
</template>
|
||||
<template #body>
|
||||
<FormKit type="form" :actions="false" @submit="simulateUpload">
|
||||
<!-- File Upload -->
|
||||
<div class="mb-6">
|
||||
<label class="block text-sm font-medium text-gray-700 mb-2">
|
||||
Plugin Package (.zip)
|
||||
</label>
|
||||
<div
|
||||
class="border-2 border-dashed border-gray-300 rounded-lg p-6 text-center hover:border-primary transition-colors"
|
||||
:class="{ 'border-primary bg-primary/5': uploadedFile }"
|
||||
>
|
||||
<div v-if="!uploadedFile">
|
||||
<Icon name="mdi:cloud-upload" class="mx-auto text-gray-400 mb-4" size="48" />
|
||||
<p class="text-gray-600 mb-2">Drag and drop your plugin file here, or</p>
|
||||
<FormKit
|
||||
type="file"
|
||||
accept=".zip"
|
||||
@change="handleFileUpload"
|
||||
wrapper-class="inline-block"
|
||||
outer-class="mb-0"
|
||||
input-class="hidden"
|
||||
>
|
||||
<template #label>
|
||||
<rs-button variant="outline" size="sm">
|
||||
Browse Files
|
||||
</rs-button>
|
||||
</template>
|
||||
</FormKit>
|
||||
<p class="text-xs text-gray-500 mt-2">Maximum file size: 50MB</p>
|
||||
</div>
|
||||
<div v-else class="space-y-2">
|
||||
<Icon name="mdi:file-check" class="mx-auto text-green-500 mb-2" size="32" />
|
||||
<p class="font-medium text-gray-900">{{ uploadedFile.name }}</p>
|
||||
<p class="text-sm text-gray-500">{{ formatFileSize(uploadedFile.size) }}</p>
|
||||
<rs-button variant="outline" size="sm" @click="resetUpload">
|
||||
Choose Different File
|
||||
</rs-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Options -->
|
||||
<div class="space-y-4 mb-6">
|
||||
<FormKit
|
||||
type="checkbox"
|
||||
v-model="uploadForm.overwriteExisting"
|
||||
label="Overwrite existing plugin if it exists"
|
||||
help="This will replace any existing plugin with the same name"
|
||||
/>
|
||||
|
||||
<FormKit
|
||||
type="checkbox"
|
||||
v-model="uploadForm.agreeToTerms"
|
||||
label="I understand that this plugin will be installed as a complete CORRAD+ application"
|
||||
validation="required"
|
||||
:validation-messages="{ required: 'You must confirm to continue' }"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Upload Button -->
|
||||
<div class="flex gap-3">
|
||||
<rs-button
|
||||
btnType="submit"
|
||||
class="flex-1"
|
||||
:disabled="!uploadedFile || isUploading"
|
||||
>
|
||||
<Icon v-if="!isUploading" name="mdi:upload" class="mr-2" size="16" />
|
||||
<Icon v-else name="mdi:loading" class="mr-2 animate-spin" size="16" />
|
||||
{{ isUploading ? 'Installing...' : 'Upload & Install' }}
|
||||
</rs-button>
|
||||
<rs-button variant="outline" @click="resetUpload" :disabled="isUploading">
|
||||
Reset
|
||||
</rs-button>
|
||||
</div>
|
||||
</FormKit>
|
||||
</template>
|
||||
</rs-card>
|
||||
|
||||
<!-- Upload Progress & Instructions -->
|
||||
<div class="space-y-6">
|
||||
<!-- Progress -->
|
||||
<rs-card v-if="isUploading || uploadProgress > 0">
|
||||
<template #header>
|
||||
<div class="flex items-center">
|
||||
<Icon name="mdi:progress-upload" class="mr-2" />
|
||||
Installation Progress
|
||||
</div>
|
||||
</template>
|
||||
<template #body>
|
||||
<div class="space-y-4">
|
||||
<!-- Progress Bar -->
|
||||
<div>
|
||||
<div class="flex justify-between text-sm mb-1">
|
||||
<span>Installing plugin...</span>
|
||||
<span>{{ uploadProgress }}%</span>
|
||||
</div>
|
||||
<div class="w-full bg-gray-200 rounded-full h-2">
|
||||
<div
|
||||
class="bg-primary h-2 rounded-full transition-all duration-300"
|
||||
:style="{ width: uploadProgress + '%' }"
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Installation Log -->
|
||||
<div class="space-y-2 max-h-40 overflow-y-auto">
|
||||
<div
|
||||
v-for="(log, index) in installationLog"
|
||||
:key="index"
|
||||
class="flex items-start space-x-2 text-sm"
|
||||
>
|
||||
<Icon
|
||||
:name="log.type === 'success' ? 'mdi:check-circle' : 'mdi:information'"
|
||||
:class="log.type === 'success' ? 'text-green-500' : 'text-blue-500'"
|
||||
size="16"
|
||||
class="mt-0.5"
|
||||
/>
|
||||
<div class="flex-1">
|
||||
<span class="text-gray-900">{{ log.message }}</span>
|
||||
<span class="text-gray-500 ml-2">{{ log.timestamp }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</rs-card>
|
||||
|
||||
<!-- Instructions -->
|
||||
<rs-card>
|
||||
<template #header>
|
||||
<div class="flex items-center">
|
||||
<Icon name="mdi:information-outline" class="mr-2" />
|
||||
Plugin Requirements
|
||||
</div>
|
||||
</template>
|
||||
<template #body>
|
||||
<div class="space-y-4 text-sm">
|
||||
<div>
|
||||
<h4 class="font-medium text-gray-900 mb-2">Plugin Structure Requirements</h4>
|
||||
<ul class="list-disc list-inside text-gray-600 space-y-1">
|
||||
<li>Must be a valid .zip file</li>
|
||||
<li>Include app.config.js with complete application metadata</li>
|
||||
<li>Authentik integration configuration (scopes, groups, permissions)</li>
|
||||
<li>CORRAD menu structure with proper routing and permissions</li>
|
||||
<li>Database migrations with SQL files</li>
|
||||
<li>API routes with method-specific permissions</li>
|
||||
<li>Installation hooks for pre/post install actions</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h4 class="font-medium text-gray-900 mb-2">app.config.js Example</h4>
|
||||
<RsCodeMirror
|
||||
:model-value="appConfigExample"
|
||||
mode="javascript"
|
||||
height="300px"
|
||||
:disabled="true"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h4 class="font-medium text-gray-900 mb-2">Complete Folder Structure</h4>
|
||||
<div class="bg-gray-100 p-3 rounded text-xs font-mono">
|
||||
<pre>notification-management/
|
||||
├── app.config.js # Complete application config
|
||||
├── pages/
|
||||
│ ├── dashboard/
|
||||
│ ├── send/
|
||||
│ ├── templates/
|
||||
│ ├── history/
|
||||
│ ├── settings/
|
||||
│ └── reports/
|
||||
├── components/
|
||||
│ ├── NotificationSender.vue
|
||||
│ ├── NotificationList.vue
|
||||
│ └── NotificationStats.vue
|
||||
├── server/
|
||||
│ └── api/
|
||||
│ ├── notifications/
|
||||
│ └── templates/
|
||||
├── composables/
|
||||
│ └── useNotifications.js
|
||||
├── migrations/
|
||||
│ ├── 001_create_notifications.sql
|
||||
│ ├── 002_create_templates.sql
|
||||
│ └── 003_create_schedules.sql
|
||||
└── scripts/
|
||||
├── pre-install.js
|
||||
├── post-install.js
|
||||
└── pre-uninstall.js</pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</rs-card>
|
||||
|
||||
<!-- Recent Uploads -->
|
||||
<rs-card>
|
||||
<template #header>
|
||||
<div class="flex items-center">
|
||||
<Icon name="mdi:history" class="mr-2" />
|
||||
Recent Uploads
|
||||
</div>
|
||||
</template>
|
||||
<template #body>
|
||||
<div class="space-y-3">
|
||||
<div class="flex items-center justify-between py-2 border-b">
|
||||
<div class="flex items-center space-x-3">
|
||||
<Icon name="mdi:check-circle" class="text-green-500" size="16" />
|
||||
<div>
|
||||
<p class="text-sm font-medium">Report Management System</p>
|
||||
<p class="text-xs text-gray-500">Uploaded 3 days ago</p>
|
||||
</div>
|
||||
</div>
|
||||
<rs-badge variant="success" size="sm">Success</rs-badge>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-between py-2 border-b">
|
||||
<div class="flex items-center space-x-3">
|
||||
<Icon name="mdi:check-circle" class="text-green-500" size="16" />
|
||||
<div>
|
||||
<p class="text-sm font-medium">Notification Management System</p>
|
||||
<p class="text-xs text-gray-500">Uploaded 1 week ago</p>
|
||||
</div>
|
||||
</div>
|
||||
<rs-badge variant="success" size="sm">Success</rs-badge>
|
||||
</div>
|
||||
|
||||
<div class="text-center py-4 text-gray-500 text-sm">
|
||||
Ready to upload your next CORRAD+ application
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</rs-card>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
@ -99,14 +99,3 @@ model site_settings {
|
||||
settingModifiedDate DateTime? @db.DateTime(0)
|
||||
siteLoginLogo String? @db.VarChar(500)
|
||||
}
|
||||
|
||||
model ticket {
|
||||
ticketId Int @id @default(autoincrement())
|
||||
title String
|
||||
description String
|
||||
dueDate DateTime
|
||||
priority String @default("medium")
|
||||
status String @default("pending")
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user