mirror of https://github.com/halo-dev/halo-admin
Merge branch 'main' into perf/username-validation
commit
8f79ece3a1
|
@ -78,6 +78,7 @@
|
||||||
"transliteration": "^2.3.5",
|
"transliteration": "^2.3.5",
|
||||||
"vue": "^3.2.45",
|
"vue": "^3.2.45",
|
||||||
"vue-grid-layout": "3.0.0-beta1",
|
"vue-grid-layout": "3.0.0-beta1",
|
||||||
|
"vue-i18n": "^9.2.2",
|
||||||
"vue-router": "^4.1.6",
|
"vue-router": "^4.1.6",
|
||||||
"vuedraggable": "^4.1.0"
|
"vuedraggable": "^4.1.0"
|
||||||
},
|
},
|
||||||
|
|
|
@ -98,6 +98,7 @@ importers:
|
||||||
vitest: ^0.25.3
|
vitest: ^0.25.3
|
||||||
vue: ^3.2.45
|
vue: ^3.2.45
|
||||||
vue-grid-layout: 3.0.0-beta1
|
vue-grid-layout: 3.0.0-beta1
|
||||||
|
vue-i18n: ^9.2.2
|
||||||
vue-router: ^4.1.6
|
vue-router: ^4.1.6
|
||||||
vue-tsc: ^1.0.24
|
vue-tsc: ^1.0.24
|
||||||
vuedraggable: ^4.1.0
|
vuedraggable: ^4.1.0
|
||||||
|
@ -147,6 +148,7 @@ importers:
|
||||||
transliteration: 2.3.5
|
transliteration: 2.3.5
|
||||||
vue: 3.2.45
|
vue: 3.2.45
|
||||||
vue-grid-layout: 3.0.0-beta1_farzh4kmmmdsqeu7trbjloi3zi
|
vue-grid-layout: 3.0.0-beta1_farzh4kmmmdsqeu7trbjloi3zi
|
||||||
|
vue-i18n: 9.2.2_vue@3.2.45
|
||||||
vue-router: 4.1.6_vue@3.2.45
|
vue-router: 4.1.6_vue@3.2.45
|
||||||
vuedraggable: 4.1.0_vue@3.2.45
|
vuedraggable: 4.1.0_vue@3.2.45
|
||||||
devDependencies:
|
devDependencies:
|
||||||
|
@ -2632,6 +2634,44 @@ packages:
|
||||||
resolution: {integrity: sha512-sZAW08CkqgvqRjUIaLRjScjObcCzN9D75yekLA21EClYAZIhi4A+GEt2z/WqOCOksTaEPLYmQyhkpXcboc0LhQ==}
|
resolution: {integrity: sha512-sZAW08CkqgvqRjUIaLRjScjObcCzN9D75yekLA21EClYAZIhi4A+GEt2z/WqOCOksTaEPLYmQyhkpXcboc0LhQ==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/@intlify/core-base/9.2.2:
|
||||||
|
resolution: {integrity: sha512-JjUpQtNfn+joMbrXvpR4hTF8iJQ2sEFzzK3KIESOx+f+uwIjgw20igOyaIdhfsVVBCds8ZM64MoeNSx+PHQMkA==}
|
||||||
|
engines: {node: '>= 14'}
|
||||||
|
dependencies:
|
||||||
|
'@intlify/devtools-if': 9.2.2
|
||||||
|
'@intlify/message-compiler': 9.2.2
|
||||||
|
'@intlify/shared': 9.2.2
|
||||||
|
'@intlify/vue-devtools': 9.2.2
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/@intlify/devtools-if/9.2.2:
|
||||||
|
resolution: {integrity: sha512-4ttr/FNO29w+kBbU7HZ/U0Lzuh2cRDhP8UlWOtV9ERcjHzuyXVZmjyleESK6eVP60tGC9QtQW9yZE+JeRhDHkg==}
|
||||||
|
engines: {node: '>= 14'}
|
||||||
|
dependencies:
|
||||||
|
'@intlify/shared': 9.2.2
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/@intlify/message-compiler/9.2.2:
|
||||||
|
resolution: {integrity: sha512-IUrQW7byAKN2fMBe8z6sK6riG1pue95e5jfokn8hA5Q3Bqy4MBJ5lJAofUsawQJYHeoPJ7svMDyBaVJ4d0GTtA==}
|
||||||
|
engines: {node: '>= 14'}
|
||||||
|
dependencies:
|
||||||
|
'@intlify/shared': 9.2.2
|
||||||
|
source-map: 0.6.1
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/@intlify/shared/9.2.2:
|
||||||
|
resolution: {integrity: sha512-wRwTpsslgZS5HNyM7uDQYZtxnbI12aGiBZURX3BTR9RFIKKRWpllTsgzHWvj3HKm3Y2Sh5LPC1r0PDCKEhVn9Q==}
|
||||||
|
engines: {node: '>= 14'}
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/@intlify/vue-devtools/9.2.2:
|
||||||
|
resolution: {integrity: sha512-+dUyqyCHWHb/UcvY1MlIpO87munedm3Gn6E9WWYdWrMuYLcoIoOEVDWSS8xSwtlPU+kA+MEQTP6Q1iI/ocusJg==}
|
||||||
|
engines: {node: '>= 14'}
|
||||||
|
dependencies:
|
||||||
|
'@intlify/core-base': 9.2.2
|
||||||
|
'@intlify/shared': 9.2.2
|
||||||
|
dev: false
|
||||||
|
|
||||||
/@istanbuljs/schema/0.1.3:
|
/@istanbuljs/schema/0.1.3:
|
||||||
resolution: {integrity: sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==}
|
resolution: {integrity: sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
|
@ -11029,6 +11069,19 @@ packages:
|
||||||
- '@interactjs/utils'
|
- '@interactjs/utils'
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/vue-i18n/9.2.2_vue@3.2.45:
|
||||||
|
resolution: {integrity: sha512-yswpwtj89rTBhegUAv9Mu37LNznyu3NpyLQmozF3i1hYOhwpG8RjcjIFIIfnu+2MDZJGSZPXaKWvnQA71Yv9TQ==}
|
||||||
|
engines: {node: '>= 14'}
|
||||||
|
peerDependencies:
|
||||||
|
vue: ^3.0.0
|
||||||
|
dependencies:
|
||||||
|
'@intlify/core-base': 9.2.2
|
||||||
|
'@intlify/shared': 9.2.2
|
||||||
|
'@intlify/vue-devtools': 9.2.2
|
||||||
|
'@vue/devtools-api': 6.4.5
|
||||||
|
vue: 3.2.45
|
||||||
|
dev: false
|
||||||
|
|
||||||
/vue-resize/2.0.0-alpha.1_vue@3.2.45:
|
/vue-resize/2.0.0-alpha.1_vue@3.2.45:
|
||||||
resolution: {integrity: sha512-7+iqOueLU7uc9NrMfrzbG8hwMqchfVfSzpVlCMeJQe4pyibqyoifDNbKTZvwxZKDvGkB+PdFeKvnGZMoEb8esg==}
|
resolution: {integrity: sha512-7+iqOueLU7uc9NrMfrzbG8hwMqchfVfSzpVlCMeJQe4pyibqyoifDNbKTZvwxZKDvGkB+PdFeKvnGZMoEb8esg==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
|
|
|
@ -36,6 +36,7 @@ const props = withDefaults(
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(event: "uploaded", response: SuccessResponse): void;
|
(event: "uploaded", response: SuccessResponse): void;
|
||||||
|
(event: "error", file, response): void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const uppy = computed(() => {
|
const uppy = computed(() => {
|
||||||
|
@ -72,6 +73,10 @@ uppy.value.on("upload-success", (_, response: SuccessResponse) => {
|
||||||
emit("uploaded", response);
|
emit("uploaded", response);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
uppy.value.on("upload-error", (file, _, response) => {
|
||||||
|
emit("error", file, response);
|
||||||
|
});
|
||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
uppy.value.close({ reason: "unmount" });
|
uppy.value.close({ reason: "unmount" });
|
||||||
});
|
});
|
||||||
|
|
|
@ -18,7 +18,7 @@ import {
|
||||||
useRouter,
|
useRouter,
|
||||||
type RouteRecordRaw,
|
type RouteRecordRaw,
|
||||||
} from "vue-router";
|
} from "vue-router";
|
||||||
import { computed, onMounted, onUnmounted, ref } from "vue";
|
import { computed, nextTick, onMounted, onUnmounted, ref, watch } from "vue";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import GlobalSearchModal from "@/components/global-search/GlobalSearchModal.vue";
|
import GlobalSearchModal from "@/components/global-search/GlobalSearchModal.vue";
|
||||||
import LoginModal from "@/components/login/LoginModal.vue";
|
import LoginModal from "@/components/login/LoginModal.vue";
|
||||||
|
@ -28,6 +28,8 @@ import { useRoleStore } from "@/stores/role";
|
||||||
import { hasPermission } from "@/utils/permission";
|
import { hasPermission } from "@/utils/permission";
|
||||||
import { useUserStore } from "@/stores/user";
|
import { useUserStore } from "@/stores/user";
|
||||||
import { rbacAnnotations } from "@/constants/annotations";
|
import { rbacAnnotations } from "@/constants/annotations";
|
||||||
|
import { useScroll } from "@vueuse/core";
|
||||||
|
import { defineStore } from "pinia";
|
||||||
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
@ -189,6 +191,31 @@ const generateMenus = () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
onMounted(generateMenus);
|
onMounted(generateMenus);
|
||||||
|
|
||||||
|
// store scroll position
|
||||||
|
const navbarScroller = ref();
|
||||||
|
const { y } = useScroll(navbarScroller);
|
||||||
|
|
||||||
|
const useNavbarScrollStore = defineStore("navbar", {
|
||||||
|
state: () => ({
|
||||||
|
y: 0,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
const navbarScrollStore = useNavbarScrollStore();
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => y.value,
|
||||||
|
() => {
|
||||||
|
navbarScrollStore.y = y.value;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
nextTick(() => {
|
||||||
|
y.value = navbarScrollStore.y;
|
||||||
|
});
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
@ -203,7 +230,7 @@ onMounted(generateMenus);
|
||||||
/>
|
/>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex-1 overflow-y-auto">
|
<div ref="navbarScroller" class="flex-1 overflow-y-auto">
|
||||||
<div class="px-3">
|
<div class="px-3">
|
||||||
<div
|
<div
|
||||||
class="flex cursor-pointer items-center rounded bg-gray-100 p-2 text-gray-400 transition-all hover:text-gray-900"
|
class="flex cursor-pointer items-center rounded bg-gray-100 p-2 text-gray-400 transition-all hover:text-gray-900"
|
||||||
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
import { createI18n } from "vue-i18n";
|
||||||
|
import zh from "./lang/zh";
|
||||||
|
|
||||||
|
const messages = {
|
||||||
|
zh,
|
||||||
|
};
|
||||||
|
|
||||||
|
const i18n = createI18n({
|
||||||
|
legacy: false,
|
||||||
|
locale: "zh",
|
||||||
|
messages,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default i18n;
|
|
@ -0,0 +1,73 @@
|
||||||
|
const zh = {
|
||||||
|
rbac: {
|
||||||
|
"Attachments Management": "附件",
|
||||||
|
"Attachment Manage": "附件管理",
|
||||||
|
"Attachment View": "附件查看",
|
||||||
|
"role-template-view-attachments": "附件查看",
|
||||||
|
|
||||||
|
"Comments Management": "评论",
|
||||||
|
"Comment Manage": "评论管理",
|
||||||
|
"Comment View": "评论查看",
|
||||||
|
"role-template-view-comments": "评论查看",
|
||||||
|
|
||||||
|
"ConfigMaps Management": "配置",
|
||||||
|
"ConfigMap Manage": "配置管理",
|
||||||
|
"ConfigMap View": "配置查看",
|
||||||
|
"role-template-view-configmaps": "配置查看",
|
||||||
|
|
||||||
|
"Menus Management": "菜单",
|
||||||
|
"Menu Manage": "菜单管理",
|
||||||
|
"Menu View": "菜单查看",
|
||||||
|
"role-template-view-menus": "菜单查看",
|
||||||
|
|
||||||
|
"Permissions Management": "权限",
|
||||||
|
"Permissions Manage": "权限管理",
|
||||||
|
"Permissions View": "权限查看",
|
||||||
|
"role-template-view-permissions": "权限查看",
|
||||||
|
"role-template-manage-permissions": "权限管理",
|
||||||
|
|
||||||
|
"Plugins Management": "插件",
|
||||||
|
"Plugin Manage": "插件管理",
|
||||||
|
"Plugin View": "插件查看",
|
||||||
|
"role-template-view-plugins": "插件查看",
|
||||||
|
|
||||||
|
"Posts Management": "文章",
|
||||||
|
"Post Manage": "文章管理",
|
||||||
|
"Post View": "文章查看",
|
||||||
|
"role-template-view-posts": "文章查看",
|
||||||
|
"role-template-manage-snaphosts": "版本管理",
|
||||||
|
"role-template-view-snaphosts": "版本查看",
|
||||||
|
"role-template-manage-tags": "标签管理",
|
||||||
|
"role-template-view-tags": "标签查看",
|
||||||
|
"role-template-manage-categories": "分类管理",
|
||||||
|
"role-template-view-categories": "分类查看",
|
||||||
|
|
||||||
|
"Roles Management": "角色",
|
||||||
|
"Role Manage": "角色管理",
|
||||||
|
"Role View": "角色查看",
|
||||||
|
"role-template-view-roles": "角色查看",
|
||||||
|
|
||||||
|
"Settings Management": "设置表单",
|
||||||
|
"Setting Manage": "设置表单管理",
|
||||||
|
"Setting View": "设置表单查看",
|
||||||
|
"role-template-view-settings": "设置表单查看",
|
||||||
|
|
||||||
|
"SinglePages Management": "页面",
|
||||||
|
"SinglePage Manage": "页面管理",
|
||||||
|
"SinglePage View": "页面查看",
|
||||||
|
"role-template-view-singlepages": "页面查看",
|
||||||
|
|
||||||
|
"Themes Management": "主题",
|
||||||
|
"Theme Manage": "主题管理",
|
||||||
|
"Theme View": "主题查看",
|
||||||
|
"role-template-view-themes": "主题查看",
|
||||||
|
|
||||||
|
"Users Management": "用户",
|
||||||
|
"User manage": "用户管理",
|
||||||
|
"User View": "用户查看",
|
||||||
|
"role-template-view-users": "用户查看",
|
||||||
|
"role-template-change-password": "修改密码",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default zh;
|
|
@ -20,12 +20,14 @@ import { useThemeStore } from "./stores/theme";
|
||||||
import { useSystemStatesStore } from "./stores/system-states";
|
import { useSystemStatesStore } from "./stores/system-states";
|
||||||
import { useUserStore } from "./stores/user";
|
import { useUserStore } from "./stores/user";
|
||||||
import { useSystemConfigMapStore } from "./stores/system-configmap";
|
import { useSystemConfigMapStore } from "./stores/system-configmap";
|
||||||
|
import i18n from "./locales";
|
||||||
|
|
||||||
const app = createApp(App);
|
const app = createApp(App);
|
||||||
|
|
||||||
setupComponents(app);
|
setupComponents(app);
|
||||||
|
|
||||||
app.use(createPinia());
|
app.use(createPinia());
|
||||||
|
app.use(i18n);
|
||||||
|
|
||||||
function registerModule(pluginModule: PluginModule, core: boolean) {
|
function registerModule(pluginModule: PluginModule, core: boolean) {
|
||||||
if (pluginModule.components) {
|
if (pluginModule.components) {
|
||||||
|
|
|
@ -347,37 +347,37 @@ function handleClearKeyword() {
|
||||||
</template>
|
</template>
|
||||||
<template #start>
|
<template #start>
|
||||||
<VEntityField :title="post.post.spec.title" width="27rem">
|
<VEntityField :title="post.post.spec.title" width="27rem">
|
||||||
<template #extra>
|
|
||||||
<VSpace class="mt-1 sm:mt-0">
|
|
||||||
<PostTag
|
|
||||||
v-for="(tag, tagIndex) in post.tags"
|
|
||||||
:key="tagIndex"
|
|
||||||
:tag="tag"
|
|
||||||
route
|
|
||||||
></PostTag>
|
|
||||||
</VSpace>
|
|
||||||
</template>
|
|
||||||
<template #description>
|
<template #description>
|
||||||
<VSpace class="flex-wrap !gap-y-1">
|
<div class="flex flex-col gap-1.5">
|
||||||
<p
|
<VSpace class="flex-wrap !gap-y-1">
|
||||||
v-if="post.categories.length"
|
<p
|
||||||
class="inline-flex flex-wrap gap-1 text-xs text-gray-500"
|
v-if="post.categories.length"
|
||||||
>
|
class="inline-flex flex-wrap gap-1 text-xs text-gray-500"
|
||||||
分类:<span
|
|
||||||
v-for="(category, categoryIndex) in post.categories"
|
|
||||||
:key="categoryIndex"
|
|
||||||
class="cursor-pointer hover:text-gray-900"
|
|
||||||
>
|
>
|
||||||
{{ category.spec.displayName }}
|
分类:<span
|
||||||
|
v-for="(category, categoryIndex) in post.categories"
|
||||||
|
:key="categoryIndex"
|
||||||
|
class="cursor-pointer hover:text-gray-900"
|
||||||
|
>
|
||||||
|
{{ category.spec.displayName }}
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
|
<span class="text-xs text-gray-500">
|
||||||
|
访问量 {{ post.stats.visit || 0 }}
|
||||||
</span>
|
</span>
|
||||||
</p>
|
<span class="text-xs text-gray-500">
|
||||||
<span class="text-xs text-gray-500">
|
评论 {{ post.stats.totalComment || 0 }}
|
||||||
访问量 {{ post.stats.visit || 0 }}
|
</span>
|
||||||
</span>
|
</VSpace>
|
||||||
<span class="text-xs text-gray-500">
|
<VSpace v-if="post.tags.length" class="flex-wrap">
|
||||||
评论 {{ post.stats.totalComment || 0 }}
|
<PostTag
|
||||||
</span>
|
v-for="(tag, tagIndex) in post.tags"
|
||||||
</VSpace>
|
:key="tagIndex"
|
||||||
|
:tag="tag"
|
||||||
|
route
|
||||||
|
></PostTag>
|
||||||
|
</VSpace>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</VEntityField>
|
</VEntityField>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -783,12 +783,6 @@ const hasFilters = computed(() => {
|
||||||
>
|
>
|
||||||
<VStatusDot state="success" animate />
|
<VStatusDot state="success" animate />
|
||||||
</RouterLink>
|
</RouterLink>
|
||||||
<PostTag
|
|
||||||
v-for="(tag, tagIndex) in post.tags"
|
|
||||||
:key="tagIndex"
|
|
||||||
:tag="tag"
|
|
||||||
route
|
|
||||||
></PostTag>
|
|
||||||
<a
|
<a
|
||||||
v-if="post.post.status?.permalink"
|
v-if="post.post.status?.permalink"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
|
@ -801,35 +795,45 @@ const hasFilters = computed(() => {
|
||||||
</VSpace>
|
</VSpace>
|
||||||
</template>
|
</template>
|
||||||
<template #description>
|
<template #description>
|
||||||
<VSpace class="flex-wrap !gap-y-1">
|
<div class="flex flex-col gap-1.5">
|
||||||
<p
|
<VSpace class="flex-wrap !gap-y-1">
|
||||||
v-if="post.categories.length"
|
<p
|
||||||
class="inline-flex flex-wrap gap-1 text-xs text-gray-500"
|
v-if="post.categories.length"
|
||||||
>
|
class="inline-flex flex-wrap gap-1 text-xs text-gray-500"
|
||||||
分类:<a
|
|
||||||
v-for="(category, categoryIndex) in post.categories"
|
|
||||||
:key="categoryIndex"
|
|
||||||
:href="category.status?.permalink"
|
|
||||||
:title="category.status?.permalink"
|
|
||||||
target="_blank"
|
|
||||||
class="cursor-pointer hover:text-gray-900"
|
|
||||||
>
|
>
|
||||||
{{ category.spec.displayName }}
|
分类:<a
|
||||||
</a>
|
v-for="(category, categoryIndex) in post.categories"
|
||||||
</p>
|
:key="categoryIndex"
|
||||||
<span class="text-xs text-gray-500">
|
:href="category.status?.permalink"
|
||||||
访问量 {{ post.stats.visit || 0 }}
|
:title="category.status?.permalink"
|
||||||
</span>
|
target="_blank"
|
||||||
<span class="text-xs text-gray-500">
|
class="cursor-pointer hover:text-gray-900"
|
||||||
评论 {{ post.stats.totalComment || 0 }}
|
>
|
||||||
</span>
|
{{ category.spec.displayName }}
|
||||||
<span
|
</a>
|
||||||
v-if="post.post.spec.pinned"
|
</p>
|
||||||
class="text-xs text-gray-500"
|
<span class="text-xs text-gray-500">
|
||||||
>
|
访问量 {{ post.stats.visit || 0 }}
|
||||||
已置顶
|
</span>
|
||||||
</span>
|
<span class="text-xs text-gray-500">
|
||||||
</VSpace>
|
评论 {{ post.stats.totalComment || 0 }}
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
v-if="post.post.spec.pinned"
|
||||||
|
class="text-xs text-gray-500"
|
||||||
|
>
|
||||||
|
已置顶
|
||||||
|
</span>
|
||||||
|
</VSpace>
|
||||||
|
<VSpace v-if="post.tags.length" class="flex-wrap">
|
||||||
|
<PostTag
|
||||||
|
v-for="(tag, tagIndex) in post.tags"
|
||||||
|
:key="tagIndex"
|
||||||
|
:tag="tag"
|
||||||
|
route
|
||||||
|
></PostTag>
|
||||||
|
</VSpace>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</VEntityField>
|
</VEntityField>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -78,6 +78,7 @@ const handleCopy = () => {
|
||||||
- 构建时间:${formatDatetime(info.value?.build?.time)}
|
- 构建时间:${formatDatetime(info.value?.build?.time)}
|
||||||
- Git Commit:${info.value?.git?.commit.id}
|
- Git Commit:${info.value?.git?.commit.id}
|
||||||
- Java:${info.value?.java.runtime.name} / ${info.value?.java.runtime.version}
|
- Java:${info.value?.java.runtime.name} / ${info.value?.java.runtime.version}
|
||||||
|
- 数据库:${info.value?.database.name} / ${info.value?.database.version}
|
||||||
- 操作系统:${info.value?.os.name} / ${info.value?.os.version}
|
- 操作系统:${info.value?.os.name} / ${info.value?.os.version}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
@ -85,6 +86,28 @@ const handleCopy = () => {
|
||||||
|
|
||||||
Toast.success("复制成功");
|
Toast.success("复制成功");
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleDownloadLogfile = () => {
|
||||||
|
axios
|
||||||
|
.get(`${import.meta.env.VITE_API_URL}/actuator/logfile`)
|
||||||
|
.then((response) => {
|
||||||
|
const blob = new Blob([response.data]);
|
||||||
|
const downloadElement = document.createElement("a");
|
||||||
|
const href = window.URL.createObjectURL(blob);
|
||||||
|
downloadElement.href = href;
|
||||||
|
downloadElement.download = `halo-log-${formatDatetime(new Date())}.log`;
|
||||||
|
document.body.appendChild(downloadElement);
|
||||||
|
downloadElement.click();
|
||||||
|
document.body.removeChild(downloadElement);
|
||||||
|
window.URL.revokeObjectURL(href);
|
||||||
|
|
||||||
|
Toast.success("下载成功");
|
||||||
|
})
|
||||||
|
.catch((e) => {
|
||||||
|
Toast.error("下载失败");
|
||||||
|
console.log("Failed to download log file.", e);
|
||||||
|
});
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
@ -225,6 +248,14 @@ const handleCopy = () => {
|
||||||
{{ info.java.runtime.name }} / {{ info.java.runtime.version }}
|
{{ info.java.runtime.name }} / {{ info.java.runtime.version }}
|
||||||
</dd>
|
</dd>
|
||||||
</div>
|
</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">
|
||||||
|
{{ [info.database.name, info.database.version].join(" / ") }}
|
||||||
|
</dd>
|
||||||
|
</div>
|
||||||
<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"
|
class="bg-white px-4 py-5 hover:bg-gray-50 sm:grid sm:grid-cols-6 sm:gap-4 sm:px-6"
|
||||||
>
|
>
|
||||||
|
@ -233,6 +264,16 @@ const handleCopy = () => {
|
||||||
{{ info.os.name }} {{ info.os.version }} / {{ info.os.arch }}
|
{{ info.os.name }} {{ info.os.version }} / {{ info.os.arch }}
|
||||||
</dd>
|
</dd>
|
||||||
</div>
|
</div>
|
||||||
|
<div
|
||||||
|
class="items-center 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">
|
||||||
|
<VButton size="sm" @click="handleDownloadLogfile()">
|
||||||
|
下载
|
||||||
|
</VButton>
|
||||||
|
</dd>
|
||||||
|
</div>
|
||||||
</dl>
|
</dl>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -12,6 +12,12 @@ export interface Info {
|
||||||
build?: Build;
|
build?: Build;
|
||||||
java: Java;
|
java: Java;
|
||||||
os: Os;
|
os: Os;
|
||||||
|
database: Database;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Database {
|
||||||
|
name: string;
|
||||||
|
version: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Commit {
|
export interface Commit {
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { VModal, Dialog } from "@halo-dev/components";
|
import { VModal, Dialog, Toast } from "@halo-dev/components";
|
||||||
import UppyUpload from "@/components/upload/UppyUpload.vue";
|
import UppyUpload from "@/components/upload/UppyUpload.vue";
|
||||||
import { apiClient } from "@/utils/api-client";
|
import { apiClient } from "@/utils/api-client";
|
||||||
import type { Plugin } from "@halo-dev/api-client";
|
import type { Plugin } from "@halo-dev/api-client";
|
||||||
import { computed, ref, watch } from "vue";
|
import { computed, ref, watch } from "vue";
|
||||||
import type { SuccessResponse } from "@uppy/core";
|
import type { SuccessResponse, ErrorResponse } from "@uppy/core";
|
||||||
|
import type { UppyFile } from "@uppy/utils";
|
||||||
|
|
||||||
const props = withDefaults(
|
const props = withDefaults(
|
||||||
defineProps<{
|
defineProps<{
|
||||||
|
@ -76,6 +77,40 @@ const onUploaded = async (response: SuccessResponse) => {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
interface PluginInstallationErrorResponse {
|
||||||
|
detail: string;
|
||||||
|
instance: string;
|
||||||
|
pluginName: string;
|
||||||
|
requestId: string;
|
||||||
|
status: number;
|
||||||
|
timestamp: string;
|
||||||
|
title: string;
|
||||||
|
type: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const PLUGIN_ALREADY_EXISTS_TYPE =
|
||||||
|
"https://halo.run/probs/plugin-alreay-exists";
|
||||||
|
|
||||||
|
const onError = (file: UppyFile<unknown>, response: ErrorResponse) => {
|
||||||
|
const body = response.body as PluginInstallationErrorResponse;
|
||||||
|
if (body.type === PLUGIN_ALREADY_EXISTS_TYPE) {
|
||||||
|
Dialog.info({
|
||||||
|
title: "插件已存在",
|
||||||
|
description: "当前安装的插件已存在,是否升级?",
|
||||||
|
onConfirm: async () => {
|
||||||
|
await apiClient.plugin.upgradePlugin({
|
||||||
|
name: body.pluginName,
|
||||||
|
file: file.data as File,
|
||||||
|
});
|
||||||
|
|
||||||
|
Toast.success("升级成功");
|
||||||
|
|
||||||
|
window.location.reload();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => props.visible,
|
() => props.visible,
|
||||||
(newValue) => {
|
(newValue) => {
|
||||||
|
@ -106,6 +141,7 @@ watch(
|
||||||
:endpoint="endpoint"
|
:endpoint="endpoint"
|
||||||
auto-proceed
|
auto-proceed
|
||||||
@uploaded="onUploaded"
|
@uploaded="onUploaded"
|
||||||
|
@error="onError"
|
||||||
/>
|
/>
|
||||||
</VModal>
|
</VModal>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -249,7 +249,7 @@ onMounted(() => {
|
||||||
>
|
>
|
||||||
<dt class="text-sm font-medium text-gray-900">
|
<dt class="text-sm font-medium text-gray-900">
|
||||||
<div>
|
<div>
|
||||||
{{ group.module }}
|
{{ $t(`rbac.${group.module}`, group.module as string) }}
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="
|
v-if="
|
||||||
|
@ -300,9 +300,16 @@ onMounted(() => {
|
||||||
<div class="flex flex-1 flex-col gap-y-3">
|
<div class="flex flex-1 flex-col gap-y-3">
|
||||||
<span class="font-medium text-gray-900">
|
<span class="font-medium text-gray-900">
|
||||||
{{
|
{{
|
||||||
role.metadata.annotations?.[
|
$t(
|
||||||
rbacAnnotations.DISPLAY_NAME
|
`rbac.${
|
||||||
]
|
role.metadata.annotations?.[
|
||||||
|
rbacAnnotations.DISPLAY_NAME
|
||||||
|
]
|
||||||
|
}`,
|
||||||
|
role.metadata.annotations?.[
|
||||||
|
rbacAnnotations.DISPLAY_NAME
|
||||||
|
] as string
|
||||||
|
)
|
||||||
}}
|
}}
|
||||||
</span>
|
</span>
|
||||||
<span
|
<span
|
||||||
|
@ -319,7 +326,11 @@ onMounted(() => {
|
||||||
role.metadata.annotations?.[
|
role.metadata.annotations?.[
|
||||||
rbacAnnotations.DEPENDENCIES
|
rbacAnnotations.DEPENDENCIES
|
||||||
]
|
]
|
||||||
).join(", ")
|
)
|
||||||
|
.map((item: string) =>
|
||||||
|
$t(`rbac.${item}`, item as string)
|
||||||
|
)
|
||||||
|
.join(",")
|
||||||
}}
|
}}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -156,7 +156,9 @@ const handleResetForm = () => {
|
||||||
class="flex flex-col gap-3 bg-white py-5 first:pt-0"
|
class="flex flex-col gap-3 bg-white py-5 first:pt-0"
|
||||||
>
|
>
|
||||||
<dt class="text-sm font-medium text-gray-900">
|
<dt class="text-sm font-medium text-gray-900">
|
||||||
<div>{{ group.module }}</div>
|
<div>
|
||||||
|
{{ $t(`rbac.${group.module}`, group.module as string) }}
|
||||||
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="
|
v-if="
|
||||||
group.roles.length &&
|
group.roles.length &&
|
||||||
|
@ -197,9 +199,16 @@ const handleResetForm = () => {
|
||||||
<div class="flex flex-1 flex-col gap-y-3">
|
<div class="flex flex-1 flex-col gap-y-3">
|
||||||
<span class="font-medium text-gray-900">
|
<span class="font-medium text-gray-900">
|
||||||
{{
|
{{
|
||||||
roleTemplate.metadata.annotations?.[
|
$t(
|
||||||
rbacAnnotations.DISPLAY_NAME
|
`rbac.${
|
||||||
]
|
roleTemplate.metadata.annotations?.[
|
||||||
|
rbacAnnotations.DISPLAY_NAME
|
||||||
|
]
|
||||||
|
}`,
|
||||||
|
roleTemplate.metadata.annotations?.[
|
||||||
|
rbacAnnotations.DISPLAY_NAME
|
||||||
|
] as string
|
||||||
|
)
|
||||||
}}
|
}}
|
||||||
</span>
|
</span>
|
||||||
<span
|
<span
|
||||||
|
@ -216,7 +225,11 @@ const handleResetForm = () => {
|
||||||
roleTemplate.metadata.annotations?.[
|
roleTemplate.metadata.annotations?.[
|
||||||
rbacAnnotations.DEPENDENCIES
|
rbacAnnotations.DEPENDENCIES
|
||||||
]
|
]
|
||||||
).join(", ")
|
)
|
||||||
|
.map((item: string) =>
|
||||||
|
$t(`rbac.${item}`, item as string)
|
||||||
|
)
|
||||||
|
.join(",")
|
||||||
}}
|
}}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -65,6 +65,7 @@ const userStore = useUserStore();
|
||||||
const roleStore = useRoleStore();
|
const roleStore = useRoleStore();
|
||||||
|
|
||||||
const ANONYMOUSUSER_NAME = "anonymousUser";
|
const ANONYMOUSUSER_NAME = "anonymousUser";
|
||||||
|
const DELETEDUSER_NAME = "ghost";
|
||||||
|
|
||||||
const handleFetchUsers = async (options?: {
|
const handleFetchUsers = async (options?: {
|
||||||
mute?: boolean;
|
mute?: boolean;
|
||||||
|
@ -85,7 +86,10 @@ const handleFetchUsers = async (options?: {
|
||||||
page: users.value.page,
|
page: users.value.page,
|
||||||
size: users.value.size,
|
size: users.value.size,
|
||||||
keyword: keyword.value,
|
keyword: keyword.value,
|
||||||
fieldSelector: [`name!=${ANONYMOUSUSER_NAME}`],
|
fieldSelector: [
|
||||||
|
`name!=${ANONYMOUSUSER_NAME}`,
|
||||||
|
`name!=${DELETEDUSER_NAME}`,
|
||||||
|
],
|
||||||
sort: [selectedSortItem.value?.value].filter(
|
sort: [selectedSortItem.value?.value].filter(
|
||||||
(item) => !!item
|
(item) => !!item
|
||||||
) as string[],
|
) as string[],
|
||||||
|
|
Loading…
Reference in New Issue