mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-09-30 14:26:36 +08:00
feat(settings): add GeoIP provider selection to settings page
- Introduced a new ResourceSelect component for selecting the GeoIP provider. - Added options for disabling GeoIP, using MaxMind mmdb, and various external GeoIP services. - Implemented saving functionality for the selected GeoIP provider with a corresponding button. - Updated default operation fields to include GeoIPProvider with a default value of 'disabled'.
This commit is contained in:
@@ -9,6 +9,7 @@ import { useEffect, useMemo, useState } from 'react';
|
||||
|
||||
import { EmptyState } from '@/components/feedback/empty-state';
|
||||
import { useTheme } from '@/components/providers/theme-provider';
|
||||
import worldGeoJson from '@/features/dashboard/data/world-geo.json';
|
||||
import { StatusBadge } from '@/components/ui/status-badge';
|
||||
import type {
|
||||
DashboardConfig,
|
||||
@@ -71,6 +72,56 @@ type MapNodeDatum = {
|
||||
};
|
||||
};
|
||||
|
||||
type WorldGeoJson = {
|
||||
type: string;
|
||||
features?: unknown[];
|
||||
};
|
||||
|
||||
let worldMapRegistrationAttempted = false;
|
||||
let worldMapRegistrationSucceeded = false;
|
||||
let worldMapRegistrationError: Error | null = null;
|
||||
|
||||
function ensureWorldMapRegistered() {
|
||||
if (worldMapRegistrationSucceeded || echarts.getMap('world')) {
|
||||
worldMapRegistrationSucceeded = true;
|
||||
worldMapRegistrationError = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (worldMapRegistrationAttempted) {
|
||||
return false;
|
||||
}
|
||||
|
||||
worldMapRegistrationAttempted = true;
|
||||
|
||||
try {
|
||||
const geoJson = worldGeoJson as WorldGeoJson;
|
||||
|
||||
if (geoJson.type !== 'FeatureCollection' || !Array.isArray(geoJson.features)) {
|
||||
throw new Error('invalid world geojson payload');
|
||||
}
|
||||
|
||||
echarts.registerMap(
|
||||
'world',
|
||||
geoJson as unknown as Parameters<typeof echarts.registerMap>[1],
|
||||
);
|
||||
|
||||
if (!echarts.getMap('world')) {
|
||||
throw new Error('world map registration failed');
|
||||
}
|
||||
|
||||
worldMapRegistrationSucceeded = true;
|
||||
worldMapRegistrationError = null;
|
||||
return true;
|
||||
} catch (error) {
|
||||
worldMapRegistrationSucceeded = false;
|
||||
worldMapRegistrationError =
|
||||
error instanceof Error ? error : new Error('unknown world map registration error');
|
||||
console.error('Failed to register ECharts world map', worldMapRegistrationError);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function formatPercent(value: number) {
|
||||
if (!Number.isFinite(value)) {
|
||||
return '0%';
|
||||
@@ -271,32 +322,9 @@ export function WorldStage({
|
||||
const [mapFailed, setMapFailed] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
let disposed = false;
|
||||
|
||||
import('@/features/dashboard/data/world-geo.json')
|
||||
.then(({ default: worldGeoJson }) => {
|
||||
if (!echarts.getMap('world')) {
|
||||
echarts.registerMap('world', worldGeoJson);
|
||||
}
|
||||
if (!echarts.getMap('world')) {
|
||||
throw new Error('world map registration failed');
|
||||
}
|
||||
if (!disposed) {
|
||||
setMapReady(true);
|
||||
setMapFailed(false);
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('Failed to register ECharts world map', error);
|
||||
if (!disposed) {
|
||||
setMapReady(false);
|
||||
setMapFailed(true);
|
||||
}
|
||||
});
|
||||
|
||||
return () => {
|
||||
disposed = true;
|
||||
};
|
||||
const ready = ensureWorldMapRegistered();
|
||||
setMapReady(ready);
|
||||
setMapFailed(!ready);
|
||||
}, []);
|
||||
|
||||
const onlineRate =
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -35,6 +35,7 @@ import {
|
||||
PrimaryButton,
|
||||
ResourceField,
|
||||
ResourceInput,
|
||||
ResourceSelect,
|
||||
ResourceTextarea,
|
||||
SecondaryButton,
|
||||
ToggleField,
|
||||
@@ -71,6 +72,7 @@ const defaultOperationFields = {
|
||||
AgentHeartbeatInterval: '10000',
|
||||
NodeOfflineThreshold: '120000',
|
||||
AgentUpdateRepo: 'Rain-kl/ATSFlare',
|
||||
GeoIPProvider: 'disabled',
|
||||
OpenRestyWorkerProcesses: 'auto',
|
||||
OpenRestyWorkerConnections: '4096',
|
||||
OpenRestyWorkerRlimitNofile: '65535',
|
||||
@@ -337,6 +339,7 @@ export function SettingsPage() {
|
||||
AgentHeartbeatInterval: optionMap.AgentHeartbeatInterval ?? '10000',
|
||||
NodeOfflineThreshold: optionMap.NodeOfflineThreshold ?? '120000',
|
||||
AgentUpdateRepo: optionMap.AgentUpdateRepo ?? 'Rain-kl/ATSFlare',
|
||||
GeoIPProvider: optionMap.GeoIPProvider ?? 'disabled',
|
||||
OpenRestyWorkerProcesses: optionMap.OpenRestyWorkerProcesses ?? 'auto',
|
||||
OpenRestyWorkerConnections:
|
||||
optionMap.OpenRestyWorkerConnections ?? '4096',
|
||||
@@ -1040,6 +1043,47 @@ export function SettingsPage() {
|
||||
/>
|
||||
</ResourceField>
|
||||
</AppCard>
|
||||
<AppCard
|
||||
title="IP 归属方式"
|
||||
description="控制世界地图等场景使用的 IP 归属解析来源。选择 MaxMind 时会按需下载本地 mmdb 数据库。"
|
||||
action={
|
||||
<PrimaryButton
|
||||
type="button"
|
||||
onClick={() =>
|
||||
void runBusyAction('operation-geoip', async () => {
|
||||
await saveOptionEntries(
|
||||
[['GeoIPProvider', operationFields.GeoIPProvider]],
|
||||
'IP 归属方式已保存。',
|
||||
);
|
||||
})
|
||||
}
|
||||
disabled={busyKey === 'operation-geoip'}
|
||||
>
|
||||
{busyKey === 'operation-geoip' ? '保存中...' : '保存归属方式'}
|
||||
</PrimaryButton>
|
||||
}
|
||||
>
|
||||
<ResourceField
|
||||
label="归属方式"
|
||||
hint="disabled 关闭解析;mmdb 使用本地数据库;其余选项调用外部 GeoIP 服务。"
|
||||
>
|
||||
<ResourceSelect
|
||||
value={operationFields.GeoIPProvider}
|
||||
onChange={(event) =>
|
||||
setOperationFields((previous) => ({
|
||||
...previous,
|
||||
GeoIPProvider: event.target.value,
|
||||
}))
|
||||
}
|
||||
>
|
||||
<option value="disabled">关闭</option>
|
||||
<option value="mmdb">MaxMind mmdb</option>
|
||||
<option value="ip-api">ip-api.com</option>
|
||||
<option value="geojs">geojs.io</option>
|
||||
<option value="ipinfo">ipinfo.io</option>
|
||||
</ResourceSelect>
|
||||
</ResourceField>
|
||||
</AppCard>
|
||||
<AppCard
|
||||
title="Discovery Token 与部署命令"
|
||||
description="适用于新节点首次接入。可直接复制一键安装命令。"
|
||||
|
||||
Reference in New Issue
Block a user