Refine setup page

pull/6739/head
Ryan Wang 2024-10-09 12:20:54 +08:00 committed by guqing
parent bba242332d
commit 5df755d4a8
29 changed files with 290 additions and 1052 deletions

View File

@ -2651,30 +2651,6 @@
]
}
},
"/apis/api.console.halo.run/v1alpha1/plugin-presets": {
"get": {
"description": "List all plugin presets in the system.",
"operationId": "ListPluginPresets",
"responses": {
"default": {
"content": {
"*/*": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/Plugin"
}
}
}
},
"description": "default response"
}
},
"tags": [
"PluginV1alpha1Console"
]
}
},
"/apis/api.console.halo.run/v1alpha1/plugins": {
"get": {
"description": "List plugins using query criteria and sort params",
@ -4309,38 +4285,6 @@
]
}
},
"/apis/api.console.halo.run/v1alpha1/system/initialize": {
"post": {
"description": "Initialize system",
"operationId": "initialize",
"requestBody": {
"content": {
"*/*": {
"schema": {
"$ref": "#/components/schemas/SystemInitializationRequest"
}
}
}
},
"responses": {
"201": {
"description": "System initialization successfully.",
"headers": {
"Location": {
"description": "Redirect URL.",
"schema": {
"type": "string"
},
"style": "simple"
}
}
}
},
"tags": [
"SystemV1alpha1Console"
]
}
},
"/apis/api.console.halo.run/v1alpha1/tags": {
"get": {
"description": "List Post Tags.",
@ -14722,6 +14666,41 @@
]
}
},
"/apis/uc.api.auth.halo.run/v1alpha1/user-connections/{registerId}/disconnect": {
"put": {
"description": "Disconnect my connection from a third-party platform.",
"operationId": "DisconnectMyConnection",
"parameters": [
{
"description": "The registration ID of the third-party platform.",
"in": "path",
"name": "registerId",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"default": {
"content": {
"*/*": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/UserConnection"
}
}
}
},
"description": "default response"
}
},
"tags": [
"UserConnectionV1alpha1Uc"
]
}
},
"/apis/uc.api.content.halo.run/v1alpha1/posts": {
"get": {
"description": "List posts owned by the current user.",
@ -20712,19 +20691,16 @@
},
"visible": {
"type": "string",
"default": "PUBLIC",
"enum": [
"PUBLIC",
"INTERNAL",
"PRIVATE"
]
],
"default": "PUBLIC"
}
}
},
"PostStatus": {
"required": [
"phase"
],
"type": "object",
"properties": {
"commentsCount": {
@ -22560,19 +22536,16 @@
},
"visible": {
"type": "string",
"default": "PUBLIC",
"enum": [
"PUBLIC",
"INTERNAL",
"PRIVATE"
]
],
"default": "PUBLIC"
}
}
},
"SinglePageStatus": {
"required": [
"phase"
],
"type": "object",
"properties": {
"commentsCount": {
@ -22972,29 +22945,6 @@
},
"description": "The subscriber to be notified"
},
"SystemInitializationRequest": {
"required": [
"password",
"username"
],
"type": "object",
"properties": {
"email": {
"type": "string"
},
"password": {
"minLength": 3,
"type": "string"
},
"siteTitle": {
"type": "string"
},
"username": {
"minLength": 1,
"type": "string"
}
}
},
"Tag": {
"required": [
"apiVersion",

View File

@ -518,30 +518,6 @@
]
}
},
"/apis/api.console.halo.run/v1alpha1/plugin-presets": {
"get": {
"description": "List all plugin presets in the system.",
"operationId": "ListPluginPresets",
"responses": {
"default": {
"content": {
"*/*": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/Plugin"
}
}
}
},
"description": "default response"
}
},
"tags": [
"PluginV1alpha1Console"
]
}
},
"/apis/api.console.halo.run/v1alpha1/plugins": {
"get": {
"description": "List plugins using query criteria and sort params",
@ -2176,38 +2152,6 @@
]
}
},
"/apis/api.console.halo.run/v1alpha1/system/initialize": {
"post": {
"description": "Initialize system",
"operationId": "initialize",
"requestBody": {
"content": {
"*/*": {
"schema": {
"$ref": "#/components/schemas/SystemInitializationRequest"
}
}
}
},
"responses": {
"201": {
"description": "System initialization successfully.",
"headers": {
"Location": {
"description": "Redirect URL.",
"schema": {
"type": "string"
},
"style": "simple"
}
}
}
},
"tags": [
"SystemV1alpha1Console"
]
}
},
"/apis/api.console.halo.run/v1alpha1/tags": {
"get": {
"description": "List Post Tags.",
@ -5476,19 +5420,16 @@
},
"visible": {
"type": "string",
"default": "PUBLIC",
"enum": [
"PUBLIC",
"INTERNAL",
"PRIVATE"
]
],
"default": "PUBLIC"
}
}
},
"PostStatus": {
"required": [
"phase"
],
"type": "object",
"properties": {
"commentsCount": {
@ -6008,19 +5949,16 @@
},
"visible": {
"type": "string",
"default": "PUBLIC",
"enum": [
"PUBLIC",
"INTERNAL",
"PRIVATE"
]
],
"default": "PUBLIC"
}
}
},
"SinglePageStatus": {
"required": [
"phase"
],
"type": "object",
"properties": {
"commentsCount": {
@ -6090,29 +6028,6 @@
}
}
},
"SystemInitializationRequest": {
"required": [
"password",
"username"
],
"type": "object",
"properties": {
"email": {
"type": "string"
},
"password": {
"minLength": 3,
"type": "string"
},
"siteTitle": {
"type": "string"
},
"username": {
"minLength": 1,
"type": "string"
}
}
},
"Tag": {
"required": [
"apiVersion",

View File

@ -11300,19 +11300,16 @@
},
"visible": {
"type": "string",
"default": "PUBLIC",
"enum": [
"PUBLIC",
"INTERNAL",
"PRIVATE"
]
],
"default": "PUBLIC"
}
}
},
"PostStatus": {
"required": [
"phase"
],
"type": "object",
"properties": {
"commentsCount": {
@ -12487,19 +12484,16 @@
},
"visible": {
"type": "string",
"default": "PUBLIC",
"enum": [
"PUBLIC",
"INTERNAL",
"PRIVATE"
]
],
"default": "PUBLIC"
}
}
},
"SinglePageStatus": {
"required": [
"phase"
],
"type": "object",
"properties": {
"commentsCount": {

View File

@ -17,6 +17,41 @@
}
],
"paths": {
"/apis/uc.api.auth.halo.run/v1alpha1/user-connections/{registerId}/disconnect": {
"put": {
"description": "Disconnect my connection from a third-party platform.",
"operationId": "DisconnectMyConnection",
"parameters": [
{
"description": "The registration ID of the third-party platform.",
"in": "path",
"name": "registerId",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"default": {
"content": {
"*/*": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/UserConnection"
}
}
}
},
"description": "default response"
}
},
"tags": [
"UserConnectionV1alpha1Uc"
]
}
},
"/apis/uc.api.content.halo.run/v1alpha1/posts": {
"get": {
"description": "List posts owned by the current user.",
@ -1853,19 +1888,16 @@
},
"visible": {
"type": "string",
"default": "PUBLIC",
"enum": [
"PUBLIC",
"INTERNAL",
"PRIVATE"
]
],
"default": "PUBLIC"
}
}
},
"PostStatus": {
"required": [
"phase"
],
"type": "object",
"properties": {
"commentsCount": {
@ -2262,6 +2294,52 @@
}
}
},
"UserConnection": {
"required": [
"apiVersion",
"kind",
"metadata",
"spec"
],
"type": "object",
"properties": {
"apiVersion": {
"type": "string"
},
"kind": {
"type": "string"
},
"metadata": {
"$ref": "#/components/schemas/Metadata"
},
"spec": {
"$ref": "#/components/schemas/UserConnectionSpec"
}
}
},
"UserConnectionSpec": {
"required": [
"providerUserId",
"registrationId",
"username"
],
"type": "object",
"properties": {
"providerUserId": {
"type": "string"
},
"registrationId": {
"type": "string"
},
"updatedAt": {
"type": "string",
"format": "date-time"
},
"username": {
"type": "string"
}
}
},
"UserDevice": {
"required": [
"active",

View File

@ -8,22 +8,18 @@ param:
notificationName: "{{randAlpha 6}}"
auth: "Basic YWRtaW46MTIzNDU2"
items:
- name: init
- name: setup
request:
api: /api.console.halo.run/v1alpha1/system/initialize
api: |
{{default "http://halo:8090" (env "SERVER")}}/system/setup
method: POST
header:
Content-Type: application/json
Content-Type: application/x-www-form-urlencoded
Accept: application/json
body: |
{
"siteTitle": "testing",
"username": "admin",
"password": "123456",
"email": "testing@halo.com",
"password_confirm": "123456"
}
siteTitle=testing&username={{.param.userName}}&password=123456&email=testing@halo.run
expect:
statusCode: 201
statusCode: 204
- name: createPost
request:
api: /api.console.halo.run/v1alpha1/posts

View File

@ -1,37 +0,0 @@
import { useGlobalInfoStore } from "@/stores/global-info";
import { useUserStore } from "@/stores/user";
import type { Router } from "vue-router";
export function setupCheckStatesGuard(router: Router) {
router.beforeEach(async (to, _, next) => {
const userStore = useUserStore();
const { globalInfo } = useGlobalInfoStore();
const { userInitialized, dataInitialized } = globalInfo || {};
if (to.name === "Setup" && userInitialized) {
next({ name: "Dashboard" });
return;
}
if (to.name === "SetupInitialData" && dataInitialized) {
next({ name: "Dashboard" });
return;
}
if (userInitialized === false && to.name !== "Setup") {
next({ name: "Setup" });
return;
}
if (
dataInitialized === false &&
!userStore.isAnonymous &&
to.name !== "SetupInitialData"
) {
next({ name: "SetupInitialData" });
return;
}
next();
});
}

View File

@ -6,7 +6,6 @@ import {
type RouteLocationNormalizedLoaded,
} from "vue-router";
import { setupAuthCheckGuard } from "./guards/auth-check";
import { setupCheckStatesGuard } from "./guards/check-states";
import { setupPermissionGuard } from "./guards/permission";
const router = createRouter({
@ -22,7 +21,6 @@ const router = createRouter({
},
});
setupCheckStatesGuard(router);
setupAuthCheckGuard(router);
setupPermissionGuard(router);

View File

@ -1,8 +1,6 @@
import Forbidden from "@/views/exceptions/Forbidden.vue";
import NotFound from "@/views/exceptions/NotFound.vue";
import BasicLayout from "@console/layouts/BasicLayout.vue";
import Setup from "@console/views/system/Setup.vue";
import SetupInitialData from "@console/views/system/SetupInitialData.vue";
import type { RouteRecordRaw } from "vue-router";
export const routes: Array<RouteRecordRaw> = [
@ -22,22 +20,6 @@ export const routes: Array<RouteRecordRaw> = [
},
],
},
{
path: "/setup",
component: Setup,
name: "Setup",
meta: {
title: "core.setup.title",
},
},
{
path: "/setup-initial-data",
name: "SetupInitialData",
component: SetupInitialData,
meta: {
title: "core.setup.title",
},
},
];
export default routes;

View File

@ -1,135 +0,0 @@
<script lang="ts" setup>
import LocaleChange from "@/components/common/LocaleChange.vue";
import { useGlobalInfoStore } from "@/stores/global-info";
import type { SystemInitializationRequest } from "@halo-dev/api-client";
import { consoleApiClient } from "@halo-dev/api-client";
import { Toast, VButton } from "@halo-dev/components";
import { ref } from "vue";
import { useI18n } from "vue-i18n";
import { useRouter } from "vue-router";
import IconLogo from "~icons/core/logo?width=5rem&height=2rem";
const router = useRouter();
const { t } = useI18n();
const loading = ref(false);
const formState = ref<SystemInitializationRequest>({
siteTitle: "",
username: "",
password: "",
email: "",
});
const handleSubmit = async () => {
loading.value = true;
await consoleApiClient.system.initialize({
systemInitializationRequest: formState.value,
});
const globalInfoStore = useGlobalInfoStore();
await globalInfoStore.fetchGlobalInfo();
loading.value = false;
router.push({ name: "Login" });
Toast.success(t("core.setup.operations.submit.toast_success"));
};
const inputClasses = {
outer: "!py-3 first:!pt-0 last:!pb-0",
};
</script>
<template>
<div
class="flex h-screen flex-col items-center overflow-auto bg-white/90 pt-[20vh]"
>
<IconLogo class="mb-8 flex-none" />
<div class="flex w-72 flex-col">
<!-- @vue-ignore -->
<FormKit
id="setup-form"
v-model="formState"
name="setup-form"
:actions="false"
:classes="{
form: '!divide-none',
}"
:config="{ validationVisibility: 'submit' }"
type="form"
@submit="handleSubmit"
@keyup.enter="$formkit.submit('setup-form')"
>
<FormKit
name="siteTitle"
:classes="inputClasses"
:autofocus="true"
type="text"
:validation-label="$t('core.setup.fields.site_title.label')"
:placeholder="$t('core.setup.fields.site_title.label')"
validation="required:trim|length:0,100"
></FormKit>
<FormKit
name="email"
:classes="inputClasses"
type="text"
:validation-label="$t('core.setup.fields.email.label')"
:placeholder="$t('core.setup.fields.email.label')"
validation="required|email|length:0,100"
></FormKit>
<FormKit
name="username"
:classes="inputClasses"
type="text"
:validation-label="$t('core.setup.fields.username.label')"
:placeholder="$t('core.setup.fields.username.label')"
:validation="[
['required'],
['length:0,63'],
[
'matches',
/^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$/,
],
]"
></FormKit>
<FormKit
name="password"
:classes="inputClasses"
type="password"
:validation-label="$t('core.setup.fields.password.label')"
:placeholder="$t('core.setup.fields.password.label')"
validation="required:trim|length:5,100|matches:/^\S.*\S$/"
autocomplete="current-password"
></FormKit>
<FormKit
name="password_confirm"
:classes="inputClasses"
type="password"
:validation-label="$t('core.setup.fields.confirm_password.label')"
:placeholder="$t('core.setup.fields.confirm_password.label')"
validation="confirm|required:trim|length:5,100|matches:/^\S.*\S$/"
autocomplete="current-password"
></FormKit>
</FormKit>
<VButton
block
class="mt-8"
type="secondary"
:loading="loading"
@click="$formkit.submit('setup-form')"
>
{{ $t("core.setup.operations.submit.button") }}
</VButton>
</div>
<div
class="bottom-0 mb-10 mt-auto flex items-center justify-center gap-2.5"
>
<LocaleChange />
</div>
</div>
</template>

