diff --git a/web/src/components/XTerminal.vue b/web/src/components/XTerminal.vue new file mode 100644 index 0000000..37f1eaa --- /dev/null +++ b/web/src/components/XTerminal.vue @@ -0,0 +1,243 @@ + + + + + diff --git a/web/src/constants/index.ts b/web/src/constants/index.ts new file mode 100644 index 0000000..ab189b5 --- /dev/null +++ b/web/src/constants/index.ts @@ -0,0 +1,19 @@ +// 应用路径常量 +export const PATHS = { + // 脚本文件目录 + SCRIPTS_DIR: '/app/data/scripts', + // 数据目录 + DATA_DIR: '/app/data', + // 配置目录 + CONFIGS_DIR: '/app/configs', + // 环境目录 + ENVS_DIR: '/app/envs', +} as const + +// 文件扩展名对应的运行命令 +export const FILE_RUNNERS: Record = { + py: 'python', + js: 'node', + sh: 'bash', + bash: 'bash', +} as const diff --git a/web/src/views/editor/Editor.vue b/web/src/views/editor/Editor.vue index b7bc171..1ce954a 100644 --- a/web/src/views/editor/Editor.vue +++ b/web/src/views/editor/Editor.vue @@ -1,5 +1,5 @@ + + + diff --git a/web/src/views/loginlogs/LoginLogs.vue b/web/src/views/loginlogs/LoginLogs.vue index 810b804..730648a 100644 --- a/web/src/views/loginlogs/LoginLogs.vue +++ b/web/src/views/loginlogs/LoginLogs.vue @@ -3,11 +3,17 @@ import { ref, onMounted } from 'vue' import { Button } from '@/components/ui/button' import { Input } from '@/components/ui/input' import Pagination from '@/components/Pagination.vue' -import { RefreshCw, Search } from 'lucide-vue-next' +import { RefreshCw, Search, Loader2 } from 'lucide-vue-next' import TextOverflow from '@/components/TextOverflow.vue' import { api } from '@/api' import { toast } from 'vue-sonner' import { useSiteSettings } from '@/composables/useSiteSettings' +import { + Dialog, + DialogContent, + DialogHeader, + DialogTitle +} from '@/components/ui/dialog' const { pageSize } = useSiteSettings() @@ -21,6 +27,21 @@ interface LoginLog { created_at: string } +interface IpGeoInfo { + ip: string + country: string + country_code: string + organization: string + isp: string + asn: number + asn_organization: string + timezone: string + latitude: number + longitude: number + continent_code: string + offset: number +} + const logs = ref([]) const filterUsername = ref('') const currentPage = ref(1) @@ -28,6 +49,29 @@ const total = ref(0) const loading = ref(false) let searchTimer: ReturnType | null = null +// IP 地理位置弹窗 +const ipDialogOpen = ref(false) +const ipGeoInfo = ref(null) +const ipGeoLoading = ref(false) +const selectedIp = ref('') + +async function showIpInfo(ip: string) { + selectedIp.value = ip + ipDialogOpen.value = true + ipGeoLoading.value = true + ipGeoInfo.value = null + + try { + const res = await fetch(`https://api.ip.sb/geoip/${ip}`) + if (!res.ok) throw new Error('请求失败') + ipGeoInfo.value = await res.json() + } catch { + toast.error('获取 IP 信息失败') + } finally { + ipGeoLoading.value = false + } +} + async function loadLogs() { loading.value = true try { @@ -86,36 +130,68 @@ onMounted(loadLogs)
-
- 用户名 - IP 地址 - 状态 - - 时间 +
+ 用户名 + IP 地址 + 状态 + + 时间
-
+
暂无登录日志
- {{ log.username }} - {{ log.ip }} - + {{ log.username }} + {{ log.ip }} + -
+ + + + + + IP 信息 - {{ selectedIp }} + +
+ +
+
+
国家
+
{{ ipGeoInfo.country }} ({{ ipGeoInfo.country_code }})
+
运营商
+
{{ ipGeoInfo.isp || '-' }}
+
组织
+
{{ ipGeoInfo.organization || '-' }}
+
ASN
+
{{ ipGeoInfo.asn }} - {{ ipGeoInfo.asn_organization || '-' }}
+
时区
+
{{ ipGeoInfo.timezone || '-' }}
+
坐标
+
{{ ipGeoInfo.latitude }}, {{ ipGeoInfo.longitude }}
+
+
+ 无法获取 IP 信息 +
+
+
diff --git a/web/src/views/terminal/Terminal.vue b/web/src/views/terminal/Terminal.vue index 79eb9ff..b3ce9c5 100644 --- a/web/src/views/terminal/Terminal.vue +++ b/web/src/views/terminal/Terminal.vue @@ -1,174 +1,14 @@ - -