mirror of
https://github.com/AstrBotDevs/AstrBot
synced 2026-07-16 09:40:30 +08:00
fix: harden runtime bootstrap and unify confirm handling
- Simplify bootstrap log buffering and add a public initialize hook for non-main startup paths. - Guard aiohttp TLS patching with feature/type checks and keep graceful fallback when internals are unavailable. - Standardize dashboard confirmation flow via shared confirm helpers across composition and options API components.
This commit is contained in:
@@ -141,10 +141,10 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { inject, ref } from 'vue';
|
||||
import { ref } from 'vue';
|
||||
import { useI18n, useModuleI18n } from '@/i18n/composables';
|
||||
import type { Session } from '@/composables/useSessions';
|
||||
import { askForConfirmation, type ConfirmDialogHandler } from '@/utils/confirmDialog';
|
||||
import { askForConfirmation, useConfirmDialog } from '@/utils/confirmDialog';
|
||||
import LanguageSwitcher from '@/components/shared/LanguageSwitcher.vue';
|
||||
import StyledMenu from '@/components/shared/StyledMenu.vue';
|
||||
import ProviderConfigDialog from '@/components/chat/ProviderConfigDialog.vue';
|
||||
@@ -184,7 +184,7 @@ const emit = defineEmits<{
|
||||
const { t } = useI18n();
|
||||
const { tm } = useModuleI18n('features/chat');
|
||||
|
||||
const confirmDialog = inject<ConfirmDialogHandler | undefined>('$confirm', undefined);
|
||||
const confirmDialog = useConfirmDialog();
|
||||
|
||||
const sidebarCollapsed = ref(true);
|
||||
const showProviderConfigDialog = ref(false);
|
||||
|
||||
@@ -42,9 +42,9 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { inject, ref } from 'vue';
|
||||
import { ref } from 'vue';
|
||||
import { useModuleI18n } from '@/i18n/composables';
|
||||
import { askForConfirmation, type ConfirmDialogHandler } from '@/utils/confirmDialog';
|
||||
import { askForConfirmation, useConfirmDialog } from '@/utils/confirmDialog';
|
||||
|
||||
export interface Project {
|
||||
project_id: string;
|
||||
@@ -73,7 +73,7 @@ const emit = defineEmits<{
|
||||
|
||||
const { tm } = useModuleI18n('features/chat');
|
||||
|
||||
const confirmDialog = inject<ConfirmDialogHandler | undefined>('$confirm', undefined);
|
||||
const confirmDialog = useConfirmDialog();
|
||||
|
||||
const expanded = ref(props.initialExpanded);
|
||||
|
||||
|
||||
@@ -45,10 +45,9 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { inject } from 'vue';
|
||||
import { useModuleI18n } from '@/i18n/composables';
|
||||
import type { Project } from '@/components/chat/ProjectList.vue';
|
||||
import { askForConfirmation, type ConfirmDialogHandler } from '@/utils/confirmDialog';
|
||||
import { askForConfirmation, useConfirmDialog } from '@/utils/confirmDialog';
|
||||
|
||||
interface Session {
|
||||
session_id: string;
|
||||
@@ -71,7 +70,7 @@ const emit = defineEmits<{
|
||||
|
||||
const { tm } = useModuleI18n('features/chat');
|
||||
|
||||
const confirmDialog = inject<ConfirmDialogHandler | undefined>('$confirm', undefined);
|
||||
const confirmDialog = useConfirmDialog();
|
||||
|
||||
function formatDate(dateString: string): string {
|
||||
return new Date(dateString).toLocaleString();
|
||||
|
||||
@@ -218,7 +218,7 @@ import axios from 'axios';
|
||||
import { VueMonacoEditor } from '@guolao/vue-monaco-editor';
|
||||
import ItemCard from '@/components/shared/ItemCard.vue';
|
||||
import { useI18n, useModuleI18n } from '@/i18n/composables';
|
||||
import { askForConfirmation as askForConfirmationDialog, resolveConfirmDialog } from '@/utils/confirmDialog';
|
||||
import { askForConfirmation as askForConfirmationDialog } from '@/utils/confirmDialog';
|
||||
|
||||
export default {
|
||||
name: 'McpServersSection',
|
||||
@@ -386,7 +386,7 @@ export default {
|
||||
async deleteServer(server) {
|
||||
const serverName = server.name || server;
|
||||
const message = this.tm('dialogs.confirmDelete', { name: serverName });
|
||||
if (!(await askForConfirmationDialog(message, resolveConfirmDialog(this.$confirm)))) {
|
||||
if (!(await askForConfirmationDialog(message, this.$confirm))) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -367,15 +367,15 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, inject, watch } from 'vue'
|
||||
import { ref, computed, watch } from 'vue'
|
||||
import axios from 'axios'
|
||||
import { useI18n } from '@/i18n/composables'
|
||||
import { askForConfirmation, resolveConfirmDialog } from '@/utils/confirmDialog'
|
||||
import { askForConfirmation, useConfirmDialog } from '@/utils/confirmDialog'
|
||||
import WaitingForRestart from './WaitingForRestart.vue'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const confirmDialog = resolveConfirmDialog(inject('$confirm', undefined))
|
||||
const confirmDialog = useConfirmDialog()
|
||||
|
||||
const isOpen = ref(false)
|
||||
const activeTab = ref('export')
|
||||
|
||||
@@ -307,7 +307,7 @@
|
||||
<script>
|
||||
import axios from 'axios';
|
||||
import { useModuleI18n } from '@/i18n/composables';
|
||||
import { askForConfirmation as askForConfirmationDialog, resolveConfirmDialog } from '@/utils/confirmDialog';
|
||||
import { askForConfirmation as askForConfirmationDialog } from '@/utils/confirmDialog';
|
||||
|
||||
export default {
|
||||
name: 'PersonaForm',
|
||||
@@ -598,7 +598,12 @@ export default {
|
||||
async deletePersona() {
|
||||
if (!this.editingPersona) return;
|
||||
|
||||
if (!(await askForConfirmationDialog(this.tm('messages.deleteConfirm', { id: this.editingPersona.persona_id }), resolveConfirmDialog(this.$confirm)))) {
|
||||
if (
|
||||
!(await askForConfirmationDialog(
|
||||
this.tm('messages.deleteConfirm', { id: this.editingPersona.persona_id }),
|
||||
this.$confirm,
|
||||
))
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { ref, computed, inject, onMounted, nextTick, watch } from 'vue'
|
||||
import { ref, computed, onMounted, nextTick, watch } from 'vue'
|
||||
import axios from 'axios'
|
||||
import { getProviderIcon } from '@/utils/providerUtils'
|
||||
import { askForConfirmation as askForConfirmationDialog, type ConfirmDialogHandler } from '@/utils/confirmDialog'
|
||||
import { askForConfirmation as askForConfirmationDialog, useConfirmDialog } from '@/utils/confirmDialog'
|
||||
|
||||
export interface UseProviderSourcesOptions {
|
||||
defaultTab?: string
|
||||
@@ -38,7 +38,7 @@ export function resolveDefaultTab(value?: string) {
|
||||
export function useProviderSources(options: UseProviderSourcesOptions) {
|
||||
const { tm, showMessage } = options
|
||||
|
||||
const confirmDialog = inject<ConfirmDialogHandler | undefined>('$confirm', undefined)
|
||||
const confirmDialog = useConfirmDialog()
|
||||
|
||||
async function askForConfirmation(message: string) {
|
||||
return askForConfirmationDialog(message, confirmDialog)
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { inject } from 'vue'
|
||||
|
||||
export type ConfirmDialogOptions = {
|
||||
title?: string
|
||||
message?: string
|
||||
@@ -13,10 +15,16 @@ export function resolveConfirmDialog(candidate: unknown): ConfirmDialogHandler |
|
||||
return undefined
|
||||
}
|
||||
|
||||
export function useConfirmDialog(): ConfirmDialogHandler | undefined {
|
||||
return resolveConfirmDialog(inject('$confirm', undefined))
|
||||
}
|
||||
|
||||
export async function askForConfirmation(
|
||||
message: string,
|
||||
confirmDialog?: ConfirmDialogHandler
|
||||
candidate?: unknown
|
||||
): Promise<boolean> {
|
||||
const confirmDialog = resolveConfirmDialog(candidate)
|
||||
|
||||
if (confirmDialog) {
|
||||
try {
|
||||
return await confirmDialog({ message })
|
||||
|
||||
@@ -190,7 +190,7 @@ import WaitingForRestart from '@/components/shared/WaitingForRestart.vue';
|
||||
import StandaloneChat from '@/components/chat/StandaloneChat.vue';
|
||||
import { VueMonacoEditor } from '@guolao/vue-monaco-editor'
|
||||
import { useI18n, useModuleI18n } from '@/i18n/composables';
|
||||
import { askForConfirmation as askForConfirmationDialog, resolveConfirmDialog } from '@/utils/confirmDialog';
|
||||
import { askForConfirmation as askForConfirmationDialog } from '@/utils/confirmDialog';
|
||||
|
||||
export default {
|
||||
name: 'ConfigPage',
|
||||
@@ -476,7 +476,7 @@ export default {
|
||||
},
|
||||
async confirmDeleteConfig(config) {
|
||||
const message = this.tm('configManagement.confirmDelete').replace('{name}', config.name);
|
||||
if (await askForConfirmationDialog(message, resolveConfirmDialog(this.$confirm))) {
|
||||
if (await askForConfirmationDialog(message, this.$confirm)) {
|
||||
this.deleteConfig(config.id);
|
||||
}
|
||||
},
|
||||
|
||||
@@ -333,7 +333,7 @@ import { useCommonStore } from '@/stores/common';
|
||||
import { useCustomizerStore } from '@/stores/customizer';
|
||||
import { useI18n, useModuleI18n } from '@/i18n/composables';
|
||||
import MessageList from '@/components/chat/MessageList.vue';
|
||||
import { askForConfirmation as askForConfirmationDialog, resolveConfirmDialog } from '@/utils/confirmDialog';
|
||||
import { askForConfirmation as askForConfirmationDialog } from '@/utils/confirmDialog';
|
||||
|
||||
export default {
|
||||
name: 'ConversationPage',
|
||||
@@ -747,7 +747,7 @@ export default {
|
||||
// 关闭对话历史对话框
|
||||
async closeHistoryDialog() {
|
||||
if (this.isEditingHistory) {
|
||||
if (await askForConfirmationDialog(this.tm('dialogs.view.confirmClose'), resolveConfirmDialog(this.$confirm))) {
|
||||
if (await askForConfirmationDialog(this.tm('dialogs.view.confirmClose'), this.$confirm)) {
|
||||
this.dialogView = false;
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -197,7 +197,7 @@ import AddNewPlatform from '@/components/platform/AddNewPlatform.vue';
|
||||
import { useCommonStore } from '@/stores/common';
|
||||
import { useI18n, useModuleI18n } from '@/i18n/composables';
|
||||
import { getPlatformIcon, getTutorialLink } from '@/utils/platformUtils';
|
||||
import { askForConfirmation as askForConfirmationDialog, resolveConfirmDialog } from '@/utils/confirmDialog';
|
||||
import { askForConfirmation as askForConfirmationDialog } from '@/utils/confirmDialog';
|
||||
|
||||
export default {
|
||||
name: 'PlatformPage',
|
||||
@@ -454,7 +454,7 @@ export default {
|
||||
|
||||
async deletePlatform(platform) {
|
||||
const message = `${this.messages.deleteConfirm} ${platform.id}?`;
|
||||
if (!(await askForConfirmationDialog(message, resolveConfirmDialog(this.$confirm)))) {
|
||||
if (!(await askForConfirmationDialog(message, this.$confirm))) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -522,7 +522,7 @@
|
||||
<script>
|
||||
import axios from 'axios'
|
||||
import { useI18n, useModuleI18n } from '@/i18n/composables'
|
||||
import { askForConfirmation as askForConfirmationDialog, resolveConfirmDialog } from '@/utils/confirmDialog'
|
||||
import { askForConfirmation as askForConfirmationDialog } from '@/utils/confirmDialog'
|
||||
|
||||
export default {
|
||||
name: 'SessionManagementPage',
|
||||
@@ -1505,7 +1505,7 @@ export default {
|
||||
|
||||
async deleteGroup(group) {
|
||||
const message = `确定要删除分组 "${group.name}" 吗?`
|
||||
if (!(await askForConfirmationDialog(message, resolveConfirmDialog(this.$confirm)))) return
|
||||
if (!(await askForConfirmationDialog(message, this.$confirm))) return
|
||||
|
||||
try {
|
||||
const response = await axios.post('/api/session/group/delete', { id: group.id })
|
||||
|
||||
@@ -240,16 +240,16 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, inject, onMounted } from 'vue'
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import axios from 'axios'
|
||||
import { useModuleI18n } from '@/i18n/composables'
|
||||
import { askForConfirmation, type ConfirmDialogHandler } from '@/utils/confirmDialog'
|
||||
import { askForConfirmation, useConfirmDialog } from '@/utils/confirmDialog'
|
||||
|
||||
const { tm: t } = useModuleI18n('features/knowledge-base/document')
|
||||
const route = useRoute()
|
||||
|
||||
const confirmDialog = inject<ConfirmDialogHandler | undefined>('$confirm', undefined)
|
||||
const confirmDialog = useConfirmDialog()
|
||||
|
||||
const kbId = ref(route.params.kbId as string)
|
||||
const docId = ref(route.params.docId as string)
|
||||
|
||||
@@ -260,7 +260,7 @@ import PersonaCard from './PersonaCard.vue';
|
||||
import PersonaForm from '@/components/shared/PersonaForm.vue';
|
||||
import CreateFolderDialog from './CreateFolderDialog.vue';
|
||||
import MoveToFolderDialog from './MoveToFolderDialog.vue';
|
||||
import { askForConfirmation as askForConfirmationDialog, resolveConfirmDialog } from '@/utils/confirmDialog';
|
||||
import { askForConfirmation as askForConfirmationDialog } from '@/utils/confirmDialog';
|
||||
|
||||
import type { Folder, FolderTreeNode } from '@/components/folder/types';
|
||||
|
||||
@@ -421,8 +421,12 @@ export default defineComponent({
|
||||
},
|
||||
|
||||
async confirmDeletePersona(persona: Persona) {
|
||||
const confirmDialog = resolveConfirmDialog((this as any).$confirm);
|
||||
if (!(await askForConfirmationDialog(this.tm('messages.deleteConfirm', { id: persona.persona_id }), confirmDialog))) {
|
||||
if (
|
||||
!(await askForConfirmationDialog(
|
||||
this.tm('messages.deleteConfirm', { id: persona.persona_id }),
|
||||
(this as any).$confirm,
|
||||
))
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
2
main.py
2
main.py
@@ -94,7 +94,7 @@ async def check_dashboard_files(webui_dir: str | None = None):
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
runtime_bootstrap.flush_bootstrap_records(logger)
|
||||
runtime_bootstrap.initialize_runtime_bootstrap(logger)
|
||||
|
||||
parser = argparse.ArgumentParser(description="AstrBot")
|
||||
parser.add_argument(
|
||||
|
||||
@@ -1,67 +1,80 @@
|
||||
import ssl
|
||||
from collections.abc import Callable
|
||||
from dataclasses import dataclass
|
||||
from typing import Any
|
||||
|
||||
import aiohttp.connector as aiohttp_connector
|
||||
import certifi
|
||||
|
||||
|
||||
@dataclass
|
||||
class BootstrapRecord:
|
||||
level: str
|
||||
message: str
|
||||
|
||||
|
||||
_BOOTSTRAP_RECORDS: list[BootstrapRecord] = []
|
||||
_BOOTSTRAP_RECORDS: list[tuple[str, str]] = []
|
||||
_TLS_BOOTSTRAP_DONE = False
|
||||
|
||||
|
||||
def _record(level: str, message: str) -> None:
|
||||
_BOOTSTRAP_RECORDS.append(BootstrapRecord(level=level, message=message))
|
||||
_BOOTSTRAP_RECORDS.append((level, message))
|
||||
|
||||
|
||||
def flush_bootstrap_records(log_obj: Any) -> None:
|
||||
if not _BOOTSTRAP_RECORDS:
|
||||
return
|
||||
|
||||
level_methods: dict[str, Callable[[str], Any]] = {
|
||||
"info": getattr(log_obj, "info", None),
|
||||
"warning": getattr(log_obj, "warning", None),
|
||||
"error": getattr(log_obj, "error", None),
|
||||
}
|
||||
fallback = getattr(log_obj, "info", None)
|
||||
|
||||
for record in _BOOTSTRAP_RECORDS:
|
||||
logger_method = level_methods.get(record.level, fallback)
|
||||
for level, message in _BOOTSTRAP_RECORDS:
|
||||
logger_method = getattr(log_obj, level, None) or getattr(log_obj, "info", None)
|
||||
if callable(logger_method):
|
||||
logger_method(record.message)
|
||||
logger_method(message)
|
||||
|
||||
_BOOTSTRAP_RECORDS.clear()
|
||||
|
||||
|
||||
def configure_runtime_ca_bundle() -> None:
|
||||
def _try_patch_aiohttp_ssl_context(ssl_context: ssl.SSLContext) -> bool:
|
||||
attr_name = "_SSL_CONTEXT_VERIFIED"
|
||||
|
||||
if not hasattr(aiohttp_connector, attr_name):
|
||||
_record(
|
||||
"warning",
|
||||
"aiohttp connector does not expose _SSL_CONTEXT_VERIFIED; skipped patch.",
|
||||
)
|
||||
return False
|
||||
|
||||
current_value = getattr(aiohttp_connector, attr_name, None)
|
||||
if current_value is not None and not isinstance(current_value, ssl.SSLContext):
|
||||
_record(
|
||||
"warning",
|
||||
"aiohttp connector exposes _SSL_CONTEXT_VERIFIED with unexpected type; skipped patch.",
|
||||
)
|
||||
return False
|
||||
|
||||
setattr(aiohttp_connector, attr_name, ssl_context)
|
||||
_record(
|
||||
"info",
|
||||
"Configured aiohttp verified SSL context with system+certifi trust chain.",
|
||||
)
|
||||
return True
|
||||
|
||||
|
||||
def configure_runtime_ca_bundle() -> bool:
|
||||
global _TLS_BOOTSTRAP_DONE
|
||||
|
||||
if _TLS_BOOTSTRAP_DONE:
|
||||
return True
|
||||
|
||||
try:
|
||||
_record("info", "Bootstrapping runtime CA bundle.")
|
||||
ssl_context = ssl.create_default_context()
|
||||
ssl_context.load_verify_locations(cafile=certifi.where())
|
||||
|
||||
if hasattr(aiohttp_connector, "_SSL_CONTEXT_VERIFIED"):
|
||||
aiohttp_connector._SSL_CONTEXT_VERIFIED = ssl_context
|
||||
_record(
|
||||
"info",
|
||||
"Configured aiohttp verified SSL context with system+certifi trust chain.",
|
||||
)
|
||||
else:
|
||||
_record(
|
||||
"warning",
|
||||
"aiohttp connector does not expose _SSL_CONTEXT_VERIFIED; skipped patch.",
|
||||
)
|
||||
_TLS_BOOTSTRAP_DONE = _try_patch_aiohttp_ssl_context(ssl_context)
|
||||
return _TLS_BOOTSTRAP_DONE
|
||||
except Exception as exc:
|
||||
_record(
|
||||
"error",
|
||||
f"Failed to configure runtime CA bundle for aiohttp: {exc!r}",
|
||||
)
|
||||
return
|
||||
return False
|
||||
|
||||
|
||||
def initialize_runtime_bootstrap(log_obj: Any | None = None) -> bool:
|
||||
configured = configure_runtime_ca_bundle()
|
||||
if log_obj is not None:
|
||||
flush_bootstrap_records(log_obj)
|
||||
return configured
|
||||
|
||||
|
||||
configure_runtime_ca_bundle()
|
||||
|
||||
Reference in New Issue
Block a user