View File

@ -1,202 +0,0 @@
<script lang="ts" setup>
import H2WarningAlert from "@/components/alerts/H2WarningAlert.vue";
import { useGlobalInfoStore } from "@/stores/global-info";
import { useUserStore } from "@/stores/user";
import type { Info } from "@/types";
import {
consoleApiClient,
coreApiClient,
type Category,
type Plugin,
type PostRequest,
type SinglePageRequest,
type Tag,
} from "@halo-dev/api-client";
import { VButton, VLoading } from "@halo-dev/components";
import { useMutation, useQuery } from "@tanstack/vue-query";
import axios from "axios";
import { onMounted, ref, watch } from "vue";
import { useRouter } from "vue-router";
import category from "./setup-data/category.json";
import menuItems from "./setup-data/menu-items.json";
import menu from "./setup-data/menu.json";
import post from "./setup-data/post.json";
import singlePage from "./setup-data/singlePage.json";
import tag from "./setup-data/tag.json";
const router = useRouter();
const globalInfoStore = useGlobalInfoStore();
const { mutateAsync: pluginInstallMutate } = useMutation({
mutationKey: ["plugin-install"],
mutationFn: async (plugin: Plugin) => {
const { data } = await consoleApiClient.plugin.plugin.installPlugin(
{
source: "PRESET",
presetName: plugin.metadata.name as string,
},
{
mute: true,
}
);
return data;
},
retry: 3,
retryDelay: 1000,
async onSuccess(data) {
await pluginStartMutate(data);
},
});
const { mutateAsync: pluginStartMutate } = useMutation({
mutationKey: ["plugin-start"],
mutationFn: async (plugin: Plugin) => {
return await consoleApiClient.plugin.plugin.changePluginRunningState(
{
name: plugin.metadata.name,
pluginRunningStateRequest: {
enable: true,
},
},
{ mute: true }
);
},
retry: 3,
retryDelay: 1000,
});
const processing = ref(false);
async function setupInitialData() {
try {
processing.value = true;
// Create category / tag / post
await coreApiClient.content.category.createCategory({
category: category as Category,
});
await coreApiClient.content.tag.createTag({
tag: tag as Tag,
});
const { data: postData } = await consoleApiClient.content.post.draftPost({
postRequest: post as PostRequest,
});
await consoleApiClient.content.post.publishPost({
name: postData.metadata.name,
});
// Create singlePage
const { data: singlePageData } =
await consoleApiClient.content.singlePage.draftSinglePage({
singlePageRequest: singlePage as SinglePageRequest,
});
await consoleApiClient.content.singlePage.publishSinglePage({
name: singlePageData.metadata.name,
});
// Create menu and menu items
const menuItemPromises = menuItems.map((item) => {
return coreApiClient.menuItem.createMenuItem({
menuItem: item,
});
});
await Promise.all(menuItemPromises);
await coreApiClient.menu.createMenu({ menu: menu });
// Install preset plugins
const { data: presetPlugins } =
await consoleApiClient.plugin.plugin.listPluginPresets();
for (let i = 0; i < presetPlugins.length; i++) {
const presetPlugin = presetPlugins[i];
try {
await pluginInstallMutate(presetPlugin);
} catch (error) {
console.error("Failed to install plugin: ", presetPlugin.metadata.name);
}
}
} catch (error) {
console.error(error);
} finally {
await coreApiClient.configMap.createConfigMap({
configMap: {
metadata: {
name: "system-states",
},
kind: "ConfigMap",
apiVersion: "v1alpha1",
data: {
states: JSON.stringify({ isSetup: true }),
},
},
});
// Reload page to fetch plugin's bundle files
window.location.reload();
}
}
const userStore = useUserStore();
const { data: info } = useQuery<Info>({
queryKey: ["system-info"],
queryFn: async () => {
const { data } = await axios.get<Info>(`/actuator/info`, {
withCredentials: true,
});
return data;
},
retry: 0,
});
watch(
() => info.value,
(value) => {
if (!value) {
return;
}
if (!value.database.name.startsWith("H2")) {
setupInitialData();
}
},
{
immediate: true,
}
);
onMounted(async () => {
await globalInfoStore.fetchGlobalInfo();
if (userStore.isAnonymous) {
window.location.href = "/";
return;
}
if (globalInfoStore.globalInfo?.dataInitialized) {
router.push({ name: "Dashboard" });
return;
}
});
</script>
<template>
<div class="flex h-screen flex-col items-center justify-center">
<template v-if="info?.database.name.startsWith('H2') && !processing">
<H2WarningAlert class="max-w-md">
<template #actions>
<VButton type="secondary" size="sm" @click="setupInitialData()">
{{ $t("core.common.buttons.continue") }}
</VButton>
</template>
</H2WarningAlert>
</template>
<template v-if="processing">
<VLoading />
<div class="text-xs text-gray-600">
{{ $t("core.setup.operations.setup_initial_data.loading") }}
</div>
</template>
</div>
</template>

