fix: surface https backend requirement in dashboard (#6623)

* fix: surface https backend requirement in dashboard

* Update dashboard/src/stores/api.ts

Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>

---------

Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
This commit is contained in:
LIghtJUNction
2026-03-19 20:35:32 +08:00
committed by GitHub
parent cc88ac6bef
commit a4e6e16fd8
5 changed files with 103 additions and 214 deletions

View File

@@ -172,7 +172,12 @@ class AstrBotDashboard:
self.app = cors(
self.app,
allow_origin="*",
allow_headers=["Authorization", "Content-Type", "X-API-Key"],
allow_headers=[
"Authorization",
"Content-Type",
"X-API-Key",
"Accept-Language",
],
allow_methods=["GET", "POST", "PUT", "DELETE", "OPTIONS"],
)

View File

@@ -103,7 +103,7 @@ async function initApp() {
const headers = new Headers(
init?.headers ||
(typeof input !== "string" && "headers" in input
? (input ).headers
? input.headers
: undefined),
);
if (token && !headers.has("Authorization")) {

View File

@@ -3,10 +3,7 @@
<!-- Proxy Settings -->
<!-- <div class="text-h6 mb-2">{{ tm('network.title') }}</div> -->
<v-list
lines="two"
subheader
>
<v-list lines="two" subheader>
<v-list-subheader>{{ tm("network.title") }}</v-list-subheader>
<v-list-item
@@ -148,23 +145,15 @@
target="_blank"
rel="noopener noreferrer"
>
<v-icon size="18">
mdi-help-circle-outline
</v-icon>
<v-icon size="18"> mdi-help-circle-outline </v-icon>
</v-btn>
</template>
<span>{{ tm("apiKey.docsLink") }}</span>
</v-tooltip>
</div>
</template>
<v-row
class="mt-2"
dense
>
<v-col
cols="12"
md="4"
>
<v-row class="mt-2" dense>
<v-col cols="12" md="4">
<v-text-field
v-model="newApiKeyName"
:label="tm('apiKey.name')"
@@ -173,10 +162,7 @@
hide-details
/>
</v-col>
<v-col
cols="12"
md="3"
>
<v-col cols="12" md="3">
<v-select
v-model="newApiKeyExpiresInDays"
:items="apiKeyExpiryOptions"
@@ -186,31 +172,18 @@
hide-details
/>
</v-col>
<v-col
v-if="newApiKeyExpiresInDays === 'permanent'"
cols="12"
>
<v-alert
type="warning"
variant="tonal"
density="comfortable"
>
<v-col v-if="newApiKeyExpiresInDays === 'permanent'" cols="12">
<v-alert type="warning" variant="tonal" density="comfortable">
{{ tm("apiKey.permanentWarning") }}
</v-alert>
</v-col>
<v-col
cols="12"
md="5"
class="d-flex align-center"
>
<v-col cols="12" md="5" class="d-flex align-center">
<v-btn
color="primary"
:loading="apiKeyCreating"
@click="createApiKey"
>
<v-icon class="mr-2">
mdi-key-plus
</v-icon>
<v-icon class="mr-2"> mdi-key-plus </v-icon>
{{ tm("apiKey.create") }}
</v-btn>
</v-col>
@@ -219,10 +192,7 @@
<div class="text-caption text-medium-emphasis mb-1">
{{ tm("apiKey.scopes") }}
</div>
<v-chip-group
v-model="newApiKeyScopes"
multiple
>
<v-chip-group v-model="newApiKeyScopes" multiple>
<v-chip
v-for="scope in availableScopes"
:key="scope.value"
@@ -239,14 +209,8 @@
</v-chip-group>
</v-col>
<v-col
v-if="createdApiKeyPlaintext"
cols="12"
>
<v-alert
type="warning"
variant="tonal"
>
<v-col v-if="createdApiKeyPlaintext" cols="12">
<v-alert type="warning" variant="tonal">
<div class="d-flex align-center justify-space-between flex-wrap">
<span>{{ tm("apiKey.plaintextHint") }}</span>
<v-btn
@@ -255,9 +219,8 @@
color="primary"
@click="copyCreatedApiKey"
>
<v-icon class="mr-1">
mdi-content-copy
</v-icon>{{ tm("apiKey.copy") }}
<v-icon class="mr-1"> mdi-content-copy </v-icon
>{{ tm("apiKey.copy") }}
</v-btn>
</div>
<code style="word-break: break-all">{{
@@ -280,10 +243,7 @@
</tr>
</thead>
<tbody>
<tr
v-for="item in apiKeys"
:key="item.key_id"
>
<tr v-for="item in apiKeys" :key="item.key_id">
<td>{{ item.name }}</td>
<td>
<code>{{ item.key_prefix }}</code>
@@ -328,10 +288,7 @@
</td>
</tr>
<tr v-if="apiKeys.length === 0">
<td
colspan="7"
class="text-center text-medium-emphasis"
>
<td colspan="7" class="text-center text-medium-emphasis">
{{ tm("apiKey.empty") }}
</td>
</tr>
@@ -345,11 +302,7 @@
:subtitle="tm('system.migration.subtitle')"
:title="tm('system.migration.title')"
>
<v-btn
style="margin-top: 16px"
color="primary"
@click="startMigration"
>
<v-btn style="margin-top: 16px" color="primary" @click="startMigration">
{{ tm("system.migration.button") }}
</v-btn>
</v-list-item>
@@ -371,14 +324,8 @@
>
<template #append />
<v-row
class="mt-2"
dense
>
<v-col
cols="12"
md="4"
>
<v-row class="mt-2" dense>
<v-col cols="12" md="4">
<v-text-field
v-model="primaryColor"
:label="tm('style.color.primary')"
@@ -400,10 +347,7 @@
</template>
</v-text-field>
</v-col>
<v-col
cols="12"
md="4"
>
<v-col cols="12" md="4">
<v-text-field
v-model="secondaryColor"
:label="tm('style.color.secondary')"
@@ -425,18 +369,9 @@
</template>
</v-text-field>
</v-col>
<v-col
cols="12"
md="4"
>
<v-btn
color="primary"
block
@click="applyThemeColors"
>
<v-icon start>
mdi-pencil-ruler
</v-icon>
<v-col cols="12" md="4">
<v-btn color="primary" block @click="applyThemeColors">
<v-icon start> mdi-pencil-ruler </v-icon>
{{ t("core.common.save") }}
</v-btn>
</v-col>
@@ -473,10 +408,7 @@
</div>
</v-list-item>
<v-list-item
:subtitle="tm('reset.subtitle')"
:title="tm('reset.title')"
>
<v-list-item :subtitle="tm('reset.subtitle')" :title="tm('reset.title')">
<div class="d-flex align-center mt-2">
<v-btn
color="error"
@@ -595,6 +527,12 @@ const isCustomPreset = (name) => {
};
const saveApiUrl = () => {
const validationError = getApiBaseUrlValidationError(apiBaseUrl.value);
if (validationError) {
toastStore.error(validationError);
return;
}
apiStore.setApiBaseUrl(apiBaseUrl.value);
window.location.reload();
};
@@ -651,8 +589,10 @@ const autoThemeSwitcher = computed({
get: () => customizer.autoSwitchTheme,
set: (value) => {
customizer.SET_AUTO_SYNC(value);
if (value) { customizer.APPLY_SYSTEM_THEME() }
}
if (value) {
customizer.APPLY_SYSTEM_THEME();
}
},
});
const wfr = ref(null);

View File

@@ -1,9 +1,6 @@
<template>
<div class="welcome-page">
<v-container
fluid
class="pa-0"
>
<v-container fluid class="pa-0">
<v-row class="px-4 py-3 pb-6">
<v-col cols="12">
<h1 class="text-h1 font-weight-bold mb-2 d-flex align-center">
@@ -17,11 +14,7 @@
<v-row class="px-4">
<v-col cols="12">
<v-card
class="welcome-card pa-6"
elevation="0"
border
>
<v-card class="welcome-card pa-6" elevation="0" border>
<div class="mb-4 text-h3 font-weight-bold">
{{ tm("onboard.title") }}
</div>
@@ -52,7 +45,7 @@
<p class="text-body-2 text-medium-emphasis mb-3">
{{
tm("onboard.step0Desc") ||
"配置 AstrBot 的后端 API 地址。"
"配置 AstrBot 的后端 API 地址。"
}}
</p>
<div class="d-flex align-center">
@@ -185,19 +178,12 @@
<v-row class="px-4 mt-4">
<v-col cols="12">
<v-card
class="welcome-card pa-6"
elevation="0"
border
>
<v-card class="welcome-card pa-6" elevation="0" border>
<div class="mb-4 text-h3 font-weight-bold">
{{ tm("resources.title") }}
</div>
<v-row>
<v-col
cols="12"
sm="4"
>
<v-col cols="12" sm="4">
<!-- GitHub Card -->
<v-card
variant="outlined"
@@ -206,12 +192,7 @@
target="_blank"
>
<div class="d-flex align-center mb-3">
<v-icon
size="32"
class="mr-3"
>
mdi-github
</v-icon>
<v-icon size="32" class="mr-3"> mdi-github </v-icon>
<span class="text-h6 font-weight-bold">GitHub</span>
</div>
<p class="text-body-2 text-medium-emphasis mb-0">
@@ -220,10 +201,7 @@
</v-card>
</v-col>
<v-col
cols="12"
sm="4"
>
<v-col cols="12" sm="4">
<!-- Docs Card -->
<v-card
variant="outlined"
@@ -232,10 +210,7 @@
target="_blank"
>
<div class="d-flex align-center mb-3">
<v-icon
size="32"
class="mr-3"
>
<v-icon size="32" class="mr-3">
mdi-book-open-variant
</v-icon>
<span class="text-h6 font-weight-bold">{{
@@ -248,10 +223,7 @@
</v-card>
</v-col>
<v-col
cols="12"
sm="4"
>
<v-col cols="12" sm="4">
<!-- Afdian Card -->
<v-card
variant="outlined"
@@ -260,12 +232,7 @@
target="_blank"
>
<div class="d-flex align-center mb-3">
<v-icon
size="32"
class="mr-3"
>
mdi-hand-heart
</v-icon>
<v-icon size="32" class="mr-3"> mdi-hand-heart </v-icon>
<span class="text-h6 font-weight-bold">{{
tm("resources.afdianTitle")
}}</span>
@@ -280,16 +247,9 @@
</v-col>
</v-row>
<v-row
v-if="showAnnouncement"
class="px-4 mb-4"
>
<v-row v-if="showAnnouncement" class="px-4 mb-4">
<v-col cols="12">
<v-card
class="welcome-card pa-6"
elevation="0"
border
>
<v-card class="welcome-card pa-6" elevation="0" border>
<div class="mb-4 text-h3 font-weight-bold">
{{ tm("announcement.title") }}
</div>
@@ -457,32 +417,34 @@ async function loadPlatformConfigBase() {
async function checkAndSaveBackend() {
checkingBackend.value = true;
try {
// try to connect
const url = apiBaseUrl.value.replace(/\/+$/, "");
// temp set axios base url to check
const originalBase = axios.defaults.baseURL;
axios.defaults.baseURL = url;
const originalBase = axios.defaults.baseURL;
const normalizedUrl = normalizeConfiguredApiBaseUrl(apiBaseUrl.value);
const validationError = getApiBaseUrlValidationError(normalizedUrl);
if (validationError) {
backendStepState.value = "pending";
showError(validationError);
checkingBackend.value = false;
return;
}
try {
setApiBaseUrl(normalizedUrl);
await axios.get("/api/stat/version");
// if success, save
apiStore.setApiBaseUrl(url);
apiStore.setApiBaseUrl(normalizedUrl);
apiBaseUrl.value = normalizedUrl;
backendStepState.value = "completed";
showSuccess("Connected to AstrBot Backend successfully!");
// load subsequent data
await loadPlatformConfigBase();
if ((platformConfigData.value.platform || []).length > 0) {
platformStepState.value = "completed";
}
} catch (e) {
showError("Failed to connect to backend: " + e);
} catch (error) {
setApiBaseUrl(originalBase);
backendStepState.value = "pending";
// restore if failed (though user might want to try another)
// but here we just keep the axios instance dirty or reset?
// actually apiStore.init() logic should be used but simpler:
// we don't reset axios defaults here because the user might be trying to correct it.
showError(`Failed to connect to backend: ${error}`);
} finally {
checkingBackend.value = false;
}

View File

@@ -7,6 +7,8 @@ import { useApiStore } from "@/stores/api";
import { useRouter } from "vue-router";
import { useCustomizerStore } from "@/stores/customizer";
import { useModuleI18n } from "@/i18n/composables";
import { useToast } from "@/utils/toast";
import { getApiBaseUrlValidationError } from "@/utils/request";
const cardVisible = ref(false);
const router = useRouter();
@@ -14,6 +16,7 @@ const authStore = useAuthStore();
const apiStore = useApiStore();
const customizer = useCustomizerStore();
const { tm: t } = useModuleI18n("features/auth");
const toast = useToast();
const serverConfigDialog = ref(false);
const apiUrl = ref(apiStore.apiBaseUrl);
@@ -23,6 +26,12 @@ const newPresetName = ref("");
const newPresetUrl = ref("");
function saveApiUrl() {
const validationError = getApiBaseUrlValidationError(apiUrl.value);
if (validationError) {
toast.error(validationError);
return;
}
apiStore.setApiBaseUrl(apiUrl.value);
serverConfigDialog.value = false;
window.location.reload();
@@ -64,17 +73,14 @@ onMounted(() => {
<template>
<div class="login-page-container">
<v-card
class="login-card"
elevation="1"
>
<v-card class="login-card" elevation="1">
<v-card-title>
<div class="d-flex justify-space-between align-center w-100">
<img
width="80"
src="@/assets/images/icon-no-shadow.svg"
alt="AstrBot Logo"
>
/>
<div class="d-flex align-center gap-1">
<LanguageSwitcher />
<v-divider
@@ -94,16 +100,10 @@ onMounted(() => {
size="small"
@click="serverConfigDialog = true"
>
<v-icon
size="18"
:color="'rgb(var(--v-theme-primary))'"
>
<v-icon size="18" :color="'rgb(var(--v-theme-primary))'">
mdi-server
</v-icon>
<v-tooltip
activator="parent"
location="top"
>
<v-tooltip activator="parent" location="top">
{{ t("serverConfig.tooltip") }}
</v-tooltip>
</v-btn>
@@ -115,31 +115,27 @@ onMounted(() => {
size="small"
@click="toggleTheme"
>
<v-icon
size="18"
:color="'rgb(var(--v-theme-primary))'"
>
{{ customizer.isDarkTheme ? 'mdi-weather-night' : 'mdi-white-balance-sunny' }}
<v-icon size="18" :color="'rgb(var(--v-theme-primary))'">
{{
customizer.isDarkTheme
? "mdi-weather-night"
: "mdi-white-balance-sunny"
}}
</v-icon>
<v-tooltip
activator="parent"
location="top"
>
{{ customizer.isDarkTheme ? t('theme.switchToLight') : t('theme.switchToDark') }}
<v-tooltip activator="parent" location="top">
{{
customizer.isDarkTheme
? t("theme.switchToLight")
: t("theme.switchToDark")
}}
</v-tooltip>
</v-btn>
</div>
</div>
<div
class="ml-2"
style="font-size: 26px"
>
<div class="ml-2" style="font-size: 26px">
{{ t("logo.title") }}
</div>
<div
class="mt-2 ml-2"
style="font-size: 14px; color: grey"
>
<div class="mt-2 ml-2" style="font-size: 14px; color: grey">
{{ t("logo.subtitle") }}
</div>
</v-card-title>
@@ -148,10 +144,7 @@ onMounted(() => {
</v-card-text>
</v-card>
<v-dialog
v-model="serverConfigDialog"
max-width="450"
>
<v-dialog v-model="serverConfigDialog" max-width="450">
<v-card>
<v-card-title>{{ t("serverConfig.title") }}</v-card-title>
<v-card-text>
@@ -162,7 +155,7 @@ onMounted(() => {
<div
v-if="
(apiStore.presets && apiStore.presets.length > 0) ||
apiStore.customPresets
apiStore.customPresets
"
class="mb-4"
>
@@ -243,22 +236,11 @@ onMounted(() => {
</v-card-text>
<v-card-actions>
<v-spacer />
<v-btn
variant="text"
@click="serverConfigDialog = false"
>
{{
t("serverConfig.cancel")
}}
<v-btn variant="text" @click="serverConfigDialog = false">
{{ t("serverConfig.cancel") }}
</v-btn>
<v-btn
color="primary"
variant="flat"
@click="saveApiUrl"
>
{{
t("serverConfig.save")
}}
<v-btn color="primary" variant="flat" @click="saveApiUrl">
{{ t("serverConfig.save") }}
</v-btn>
</v-card-actions>
</v-card>