2022-07-15 08:26:27 +00:00
|
|
|
import type { DirectiveBinding } from "vue";
|
2022-07-29 03:41:01 +00:00
|
|
|
import { createApp } from "vue";
|
2022-03-03 10:26:15 +00:00
|
|
|
import { createPinia } from "pinia";
|
|
|
|
import App from "./App.vue";
|
|
|
|
import router from "./router";
|
2022-06-24 15:05:49 +00:00
|
|
|
import type {
|
|
|
|
MenuGroupType,
|
|
|
|
MenuItemType,
|
|
|
|
Plugin,
|
2022-10-09 06:56:33 +00:00
|
|
|
} from "@halo-dev/console-shared";
|
2022-10-18 03:30:10 +00:00
|
|
|
import { Toast } from "@halo-dev/components";
|
2022-09-22 12:46:12 +00:00
|
|
|
import { apiClient } from "@/utils/api-client";
|
2022-06-24 15:05:49 +00:00
|
|
|
import { menus, minimenus, registerMenu } from "./router/menus.config";
|
2022-06-16 09:01:18 +00:00
|
|
|
// setup
|
|
|
|
import "./setup/setupStyles";
|
|
|
|
import { setupComponents } from "./setup/setupComponents";
|
2022-06-20 04:25:36 +00:00
|
|
|
// core modules
|
|
|
|
import { coreModules } from "./modules";
|
2022-06-23 03:28:23 +00:00
|
|
|
import { useScriptTag } from "@vueuse/core";
|
|
|
|
import { usePluginStore } from "@/stores/plugin";
|
2022-07-13 07:36:21 +00:00
|
|
|
import type { User } from "@halo-dev/api-client";
|
2022-07-15 08:26:27 +00:00
|
|
|
import { hasPermission } from "@/utils/permission";
|
|
|
|
import { useRoleStore } from "@/stores/role";
|
2022-06-17 06:12:15 +00:00
|
|
|
|
|
|
|
const app = createApp(App);
|
2022-03-03 10:26:15 +00:00
|
|
|
|
2022-06-16 09:01:18 +00:00
|
|
|
setupComponents(app);
|
2022-05-26 09:37:08 +00:00
|
|
|
|
2022-06-16 09:01:18 +00:00
|
|
|
app.use(createPinia());
|
2022-05-26 09:37:08 +00:00
|
|
|
|
2022-06-20 04:25:36 +00:00
|
|
|
function registerModule(pluginModule: Plugin) {
|
2022-06-17 06:12:15 +00:00
|
|
|
if (pluginModule.components) {
|
2022-06-20 04:25:36 +00:00
|
|
|
if (!Array.isArray(pluginModule.components)) {
|
|
|
|
console.error(`${pluginModule.name}: Plugin components must be an array`);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2022-06-17 06:12:15 +00:00
|
|
|
for (const component of pluginModule.components) {
|
|
|
|
component.name && app.component(component.name, component);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (pluginModule.routes) {
|
2022-06-20 04:25:36 +00:00
|
|
|
if (!Array.isArray(pluginModule.routes)) {
|
|
|
|
console.error(`${pluginModule.name}: Plugin routes must be an array`);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2022-06-17 06:12:15 +00:00
|
|
|
for (const route of pluginModule.routes) {
|
2022-08-09 04:05:40 +00:00
|
|
|
if ("parentName" in route) {
|
|
|
|
router.addRoute(route.parentName, route.route);
|
|
|
|
} else {
|
|
|
|
router.addRoute(route);
|
|
|
|
}
|
2022-06-17 06:12:15 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (pluginModule.menus) {
|
2022-06-20 04:25:36 +00:00
|
|
|
if (!Array.isArray(pluginModule.menus)) {
|
|
|
|
console.error(`${pluginModule.name}: Plugin menus must be an array`);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2022-06-17 06:12:15 +00:00
|
|
|
for (const group of pluginModule.menus) {
|
|
|
|
for (const menu of group.items) {
|
|
|
|
registerMenu(group.name, menu);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function loadCoreModules() {
|
2022-06-20 04:25:36 +00:00
|
|
|
coreModules.forEach(registerModule);
|
2022-06-17 06:12:15 +00:00
|
|
|
}
|
|
|
|
|
2022-06-23 03:28:23 +00:00
|
|
|
const pluginStore = usePluginStore();
|
|
|
|
|
2022-06-23 13:22:24 +00:00
|
|
|
function loadStyle(href: string) {
|
|
|
|
return new Promise(function (resolve, reject) {
|
|
|
|
let shouldAppend = false;
|
|
|
|
let el: HTMLLinkElement | null = document.querySelector(
|
|
|
|
'script[src="' + href + '"]'
|
|
|
|
);
|
|
|
|
if (!el) {
|
|
|
|
el = document.createElement("link");
|
|
|
|
el.rel = "stylesheet";
|
|
|
|
el.type = "text/css";
|
|
|
|
el.href = href;
|
|
|
|
shouldAppend = true;
|
|
|
|
} else if (el.hasAttribute("data-loaded")) {
|
|
|
|
resolve(el);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
el.addEventListener("error", reject);
|
|
|
|
el.addEventListener("abort", reject);
|
|
|
|
el.addEventListener("load", function loadStyleHandler() {
|
|
|
|
el?.setAttribute("data-loaded", "true");
|
|
|
|
resolve(el);
|
|
|
|
});
|
|
|
|
|
|
|
|
if (shouldAppend) document.head.prepend(el);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2022-07-25 12:02:52 +00:00
|
|
|
const pluginErrorMessages: Array<string> = [];
|
|
|
|
|
2022-06-20 07:08:17 +00:00
|
|
|
async function loadPluginModules() {
|
2022-07-14 08:48:54 +00:00
|
|
|
const { data } =
|
2022-07-13 07:36:21 +00:00
|
|
|
await apiClient.extension.plugin.listpluginHaloRunV1alpha1Plugin();
|
2022-06-23 03:28:23 +00:00
|
|
|
|
|
|
|
// Get all started plugins
|
2022-07-14 08:48:54 +00:00
|
|
|
const plugins = data.items.filter(
|
2022-07-13 07:36:21 +00:00
|
|
|
(plugin) => plugin.status?.phase === "STARTED" && plugin.spec.enabled
|
2022-06-23 03:28:23 +00:00
|
|
|
);
|
|
|
|
|
|
|
|
for (const plugin of plugins) {
|
2022-07-13 07:36:21 +00:00
|
|
|
const { entry, stylesheet } = plugin.status || {
|
|
|
|
entry: "",
|
|
|
|
stylesheet: "",
|
|
|
|
};
|
2022-06-23 03:28:23 +00:00
|
|
|
|
|
|
|
if (entry) {
|
2022-07-25 12:02:52 +00:00
|
|
|
try {
|
|
|
|
const { load } = useScriptTag(
|
|
|
|
`${import.meta.env.VITE_API_URL}${plugin.status?.entry}`
|
|
|
|
);
|
2022-07-25 13:14:56 +00:00
|
|
|
|
2022-07-25 12:02:52 +00:00
|
|
|
await load();
|
2022-07-25 13:14:56 +00:00
|
|
|
|
2022-07-25 12:02:52 +00:00
|
|
|
const pluginModule = window[plugin.metadata.name];
|
|
|
|
|
|
|
|
if (pluginModule) {
|
|
|
|
// @ts-ignore
|
|
|
|
plugin.spec.module = pluginModule;
|
|
|
|
registerModule(pluginModule);
|
|
|
|
}
|
|
|
|
} catch (e) {
|
2022-10-18 03:30:10 +00:00
|
|
|
const message = `${plugin.metadata.name}: 加载插件入口文件失败`;
|
2022-07-25 12:02:52 +00:00
|
|
|
console.error(message, e);
|
|
|
|
pluginErrorMessages.push(message);
|
2022-06-23 03:28:23 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-06-23 13:22:24 +00:00
|
|
|
if (stylesheet) {
|
2022-07-13 09:39:31 +00:00
|
|
|
try {
|
2022-07-25 02:41:24 +00:00
|
|
|
await loadStyle(`${import.meta.env.VITE_API_URL}${stylesheet}`);
|
2022-07-13 09:39:31 +00:00
|
|
|
} catch (e) {
|
2022-10-18 03:30:10 +00:00
|
|
|
const message = `${plugin.metadata.name}: 加载插件样式文件失败`;
|
2022-07-25 12:02:52 +00:00
|
|
|
console.error(message, e);
|
|
|
|
pluginErrorMessages.push(message);
|
2022-07-13 09:39:31 +00:00
|
|
|
}
|
2022-06-23 13:22:24 +00:00
|
|
|
}
|
|
|
|
|
2022-06-23 03:28:23 +00:00
|
|
|
pluginStore.registerPlugin(plugin);
|
|
|
|
}
|
2022-07-25 12:02:52 +00:00
|
|
|
|
|
|
|
if (pluginErrorMessages.length > 0) {
|
2022-10-18 03:30:10 +00:00
|
|
|
pluginErrorMessages.forEach((message) => {
|
|
|
|
Toast.error(message);
|
|
|
|
});
|
2022-07-25 12:02:52 +00:00
|
|
|
}
|
2022-06-17 06:12:15 +00:00
|
|
|
}
|
|
|
|
|
2022-07-04 04:33:00 +00:00
|
|
|
async function loadCurrentUser() {
|
2022-07-14 08:48:54 +00:00
|
|
|
const { data: user } = await apiClient.user.getCurrentUserDetail();
|
|
|
|
app.provide<User>("currentUser", user);
|
|
|
|
|
refactor: method parameters of api client (#605)
<!-- Thanks for sending a pull request! Here are some tips for you:
1. 如果这是你的第一次,请阅读我们的贡献指南:<https://github.com/halo-dev/halo/blob/master/CONTRIBUTING.md>。
1. If this is your first time, please read our contributor guidelines: <https://github.com/halo-dev/halo/blob/master/CONTRIBUTING.md>.
2. 请根据你解决问题的类型为 Pull Request 添加合适的标签。
2. Please label this pull request according to what type of issue you are addressing, especially if this is a release targeted pull request.
3. 请确保你已经添加并运行了适当的测试。
3. Ensure you have added or ran the appropriate tests for your PR.
-->
#### What type of PR is this?
/kind improvement
/milestone 2.0
<!--
添加其中一个类别:
Add one of the following kinds:
/kind bug
/kind cleanup
/kind documentation
/kind feature
/kind optimization
适当添加其中一个或多个类别(可选):
Optionally add one or more of the following kinds if applicable:
/kind api-change
/kind deprecation
/kind failing-test
/kind flake
/kind regression
-->
#### What this PR does / why we need it:
修改 api-client 的请求参数结构,改为所有参数由一个对象包裹,而不是将各个参数作为方法的参数,防止因为后端参数结构发生改变,或者生成 api-client 时参数顺序发生改变导致请求异常。如:
```diff
await apiClient.extension.storage.group.updatestorageHaloRunV1alpha1Group(
- formState.value.metadata.name,
- formState.value
+ {
+ name: formState.value.metadata.name,
+ group: formState.value,
+ }
);
```
#### Which issue(s) this PR fixes:
<!--
PR 合并时自动关闭 issue。
Automatically closes linked issue when PR is merged.
用法:`Fixes #<issue 号>`,或者 `Fixes (粘贴 issue 完整链接)`
Usage: `Fixes #<issue number>`, or `Fixes (paste link of issue)`.
-->
None
#### Screenshots:
<!--
如果此 PR 有 UI 的改动,最好截图说明这个 PR 的改动。
If there are UI changes to this PR, it is best to take a screenshot to illustrate the changes to this PR.
eg.
Before:
![screenshot-before](https://user-images.githubusercontent.com/screenshot.png)
After:
![screenshot-after](https://user-images.githubusercontent.com/screenshot.png)
-->
None
#### Special notes for your reviewer:
/cc @halo-dev/sig-halo-admin
#### Does this PR introduce a user-facing change?
<!--
如果当前 Pull Request 的修改不会造成用户侧的任何变更,在 `release-note` 代码块儿中填写 `NONE`。
否则请填写用户侧能够理解的 Release Note。如果当前 Pull Request 包含破坏性更新(Break Change),
Release Note 需要以 `action required` 开头。
If no, just write "NONE" in the release-note block below.
If yes, a release note is required:
Enter your extended release note in the block below. If the PR requires additional action from users switching to the new release, include the string "action required".
-->
```release-note
None
```
2022-09-06 02:26:11 +00:00
|
|
|
const { data: currentPermissions } = await apiClient.user.getPermissions({
|
|
|
|
name: "-",
|
|
|
|
});
|
2022-07-15 08:26:27 +00:00
|
|
|
const roleStore = useRoleStore();
|
|
|
|
roleStore.$patch({
|
|
|
|
permissions: currentPermissions,
|
|
|
|
});
|
|
|
|
app.directive(
|
|
|
|
"permission",
|
|
|
|
(el: HTMLElement, binding: DirectiveBinding<string[]>) => {
|
2022-09-30 11:03:38 +00:00
|
|
|
const uiPermissions = Array.from<string>(
|
|
|
|
currentPermissions.uiPermissions
|
|
|
|
);
|
2022-07-15 08:26:27 +00:00
|
|
|
const { value } = binding;
|
|
|
|
const { any, enable } = binding.modifiers;
|
|
|
|
|
|
|
|
if (hasPermission(uiPermissions, value, any)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
enable ? (el.style.backgroundColor = "red") : el.remove();
|
|
|
|
}
|
|
|
|
);
|
2022-07-04 04:33:00 +00:00
|
|
|
}
|
|
|
|
|
2022-06-20 07:08:17 +00:00
|
|
|
(async function () {
|
|
|
|
await initApp();
|
|
|
|
})();
|
2022-06-17 06:12:15 +00:00
|
|
|
|
|
|
|
async function initApp() {
|
2022-07-19 06:07:28 +00:00
|
|
|
// TODO 实验性
|
2022-07-19 03:18:47 +00:00
|
|
|
const theme = localStorage.getItem("theme");
|
|
|
|
if (theme) {
|
|
|
|
document.body.classList.add(theme);
|
|
|
|
}
|
|
|
|
|
2022-06-20 04:25:36 +00:00
|
|
|
try {
|
|
|
|
loadCoreModules();
|
2022-09-30 08:38:13 +00:00
|
|
|
|
|
|
|
try {
|
|
|
|
await loadPluginModules();
|
|
|
|
} catch (e) {
|
|
|
|
console.error("Failed to load plugins", e);
|
|
|
|
}
|
|
|
|
|
2022-07-04 04:33:00 +00:00
|
|
|
await loadCurrentUser();
|
2022-06-20 04:25:36 +00:00
|
|
|
} catch (e) {
|
|
|
|
console.error(e);
|
2022-06-23 03:28:23 +00:00
|
|
|
} finally {
|
2022-09-30 08:38:13 +00:00
|
|
|
app.provide<MenuGroupType[]>("menus", menus);
|
|
|
|
app.provide<MenuItemType[]>("minimenus", minimenus);
|
2022-10-11 09:00:14 +00:00
|
|
|
app.provide<string>("apiUrl", import.meta.env.VITE_API_URL);
|
2022-09-30 08:38:13 +00:00
|
|
|
|
2022-06-23 03:28:23 +00:00
|
|
|
app.use(router);
|
|
|
|
app.mount("#app");
|
2022-06-20 04:25:36 +00:00
|
|
|
}
|
2022-06-17 06:12:15 +00:00
|
|
|
}
|