View File

@ -1,16 +0,0 @@
{
"spec": {
"displayName": "默认分类",
"slug": "default",
"description": "这是你的默认分类,如不需要,删除即可。",
"cover": "",
"template": "",
"priority": 0,
"children": []
},
"apiVersion": "content.halo.run/v1alpha1",
"kind": "Category",
"metadata": {
"name": "76514a40-6ef1-4ed9-b58a-e26945bde3ca"
}
}

View File

@ -1,62 +0,0 @@
[
{
"spec": {
"displayName": "首页",
"href": "/",
"children": [],
"priority": 0
},
"apiVersion": "v1alpha1",
"kind": "MenuItem",
"metadata": { "name": "88c3f10b-321c-4092-86a8-70db00251b74" }
},
{
"spec": {
"children": [],
"priority": 1,
"targetRef": {
"group": "content.halo.run",
"version": "v1alpha1",
"kind": "Post",
"name": "5152aea5-c2e8-4717-8bba-2263d46e19d5"
}
},
"apiVersion": "v1alpha1",
"kind": "MenuItem",
"metadata": { "name": "c4c814d1-0c2c-456b-8c96-4864965fee94" }
},
{
"spec": {
"displayName": "",
"href": "",
"children": [],
"priority": 2,
"targetRef": {
"group": "content.halo.run",
"version": "v1alpha1",
"kind": "Tag",
"name": "c33ceabb-d8f1-4711-8991-bb8f5c92ad7c"
}
},
"apiVersion": "v1alpha1",
"kind": "MenuItem",
"metadata": { "name": "35869bd3-33b5-448b-91ee-cf6517a59644" }
},
{
"spec": {
"displayName": "",
"href": "",
"children": [],
"priority": 3,
"targetRef": {
"group": "content.halo.run",
"version": "v1alpha1",
"kind": "SinglePage",
"name": "373a5f79-f44f-441a-9df1-85a4f553ece8"
}
},
"apiVersion": "v1alpha1",
"kind": "MenuItem",
"metadata": { "name": "b0d041fa-dc99-48f6-a193-8604003379cf" }
}
]

