feat: plugin management (#583)

* chore: remove unnecessary files

Signed-off-by: Ryan Wang <i@ryanc.cc>

* feat: plugin management

Signed-off-by: Ryan Wang <i@ryanc.cc>

* feat: refine plugin detail page

Signed-off-by: Ryan Wang <i@ryanc.cc>

* feat: refine plugin detail page

Signed-off-by: Ryan Wang <i@ryanc.cc>

* fix: theme plugin router link

Signed-off-by: Ryan Wang <i@ryanc.cc>

* Update src/modules/system/plugins/PluginList.vue

Co-authored-by: John Niang <johnniang@fastmail.com>

* feat: plugin detail page support start/stop plugin

Signed-off-by: Ryan Wang <i@ryanc.cc>

Co-authored-by: John Niang <johnniang@fastmail.com>
pull/588/head
Ryan Wang 2022-06-23 11:28:23 +08:00 committed by GitHub
parent 5a16fa7d1f
commit 72e40542f8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 257 additions and 389 deletions

View File

@ -29,6 +29,7 @@
"@halo-dev/admin-shared": "workspace:*",
"@halo-dev/components": "workspace:*",
"@vueuse/core": "^8.6.0",
"axios": "^0.27.2",
"filepond": "^4.30.4",
"filepond-plugin-image-preview": "^4.6.11",
"floating-vue": "2.0.0-beta.16",

View File

@ -18,7 +18,7 @@ const handleChange = () => {
<button
:class="{
'bg-gray-200': !modelValue,
'bg-themeable-primary-600': modelValue,
'!bg-themeable-primary-600': modelValue,
}"
aria-checked="false"
class="switch-inner"

View File

@ -21,6 +21,7 @@ importers:
'@vue/tsconfig': ^0.1.3
'@vueuse/core': ^8.6.0
autoprefixer: ^10.4.7
axios: ^0.27.2
c8: ^7.11.3
cypress: ^9.7.0
eslint: ^8.17.0
@ -60,6 +61,7 @@ importers:
'@halo-dev/admin-shared': link:packages/shared
'@halo-dev/components': link:packages/components
'@vueuse/core': 8.6.0_vue@3.2.37
axios: 0.27.2
filepond: 4.30.4
filepond-plugin-image-preview: 4.6.11_filepond@4.30.4
floating-vue: 2.0.0-beta.16_vue@3.2.37
@ -2751,6 +2753,15 @@ packages:
- debug
dev: false
/axios/0.27.2:
resolution: {integrity: sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==}
dependencies:
follow-redirects: 1.14.9
form-data: 4.0.0
transitivePeerDependencies:
- debug
dev: false
/babel-plugin-dynamic-import-node/2.3.3:
resolution: {integrity: sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ==}
dependencies:

View File

@ -1,5 +0,0 @@
<svg role="img" viewBox="0 0 24 24" fill="#5468FF" xmlns="http://www.w3.org/2000/svg">
<title>Algolia</title>
<path
d="M3.16 0A3.156 3.156 0 000 3.152v17.69A3.161 3.161 0 003.16 24h17.68c1.747 0 3.16-1.42 3.16-3.16V3.16C24 1.413 22.58 0 20.84 0H3.16zm7.87 3.494h2.429A1.04 1.04 0 0114.5 4.535v.822a.14.14 0 01-.174.14 7.679 7.647 0 00-2.043-.276 7.564 7.533 0 00-2.113.297c-.095.021-.182-.045-.182-.14v-.843a1.044 1.04 0 011.041-1.04zm-4.35 2.22a1.04 1.035 0 01.75.306l.414.416c.058.065.051.167-.022.21a7.544 7.513 0 00-.941.801 8 7.967 0 00-.793.932c-.058.065-.153.08-.219.016l-.408-.409a1.043 1.04 0 010-1.472l.496-.494a1.04 1.035 0 01.723-.305zm5.597.35a6.774 6.774 0 016.787 6.778 6.784 6.784 0 01-6.787 6.783c-3.748 0-6.789-3.028-6.789-6.777a6.786 6.786 0 016.79-6.784zm0 2.008a4.783 4.783 0 00-4.783 4.776 4.783 4.783 0 004.783 4.775 4.777 4.777 0 004.784-4.775 4.782 4.782 0 00-4.784-4.776zm.145.838a3.935 3.919 0 013.281 1.988c.036.073.015.16-.057.196l-3.166 1.638c-.093.052-.205-.023-.205-.125V9.05h.002c0-.08.072-.139.145-.139Z"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.0 KiB

View File

@ -1,5 +0,0 @@
<svg role="img" viewBox="0 0 24 24" fill="#ff6a00" xmlns="http://www.w3.org/2000/svg">
<title>Alibaba Cloud</title>
<path
d="M3.996 4.517h5.291L8.01 6.324 4.153 7.506a1.668 1.668 0 0 0-1.165 1.601v5.786a1.668 1.668 0 0 0 1.165 1.6l3.857 1.183 1.277 1.807H3.996A3.996 3.996 0 0 1 0 15.487V8.513a3.996 3.996 0 0 1 3.996-3.996m16.008 0h-5.291l1.277 1.807 3.857 1.182c.715.227 1.17.889 1.165 1.601v5.786a1.668 1.668 0 0 1-1.165 1.6l-3.857 1.183-1.277 1.807h5.291A3.996 3.996 0 0 0 24 15.487V8.513a3.996 3.996 0 0 0-3.996-3.996m-4.007 8.345H8.002v-1.804h7.995Z"/>
</svg>

Before

Width:  |  Height:  |  Size: 574 B

View File

@ -1 +0,0 @@
<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>Amazon AWS</title><path d="M6.763 10.036c0 .296.032.535.088.71.064.176.144.368.256.576.04.063.056.127.056.183 0 .08-.048.16-.152.24l-.503.335a.383.383 0 0 1-.208.072c-.08 0-.16-.04-.239-.112a2.47 2.47 0 0 1-.287-.375 6.18 6.18 0 0 1-.248-.471c-.622.734-1.405 1.101-2.347 1.101-.67 0-1.205-.191-1.596-.574-.391-.384-.59-.894-.59-1.533 0-.678.239-1.23.726-1.644.487-.415 1.133-.623 1.955-.623.272 0 .551.024.846.064.296.04.6.104.918.176v-.583c0-.607-.127-1.03-.375-1.277-.255-.248-.686-.367-1.3-.367-.28 0-.568.031-.863.103-.295.072-.583.16-.862.272a2.287 2.287 0 0 1-.28.104.488.488 0 0 1-.127.023c-.112 0-.168-.08-.168-.247v-.391c0-.128.016-.224.056-.28a.597.597 0 0 1 .224-.167c.279-.144.614-.264 1.005-.36a4.84 4.84 0 0 1 1.246-.151c.95 0 1.644.216 2.091.647.439.43.662 1.085.662 1.963v2.586zm-3.24 1.214c.263 0 .534-.048.822-.144.287-.096.543-.271.758-.51.128-.152.224-.32.272-.512.047-.191.08-.423.08-.694v-.335a6.66 6.66 0 0 0-.735-.136 6.02 6.02 0 0 0-.75-.048c-.535 0-.926.104-1.19.32-.263.215-.39.518-.39.917 0 .375.095.655.295.846.191.2.47.296.838.296zm6.41.862c-.144 0-.24-.024-.304-.08-.064-.048-.12-.16-.168-.311L7.586 5.55a1.398 1.398 0 0 1-.072-.32c0-.128.064-.2.191-.2h.783c.151 0 .255.025.31.08.065.048.113.16.16.312l1.342 5.284 1.245-5.284c.04-.16.088-.264.151-.312a.549.549 0 0 1 .32-.08h.638c.152 0 .256.025.32.08.063.048.12.16.151.312l1.261 5.348 1.381-5.348c.048-.16.104-.264.16-.312a.52.52 0 0 1 .311-.08h.743c.127 0 .2.065.2.2 0 .04-.009.08-.017.128a1.137 1.137 0 0 1-.056.2l-1.923 6.17c-.048.16-.104.263-.168.311a.51.51 0 0 1-.303.08h-.687c-.151 0-.255-.024-.32-.08-.063-.056-.119-.16-.15-.32l-1.238-5.148-1.23 5.14c-.04.16-.087.264-.15.32-.065.056-.177.08-.32.08zm10.256.215c-.415 0-.83-.048-1.229-.143-.399-.096-.71-.2-.918-.32-.128-.071-.215-.151-.247-.223a.563.563 0 0 1-.048-.224v-.407c0-.167.064-.247.183-.247.048 0 .096.008.144.024.048.016.12.048.2.08.271.12.566.215.878.279.319.064.63.096.95.096.502 0 .894-.088 1.165-.264a.86.86 0 0 0 .415-.758.777.777 0 0 0-.215-.559c-.144-.151-.416-.287-.807-.415l-1.157-.36c-.583-.183-1.014-.454-1.277-.813a1.902 1.902 0 0 1-.4-1.158c0-.335.073-.63.216-.886.144-.255.335-.479.575-.654.24-.184.51-.32.83-.415.32-.096.655-.136 1.006-.136.175 0 .359.008.535.032.183.024.35.056.518.088.16.04.312.08.455.127.144.048.256.096.336.144a.69.69 0 0 1 .24.2.43.43 0 0 1 .071.263v.375c0 .168-.064.256-.184.256a.83.83 0 0 1-.303-.096 3.652 3.652 0 0 0-1.532-.311c-.455 0-.815.071-1.062.223-.248.152-.375.383-.375.71 0 .224.08.416.24.567.159.152.454.304.877.44l1.134.358c.574.184.99.44 1.237.767.247.327.367.702.367 1.117 0 .343-.072.655-.207.926-.144.272-.336.511-.583.703-.248.2-.543.343-.886.447-.36.111-.734.167-1.142.167zM21.698 16.207c-2.626 1.94-6.442 2.969-9.722 2.969-4.598 0-8.74-1.7-11.87-4.526-.247-.223-.024-.527.272-.351 3.384 1.963 7.559 3.153 11.877 3.153 2.914 0 6.114-.607 9.06-1.852.439-.2.814.287.383.607zM22.792 14.961c-.336-.43-2.22-.207-3.074-.103-.255.032-.295-.192-.063-.36 1.5-1.053 3.967-.75 4.254-.399.287.36-.08 2.826-1.485 4.007-.215.184-.423.088-.327-.151.32-.79 1.03-2.57.695-2.994z"/></svg>

Before

Width:  |  Height:  |  Size: 3.1 KiB

View File

@ -1,5 +0,0 @@
<svg role="img" viewBox="0 0 24 24" fill="#005571" xmlns="http://www.w3.org/2000/svg">
<title>Elasticsearch</title>
<path
d="M13.394 0C8.683 0 4.609 2.716 2.644 6.667h15.641a4.77 4.77 0 0 0 3.073-1.11c.446-.375.864-.785 1.247-1.243l.001-.002A11.974 11.974 0 0 0 13.394 0zM1.804 8.889a12.009 12.009 0 0 0 0 6.222h14.7a3.111 3.111 0 1 0 0-6.222zm.84 8.444C4.61 21.283 8.684 24 13.395 24c3.701 0 7.011-1.677 9.212-4.312l-.001-.002a9.958 9.958 0 0 0-1.247-1.243 4.77 4.77 0 0 0-3.073-1.11z"/>
</svg>

Before

Width:  |  Height:  |  Size: 504 B

View File

@ -1,5 +0,0 @@
<svg role="img" viewBox="0 0 24 24" fill="#181717" xmlns="http://www.w3.org/2000/svg">
<title>GitHub</title>
<path
d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12"/>
</svg>

Before

Width:  |  Height:  |  Size: 849 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 458 KiB

View File

@ -1 +0,0 @@
<svg role="img" viewBox="0 0 24 24" fill="#DC382D" xmlns="http://www.w3.org/2000/svg"><title>Redis</title><path d="M10.5 2.661l.54.997-1.797.644 2.409.218.748 1.246.467-1.121 2.077-.208-1.61-.613.426-1.017-1.578.519zm6.905 2.077L13.76 6.182l3.292 1.298.353-.146 3.293-1.298zm-10.51.312a2.97 1.153 0 0 0-2.97 1.152 2.97 1.153 0 0 0 2.97 1.153 2.97 1.153 0 0 0 2.97-1.153 2.97 1.153 0 0 0-2.97-1.152zM24 6.805s-8.983 4.278-10.395 4.953c-1.226.561-1.901.561-3.261.094C8.318 11.022 0 7.241 0 7.241v1.038c0 .24.332.499.966.8 1.277.613 8.34 3.677 9.45 4.206 1.112.53 1.9.54 3.313-.197 1.412-.738 8.049-3.905 9.326-4.57.654-.342.945-.602.945-.84zm-10.042.602L8.39 8.26l3.884 1.61zM24 10.637s-8.983 4.279-10.395 4.954c-1.226.56-1.901.56-3.261.093C8.318 14.854 0 11.074 0 11.074v1.038c0 .238.332.498.966.8 1.277.612 8.34 3.676 9.45 4.205 1.112.53 1.9.54 3.313-.197 1.412-.737 8.049-3.905 9.326-4.57.654-.332.945-.602.945-.84zm0 3.842l-10.395 4.954c-1.226.56-1.901.56-3.261.094C8.318 18.696 0 14.916 0 14.916v1.038c0 .239.332.499.966.8 1.277.613 8.34 3.676 9.45 4.206 1.112.53 1.9.54 3.313-.198 1.412-.737 8.049-3.904 9.326-4.569.654-.343.945-.613.945-.841z"/></svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -1 +0,0 @@
<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>W3C</title><path d="M23.642 5.602l-.931 1.858s-.4-.738-.795-1.076c-.377-.322-.864-.62-1.48-.556-.597.062-1.27.587-1.722 1.46-.513.994-.688 2.001-.692 3.112-.005 1.556.57 2.618.57 2.618s-.132-.494-.11-1.33c.014-.52.017-1.089.41-2.261.33-.98 1.084-1.775 1.75-1.912.517-.107.847-.03 1.356.329.603.425.966 1.193.966 1.193l.946-1.81zM0 5.674l3.77 12.723h.156l2.356-7.886 2.357 7.886h.157l3.228-10.895.152-.258h2.655l-2.2 3.802v.754h.629c.806 0 1.398.246 1.775.738.324.42.487 1.011.487 1.776 0 .691-.152 1.283-.455 1.775-.304.492-.676.738-1.116.738-.419 0-.783-.138-1.092-.416-.308-.277-.557-.657-.746-1.139l-1.288.534c.261.796.665 1.427 1.21 1.893.544.466 1.183.699 1.916.699.974 0 1.767-.393 2.38-1.178.613-.785.919-1.754.919-2.906 0-.932-.21-1.743-.628-2.435-.42-.69-1.037-1.167-1.854-1.43l2.326-4.006v-.77h-6.177L8.64 13.419 6.362 5.674h-1.65l.754 2.529-1.54 5.215L1.65 5.674zm17.44 8.88s.233.755.379 1.076c.084.185.342.75.708 1.24.341.46 1.004 1.248 2.011 1.426 1.008.178 1.7-.274 1.871-.384.172-.11.533-.412.761-.657.239-.255.465-.58.59-.775.091-.143.24-.432.24-.432l-.241-1.255s-.418.748-.678 1.036c-.261.288-.727.794-1.302 1.048-.576.253-.877.302-1.446.247-.569-.054-1.097-.383-1.282-.52-.185-.138-.658-.542-.925-.92-.268-.376-.686-1.13-.686-1.13z"/></svg>

Before

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -1,5 +0,0 @@
<svg role="img" viewBox="0 0 24 24" fill="#07C160" xmlns="http://www.w3.org/2000/svg">
<title>WeChat</title>
<path
d="M8.691 2.188C3.891 2.188 0 5.476 0 9.53c0 2.212 1.17 4.203 3.002 5.55a.59.59 0 0 1 .213.665l-.39 1.48c-.019.07-.048.141-.048.213 0 .163.13.295.29.295a.326.326 0 0 0 .167-.054l1.903-1.114a.864.864 0 0 1 .717-.098 10.16 10.16 0 0 0 2.837.403c.276 0 .543-.027.811-.05-.857-2.578.157-4.972 1.932-6.446 1.703-1.415 3.882-1.98 5.853-1.838-.576-3.583-4.196-6.348-8.596-6.348zM5.785 5.991c.642 0 1.162.529 1.162 1.18a1.17 1.17 0 0 1-1.162 1.178A1.17 1.17 0 0 1 4.623 7.17c0-.651.52-1.18 1.162-1.18zm5.813 0c.642 0 1.162.529 1.162 1.18a1.17 1.17 0 0 1-1.162 1.178 1.17 1.17 0 0 1-1.162-1.178c0-.651.52-1.18 1.162-1.18zm5.34 2.867c-1.797-.052-3.746.512-5.28 1.786-1.72 1.428-2.687 3.72-1.78 6.22.942 2.453 3.666 4.229 6.884 4.229.826 0 1.622-.12 2.361-.336a.722.722 0 0 1 .598.082l1.584.926a.272.272 0 0 0 .14.047c.134 0 .24-.111.24-.247 0-.06-.023-.12-.038-.177l-.327-1.233a.582.582 0 0 1-.023-.156.49.49 0 0 1 .201-.398C23.024 18.48 24 16.82 24 14.98c0-3.21-2.931-5.837-6.656-6.088V8.89c-.135-.01-.27-.027-.407-.03zm-2.53 3.274c.535 0 .969.44.969.982a.976.976 0 0 1-.969.983.976.976 0 0 1-.969-.983c0-.542.434-.982.97-.982zm4.844 0c.535 0 .969.44.969.982a.976.976 0 0 1-.969.983.976.976 0 0 1-.969-.983c0-.542.434-.982.969-.982z"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -10,6 +10,9 @@ import { registerMenu } from "@/router/menus.config";
// core modules
import { coreModules } from "./modules";
import { useScriptTag } from "@vueuse/core";
import { usePluginStore } from "@/stores/plugin";
import axiosInstance from "@/utils/api-client";
const app = createApp(App);
@ -58,8 +61,37 @@ function loadCoreModules() {
coreModules.forEach(registerModule);
}
const pluginStore = usePluginStore();
async function loadPluginModules() {
// TODO: load plugin modules
const response = await axiosInstance.get(
`/apis/plugin.halo.run/v1alpha1/plugins`
);
// Get all started plugins
const plugins = response.data.filter(
(plugin) => plugin.status.phase === "STARTED" && plugin.spec.enabled
);
for (const plugin of plugins) {
const { entry } = plugin.status;
if (entry) {
const { load } = useScriptTag(
`http://localhost:8090${plugin.status.entry}`
);
await load();
const pluginModule = window[plugin.metadata.name];
if (pluginModule) {
// @ts-ignore
plugin.spec.module = pluginModule;
registerModule(pluginModule);
}
}
pluginStore.registerPlugin(plugin);
}
}
(async function () {
@ -72,7 +104,8 @@ async function initApp() {
await loadPluginModules();
} catch (e) {
console.error(e);
}
} finally {
app.use(router);
app.mount("#app");
}
}

View File

@ -14,7 +14,7 @@ import {
} from "@halo-dev/components";
import { ref } from "vue";
import { users } from "@/modules/system/users/users-mock";
import halo from "@/assets/logo-mock/halo.png";
import halo from "@/assets/logo.svg";
const sheetsRef = ref([
{

View File

@ -249,7 +249,7 @@ const handleChangeTheme = (theme: any) => {
<RouterLink
:to="{
name: 'PluginDetail',
params: { id: 'run.halo.plugins.links' },
params: { pluginName: 'PluginLinks' },
}"
class="font-medium text-gray-900 hover:text-blue-400"
>

View File

@ -5,31 +5,59 @@ import {
VInput,
VPageHeader,
VSpace,
VSwitch,
VTabbar,
VTag,
} from "@halo-dev/components";
import { useRoute } from "vue-router";
import { plugins } from "./plugins-mock";
import { ref } from "vue";
import { computed, ref } from "vue";
import type { Plugin } from "./types";
import axiosInstance from "@/utils/api-client";
const pluginActiveId = ref("detail");
const plugin = ref<Plugin>();
const { params } = useRoute();
const plugin = plugins.find((item) => {
return item.spec.pluginClass === params.id;
const handleFetchPlugin = async () => {
try {
const response = await axiosInstance.get(
`/apis/plugin.halo.run/v1alpha1/plugins/${params.pluginName}`
);
plugin.value = response.data;
} catch (e) {
console.error(e);
}
};
const isStarted = computed(() => {
return plugin.value?.status.phase === "STARTED" && plugin.value?.spec.enabled;
});
console.log(plugin);
const handleChangePluginStatus = async () => {
try {
await axiosInstance.put(
`/apis/plugin.halo.run/v1alpha1/plugins/${plugin.value?.metadata.name}/${
isStarted.value ? "stop" : "startup"
}`
);
} catch (e) {
console.error(e);
} finally {
window.location.reload();
}
};
handleFetchPlugin();
</script>
<template>
<VPageHeader :title="plugin.metadata.name">
<VPageHeader :title="plugin?.spec?.displayName">
<template #icon>
<img :src="plugin.spec.logo" class="mr-2 h-8 w-8" />
<img :src="plugin?.spec?.logo" class="mr-2 h-8 w-8" />
</template>
<template #actions>
<VButton class="opacity-0" type="secondary"> 安装</VButton>
<VButton class="opacity-0" type="secondary">安装</VButton>
</template>
</VPageHeader>
@ -48,17 +76,27 @@ console.log(plugin);
</template>
<div v-if="pluginActiveId === 'detail'">
<div class="px-4 py-4 sm:px-6">
<h3 class="text-lg font-medium leading-6 text-gray-900">插件信息</h3>
<div class="flex items-center justify-between px-4 py-4 sm:px-6">
<div>
<h3 class="text-lg font-medium leading-6 text-gray-900">
插件信息
</h3>
<p
class="mt-1 flex max-w-2xl items-center gap-2 text-sm text-gray-500"
>
<span>{{ plugin.spec.version }}</span>
<span>{{ plugin?.spec?.version }}</span>
<VTag>
{{ plugin.metadata.enabled ? "已启用" : "未启用" }}
{{ isStarted ? "已启用" : "未启用" }}
</VTag>
</p>
</div>
<div>
<VSwitch
:model-value="isStarted"
@change="handleChangePluginStatus"
/>
</div>
</div>
<div class="border-t border-gray-200">
<dl class="divide-y divide-gray-100">
<div
@ -66,19 +104,7 @@ console.log(plugin);
>
<dt class="text-sm font-medium text-gray-900">名称</dt>
<dd class="mt-1 text-sm text-gray-900 sm:col-span-2 sm:mt-0">
{{ plugin.metadata.name }}
</dd>
</div>
<div
class="bg-white px-4 py-5 hover:bg-gray-50 sm:grid sm:grid-cols-6 sm:gap-4 sm:px-6"
>
<dt class="text-sm font-medium text-gray-900">插件类别</dt>
<dd class="mt-1 text-sm text-gray-900 sm:col-span-2 sm:mt-0">
<VTag>
extensions.halo.run/category/{{
plugin.metadata.labels["extensions.halo.run/category"]
}}
</VTag>
{{ plugin?.spec?.displayName }}
</dd>
</div>
<div
@ -86,7 +112,7 @@ console.log(plugin);
>
<dt class="text-sm font-medium text-gray-900">版本</dt>
<dd class="mt-1 text-sm text-gray-900 sm:col-span-2 sm:mt-0">
{{ plugin.spec.version }}
{{ plugin?.spec?.version }}
</dd>
</div>
<div
@ -94,7 +120,7 @@ console.log(plugin);
>
<dt class="text-sm font-medium text-gray-900">Halo 版本要求</dt>
<dd class="mt-1 text-sm text-gray-900 sm:col-span-2 sm:mt-0">
{{ plugin.spec.requires }}
{{ plugin?.spec?.requires }}
</dd>
</div>
<div
@ -102,8 +128,8 @@ console.log(plugin);
>
<dt class="text-sm font-medium text-gray-900">提供方</dt>
<dd class="mt-1 text-sm text-gray-900 sm:col-span-2 sm:mt-0">
<a :href="plugin.spec.homepage" target="_blank">
@{{ plugin.spec.author }}
<a :href="plugin?.spec?.homepage" target="_blank">
{{ plugin?.spec?.author }}
</a>
</dd>
</div>
@ -112,7 +138,22 @@ console.log(plugin);
>
<dt class="text-sm font-medium text-gray-900">协议</dt>
<dd class="mt-1 text-sm text-gray-900 sm:col-span-2 sm:mt-0">
{{ plugin.spec.license }}
<ul
v-if="plugin?.spec?.license && plugin?.spec?.license.length"
class="list-inside list-disc"
>
<li
v-for="(license, index) in plugin.spec.license"
:key="index"
>
<a v-if="license.url" :href="license.url" target="_blank">
{{ license.name }}
</a>
<span>
{{ license.name }}
</span>
</li>
</ul>
</dd>
</div>
<div
@ -120,9 +161,9 @@ console.log(plugin);
>
<dt class="text-sm font-medium text-gray-900">模型定义</dt>
<dd class="mt-1 sm:col-span-2 sm:mt-0">
<ul v-if="plugin.extensions" class="space-y-2">
<ul v-if="plugin?.extensions" class="space-y-2">
<li
v-for="(extension, index) in plugin.extensions"
v-for="(extension, index) in plugin?.extensions"
:key="index"
>
<div

View File

@ -9,23 +9,56 @@ import {
VInput,
VPageHeader,
VSpace,
VSwitch,
VTag,
} from "@halo-dev/components";
import { ref } from "vue";
import { onMounted, ref } from "vue";
import { useRouter } from "vue-router";
import { plugins } from "./plugins-mock";
import type { Plugin } from "./types";
import axiosInstance from "@/utils/api-client";
const checkAll = ref(false);
const plugins = ref<Plugin[]>([]);
const router = useRouter();
// eslint-disable-next-line
const handleRouteToDetail = (plugin: any) => {
const handleRouteToDetail = (plugin: Plugin) => {
router.push({
name: "PluginDetail",
params: { id: plugin.spec.pluginClass },
params: { pluginName: plugin.metadata.name },
});
};
function isStarted(plugin: Plugin) {
return plugin.status.phase === "STARTED" && plugin.spec.enabled;
}
const handleFetchPlugins = async () => {
try {
const response = await axiosInstance.get(
`/apis/plugin.halo.run/v1alpha1/plugins`
);
plugins.value = response.data;
} catch (e) {
console.error("Fail to fetch plugins", e);
}
};
const handleChangePluginStatus = async (plugin: Plugin) => {
try {
await axiosInstance.put(
`/apis/plugin.halo.run/v1alpha1/plugins/${plugin.metadata.name}/${
isStarted(plugin) ? "stop" : "startup"
}`
);
} catch (e) {
console.error(e);
} finally {
window.location.reload();
}
};
onMounted(handleFetchPlugins);
</script>
<template>
<VPageHeader title="插件">
@ -187,11 +220,7 @@ const handleRouteToDetail = (plugin: any) => {
</div>
</template>
<ul class="box-border h-full w-full divide-y divide-gray-100" role="list">
<li
v-for="(plugin, index) in plugins"
:key="index"
@click.stop="handleRouteToDetail(plugin)"
>
<li v-for="(plugin, index) in plugins" :key="index">
<div
:class="{
'bg-gray-100': checkAll,
@ -223,12 +252,15 @@ const handleRouteToDetail = (plugin: any) => {
</div>
<div class="flex-1">
<div class="flex flex-row items-center">
<span class="mr-2 truncate text-sm font-medium text-gray-900">
{{ plugin.metadata.name }}
<span
class="mr-2 truncate text-sm font-medium text-gray-900"
@click.stop="handleRouteToDetail(plugin)"
>
{{ plugin.spec.displayName }}
</span>
<VSpace>
<VTag>
{{ plugin.metadata.enabled ? "已启用" : "未启用" }}
{{ isStarted(plugin) ? "已启用" : "未启用" }}
</VTag>
</VSpace>
</div>
@ -258,10 +290,16 @@ const handleRouteToDetail = (plugin: any) => {
{{ plugin.spec.version }}
</span>
<time class="text-sm text-gray-500" datetime="2020-01-07">
2020-01-07
{{ plugin.metadata.creationTimestamp }}
</time>
<div class="flex items-center">
<VSwitch
:model-value="isStarted(plugin)"
@click="handleChangePluginStatus(plugin)"
/>
</div>
<span class="cursor-pointer">
<IconSettings />
<IconSettings @click.stop="handleRouteToDetail(plugin)" />
</span>
</div>
</div>

View File

@ -18,7 +18,7 @@ export default definePlugin({
component: PluginList,
},
{
path: ":id",
path: ":pluginName",
name: "PluginDetail",
component: PluginDetail,
},

View File

@ -1,301 +0,0 @@
import alibabacloud from "@/assets/logo-mock/alibabacloud.svg";
import amazonaws from "@/assets/logo-mock/amazonaws.svg";
import w3c from "@/assets/logo-mock/w3c.svg";
import halo from "@/assets/logo-mock/halo.png";
import wechat from "@/assets/logo-mock/wechat.svg";
import github from "@/assets/logo-mock/github.svg";
import redis from "@/assets/logo-mock/redis.svg";
import elasticsearch from "@/assets/logo-mock/elasticsearch.svg";
import algolia from "@/assets/logo-mock/algolia.svg";
export const plugins = [
{
apiVersion: "v1",
kind: "Plugin",
metadata: {
name: "阿里云 OSS",
enabled: true,
labels: {
"extensions.halo.run/category": "attachment",
},
},
spec: {
version: "1.0.0",
requires: ">=2.0.0",
author: "halo-dev",
logo: alibabacloud,
pluginClass: "run.halo.plugins.OSS",
pluginDependencies: {},
homepage: "https://github.com/halo-dev/halo-plugin-oss",
shortDescription: "this is a test plugin",
description: "阿里云 OSS 的上传选项,可同步文件以及上传",
license: "GPL V3",
},
},
{
apiVersion: "v1",
kind: "Plugin",
metadata: {
name: "Amazon S3",
enabled: false,
labels: {
"extensions.halo.run/category": "attachment",
},
},
spec: {
version: "1.0.0",
requires: ">=2.0.0",
author: "halo-dev",
logo: amazonaws,
pluginClass: "run.halo.plugins.s3",
pluginDependencies: {},
homepage: "https://github.com/halo-dev/halo-plugin-s3",
shortDescription: "this is a test plugin",
description: "Amazon S3 对象存储的上传选项,可同步文件以及上传",
license: "GPL V3",
},
},
{
apiVersion: "v1",
kind: "Plugin",
metadata: {
name: "OpenGraph Generator",
enabled: true,
labels: {
"extensions.halo.run/category": "common",
},
},
spec: {
version: "1.0.0",
requires: ">=2.0.0",
author: "Ryan Wang",
logo: w3c,
pluginClass: "run.halo.plugins.opengraph-gen",
pluginDependencies: {},
homepage: "https://github.com/halo-dev/halo-plugin-opengraph-gen",
shortDescription: "this is a test plugin",
description:
"自动为文章页面添加 OpenGraph 标签,并支持配置生成 OpenGraph 图片",
license: "GPL V3",
},
},
{
apiVersion: "v1",
kind: "Plugin",
metadata: {
name: "富文本编辑器",
enabled: true,
labels: {
"extensions.halo.run/category": "editor",
},
},
spec: {
version: "1.0.0",
requires: ">=2.0.0",
author: "halo-dev",
logo: halo,
pluginClass: "run.halo.plugins.richtext-editor",
pluginDependencies: {},
homepage: "https://github.com/halo-dev/halo-plugin-richtext-editor",
shortDescription: "this is a test plugin",
description: "Halo 官方提供的富文本编辑器",
license: "GPL V3",
},
},
{
apiVersion: "v1",
kind: "Plugin",
metadata: {
name: "友情链接",
enabled: true,
labels: {
"extensions.halo.run/category": "page",
},
},
spec: {
version: "1.0.0",
requires: ">=2.0.0",
author: "halo-dev",
logo: halo,
pluginClass: "run.halo.plugins.links",
pluginDependencies: {},
homepage: "https://github.com/halo-dev/halo-plugin-links",
shortDescription: "this is a test plugin",
description: "友情链接管理模块",
license: "GPL V3",
},
},
{
apiVersion: "v1",
kind: "Plugin",
metadata: {
name: "图库",
enabled: true,
labels: {
"extensions.halo.run/category": "page",
},
},
spec: {
version: "1.0.0",
requires: ">=2.0.0",
author: "halo-dev",
logo: halo,
pluginClass: "run.halo.plugins.photos",
pluginDependencies: {},
homepage: "https://github.com/halo-dev/halo-plugin-photos",
shortDescription: "this is a test plugin",
description: "图库管理模块",
license: "GPL V3",
},
},
{
apiVersion: "v1",
kind: "Plugin",
metadata: {
name: "社区",
enabled: true,
labels: {
"extensions.halo.run/category": "page",
},
},
spec: {
version: "1.0.0",
requires: ">=2.0.0",
author: "halo-dev",
logo: halo,
pluginClass: "run.halo.plugins.forum",
pluginDependencies: {},
homepage: "https://github.com/halo-dev/halo-plugin-forum",
shortDescription: "this is a test plugin",
description: "提供一个社区/论坛应有的所有模块",
license: "GPL V3",
},
extensions: [
{
name: "posts",
displayName: "帖子",
fields: ["id", "title", "content", "author", "createdAt"],
},
{
name: "discussions",
displayName: "评论",
fields: ["id", "content", "author", "createdAt"],
},
],
},
{
apiVersion: "v1",
kind: "Plugin",
metadata: {
name: "微信公众号管理",
enabled: true,
labels: {
"extensions.halo.run/category": "common",
},
},
spec: {
version: "1.0.0",
requires: ">=2.0.0",
author: "halo-dev",
logo: wechat,
pluginClass: "run.halo.plugins.wechat-channel",
pluginDependencies: {},
homepage: "https://github.com/halo-dev/halo-plugin-wechatchannel",
shortDescription: "this is a test plugin",
description: "托管微信公众号的管理,支持同步文章和管理媒体",
license: "GPL V3",
},
},
{
apiVersion: "v1",
kind: "Plugin",
metadata: {
name: "GitHub OAuth",
enabled: true,
labels: {
"extensions.halo.run/category": "auth",
},
},
spec: {
version: "1.0.0",
requires: ">=2.0.0",
author: "halo-dev",
logo: github,
pluginClass: "run.halo.plugins.github-oauth",
pluginDependencies: {},
homepage: "https://github.com/halo-dev/halo-plugin-github-oauth",
shortDescription: "this is a test plugin",
description: "允许用户通过 GitHub 账号登录",
license: "GPL V3",
},
},
{
apiVersion: "v1",
kind: "Plugin",
metadata: {
name: "Redis Cache",
enabled: true,
labels: {
"extensions.halo.run/category": "cache",
},
},
spec: {
version: "1.0.0",
requires: ">=2.0.0",
author: "halo-dev",
logo: redis,
pluginClass: "run.halo.plugins.redis-cache",
pluginDependencies: {},
homepage: "https://github.com/halo-dev/halo-plugin-redis-cache",
shortDescription: "this is a test plugin",
description: "支持使用 Redis 作为缓存",
license: "GPL V3",
},
},
{
apiVersion: "v1",
kind: "Plugin",
metadata: {
name: "ElasticSearch",
enabled: true,
labels: {
"extensions.halo.run/category": "search-engine",
},
},
spec: {
version: "1.0.0",
requires: ">=2.0.0",
author: "halo-dev",
logo: elasticsearch,
pluginClass: "run.halo.plugins.elasticsearch",
pluginDependencies: {},
homepage: "https://github.com/halo-dev/halo-plugin-elasticsearch",
shortDescription: "this is a test plugin",
description: "接入 ElasticSearch 作为全文搜索系统",
license: "GPL V3",
},
},
{
apiVersion: "v1",
kind: "Plugin",
metadata: {
name: "Algolia",
enabled: true,
labels: {
"extensions.halo.run/category": "search-engine",
},
},
spec: {
version: "1.0.0",
requires: ">=2.0.0",
author: "halo-dev",
logo: algolia,
pluginClass: "run.halo.plugins.algolia",
pluginDependencies: {},
homepage: "https://github.com/halo-dev/halo-plugin-algolia",
shortDescription: "this is a test plugin",
description: "接入 Algolia 作为全文搜索系统",
license: "GPL V3",
},
},
];

47
src/modules/system/plugins/types.d.ts vendored Normal file
View File

@ -0,0 +1,47 @@
// eslint-disable-next-line @typescript-eslint/no-empty-interface
export interface PluginDependencies {}
export interface License {
name: string;
url: string;
}
export interface Spec {
displayName: string;
version: string;
author: string;
logo: string;
pluginDependencies: PluginDependencies;
homepage: string;
description: string;
license: License[];
requires: string;
pluginClass: string;
enabled: boolean;
}
export interface Metadata {
name: string;
version: number;
creationTimestamp: Date;
}
export interface Status {
phase: string;
entry?: string;
stylesheet?: string;
}
export interface Plugin {
spec: Spec;
apiVersion: string;
kind: string;
metadata: Metadata;
status: Status;
extensions: Extension[];
}
export interface Extension {
name: string;
fields: string[];
}

13
src/stores/plugin.ts Normal file
View File

@ -0,0 +1,13 @@
import { defineStore } from "pinia";
export const usePluginStore = defineStore("plugin", {
state: () => ({
plugins: [],
}),
actions: {
registerPlugin(plugin) {
// @ts-ignore
this.plugins.push(plugin);
},
},
});

13
src/utils/api-client.ts Normal file
View File

@ -0,0 +1,13 @@
import axios from "axios";
const token =
"eyJhbGciOiJSUzUxMiJ9.eyJpc3MiOiJIYWxvIE93bmVyIiwic3ViIjoiYWRtaW4iLCJleHAiOjE2NTU4OTQxNTQsImlhdCI6MTY1NTgwNzc1NCwic2NvcGUiOlsiUk9MRV9zdXBlci1yb2xlIl19.Gnj0rM8DU2bP1KcgKBUVaKf6zs1pDqGxYvii9zxG4lFv4rVZ_uNGXyfhi9V10vRK0GM4v4NEuMtX9-DYnqAV0wR2JcoFevPrJnHHWsvnFrOQm32qeMpew3PsZ5-YAwi9n8Y9GpAcQz_6aWsEuRwm9w5CC3A67CrYPfCK5qwuR5FFLfiMRqPAqNNuZ4r2IfoSZUvXy4HxhUS-01J2BCqP3-hbdN_-tFHCDxtIO637a51EsCmRItY5wSVNmwYPaPOYV7lbHxzBIKXw5RNXg6SrQCSLTVaaJCXsZjwIirk02RQACr6oqTHPbriBVuu-SIgPXS5PJ9i4VaMCn-z8t-oZlQ";
const axiosInstance = axios.create({
headers: {
Authorization: `Bearer ${token}`,
},
baseURL: "http://localhost:8090",
});
export default axiosInstance;