Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add support for displaying enabled plugins on actuator page #4897

Merged
merged 3 commits into from Nov 27, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
265 changes: 200 additions & 65 deletions console/console-src/modules/system/actuator/Actuator.vue
Expand Up @@ -9,49 +9,80 @@ import {
Toast,
VDescription,
VDescriptionItem,
VTag,
VLoading,
} from "@halo-dev/components";
import { computed, onMounted, ref } from "vue";
import { computed } from "vue";
import type { Info, GlobalInfo, Startup } from "@/types";
import axios from "axios";
import { formatDatetime } from "@/utils/date";
import { useClipboard } from "@vueuse/core";
import { useI18n } from "vue-i18n";
import { useThemeStore } from "@console/stores/theme";
import type { Plugin } from "@halo-dev/api-client";
import { apiClient } from "@/utils/api-client";
import { useQuery } from "@tanstack/vue-query";
import { usePermission } from "@/utils/permission";

const { t } = useI18n();
const themeStore = useThemeStore();
const { currentUserHasPermission } = usePermission();

const info = ref<Info>();
const globalInfo = ref<GlobalInfo>();
const startup = ref<Startup>();
const { data: info } = useQuery<Info>({
queryKey: ["system-info"],
queryFn: async () => {
const { data } = await axios.get<Info>(
`${import.meta.env.VITE_API_URL}/actuator/info`,
{
withCredentials: true,
}
);
return data;
},
retry: 0,
});

const handleFetchActuatorInfo = async () => {
const { data } = await axios.get(
`${import.meta.env.VITE_API_URL}/actuator/info`,
{
withCredentials: true,
}
);
info.value = data;
};
const { data: globalInfo } = useQuery<GlobalInfo>({
queryKey: ["system-global-info"],
queryFn: async () => {
const { data } = await axios.get<GlobalInfo>(
`${import.meta.env.VITE_API_URL}/actuator/globalinfo`,
{
withCredentials: true,
}
);
return data;
},
retry: 0,
});

const handleFetchActuatorGlobalInfo = async () => {
const { data } = await axios.get(
`${import.meta.env.VITE_API_URL}/actuator/globalinfo`,
{
withCredentials: true,
}
);
globalInfo.value = data;
};
const { data: startup } = useQuery<Startup>({
queryKey: ["system-startup-info"],
queryFn: async () => {
const { data } = await axios.get<Startup>(
`${import.meta.env.VITE_API_URL}/actuator/startup`,
{
withCredentials: true,
}
);
return data;
},
retry: 0,
});

const handleFetchActuatorStartup = async () => {
const { data } = await axios.get(
`${import.meta.env.VITE_API_URL}/actuator/startup`,
{
withCredentials: true,
}
);
startup.value = data;
};
const { data: plugins, isLoading: isPluginsLoading } = useQuery<Plugin[]>({
queryKey: ["enabled-plugins"],
queryFn: async () => {
const { data } = await apiClient.plugin.listPlugins({
page: 0,
size: 0,
enabled: true,
});

return data.items;
},
enabled: computed(() => currentUserHasPermission(["system:plugins:view"])),
});