View File

@ -1,14 +0,0 @@
{
"spec": {
"displayName": "主菜单",
"menuItems": [
"88c3f10b-321c-4092-86a8-70db00251b74",
"c4c814d1-0c2c-456b-8c96-4864965fee94",
"35869bd3-33b5-448b-91ee-cf6517a59644",
"b0d041fa-dc99-48f6-a193-8604003379cf"
]
},
"apiVersion": "v1alpha1",
"kind": "Menu",
"metadata": { "name": "primary" }
}

View File

@ -1,35 +0,0 @@
{
"post": {
"spec": {
"title": "Hello Halo",
"slug": "hello-halo",
"template": "",
"cover": "",
"deleted": false,
"publish": false,
"publishTime": "",
"pinned": false,
"allowComment": true,
"visible": "PUBLIC",
"version": 1,
"priority": 0,
"excerpt": {
"autoGenerate": false,
"raw": "如果你看到了这一篇文章,那么证明你已经安装成功了,感谢使用 Halo 进行创作,希望能够使用愉快。"
},
"categories": ["76514a40-6ef1-4ed9-b58a-e26945bde3ca"],
"tags": ["c33ceabb-d8f1-4711-8991-bb8f5c92ad7c"],
"htmlMetas": []
},
"apiVersion": "content.halo.run/v1alpha1",
"kind": "Post",
"metadata": {
"name": "5152aea5-c2e8-4717-8bba-2263d46e19d5"
}
},
"content": {
"raw": "<h2 id=\"hello-halo\"><strong>Hello Halo</strong></h2><p>如果你看到了这一篇文章,那么证明你已经安装成功了,感谢使用 <a target=\"_blank\" rel=\"noopener noreferrer nofollow\" href=\"https://www.halo.run/\">Halo</a> 进行创作,希望能够使用愉快。</p><h2 id=\"%E7%9B%B8%E5%85%B3%E9%93%BE%E6%8E%A5\"><strong>相关链接</strong></h2><ul><li><p>官网:<a target=\"_blank\" rel=\"noopener noreferrer nofollow\" href=\"https://www.halo.run\">https://www.halo.run</a></p></li><li><p>文档:<a target=\"_blank\" rel=\"noopener noreferrer nofollow\" href=\"https://docs.halo.run\">https://docs.halo.run</a></p></li><li><p>社区:<a target=\"_blank\" rel=\"noopener noreferrer nofollow\" href=\"https://bbs.halo.run\">https://bbs.halo.run</a></p></li><li><p>应用市场:<a target=\"_blank\" rel=\"noopener noreferrer nofollow\" href=\"https://www.halo.run/store/apps\">https://www.halo.run/store/apps</a></p></li><li><p>开源地址:<a target=\"_blank\" rel=\"noopener noreferrer nofollow\" href=\"https://github.com/halo-dev/halo\">https://github.com/halo-dev/halo</a></p></li></ul><p>在使用过程中,有任何问题都可以通过以上链接找寻答案,或者联系我们。</p><blockquote><p>这是一篇自动生成的文章,请删除这篇文章之后开始你的创作吧!</p></blockquote>",
"content": "<h2 id=\"hello-halo\"><strong>Hello Halo</strong></h2><p>如果你看到了这一篇文章,那么证明你已经安装成功了,感谢使用 <a target=\"_blank\" rel=\"noopener noreferrer nofollow\" href=\"https://www.halo.run/\">Halo</a> 进行创作,希望能够使用愉快。</p><h2 id=\"%E7%9B%B8%E5%85%B3%E9%93%BE%E6%8E%A5\"><strong>相关链接</strong></h2><ul><li><p>官网:<a target=\"_blank\" rel=\"noopener noreferrer nofollow\" href=\"https://www.halo.run\">https://www.halo.run</a></p></li><li><p>文档:<a target=\"_blank\" rel=\"noopener noreferrer nofollow\" href=\"https://docs.halo.run\">https://docs.halo.run</a></p></li><li><p>社区:<a target=\"_blank\" rel=\"noopener noreferrer nofollow\" href=\"https://bbs.halo.run\">https://bbs.halo.run</a></p></li><li><p>应用市场:<a target=\"_blank\" rel=\"noopener noreferrer nofollow\" href=\"https://www.halo.run/store/apps\">https://www.halo.run/store/apps</a></p></li><li><p>开源地址:<a target=\"_blank\" rel=\"noopener noreferrer nofollow\" href=\"https://github.com/halo-dev/halo\">https://github.com/halo-dev/halo</a></p></li></ul><p>在使用过程中,有任何问题都可以通过以上链接找寻答案,或者联系我们。</p><blockquote><p>这是一篇自动生成的文章,请删除这篇文章之后开始你的创作吧!</p></blockquote>",
"rawType": "HTML"
}
}

