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