const isExternalUrlValid = computed(() => {
if (!globalInfo.value?.useAbsolutePermalink) {
Expand All @@ -67,43 +98,101 @@ const isExternalUrlValid = computed(() => {
return url.host === currentHost && url.protocol === currentProtocol;
});

onMounted(() => {
handleFetchActuatorInfo();
handleFetchActuatorGlobalInfo();
handleFetchActuatorStartup();
});

// copy system information to clipboard
const { copy, isSupported } = useClipboard({ legacy: true });

interface CopyItem {
label: string;
value?: string;
href?: string;
children?: CopyItem[];
}

const handleCopy = () => {
if (!isSupported.value) {
Toast.warning(t("core.actuator.actions.copy.toast_browser_not_supported"));
return;
}

const text = `
- ${t("core.actuator.copy_results.external_url", {
external_url: globalInfo.value?.externalUrl,
})}
- ${t("core.actuator.copy_results.start_time", {
start_time: formatDatetime(startup.value?.timeline.startTime),
})}
- ${t("core.actuator.fields.version", { version: info.value?.build?.version })}
- ${t("core.actuator.copy_results.build_time", {
build_time: formatDatetime(info.value?.build?.time),
})}
- Git Commit:${info.value?.git?.commit.id}
- Java:${info.value?.java.runtime.name} / ${info.value?.java.runtime.version}
- ${t("core.actuator.copy_results.database", {
database: [info.value?.database.name, info.value?.database.version].join(
" / "
),
})}
- ${t("core.actuator.copy_results.os", {
os: [info.value?.os.name, info.value?.os.version].join(" / "),
})}
`;
const copyItems: CopyItem[] = [
{
label: t("core.actuator.fields.external_url"),
value: globalInfo.value?.externalUrl || "",
},
{
label: t("core.actuator.fields.start_time"),
value: formatDatetime(startup.value?.timeline.startTime) || "",
},
{
label: t("core.actuator.fields.version"),
value: info.value?.build?.version || "",
},
{
label: t("core.actuator.fields.build_time"),
value: formatDatetime(info.value?.build?.time) || "",
},
{
label: "Git Commit",
value: info.value?.git?.commit.id || "",
},
{
label: "Java",
value:
[info.value?.java.runtime.name, info.value?.java.runtime.version]
.filter(Boolean)
.join(" / ")
.trim() || "",
},
{
label: t("core.actuator.fields.database"),
value:
[info.value?.database.name, info.value?.database.version]
.filter(Boolean)
.join(" / ") || "",
},
{
label: t("core.actuator.fields.os"),
value:
[info.value?.os.name, info.value?.os.version]
.filter(Boolean)
.join(" / ") || "",
},
{
label: t("core.actuator.fields.activated_theme"),
value: themeStore.activatedTheme?.spec.displayName || "",
href:
themeStore.activatedTheme?.spec.repo ||
themeStore.activatedTheme?.spec.homepage,
},
{
label: t("core.actuator.fields.enabled_plugins"),
children: plugins.value?.map((plugin) => ({
value: plugin.spec.displayName,
href: plugin.spec.repo || plugin.spec.homepage,
})) as CopyItem[],
},
];

const text = copyItems
.map((item) => {
if (item.children?.length) {
const childrenText = item.children
.map(
(child) =>
` - ${
child.href ? `[${child.value}](${child.href})` : child.value
}`
)
.filter(Boolean)
.join("\n");
return `- ${item.label}:\n${childrenText}`;
} else {
return `- ${item.label}: ${
item.href ? `[${item.value}](${item.href})` : item.value || ""
}`;
}
})
.join("\n");

copy(text);

Expand Down Expand Up @@ -182,17 +271,55 @@ const handleDownloadLogfile = () => {
</VAlert>
</VDescriptionItem>
<VDescriptionItem
v-if="startup?.timeline.startTime"
:label="$t('core.actuator.fields.start_time')"
:content="formatDatetime(startup?.timeline.startTime)"
/>
<VDescriptionItem
:label="$t('core.actuator.fields.timezone')"
:content="globalInfo?.timeZone"
/>
v-if="themeStore.activatedTheme"
:label="$t('core.actuator.fields.activated_theme')"
>
<VTag @click="$router.push({ name: 'ThemeDetail' })">
<template v-if="themeStore.activatedTheme.spec.logo" #leftIcon>
<img
class="h-3.5 w-3.5 rounded-sm"
:src="themeStore.activatedTheme.spec.logo"
:alt="themeStore.activatedTheme.spec.displayName"
/>
</template>
{{ themeStore.activatedTheme.spec.displayName }}
</VTag>
</VDescriptionItem>
<VDescriptionItem
:label="$t('core.actuator.fields.locale')"
:content="globalInfo?.locale"
/>
v-permission="['system:plugins:view']"
:label="$t('core.actuator.fields.enabled_plugins')"
>
<VLoading v-if="isPluginsLoading" />
<span v-else-if="!plugins?.length">
{{ $t("core.common.text.none") }}
</span>
<div v-else class="flex flex-wrap gap-1.5">
<VTag
v-for="plugin in plugins"
:key="plugin.metadata.name"
@click="
$router.push({
name: 'PluginDetail',
params: { name: plugin.metadata.name },
})
"
>
<template v-if="plugin.status?.logo" #leftIcon>
<img
class="h-3.5 w-3.5 rounded-sm"
:src="plugin.status?.logo"
:alt="plugin.spec.displayName"
/>
</template>
{{ plugin.spec.displayName }}
</VTag>
</div>
</VDescriptionItem>
</VDescription>
</div>
</div>
Expand Down Expand Up @@ -249,6 +376,14 @@ const handleDownloadLogfile = () => {
<VDescriptionItem :label="$t('core.actuator.fields.os')">
{{ info.os.name }} {{ info.os.version }} / {{ info.os.arch }}
</VDescriptionItem>
<VDescriptionItem
:label="$t('core.actuator.fields.timezone')"
:content="globalInfo?.timeZone"
/>
<VDescriptionItem
:label="$t('core.actuator.fields.locale')"
:content="globalInfo?.locale"
/>
<VDescriptionItem
:label="$t('core.actuator.fields.log')"
vertical-center
Expand Down
6 changes: 4 additions & 2 deletions console/src/locales/en.yaml
Expand Up @@ -1019,13 +1019,15 @@ core:
fields:
external_url: External url
start_time: Start time
timezone: Timezone
locale: Locale
timezone: System Timezone
locale: System Locale
version: Version
build_time: Build time
database: Database
os: Operating system
log: System log
activated_theme: Activated theme
enabled_plugins: Enabled plugins
fields_values:
external_url:
not_setup: Not setup
Expand Down
6 changes: 4 additions & 2 deletions console/src/locales/zh-CN.yaml
Expand Up @@ -1019,13 +1019,15 @@ core:
fields:
external_url: 外部访问地址
start_time: 启动时间
timezone: 时区
locale: 语言
timezone: 系统时区
locale: 系统语言
version: 版本
build_time: 构建时间
database: 数据库
os: 操作系统
log: 运行日志
activated_theme: 已激活主题
enabled_plugins: 已启动插件
fields_values:
external_url:
not_setup: 未设置
Expand Down
6 changes: 4 additions & 2 deletions console/src/locales/zh-TW.yaml
Expand Up @@ -1019,13 +1019,15 @@ core:
fields:
external_url: 外部訪問地址
start_time: 啟動時間
timezone: 時區
locale: 語言
timezone: 系統時區
locale: 系統語言
version: 版本
build_time: 構建時間
database: 資料庫
os: 操作系統
log: 運行日誌
activated_theme: 已啟動主題
enabled_plugins: 已啟動插件
fields_values:
external_url:
not_setup: 未設置
Expand Down