View File

@ -1,31 +0,0 @@
{
"page": {
"spec": {
"title": "关于",
"slug": "about",
"template": "",
"cover": "",
"deleted": false,
"publish": false,
"publishTime": "",
"pinned": false,
"allowComment": true,
"visible": "PUBLIC",
"version": 1,
"priority": 0,
"excerpt": {
"autoGenerate": false,
"raw": "这是一个自定义页面,你可以在后台的 页面 -> 自定义页面 找到它,你可以用于新建关于页面、联系我们页面等等。"
},
"htmlMetas": []
},
"apiVersion": "content.halo.run/v1alpha1",
"kind": "SinglePage",
"metadata": { "name": "373a5f79-f44f-441a-9df1-85a4f553ece8" }
},
"content": {
"raw": "<h2><strong>关于页面</strong></h2><p>这是一个自定义页面,你可以在后台的 <code>页面</code> -&gt; <code>自定义页面</code> 找到它,你可以用于新建关于页面、联系我们页面等等。</p><blockquote><p>这是一篇自动生成的页面,你可以在后台删除它。</p></blockquote>",
"content": "<h2><strong>关于页面</strong></h2><p>这是一个自定义页面,你可以在后台的 <code>页面</code> -&gt; <code>自定义页面</code> 找到它,你可以用于新建关于页面、联系我们页面等等。</p><blockquote><p>这是一篇自动生成的页面,你可以在后台删除它。</p></blockquote>",
"rawType": "HTML"
}
}

View File

@ -1,13 +0,0 @@
{
"spec": {
"displayName": "Halo",
"slug": "halo",
"color": "#ffffff",
"cover": ""
},
"apiVersion": "content.halo.run/v1alpha1",
"kind": "Tag",
"metadata": {
"name": "c33ceabb-d8f1-4711-8991-bb8f5c92ad7c"
}
}

View File

@ -76,6 +76,7 @@ api/thumbnail-v1alpha1-api.ts
api/thumbnail-v1alpha1-public-api.ts
api/two-factor-auth-v1alpha1-uc-api.ts
api/user-connection-v1alpha1-api.ts
api/user-connection-v1alpha1-uc-api.ts
api/user-v1alpha1-api.ts
api/user-v1alpha1-console-api.ts
base.ts
@ -303,7 +304,6 @@ models/subscription-list.ts
models/subscription-spec.ts
models/subscription-subscriber.ts
models/subscription.ts
models/system-initialization-request.ts
models/tag-list.ts
models/tag-spec.ts
models/tag-status.ts

View File

