Initial commit: OpenVPN Manager v1.0

OpenVPN Web management console with multi-instance support, client cert
issuance, traffic/connection auditing, certificate expiry reminders,
auto backup/restore.

Stack:
- Backend: Go 1.21+ (Gin + JWT)
- Frontend: Vue 3 + Element Plus + ECharts + Vite
- Storage: JSON file (db.json) + filesystem (pki/, instances/, clients/, backups/)

Features:
- Multi-instance OpenVPN management (independent port/proto/subnet/PKI)
- One-click client certificate issuance with .ovpn (embedded certs)
- Certificate expiry reminders (30-day threshold)
- Connection log parsing (status-version 3)
- Auto backup/restore (tar.gz)
- Audit log for all write operations
- JWT auth (12h TTL)
- One-line install.sh for Ubuntu/Debian/RHEL/Fedora
This commit is contained in:
cnbugs
2026-08-09 20:32:37 +08:00
commit 77f8b59290
34 changed files with 6063 additions and 0 deletions
+45
View File
@@ -0,0 +1,45 @@
<template>
<div>
<h2 style="margin-top:0">连接日志</h2>
<div style="margin-bottom:12px">
<el-select v-model="instanceId" placeholder="选择实例" clearable @change="load" style="width:240px">
<el-option v-for="i in instances" :key="i.id" :value="i.id" :label="i.name" />
</el-select>
</div>
<el-table :data="list" stripe>
<el-table-column prop="common_name" label="用户" />
<el-table-column prop="real_ip" label="来源 IP" width="160" />
<el-table-column prop="vpn_ip" label="VPN IP" width="140" />
<el-table-column prop="bytes_in" label="下行" width="100">
<template #default="{row}">{{ fmt(row.bytes_in) }}</template>
</el-table-column>
<el-table-column prop="bytes_out" label="上行" width="100">
<template #default="{row}">{{ fmt(row.bytes_out) }}</template>
</el-table-column>
<el-table-column prop="connected_at" label="开始时间" width="170">
<template #default="{row}">{{ row.connected_at?.slice(0,19) }}</template>
</el-table-column>
<el-table-column prop="disconnected_at" label="结束时间" width="170">
<template #default="{row}">{{ row.disconnected_at?.slice(0,19) || '-' }}</template>
</el-table-column>
</el-table>
</div>
</template>
<script setup>
import { onMounted, ref } from 'vue'
import { Logs, Inst } from '@/api'
const list = ref([])
const instances = ref([])
const instanceId = ref('')
async function load() { list.value = await Logs.conns(instanceId.value) }
function fmt(n) {
if (!n) return '0'
const u = ['B','KB','MB','GB','TB']; let i=0,v=n
while (v>=1024 && i<u.length-1) { v/=1024; i++ }
return v.toFixed(1)+' '+u[i]
}
onMounted(async () => { instances.value = await Inst.list(); load() })
</script>