Compare commits
1 Commits
main
...
plugin-man
Author | SHA1 | Date | |
---|---|---|---|
2fe91054e3 |
@ -25,11 +25,11 @@ const props = defineProps({
|
|||||||
// Slots
|
// Slots
|
||||||
const slots = useSlots();
|
const slots = useSlots();
|
||||||
|
|
||||||
const tabs = ref(slots.default().map((tab) => tab.props));
|
const tabs = ref(slots.default().map((tab) => tab.props).filter(Boolean));
|
||||||
const selectedTitle = ref(tabs.value[0]["title"]);
|
const selectedTitle = ref(tabs.value.length > 0 && tabs.value[0] ? tabs.value[0]["title"] : "");
|
||||||
|
|
||||||
tabs.value.forEach((tab) => {
|
tabs.value.forEach((tab) => {
|
||||||
if (typeof tab.active !== "undefined") {
|
if (tab && typeof tab.active !== "undefined") {
|
||||||
selectedTitle.value = tab.title;
|
selectedTitle.value = tab.title;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -75,6 +75,28 @@ export default [
|
|||||||
"path": "/devtool/api-editor",
|
"path": "/devtool/api-editor",
|
||||||
"icon": "material-symbols:api-rounded",
|
"icon": "material-symbols:api-rounded",
|
||||||
"child": []
|
"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": {
|
"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)
|
settingModifiedDate DateTime? @db.DateTime(0)
|
||||||
siteLoginLogo String? @db.VarChar(500)
|
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