@ -88,6 +88,7 @@ export * from './api/thumbnail-v1alpha1-api';
export * from './api/thumbnail-v1alpha1-public-api';
export * from './api/two-factor-auth-v1alpha1-uc-api';
export * from './api/user-connection-v1alpha1-api';
export * from './api/user-connection-v1alpha1-uc-api';
export * from './api/user-v1alpha1-api';
export * from './api/user-v1alpha1-console-api';

View File

@ -384,43 +384,6 @@ export const PluginV1alpha1ConsoleApiAxiosParamCreator = function (configuration
options: localVarRequestOptions,
};
},
/**
* List all plugin presets in the system.
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
listPluginPresets: async (options: RawAxiosRequestConfig = {}): Promise<RequestArgs> => {
const localVarPath = `/apis/api.console.halo.run/v1alpha1/plugin-presets`;
// use dummy base URL string because the URL constructor only accepts absolute URLs.
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
let baseOptions;
if (configuration) {
baseOptions = configuration.baseOptions;
}
const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options};
const localVarHeaderParameter = {} as any;
const localVarQueryParameter = {} as any;
// authentication basicAuth required
// http basic authentication required
setBasicAuthToObject(localVarRequestOptions, configuration)
// authentication bearerAuth required
// http bearer authentication required
await setBearerAuthToObject(localVarHeaderParameter, configuration)
setSearchParams(localVarUrlObj, localVarQueryParameter);
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
return {
url: toPathString(localVarUrlObj),
options: localVarRequestOptions,
};
},
/**
* List plugins using query criteria and sort params
* @param {number} [page] Page number. Default is 0.
@ -884,17 +847,6 @@ export const PluginV1alpha1ConsoleApiFp = function(configuration?: Configuration
const localVarOperationServerBasePath = operationServerMap['PluginV1alpha1ConsoleApi.installPluginFromUri']?.[localVarOperationServerIndex]?.url;
return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath);
},
/**
* List all plugin presets in the system.
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
async listPluginPresets(options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<Array<Plugin>>> {
const localVarAxiosArgs = await localVarAxiosParamCreator.listPluginPresets(options);
const localVarOperationServerIndex = configuration?.serverIndex ?? 0;
const localVarOperationServerBasePath = operationServerMap['PluginV1alpha1ConsoleApi.listPluginPresets']?.[localVarOperationServerIndex]?.url;
return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath);
},
/**
* List plugins using query criteria and sort params
* @param {number} [page] Page number. Default is 0.
@ -1072,14 +1024,6 @@ export const PluginV1alpha1ConsoleApiFactory = function (configuration?: Configu
installPluginFromUri(requestParameters: PluginV1alpha1ConsoleApiInstallPluginFromUriRequest, options?: RawAxiosRequestConfig): AxiosPromise<Plugin> {
return localVarFp.installPluginFromUri(requestParameters.installFromUriRequest, options).then((request) => request(axios, basePath));
},
/**
* List all plugin presets in the system.
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
listPluginPresets(options?: RawAxiosRequestConfig): AxiosPromise<Array<Plugin>> {
return localVarFp.listPluginPresets(options).then((request) => request(axios, basePath));
},
/**
* List plugins using query criteria and sort params
* @param {PluginV1alpha1ConsoleApiListPluginsRequest} requestParameters Request parameters.
@ -1527,16 +1471,6 @@ export class PluginV1alpha1ConsoleApi extends BaseAPI {
return PluginV1alpha1ConsoleApiFp(this.configuration).installPluginFromUri(requestParameters.installFromUriRequest, options).then((request) => request(this.axios, this.basePath));
}
/**
* List all plugin presets in the system.
* @param {*} [options] Override http request option.
* @throws {RequiredError}
* @memberof PluginV1alpha1ConsoleApi
*/
public listPluginPresets(options?: RawAxiosRequestConfig) {
return PluginV1alpha1ConsoleApiFp(this.configuration).listPluginPresets(options).then((request) => request(this.axios, this.basePath));
}
/**
* List plugins using query criteria and sort params
* @param {PluginV1alpha1ConsoleApiListPluginsRequest} requestParameters Request parameters.

View File

@ -23,8 +23,6 @@ import { DUMMY_BASE_URL, assertParamExists, setApiKeyToObject, setBasicAuthToObj
import { BASE_PATH, COLLECTION_FORMATS, RequestArgs, BaseAPI, RequiredError, operationServerMap } from '../base';
// @ts-ignore
import { DashboardStats } from '../models';
// @ts-ignore
import { SystemInitializationRequest } from '../models';
/**
* SystemV1alpha1ConsoleApi - axios parameter creator
* @export
@ -63,47 +61,6 @@ export const SystemV1alpha1ConsoleApiAxiosParamCreator = function (configuration
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
return {
url: toPathString(localVarUrlObj),
options: localVarRequestOptions,
};
},
/**
* Initialize system
* @param {SystemInitializationRequest} [systemInitializationRequest]
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
initialize: async (systemInitializationRequest?: SystemInitializationRequest, options: RawAxiosRequestConfig = {}): Promise<RequestArgs> => {
const localVarPath = `/apis/api.console.halo.run/v1alpha1/system/initialize`;
// use dummy base URL string because the URL constructor only accepts absolute URLs.
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
let baseOptions;
if (configuration) {
baseOptions = configuration.baseOptions;
}
const localVarRequestOptions = { method: 'POST', ...baseOptions, ...options};
const localVarHeaderParameter = {} as any;
const localVarQueryParameter = {} as any;
// authentication basicAuth required
// http basic authentication required
setBasicAuthToObject(localVarRequestOptions, configuration)
// authentication bearerAuth required
// http bearer authentication required
await setBearerAuthToObject(localVarHeaderParameter, configuration)
localVarHeaderParameter['Content-Type'] = 'application/json';
setSearchParams(localVarUrlObj, localVarQueryParameter);
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
localVarRequestOptions.data = serializeDataIfNeeded(systemInitializationRequest, localVarRequestOptions, configuration)
return {
url: toPathString(localVarUrlObj),
options: localVarRequestOptions,
@ -130,18 +87,6 @@ export const SystemV1alpha1ConsoleApiFp = function(configuration?: Configuration
const localVarOperationServerBasePath = operationServerMap['SystemV1alpha1ConsoleApi.getStats']?.[localVarOperationServerIndex]?.url;
return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath);
},
/**
* Initialize system
* @param {SystemInitializationRequest} [systemInitializationRequest]
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
async initialize(systemInitializationRequest?: SystemInitializationRequest, options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<void>> {
const localVarAxiosArgs = await localVarAxiosParamCreator.initialize(systemInitializationRequest, options);
const localVarOperationServerIndex = configuration?.serverIndex ?? 0;
const localVarOperationServerBasePath = operationServerMap['SystemV1alpha1ConsoleApi.initialize']?.[localVarOperationServerIndex]?.url;
return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath);
},
}
};
@ -160,32 +105,9 @@ export const SystemV1alpha1ConsoleApiFactory = function (configuration?: Configu
getStats(options?: RawAxiosRequestConfig): AxiosPromise<DashboardStats> {
return localVarFp.getStats(options).then((request) => request(axios, basePath));
},
/**
* Initialize system
* @param {SystemV1alpha1ConsoleApiInitializeRequest} requestParameters Request parameters.
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
initialize(requestParameters: SystemV1alpha1ConsoleApiInitializeRequest = {}, options?: RawAxiosRequestConfig): AxiosPromise<void> {
return localVarFp.initialize(requestParameters.systemInitializationRequest, options).then((request) => request(axios, basePath));
},
};
};
/**
* Request parameters for initialize operation in SystemV1alpha1ConsoleApi.
* @export
* @interface SystemV1alpha1ConsoleApiInitializeRequest
*/
export interface SystemV1alpha1ConsoleApiInitializeRequest {
/**
*
* @type {SystemInitializationRequest}
* @memberof SystemV1alpha1ConsoleApiInitialize
*/
readonly systemInitializationRequest?: SystemInitializationRequest
}
/**
* SystemV1alpha1ConsoleApi - object-oriented interface
* @export
@ -202,16 +124,5 @@ export class SystemV1alpha1ConsoleApi extends BaseAPI {
public getStats(options?: RawAxiosRequestConfig) {
return SystemV1alpha1ConsoleApiFp(this.configuration).getStats(options).then((request) => request(this.axios, this.basePath));
}
/**
* Initialize system
* @param {SystemV1alpha1ConsoleApiInitializeRequest} requestParameters Request parameters.
* @param {*} [options] Override http request option.
* @throws {RequiredError}
* @memberof SystemV1alpha1ConsoleApi
*/
public initialize(requestParameters: SystemV1alpha1ConsoleApiInitializeRequest = {}, options?: RawAxiosRequestConfig) {
return SystemV1alpha1ConsoleApiFp(this.configuration).initialize(requestParameters.systemInitializationRequest, options).then((request) => request(this.axios, this.basePath));
}
}

