mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-02 01:10:23 +08:00
fix(public): keep original GitHub links visible
(cherry picked from commit 7c93552697c9c35b77df35e117900ff8e9b62994)
This commit is contained in:
@@ -10,7 +10,7 @@ use crate::handlers::shared::{
|
||||
decrypt_catalog_secret_with_fallbacks, encrypt_catalog_secret_with_fallbacks,
|
||||
escape_admin_email_template_html, module_available_from_env, query_param_bool,
|
||||
query_param_optional_bool, query_param_value, read_admin_email_template_payload,
|
||||
render_admin_email_template_html, system_config_bool, system_config_string,
|
||||
render_admin_email_template_html, system_config_string,
|
||||
unix_secs_to_rfc3339,
|
||||
};
|
||||
use crate::{AppState, GatewayError};
|
||||
@@ -239,17 +239,10 @@ pub(crate) async fn maybe_build_local_public_support_response(
|
||||
.flatten()
|
||||
.and_then(|value| value.as_str().map(ToOwned::to_owned))
|
||||
.unwrap_or_else(|| "AI Gateway".to_string());
|
||||
let show_github_link = state
|
||||
.read_system_config_json_value("show_github_link")
|
||||
.await
|
||||
.ok()
|
||||
.flatten();
|
||||
let show_github_link = system_config_bool(show_github_link.as_ref(), true);
|
||||
return Some(
|
||||
Json(json!({
|
||||
"site_name": site_name,
|
||||
"site_subtitle": site_subtitle,
|
||||
"show_github_link": show_github_link,
|
||||
}))
|
||||
.into_response(),
|
||||
);
|
||||
|
||||
@@ -2455,6 +2455,7 @@ async fn gateway_completes_admin_provider_oauth_key_locally_with_trusted_admin_p
|
||||
.expect("account_state_recheck_error should be string when recheck is attempted");
|
||||
assert!(
|
||||
account_state_recheck_error == "backend-api/me API 返回状态码 401"
|
||||
|| account_state_recheck_error == "backend-api/me API 返回状态码 403"
|
||||
|| account_state_recheck_error.starts_with("backend-api/me 请求执行失败:"),
|
||||
"unexpected account_state_recheck_error: {account_state_recheck_error}"
|
||||
);
|
||||
@@ -4926,6 +4927,7 @@ async fn gateway_refreshes_admin_provider_oauth_key_locally_with_trusted_admin_p
|
||||
.expect("account_state_recheck_error should be string when attempted");
|
||||
assert!(
|
||||
account_state_recheck_error == "backend-api/me API 返回状态码 401"
|
||||
|| account_state_recheck_error == "backend-api/me API 返回状态码 403"
|
||||
|| account_state_recheck_error.starts_with("backend-api/me 请求执行失败:"),
|
||||
"unexpected account_state_recheck_error: {account_state_recheck_error}"
|
||||
);
|
||||
|
||||
@@ -669,7 +669,6 @@ async fn gateway_handles_public_catalog_site_info_without_proxying_upstream() {
|
||||
vec![
|
||||
("site_name".to_string(), json!("Aether Local")),
|
||||
("site_subtitle".to_string(), json!("Rust Only")),
|
||||
("show_github_link".to_string(), json!(false)),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -686,7 +685,7 @@ async fn gateway_handles_public_catalog_site_info_without_proxying_upstream() {
|
||||
let payload: serde_json::Value = response.json().await.expect("json body should parse");
|
||||
assert_eq!(payload["site_name"], "Aether Local");
|
||||
assert_eq!(payload["site_subtitle"], "Rust Only");
|
||||
assert_eq!(payload["show_github_link"], false);
|
||||
assert_eq!(payload.as_object().map(|object| object.len()), Some(2));
|
||||
assert_eq!(*upstream_hits.lock().expect("mutex should lock"), 0);
|
||||
|
||||
gateway_handle.abort();
|
||||
|
||||
@@ -14,21 +14,19 @@ describe('useSiteInfo', () => {
|
||||
apiClientMocks.get.mockReset()
|
||||
})
|
||||
|
||||
it('loads github link display setting from public site info', async () => {
|
||||
it('loads public site info', async () => {
|
||||
apiClientMocks.get.mockResolvedValue({
|
||||
data: {
|
||||
site_name: 'Custom Aether',
|
||||
site_subtitle: 'Gateway',
|
||||
show_github_link: false,
|
||||
},
|
||||
})
|
||||
|
||||
const { useSiteInfo } = await import('../useSiteInfo')
|
||||
const { siteName, siteSubtitle, showGithubLink, refreshSiteInfo } = useSiteInfo()
|
||||
const { siteName, siteSubtitle, refreshSiteInfo } = useSiteInfo()
|
||||
await refreshSiteInfo()
|
||||
|
||||
expect(siteName.value).toBe('Custom Aether')
|
||||
expect(siteSubtitle.value).toBe('Gateway')
|
||||
expect(showGithubLink.value).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -4,13 +4,11 @@ import apiClient from '@/api/client'
|
||||
interface SiteInfo {
|
||||
site_name: string
|
||||
site_subtitle: string
|
||||
show_github_link?: boolean
|
||||
}
|
||||
|
||||
// 模块级缓存,所有组件共享同一份数据
|
||||
const siteName = ref('Aether')
|
||||
const siteSubtitle = ref('AI Gateway')
|
||||
const showGithubLink = ref(true)
|
||||
const loaded = ref(false)
|
||||
let fetchPromise: Promise<void> | null = null
|
||||
|
||||
@@ -19,7 +17,6 @@ async function fetchSiteInfo() {
|
||||
const response = await apiClient.get<SiteInfo>('/api/public/site-info')
|
||||
siteName.value = response.data.site_name
|
||||
siteSubtitle.value = response.data.site_subtitle
|
||||
showGithubLink.value = response.data.show_github_link !== false
|
||||
loaded.value = true
|
||||
} catch {
|
||||
// 加载失败时保持默认值,允许后续重试
|
||||
@@ -38,7 +35,7 @@ export function useSiteInfo() {
|
||||
if (!loaded.value && !fetchPromise) {
|
||||
fetchPromise = fetchSiteInfo()
|
||||
}
|
||||
return { siteName, siteSubtitle, showGithubLink, refreshSiteInfo }
|
||||
return { siteName, siteSubtitle, refreshSiteInfo }
|
||||
}
|
||||
|
||||
// 站点名称变化时同步更新 document.title
|
||||
|
||||
@@ -325,7 +325,6 @@
|
||||
</button>
|
||||
<!-- GitHub Link -->
|
||||
<a
|
||||
v-if="showGithubLink"
|
||||
href="https://github.com/fawney19/Aether"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
@@ -413,7 +412,7 @@ const route = useRoute()
|
||||
const authStore = useAuthStore()
|
||||
const moduleStore = useModuleStore()
|
||||
const { themeMode, toggleDarkMode } = useDarkMode()
|
||||
const { siteName, siteSubtitle, showGithubLink } = useSiteInfo()
|
||||
const { siteName, siteSubtitle } = useSiteInfo()
|
||||
const isDemo = computed(() => isDemoMode())
|
||||
const isAdmin = computed(() => authStore.user?.role === 'admin')
|
||||
|
||||
|
||||
@@ -14,13 +14,11 @@
|
||||
id="section-site-info"
|
||||
:site-name="systemConfig.site_name"
|
||||
:site-subtitle="systemConfig.site_subtitle"
|
||||
:show-github-link="systemConfig.show_github_link"
|
||||
:loading="siteInfoLoading"
|
||||
:has-changes="hasSiteInfoChanges"
|
||||
@save="saveSiteInfo"
|
||||
@update:site-name="systemConfig.site_name = $event"
|
||||
@update:site-subtitle="systemConfig.site_subtitle = $event"
|
||||
@update:show-github-link="systemConfig.show_github_link = $event"
|
||||
/>
|
||||
|
||||
<!-- 配置导出/导入 -->
|
||||
|
||||
@@ -51,21 +51,6 @@
|
||||
显示在导航栏品牌名称下方
|
||||
</p>
|
||||
</div>
|
||||
<div class="md:col-span-2 flex items-center justify-between gap-4 rounded-lg border border-border/60 bg-muted/20 p-4">
|
||||
<div>
|
||||
<Label class="block text-sm font-medium">
|
||||
GitHub 仓库入口
|
||||
</Label>
|
||||
<p class="mt-1 text-xs text-muted-foreground">
|
||||
控制首页、指南页和控制台顶部的 GitHub 链接是否展示
|
||||
</p>
|
||||
</div>
|
||||
<Switch
|
||||
:model-value="showGithubLink"
|
||||
:disabled="loading"
|
||||
@update:model-value="$emit('update:showGithubLink', $event)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</CardSection>
|
||||
</template>
|
||||
@@ -74,13 +59,11 @@
|
||||
import Button from '@/components/ui/button.vue'
|
||||
import Input from '@/components/ui/input.vue'
|
||||
import Label from '@/components/ui/label.vue'
|
||||
import Switch from '@/components/ui/switch.vue'
|
||||
import { CardSection } from '@/components/layout'
|
||||
|
||||
defineProps<{
|
||||
siteName: string
|
||||
siteSubtitle: string
|
||||
showGithubLink: boolean
|
||||
loading: boolean
|
||||
hasChanges: boolean
|
||||
}>()
|
||||
@@ -89,6 +72,5 @@ defineEmits<{
|
||||
save: []
|
||||
'update:siteName': [value: string]
|
||||
'update:siteSubtitle': [value: string]
|
||||
'update:showGithubLink': [value: boolean]
|
||||
}>()
|
||||
</script>
|
||||
|
||||
@@ -35,23 +35,21 @@ vi.mock('@/components/ui/button.vue', () => ({
|
||||
|
||||
const mountedApps: Array<{ app: App, root: HTMLElement }> = []
|
||||
|
||||
function mountSection(onUpdateShowGithubLink = vi.fn()) {
|
||||
function mountSection() {
|
||||
const root = document.createElement('div')
|
||||
document.body.appendChild(root)
|
||||
const app = createApp(SiteInfoSection, {
|
||||
siteName: 'Aether',
|
||||
siteSubtitle: 'AI Gateway',
|
||||
showGithubLink: false,
|
||||
loading: false,
|
||||
hasChanges: true,
|
||||
onSave: vi.fn(),
|
||||
'onUpdate:siteName': vi.fn(),
|
||||
'onUpdate:siteSubtitle': vi.fn(),
|
||||
'onUpdate:showGithubLink': onUpdateShowGithubLink,
|
||||
})
|
||||
app.mount(root)
|
||||
mountedApps.push({ app, root })
|
||||
return { root, onUpdateShowGithubLink }
|
||||
return { root }
|
||||
}
|
||||
|
||||
afterEach(() => {
|
||||
@@ -63,15 +61,11 @@ afterEach(() => {
|
||||
})
|
||||
|
||||
describe('SiteInfoSection', () => {
|
||||
it('renders and emits the github link display switch', async () => {
|
||||
const { root, onUpdateShowGithubLink } = mountSection()
|
||||
it('renders site name and subtitle fields', async () => {
|
||||
const { root } = mountSection()
|
||||
await nextTick()
|
||||
|
||||
expect(root.textContent).toContain('GitHub 仓库入口')
|
||||
const switchButton = root.querySelector('[role="switch"]') as HTMLButtonElement | null
|
||||
expect(switchButton?.getAttribute('aria-checked')).toBe('false')
|
||||
|
||||
switchButton?.click()
|
||||
expect(onUpdateShowGithubLink).toHaveBeenCalledWith(true)
|
||||
expect(root.textContent).toContain('站点名称')
|
||||
expect(root.textContent).toContain('站点副标题')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -8,7 +8,6 @@ export interface SystemConfig {
|
||||
// 站点信息
|
||||
site_name: string
|
||||
site_subtitle: string
|
||||
show_github_link: boolean
|
||||
// 网络代理
|
||||
system_proxy_node_id: string | null
|
||||
// 基础配置
|
||||
@@ -55,7 +54,6 @@ const CONFIG_KEYS = [
|
||||
// 站点信息
|
||||
'site_name',
|
||||
'site_subtitle',
|
||||
'show_github_link',
|
||||
// 网络代理
|
||||
'system_proxy_node_id',
|
||||
// 基础配置
|
||||
@@ -102,7 +100,6 @@ function createDefaultConfig(): SystemConfig {
|
||||
// 站点信息
|
||||
site_name: 'Aether',
|
||||
site_subtitle: 'AI Gateway',
|
||||
show_github_link: true,
|
||||
// 网络代理
|
||||
system_proxy_node_id: null,
|
||||
// 基础配置
|
||||
@@ -166,8 +163,7 @@ export function useSystemConfig() {
|
||||
if (!originalConfig.value) return false
|
||||
return (
|
||||
systemConfig.value.site_name !== originalConfig.value.site_name ||
|
||||
systemConfig.value.site_subtitle !== originalConfig.value.site_subtitle ||
|
||||
systemConfig.value.show_github_link !== originalConfig.value.show_github_link
|
||||
systemConfig.value.site_subtitle !== originalConfig.value.site_subtitle
|
||||
)
|
||||
})
|
||||
|
||||
@@ -311,11 +307,6 @@ export function useSystemConfig() {
|
||||
value: systemConfig.value.site_subtitle,
|
||||
description: '站点副标题',
|
||||
},
|
||||
{
|
||||
key: 'show_github_link',
|
||||
value: systemConfig.value.show_github_link,
|
||||
description: '是否显示 GitHub 仓库入口',
|
||||
},
|
||||
]
|
||||
await Promise.all(
|
||||
configItems.map((item) =>
|
||||
@@ -325,7 +316,6 @@ export function useSystemConfig() {
|
||||
if (originalConfig.value) {
|
||||
originalConfig.value.site_name = systemConfig.value.site_name
|
||||
originalConfig.value.site_subtitle = systemConfig.value.site_subtitle
|
||||
originalConfig.value.show_github_link = systemConfig.value.show_github_link
|
||||
}
|
||||
await refreshSiteInfo()
|
||||
success('站点信息已保存')
|
||||
|
||||
@@ -75,7 +75,6 @@
|
||||
/>
|
||||
</button>
|
||||
<a
|
||||
v-if="showGithubLink"
|
||||
href="https://github.com/fawney19/Aether"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
@@ -90,10 +89,7 @@
|
||||
<!-- Desktop layout (>= md): Centered nav with balanced spacing -->
|
||||
<div class="h-16 hidden md:flex items-center justify-between px-8">
|
||||
<!-- Left spacer for balance (matches right icons width) -->
|
||||
<div
|
||||
class="shrink-0"
|
||||
:class="showGithubLink ? 'w-[76px]' : 'w-9'"
|
||||
/>
|
||||
<div class="w-[76px] shrink-0" />
|
||||
|
||||
<!-- Center: Logo + Nav + Login Button -->
|
||||
<div class="flex items-center">
|
||||
@@ -190,7 +186,6 @@
|
||||
/>
|
||||
</button>
|
||||
<a
|
||||
v-if="showGithubLink"
|
||||
href="https://github.com/fawney19/Aether"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
@@ -500,7 +495,7 @@ import {
|
||||
const authStore = useAuthStore()
|
||||
const { isDark, themeMode, toggleDarkMode } = useDarkMode()
|
||||
const { copyToClipboard } = useClipboard()
|
||||
const { siteName, siteSubtitle, showGithubLink } = useSiteInfo()
|
||||
const { siteName, siteSubtitle } = useSiteInfo()
|
||||
|
||||
const dashboardPath = computed(() =>
|
||||
authStore.canAccessAdmin ? '/admin/dashboard' : '/dashboard'
|
||||
|
||||
@@ -261,7 +261,6 @@
|
||||
/>
|
||||
</button>
|
||||
<a
|
||||
v-if="showGithubLink"
|
||||
href="https://github.com/fawney19/Aether"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
@@ -341,7 +340,7 @@ import { guideNavItems } from './guide-config'
|
||||
|
||||
const route = useRoute()
|
||||
const { themeMode, toggleDarkMode } = useDarkMode()
|
||||
const { siteName, siteSubtitle, showGithubLink } = useSiteInfo()
|
||||
const { siteName, siteSubtitle } = useSiteInfo()
|
||||
|
||||
const mobileMenuOpen = ref(false)
|
||||
const baseUrl = ref(typeof window !== 'undefined' ? window.location.origin : 'https://your-aether.com')
|
||||
|
||||
@@ -12,12 +12,10 @@ import {
|
||||
Zap,
|
||||
} from 'lucide-vue-next'
|
||||
import { panelClasses } from './guide-config'
|
||||
import { useSiteInfo } from '@/composables/useSiteInfo'
|
||||
|
||||
// 部署步骤数据
|
||||
const activeDeployTab = ref(0)
|
||||
const copiedStep = ref<string | null>(null)
|
||||
const { showGithubLink } = useSiteInfo()
|
||||
|
||||
const productionSteps = [
|
||||
{
|
||||
@@ -399,7 +397,6 @@ function copyStep(stepId: string, code: string) {
|
||||
<h3>1. Aether-Proxy</h3>
|
||||
<p>Rust实现, 超小资源占有, 适合性能低的VPS直接使用。</p>
|
||||
<a
|
||||
v-if="showGithubLink"
|
||||
href="https://github.com/fawney19/Aether/tree/main/aether-proxy"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
|
||||
@@ -790,6 +790,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, computed, onBeforeUnmount, nextTick, watch, markRaw } from 'vue'
|
||||
import type { Component } from 'vue'
|
||||
import { useAuthStore } from '@/stores/auth'
|
||||
import { dashboardApi, type DashboardStat, type DailyStat, type ProviderSummary } from '@/api/dashboard'
|
||||
import { getDateRangeFromPeriod } from '@/features/usage/composables'
|
||||
@@ -838,6 +839,10 @@ import type { ChartData, ChartOptions, ChartDataset, TooltipItem } from 'chart.j
|
||||
|
||||
const authStore = useAuthStore()
|
||||
|
||||
type DashboardStatCard = Omit<DashboardStat, 'icon'> & {
|
||||
icon: Component
|
||||
}
|
||||
|
||||
const statsPanelRef = ref<HTMLElement | null>(null)
|
||||
const announcementsHeight = ref<number | null>(null)
|
||||
const announcementsTimelineRef = ref<HTMLElement | null>(null)
|
||||
@@ -941,7 +946,7 @@ const getStatIconColor = (_index: number): string => {
|
||||
}
|
||||
|
||||
// 统计数据
|
||||
const stats = ref<DashboardStat[]>([])
|
||||
const stats = ref<DashboardStatCard[]>([])
|
||||
const todayStats = ref<{
|
||||
requests: number
|
||||
tokens: number
|
||||
@@ -1006,7 +1011,7 @@ const loadingAnnouncements = ref(false)
|
||||
const selectedAnnouncement = ref<Announcement | null>(null)
|
||||
const detailDialogOpen = ref(false)
|
||||
|
||||
const iconMap: Record<string, unknown> = {
|
||||
const iconMap: Record<string, Component> = {
|
||||
Users, Activity, TrendingUp, DollarSign, Key, Hash, Zap, Database
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user