View File

@ -0,0 +1,149 @@
/* tslint:disable */
/* eslint-disable */
/**
* Halo
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
*
* The version of the OpenAPI document: 2.20.0-SNAPSHOT
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
import type { Configuration } from '../configuration';
import type { AxiosPromise, AxiosInstance, RawAxiosRequestConfig } from 'axios';
import globalAxios from 'axios';
// Some imports not used depending on template conditions
// @ts-ignore
import { DUMMY_BASE_URL, assertParamExists, setApiKeyToObject, setBasicAuthToObject, setBearerAuthToObject, setOAuthToObject, setSearchParams, serializeDataIfNeeded, toPathString, createRequestFunction } from '../common';
// @ts-ignore
import { BASE_PATH, COLLECTION_FORMATS, RequestArgs, BaseAPI, RequiredError, operationServerMap } from '../base';
// @ts-ignore
import { UserConnection } from '../models';
/**
* UserConnectionV1alpha1UcApi - axios parameter creator
* @export
*/
export const UserConnectionV1alpha1UcApiAxiosParamCreator = function (configuration?: Configuration) {
return {
/**
* Disconnect my connection from a third-party platform.
* @param {string} registerId The registration ID of the third-party platform.
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
disconnectMyConnection: async (registerId: string, options: RawAxiosRequestConfig = {}): Promise<RequestArgs> => {
// verify required parameter 'registerId' is not null or undefined
assertParamExists('disconnectMyConnection', 'registerId', registerId)
const localVarPath = `/apis/uc.api.auth.halo.run/v1alpha1/user-connections/{registerId}/disconnect`
.replace(`{${"registerId"}}`, encodeURIComponent(String(registerId)));
// use dummy base URL string because the URL constructor only accepts absolute URLs.
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
let baseOptions;
if (configuration) {
baseOptions = configuration.baseOptions;
}
const localVarRequestOptions = { method: 'PUT', ...baseOptions, ...options};
const localVarHeaderParameter = {} as any;
const localVarQueryParameter = {} as any;
// authentication basicAuth required
// http basic authentication required
setBasicAuthToObject(localVarRequestOptions, configuration)
// authentication bearerAuth required
// http bearer authentication required
await setBearerAuthToObject(localVarHeaderParameter, configuration)
setSearchParams(localVarUrlObj, localVarQueryParameter);
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
return {
url: toPathString(localVarUrlObj),
options: localVarRequestOptions,
};
},
}
};
/**
* UserConnectionV1alpha1UcApi - functional programming interface
* @export
*/
export const UserConnectionV1alpha1UcApiFp = function(configuration?: Configuration) {
const localVarAxiosParamCreator = UserConnectionV1alpha1UcApiAxiosParamCreator(configuration)
return {
/**
* Disconnect my connection from a third-party platform.
* @param {string} registerId The registration ID of the third-party platform.
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
async disconnectMyConnection(registerId: string, options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<Array<UserConnection>>> {
const localVarAxiosArgs = await localVarAxiosParamCreator.disconnectMyConnection(registerId, options);
const localVarOperationServerIndex = configuration?.serverIndex ?? 0;
const localVarOperationServerBasePath = operationServerMap['UserConnectionV1alpha1UcApi.disconnectMyConnection']?.[localVarOperationServerIndex]?.url;
return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath);
},
}
};
/**
* UserConnectionV1alpha1UcApi - factory interface
* @export
*/
export const UserConnectionV1alpha1UcApiFactory = function (configuration?: Configuration, basePath?: string, axios?: AxiosInstance) {
const localVarFp = UserConnectionV1alpha1UcApiFp(configuration)
return {
/**
* Disconnect my connection from a third-party platform.
* @param {UserConnectionV1alpha1UcApiDisconnectMyConnectionRequest} requestParameters Request parameters.
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
disconnectMyConnection(requestParameters: UserConnectionV1alpha1UcApiDisconnectMyConnectionRequest, options?: RawAxiosRequestConfig): AxiosPromise<Array<UserConnection>> {
return localVarFp.disconnectMyConnection(requestParameters.registerId, options).then((request) => request(axios, basePath));
},
};
};
/**
* Request parameters for disconnectMyConnection operation in UserConnectionV1alpha1UcApi.
* @export
* @interface UserConnectionV1alpha1UcApiDisconnectMyConnectionRequest
*/
export interface UserConnectionV1alpha1UcApiDisconnectMyConnectionRequest {
/**
* The registration ID of the third-party platform.
* @type {string}
* @memberof UserConnectionV1alpha1UcApiDisconnectMyConnection
*/
readonly registerId: string
}
/**
* UserConnectionV1alpha1UcApi - object-oriented interface
* @export
* @class UserConnectionV1alpha1UcApi
* @extends {BaseAPI}
*/
export class UserConnectionV1alpha1UcApi extends BaseAPI {
/**
* Disconnect my connection from a third-party platform.
* @param {UserConnectionV1alpha1UcApiDisconnectMyConnectionRequest} requestParameters Request parameters.
* @param {*} [options] Override http request option.
* @throws {RequiredError}
* @memberof UserConnectionV1alpha1UcApi
*/
public disconnectMyConnection(requestParameters: UserConnectionV1alpha1UcApiDisconnectMyConnectionRequest, options?: RawAxiosRequestConfig) {
return UserConnectionV1alpha1UcApiFp(this.configuration).disconnectMyConnection(requestParameters.registerId, options).then((request) => request(this.axios, this.basePath));
}
}

View File

@ -217,7 +217,6 @@ export * from './subscription';
export * from './subscription-list';
export * from './subscription-spec';
export * from './subscription-subscriber';
export * from './system-initialization-request';
export * from './tag';
export * from './tag-list';
export * from './tag-spec';

View File

@ -82,6 +82,6 @@ export interface PostStatus {
* @type {string}
* @memberof PostStatus
*/
'phase': string;
'phase'?: string;
}

View File

@ -82,6 +82,6 @@ export interface SinglePageStatus {
* @type {string}
* @memberof SinglePageStatus
*/
'phase': string;
'phase'?: string;
}

View File

@ -1,48 +0,0 @@
/* tslint:disable */
/* eslint-disable */
/**
* Halo
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
*
* The version of the OpenAPI document: 2.20.0-SNAPSHOT
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
/**
*
* @export
* @interface SystemInitializationRequest
*/
export interface SystemInitializationRequest {
/**
*
* @type {string}
* @memberof SystemInitializationRequest
*/
'email'?: string;
/**
*
* @type {string}
* @memberof SystemInitializationRequest
*/
'password': string;
/**
*
* @type {string}
* @memberof SystemInitializationRequest
*/
'siteTitle'?: string;
/**
*
* @type {string}
* @memberof SystemInitializationRequest
*/
'username': string;
}

View File

@ -1470,25 +1470,6 @@ core:
message: Unauthorized access to this page
actions:
home: Back to home
setup:
title: Setup
operations:
submit:
button: Setup
toast_success: Setup successfully
setup_initial_data:
loading: Initializing data, please wait...
fields:
site_title:
label: Site title
email:
label: Email
username:
label: Username
password:
label: Password
confirm_password:
label: Confirm password
rbac:
Attachments Management: Attachments
Attachment Manage: Attachment Manage

View File

@ -1104,25 +1104,6 @@ core:
message: Acceso no autorizado a esta página
actions:
home: Ir a la página de inicio
setup:
title: Configuración
operations:
submit:
button: Configurar
toast_success: Configuración exitosa
setup_initial_data:
loading: Inicializando datos, por favor espera...
fields:
site_title:
label: Título del Sitio
email:
label: Correo Electrónico
username:
label: Nombre de Usuario
password:
label: Contraseña
confirm_password:
label: Confirmar Contraseña
rbac:
Attachments Management: Gestión de archivos adjuntos
Attachment Manage: Gestor de adjuntos

View File

@ -1369,25 +1369,6 @@ core:
message: 没有权限访问此页面
actions:
home: 返回首页
setup:
title: 系统初始化
operations:
submit:
button: 初始化
toast_success: 初始化成功
setup_initial_data:
loading: 正在初始化数据,请稍后...
fields:
site_title:
label: 站点名称
email:
label: 邮箱
username:
label: 用户名
password:
label: 密码
confirm_password:
label: 确认密码
rbac:
Attachments Management: 附件
Attachment Manage: 附件管理

View File

@ -1345,25 +1345,6 @@ core:
message: 沒有權限訪問此頁面
actions:
home: 返回首頁
setup:
title: 系統初始化
operations:
submit:
button: 初始化
toast_success: 初始化成功
setup_initial_data:
loading: 正在初始化資料,請稍後...
fields:
site_title:
label: 站點名稱
email:
label: 電子郵箱
username:
label: 用戶名
password:
label: 密碼
confirm_password:
label: 確認密碼
rbac:
Attachments Management: 附件
Attachment Manage: 附件管理