Browse Source

add nas and frpc

master
godo 5 months ago
parent
commit
866fa62f62
  1. 4
      frontend/components.d.ts
  2. 157
      frontend/src/components/setting/LocalNas.vue
  3. 34
      frontend/src/components/setting/LocalProxy.vue
  4. 154
      frontend/src/components/setting/NasClient.vue
  5. 4
      frontend/src/components/setting/NetProxy.vue
  6. 45
      frontend/src/components/setting/SetAccount.vue
  7. 3
      frontend/src/components/setting/SetCustom.vue
  8. 33
      frontend/src/components/setting/SetNas.vue
  9. 92
      frontend/src/components/setting/SetSystem.vue
  10. 22
      frontend/src/components/setting/Setting.vue
  11. 154
      frontend/src/components/setting/WebDavClient.vue
  12. 2
      godo/build.sh
  13. 3
      godo/files/pwd.go
  14. 8
      godo/libs/dir.go
  15. 15
      godo/model/client_user.go
  16. 27
      godo/model/init.go
  17. 14
      godo/model/local_proxy.go
  18. 16
      godo/model/server_user.go
  19. 59
      godo/model/sys_disk.go
  20. 13
      godo/model/sys_user.go
  21. 285
      godo/sys/frpc.go

4
frontend/components.d.ts

@ -129,6 +129,7 @@ declare module 'vue' {
InstallMember: typeof import('./src/components/install/InstallMember.vue')['default']
InstallPerson: typeof import('./src/components/install/InstallPerson.vue')['default']
LocalChat: typeof import('./src/components/localchat/LocalChat.vue')['default']
LocalNas: typeof import('./src/components/setting/LocalNas.vue')['default']
LocalProxy: typeof import('./src/components/setting/LocalProxy.vue')['default']
LockDesktop: typeof import('./src/components/desktop/LockDesktop.vue')['default']
Magnet: typeof import('./src/components/taskbar/Magnet.vue')['default']
@ -141,6 +142,7 @@ declare module 'vue' {
MobileApp: typeof import('./src/components/desktop/mobile/MobileApp.vue')['default']
MusicStore: typeof import('./src/components/builtin/MusicStore.vue')['default']
MusicViewer: typeof import('./src/components/builtin/MusicViewer.vue')['default']
NasClient: typeof import('./src/components/setting/NasClient.vue')['default']
NetProxy: typeof import('./src/components/setting/NetProxy.vue')['default']
NetWork: typeof import('./src/components/taskbar/NetWork.vue')['default']
NetworkPop: typeof import('./src/components/taskbar/NetworkPop.vue')['default']
@ -167,6 +169,7 @@ declare module 'vue' {
SetCustom: typeof import('./src/components/setting/SetCustom.vue')['default']
SetFilePwd: typeof import('./src/components/setting/SetFilePwd.vue')['default']
SetLang: typeof import('./src/components/setting/SetLang.vue')['default']
SetNas: typeof import('./src/components/setting/SetNas.vue')['default']
SetSystem: typeof import('./src/components/setting/SetSystem.vue')['default']
Setting: typeof import('./src/components/setting/Setting.vue')['default']
SetUpdate: typeof import('./src/components/setting/SetUpdate.vue')['default']
@ -182,6 +185,7 @@ declare module 'vue' {
UrlBrowser: typeof import('./src/components/builtin/UrlBrowser.vue')['default']
Version: typeof import('./src/components/builtin/Version.vue')['default']
VideoViewer: typeof import('./src/components/builtin/VideoViewer.vue')['default']
WebDavClient: typeof import('./src/components/setting/WebDavClient.vue')['default']
WinButton: typeof import('./src/components/ui/WinButton.vue')['default']
WinCheckBox: typeof import('./src/components/ui/WinCheckBox.vue')['default']
WindowGroup: typeof import('./src/components/window/WindowGroup.vue')['default']

157
frontend/src/components/setting/LocalNas.vue

@ -0,0 +1,157 @@
<script setup lang="ts">
import { Plus } from '@element-plus/icons-vue'
import { ref, computed } from "vue";
import { OpenDirDialog } from "@/util/goutil";
interface ProxyItem {
id: number;
dir: string;
username: string;
password: string;
}
const localKey = "godoos_local_nas"
const getProxies = (): ProxyItem[] => {
const proxies = localStorage.getItem(localKey);
return proxies ? JSON.parse(proxies) : [];
};
const saveProxies = (proxies: ProxyItem[]) => {
localStorage.setItem(localKey, JSON.stringify(proxies));
};
const proxies = ref<ProxyItem[]>(getProxies());
const initData = {
id: Date.now(),
dir: "",
username: "",
password: "",
}
const proxyData = ref<ProxyItem>(initData);
const proxyDialogShow = ref(false);
const isEditing = ref(false);
const pwdRef = ref<any>(null);
const addProxy = () => {
if (pwdRef.value.validate()) {
proxies.value.push({ ...proxyData.value });
saveProxies(proxies.value);
proxyDialogShow.value = false;
proxyData.value = initData;
}
};
const editNas = (proxy: ProxyItem) => {
proxyData.value = { ...proxy };
isEditing.value = true;
proxyDialogShow.value = true;
};
const updateProxy = () => {
if (pwdRef.value.validate()) {
const index = proxies.value.findIndex(p => p.id === proxyData.value.id);
if (index !== -1) {
proxies.value[index] = { ...proxyData.value };
saveProxies(proxies.value);
proxyDialogShow.value = false;
proxyData.value = initData;
isEditing.value = false;
}
}
};
function selectFile() {
OpenDirDialog().then((res: string) => {
proxyData.value.dir = res;
});
}
const deleteNas = (id: number) => {
proxies.value = proxies.value.filter(p => p.id !== id);
saveProxies(proxies.value);
};
const saveNas = () => {
pwdRef.value.validate((valid: boolean) => {
if (valid) {
if (isEditing.value) {
updateProxy();
} else {
addProxy();
}
} else {
console.log('表单验证失败');
}
});
};
const proxyRules = {
dir: [
{ required: true, message: '请输入文件路径', trigger: 'blur' },
],
username: [
{ required: true, message: '请输入用户名', trigger: 'blur' }
],
password: [
{ required: true, message: '请输入密码', trigger: 'blur' },
{ min: 6, message: '密码长度至少为6位', trigger: 'blur' }
]
};
const pageSize = 10;
const currentPage = ref(1);
const paginatedProxies = computed(() => {
const start = (currentPage.value - 1) * pageSize;
const end = start + pageSize;
return proxies.value.slice(start, end);
});
const totalPages = computed(() => Math.ceil(proxies.value.length / pageSize));
const nextPage = () => {
if (currentPage.value < totalPages.value) {
currentPage.value++;
}
};
const prevPage = () => {
if (currentPage.value > 1) {
currentPage.value--;
}
};
</script>
<template>
<div>
<el-row justify="end">
<el-button type="primary" :icon="Plus" circle @click="proxyDialogShow = true" />
</el-row>
<el-table :data="paginatedProxies" style="width: 98%;border:none">
<el-table-column prop="dir" label="本地路径" width="180" />
<el-table-column prop="username" label="用户名" width="180" />
<el-table-column label="操作">
<template #default="scope">
<el-button size="small" @click="editNas(scope.row)">编辑</el-button>
<el-button size="small" type="danger" @click="deleteNas(scope.row.id)">删除</el-button>
</template>
</el-table-column>
</el-table>
<el-pagination v-if="totalPages > 1" layout="prev, pager, next" :total="getProxies().length"
:page-size="pageSize" v-model:current-page="currentPage" @next-click="nextPage" @prev-click="prevPage" />
<el-dialog v-model="proxyDialogShow" :title="isEditing ? '编辑Nas服务端' : '添加Nas服务端'" width="400px">
<span>
<el-form :model="proxyData" :rules="proxyRules" ref="pwdRef">
<el-form-item label="路径" prop="dir">
<el-input v-model="proxyData.dir" @click="selectFile()"/>
</el-form-item>
<el-form-item label="用户" prop="username">
<el-input v-model="proxyData.username" />
</el-form-item>
<el-form-item label="密码" prop="password">
<el-input v-model="proxyData.password" type="password" />
</el-form-item>
<el-form-item>
<el-button type="primary" @click="saveNas" style="margin: 0 auto;">
确认
</el-button>
</el-form-item>
</el-form>
</span>
</el-dialog>
</div>
</template>

34
frontend/src/components/setting/LocalProxy.vue

@ -1,10 +1,11 @@
<script setup lang="ts">
import { Plus } from '@element-plus/icons-vue'
import { ref, computed } from "vue";
import { OpenDirDialog } from "@/util/goutil";
interface ProxyItem {
id: number;
port: string;
proxyType: string;
domain: string;
}
const localKey = "godoos_local_proxy"
@ -21,8 +22,14 @@ const proxies = ref<ProxyItem[]>(getProxies());
const proxyData = ref<ProxyItem>({
id: Date.now(),
port: "",
proxyType: "http",
domain: "",
});
const types = ref([
{ label: 'HTTP', value: 'http' },
{ label: 'Udp', value: 'udp' },
{ label: '静态文件访问', value: 'file' },
])
const proxyDialogShow = ref(false);
const isEditing = ref(false);
const pwdRef = ref<any>(null);
@ -32,7 +39,7 @@ const addProxy = () => {
proxies.value.push({ ...proxyData.value });
saveProxies(proxies.value);
proxyDialogShow.value = false;
proxyData.value = { id: Date.now(), port: "", domain: "" };
proxyData.value = { id: Date.now(), port: "", domain: "", proxyType: "http" };
}
};
@ -49,12 +56,16 @@ const updateProxy = () => {
proxies.value[index] = { ...proxyData.value };
saveProxies(proxies.value);
proxyDialogShow.value = false;
proxyData.value = { id: Date.now(), port: "", domain: "" };
proxyData.value = { id: Date.now(), port: "", domain: "", proxyType: "http" };
isEditing.value = false;
}
}
};
function selectFile() {
OpenDirDialog().then((res: string) => {
proxyData.value.domain = res;
});
}
const deleteProxy = (id: number) => {
proxies.value = proxies.value.filter(p => p.id !== id);
saveProxies(proxies.value);
@ -116,6 +127,7 @@ const prevPage = () => {
<el-table :data="paginatedProxies" style="width: 98%;border:none">
<el-table-column prop="port" label="本地端口" width="180" />
<el-table-column prop="domain" label="代理域名" width="180" />
<el-table-column prop="proxyType" label="代理类型" width="180" />
<el-table-column label="操作">
<template #default="scope">
<el-button size="small" @click="editProxy(scope.row)">编辑</el-button>
@ -128,10 +140,22 @@ const prevPage = () => {
<el-dialog v-model="proxyDialogShow" :title="isEditing ? '编辑代理' : '添加代理'" width="400px">
<span>
<el-form :model="proxyData" :rules="proxyRules" ref="pwdRef">
<el-form-item label="代理类型" prop="type">
<el-select v-model="proxyData.proxyType" placeholder="代理类型">
<el-option v-for="type in types" :key="type.value" :label="type.label"
:value="type.value" />
</el-select>
</el-form-item>
<el-form-item label="本地端口" prop="port">
<el-input v-model="proxyData.port" />
</el-form-item>
<el-form-item label="代理域名" prop="domain">
<el-form-item label="代理域名" prop="domain" v-if="proxyData.proxyType === 'http'">
<el-input v-model="proxyData.domain" />
</el-form-item>
<el-form-item label="文件路径" prop="domain" v-if="proxyData.proxyType === 'file'">
<el-input v-model="proxyData.domain" @click="selectFile()"/>
</el-form-item>
<el-form-item label="IP+端口" prop="domain" v-if="proxyData.proxyType === 'udp'">
<el-input v-model="proxyData.domain" />
</el-form-item>
<el-form-item>

154
frontend/src/components/setting/NasClient.vue

@ -0,0 +1,154 @@
<script setup lang="ts">
import { Plus } from '@element-plus/icons-vue'
import { ref, computed } from "vue";
interface ProxyItem {
id: number;
server_url: string;
disk: string;
username: string;
password: string;
}
const localKey = "godoos_local_nasclient"
const getProxies = (): ProxyItem[] => {
const proxies = localStorage.getItem(localKey);
return proxies ? JSON.parse(proxies) : [];
};
const saveProxies = (proxies: ProxyItem[]) => {
localStorage.setItem(localKey, JSON.stringify(proxies));
};
const proxies = ref<ProxyItem[]>(getProxies());
const initData = {
id: Date.now(),
server_url: "",
disk: "",
username: "",
password: "",
}
const proxyData = ref<ProxyItem>(initData);
const proxyDialogShow = ref(false);
const isEditing = ref(false);
const pwdRef = ref<any>(null);
const addProxy = () => {
if (pwdRef.value.validate()) {
proxies.value.push({ ...proxyData.value });
saveProxies(proxies.value);
proxyDialogShow.value = false;
proxyData.value = initData;
}
};
const editNas = (proxy: ProxyItem) => {
proxyData.value = { ...proxy };
isEditing.value = true;
proxyDialogShow.value = true;
};
const updateProxy = () => {
if (pwdRef.value.validate()) {
const index = proxies.value.findIndex(p => p.id === proxyData.value.id);
if (index !== -1) {
proxies.value[index] = { ...proxyData.value };
saveProxies(proxies.value);
proxyDialogShow.value = false;
proxyData.value = initData;
isEditing.value = false;
}
}
};
const deleteNas = (id: number) => {
proxies.value = proxies.value.filter(p => p.id !== id);
saveProxies(proxies.value);
};
const saveNas = () => {
pwdRef.value.validate((valid: boolean) => {
if (valid) {
if (isEditing.value) {
updateProxy();
} else {
addProxy();
}
} else {
console.log('表单验证失败');
}
});
};
const proxyRules = {
username: [
{ required: true, message: '请输入用户名', trigger: 'blur' }
],
password: [
{ required: true, message: '请输入密码', trigger: 'blur' },
{ min: 6, message: '密码长度至少为6位', trigger: 'blur' }
]
};
const pageSize = 10;
const currentPage = ref(1);
const paginatedProxies = computed(() => {
const start = (currentPage.value - 1) * pageSize;
const end = start + pageSize;
return proxies.value.slice(start, end);
});
const totalPages = computed(() => Math.ceil(proxies.value.length / pageSize));
const nextPage = () => {
if (currentPage.value < totalPages.value) {
currentPage.value++;
}
};
const prevPage = () => {
if (currentPage.value > 1) {
currentPage.value--;
}
};
</script>
<template>
<div>
<el-row justify="end">
<el-button type="primary" :icon="Plus" circle @click="proxyDialogShow = true" />
</el-row>
<el-table :data="paginatedProxies" style="width: 98%;border:none">
<el-table-column prop="disk" label="挂载" width="180" />
<el-table-column prop="server_url" label="地址" width="180" />
<el-table-column prop="username" label="用户名" width="180" />
<el-table-column label="操作">
<template #default="scope">
<el-button size="small" @click="editNas(scope.row)">编辑</el-button>
<el-button size="small" type="danger" @click="deleteNas(scope.row.id)">删除</el-button>
</template>
</el-table-column>
</el-table>
<el-pagination v-if="totalPages > 1" layout="prev, pager, next" :total="getProxies().length"
:page-size="pageSize" v-model:current-page="currentPage" @next-click="nextPage" @prev-click="prevPage" />
<el-dialog v-model="proxyDialogShow" :title="isEditing ? '编辑Nas客户端' : '添加Nas客户端'" width="400px">
<span>
<el-form :model="proxyData" :rules="proxyRules" ref="pwdRef">
<el-form-item label="挂载" prop="disk">
<el-input v-model="proxyData.disk"/>
</el-form-item>
<el-form-item label="地址" prop="server_url">
<el-input v-model="proxyData.server_url" />
</el-form-item>
<el-form-item label="用户" prop="username">
<el-input v-model="proxyData.username" />
</el-form-item>
<el-form-item label="密码" prop="password">
<el-input v-model="proxyData.password" type="password" />
</el-form-item>
<el-form-item>
<el-button type="primary" @click="saveNas" style="margin: 0 auto;">
确认
</el-button>
</el-form-item>
</el-form>
</span>
</el-dialog>
</div>
</template>

4
frontend/src/components/setting/NetProxy.vue

@ -37,7 +37,11 @@ const isEditing = ref(false);
const pwdRef = ref<any>(null);
const types = ref([
{ label: 'HTTP', value: 'http' },
{ label: '静态文件访问', value: 'file' },
{ label: '点对点穿透', value: 'p2p' },
{ label: 'SSH', value: 'ssh' },
{ label: 'SOCKS5', value: 'socks5' },
])
const addProxy = () => {

45
frontend/src/components/setting/SetAccount.vue

@ -68,6 +68,24 @@
<el-switch v-model="ad" active-text="开启" inactive-text="关闭" size="large" :before-change="setAd"></el-switch>
</div>
</div>
<div v-if="3 === activeIndex">
<div class="setting-item">
<h1 class="setting-title">{{ t("language") }}</h1>
</div>
<div class="setting-item">
<label></label>
<el-select v-model="modelvalue">
<el-option v-for="(item, key) in langList" :key="key" :label="item.label" :value="item.value" />
</el-select>
</div>
<div class="setting-item">
<label></label>
<el-button @click="submitLang" type="primary">
{{ t("confirm") }}
</el-button>
</div>
</div>
</div>
</div>
</template>
@ -75,15 +93,30 @@
<script lang="ts" setup>
import { ref } from 'vue';
import { Dialog, t, useSystem } from '@/system/index.ts';
import { Dialog, useSystem } from '@/system/index.ts';
import { getSystemKey, setSystemKey, getSystemConfig, setSystemConfig } from '@/system/config'
import { ElMessageBox } from 'element-plus'
import { getLang, setLang, t } from "@/i18n";
import { useI18n } from "vue-i18n";
const { locale } = useI18n();
const sys = useSystem();
const items = [t("background"), '锁屏设置', '广告设置'];
const items = [t("background"), '锁屏设置', '广告设置', '语言'];
const activeIndex = ref(0);
const account = ref(getSystemKey('account'));
const ad = ref(account.value.ad)
const config: any = ref(getSystemConfig());
const langList = [
{
label: "中文",
value: "zh-cn",
},
{
label: "English",
value: "en",
},
];
const currentLang = getLang();
const modelvalue = ref(currentLang);
const selectItem = (index: number) => {
activeIndex.value = index;
};
@ -152,7 +185,13 @@ function onColorChange(color: string) {
setSystemConfig(config.value);
sys.initBackground();
}
async function submitLang() {
setLang(modelvalue.value);
locale.value = modelvalue.value;
ElMessageBox.alert(t("save.success"), t("language"), {
confirmButtonText: "OK",
});
}
</script>
<style scoped>
@import "./setStyle.css";

3
frontend/src/components/setting/SetCustom.vue

@ -21,8 +21,7 @@
<script lang="ts" setup>
import { ref } from "vue";
import LocalProxy from "./LocalProxy.vue";
const items = ["本地代理", "远程访问"];
const items = ["本地代理", "远程代理"];
const activeIndex = ref(0);
</script>

33
frontend/src/components/setting/SetNas.vue

@ -0,0 +1,33 @@
<template>
<div class="container">
<div class="nav">
<ul>
<li v-for="(item, index) in items" :key="index" @click="activeIndex = index"
:class="{ active: index === activeIndex }">
{{ item }}
</li>
</ul>
</div>
<div class="setting">
<div v-if="0 === activeIndex">
<LocalNas />
</div>
<div v-if="1 === activeIndex">
<NasClient />
</div>
<div v-if="2 === activeIndex">
<WebDavClient />
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import { ref } from "vue";
const items = ["服务端配置", "NAS客户端", "WebDav客户端"];
const activeIndex = ref(0);
</script>
<style scoped>
@import "./setStyle.css";
</style>

92
frontend/src/components/setting/SetSystem.vue

@ -152,8 +152,8 @@
<script lang="ts" setup>
import { Dialog, join, System, t } from "@/system";
import { GetClientId } from "@/util/clientid.ts";
import {
getClientId,
getSystemConfig,
setSystemConfig,
} from "@/system/config";
@ -180,10 +180,10 @@ const storeList = [
title: "远程存储",
value: "net",
},
{
title: "webdav",
value: "webdav",
},
// {
// title: "webdav",
// value: "webdav",
// },
];
const items = ["用户角色", "存储配置", "编辑器类型", "备份还原", "文件密码箱"];
@ -284,46 +284,46 @@ function submitOsInfo() {
}
}
if (saveData.storeType === "webdav") {
const urlRegex = /^(https?:\/\/)/;
if (!urlRegex.test(saveData.webdavClient.url.trim())) {
Dialog.showMessageBox({
message: "服务器地址格式错误",
type: "error",
});
return;
}
if (
saveData.webdavClient.username === "" ||
saveData.webdavClient.password === ""
) {
Dialog.showMessageBox({
message: "用户名或密码不能为空",
type: "error",
});
return;
}
const postUrl = config.value.apiUrl + "/system/setting";
fetch(postUrl, {
method: "POST",
body: JSON.stringify([{
name: "webdavClient",
value: saveData.webdavClient,
}]),
})
.then((res) => res.json())
.then((res) => {
if (res.code === 0) {
setSystemConfig(saveData);
RestartApp();
} else {
Dialog.showMessageBox({
message: res.message,
type: "error",
});
}
});
}
// if (saveData.storeType === "webdav") {
// const urlRegex = /^(https?:\/\/)/;
// if (!urlRegex.test(saveData.webdavClient.url.trim())) {
// Dialog.showMessageBox({
// message: "",
// type: "error",
// });
// return;
// }
// if (
// saveData.webdavClient.username === "" ||
// saveData.webdavClient.password === ""
// ) {
// Dialog.showMessageBox({
// message: "",
// type: "error",
// });
// return;
// }
// const postUrl = config.value.apiUrl + "/system/setting";
// fetch(postUrl, {
// method: "POST",
// body: JSON.stringify([{
// name: "webdavClient",
// value: saveData.webdavClient,
// }]),
// })
// .then((res) => res.json())
// .then((res) => {
// if (res.code === 0) {
// setSystemConfig(saveData);
// RestartApp();
// } else {
// Dialog.showMessageBox({
// message: res.message,
// type: "error",
// });
// }
// });
// }
}
function submitEditInfo() {
const saveData = toRaw(config.value);
@ -385,7 +385,7 @@ async function saveUserInfo() {
body: JSON.stringify({
username: saveData.userInfo.username,
password: password,
clientId: getClientId(),
clientId: GetClientId(),
}),
});
if (res.status === 200) {

22
frontend/src/components/setting/Setting.vue

@ -93,23 +93,29 @@ const setList = ref([
{
key: "custom",
title: "代理",
desc: '代理/远程桌面',
desc: '本地代理、远程代理',
icon: "personal",
content: "SetCustom",
},
{
key: "language",
title: '语言',
desc: t("language"),
icon: "language",
content: "SetLang",
key: "nas",
title: "NAS服务",
desc: 'NAS/webdav服务',
icon: "disk",
content: "SetNas",
},
// {
// key: "language",
// title: '',
// desc: t("language"),
// icon: "language",
// content: "SetLang",
// },
{
key: "account",
title: "屏幕",
desc: '壁纸/锁屏/广告',
desc: '壁纸/语言/锁屏/广告',
icon: "account",
content: "SetAccount",
},

154
frontend/src/components/setting/WebDavClient.vue

@ -0,0 +1,154 @@
<script setup lang="ts">
import { Plus } from '@element-plus/icons-vue'
import { ref, computed } from "vue";
interface ProxyItem {
id: number;
url: string;
disk: string;
username: string;
password: string;
}
const localKey = "godoos_local_webdav"
const getProxies = (): ProxyItem[] => {
const proxies = localStorage.getItem(localKey);
return proxies ? JSON.parse(proxies) : [];
};
const saveProxies = (proxies: ProxyItem[]) => {
localStorage.setItem(localKey, JSON.stringify(proxies));
};
const proxies = ref<ProxyItem[]>(getProxies());
const initData = {
id: Date.now(),
url: "",
disk: "",
username: "",
password: "",
}
const proxyData = ref<ProxyItem>(initData);
const proxyDialogShow = ref(false);
const isEditing = ref(false);
const pwdRef = ref<any>(null);
const addProxy = () => {
if (pwdRef.value.validate()) {
proxies.value.push({ ...proxyData.value });
saveProxies(proxies.value);
proxyDialogShow.value = false;
proxyData.value = initData;
}
};
const editNas = (proxy: ProxyItem) => {
proxyData.value = { ...proxy };
isEditing.value = true;
proxyDialogShow.value = true;
};
const updateProxy = () => {
if (pwdRef.value.validate()) {
const index = proxies.value.findIndex(p => p.id === proxyData.value.id);
if (index !== -1) {
proxies.value[index] = { ...proxyData.value };
saveProxies(proxies.value);
proxyDialogShow.value = false;
proxyData.value = initData;
isEditing.value = false;
}
}
};
const deleteNas = (id: number) => {
proxies.value = proxies.value.filter(p => p.id !== id);
saveProxies(proxies.value);
};
const saveNas = () => {
pwdRef.value.validate((valid: boolean) => {
if (valid) {
if (isEditing.value) {
updateProxy();
} else {
addProxy();
}
} else {
console.log('表单验证失败');
}
});
};
const proxyRules = {
username: [
{ required: true, message: '请输入用户名', trigger: 'blur' }
],
password: [
{ required: true, message: '请输入密码', trigger: 'blur' },
{ min: 6, message: '密码长度至少为6位', trigger: 'blur' }
]
};
const pageSize = 10;
const currentPage = ref(1);
const paginatedProxies = computed(() => {
const start = (currentPage.value - 1) * pageSize;
const end = start + pageSize;
return proxies.value.slice(start, end);
});
const totalPages = computed(() => Math.ceil(proxies.value.length / pageSize));
const nextPage = () => {
if (currentPage.value < totalPages.value) {
currentPage.value++;
}
};
const prevPage = () => {
if (currentPage.value > 1) {
currentPage.value--;
}
};
</script>
<template>
<div>
<el-row justify="end">
<el-button type="primary" :icon="Plus" circle @click="proxyDialogShow = true" />
</el-row>
<el-table :data="paginatedProxies" style="width: 98%;border:none">
<el-table-column prop="disk" label="挂载" width="180" />
<el-table-column prop="url" label="地址" width="180" />
<el-table-column prop="username" label="用户名" width="180" />
<el-table-column label="操作">
<template #default="scope">
<el-button size="small" @click="editNas(scope.row)">编辑</el-button>
<el-button size="small" type="danger" @click="deleteNas(scope.row.id)">删除</el-button>
</template>
</el-table-column>
</el-table>
<el-pagination v-if="totalPages > 1" layout="prev, pager, next" :total="getProxies().length"
:page-size="pageSize" v-model:current-page="currentPage" @next-click="nextPage" @prev-click="prevPage" />
<el-dialog v-model="proxyDialogShow" :title="isEditing ? '编辑webdav' : '添加webdav'" width="400px">
<span>
<el-form :model="proxyData" :rules="proxyRules" ref="pwdRef">
<el-form-item label="挂载" prop="disk">
<el-input v-model="proxyData.disk"/>
</el-form-item>
<el-form-item label="地址" prop="url">
<el-input v-model="proxyData.url" />
</el-form-item>
<el-form-item label="用户" prop="username">
<el-input v-model="proxyData.username" />
</el-form-item>
<el-form-item label="密码" prop="password">
<el-input v-model="proxyData.password" type="password" />
</el-form-item>
<el-form-item>
<el-button type="primary" @click="saveNas" style="margin: 0 auto;">
确认
</el-button>
</el-form-item>
</el-form>
</span>
</el-dialog>
</div>
</template>

2
godo/build.sh

@ -28,6 +28,8 @@ for PLATFORM in "${PLATFORMS[@]}"; do
export GOOS=$OS
export GOARCH=$ARCH
export GODOTOPTYPE="web"
# 设置CGO_ENABLED=0以进行静态链接
export CGO_ENABLED=0
# 执行编译命令,并处理可能的错误
go build -ldflags="-s -w" -o "$OUTPUT_FILE" ./main.go || { echo "编译 $OS/$ARCH 失败,请检查错误并尝试解决。"; continue; }

3
godo/files/pwd.go

@ -5,7 +5,6 @@ import (
"fmt"
"godo/libs"
"io"
"log"
"net/http"
"os"
"path/filepath"
@ -175,7 +174,7 @@ func HandleWriteFile(w http.ResponseWriter, r *http.Request) {
if haslink {
needPwd = false
}
log.Printf("needPwd:%v", needPwd)
//log.Printf("needPwd:%v", needPwd)
// 即不是加密用户又不是加密文件
if !needPwd {
// 直接写入新内容

8
godo/libs/dir.go

@ -163,6 +163,14 @@ func GetVectorDb() string {
}
return filepath.Join(dbPath, "vector.db")
}
func GetSystemDb() string {
homeDir := GetDataDir()
dbPath := filepath.Join(homeDir, "db")
if !PathExists(dbPath) {
os.MkdirAll(dbPath, 0755)
}
return filepath.Join(dbPath, "system.db")
}
func GetVectorDbName(name string) string {
hasher := md5.New()
hasher.Write([]byte(name))

15
godo/model/client_user.go

@ -0,0 +1,15 @@
package model
import "gorm.io/gorm"
type ClientUser struct {
gorm.Model
ServerUrl string `json:"server_url"`
DiskId string `json:"disk_id"`
Username string `json:"username"`
Password string `json:"password"`
}
func (*ClientUser) TableName() string {
return "client_user"
}

27
godo/model/init.go

@ -0,0 +1,27 @@
package model
import (
"godo/libs"
_ "github.com/ncruces/go-sqlite3/embed"
"github.com/ncruces/go-sqlite3/gormlite"
"gorm.io/gorm"
)
var Db *gorm.DB
func InitDB() {
dbPath := libs.GetSystemDb()
db, err := gorm.Open(gormlite.Open(dbPath), &gorm.Config{})
if err != nil {
return
}
Db = db
// 自动迁移模式
db.AutoMigrate(&SysDisk{})
// 初始化 SysDisk 记录
initSysDisk(db)
db.AutoMigrate(&SysUser{})
db.AutoMigrate(&ClientUser{})
db.AutoMigrate(&ServerUser{})
}

14
godo/model/local_proxy.go

@ -0,0 +1,14 @@
package model
import "gorm.io/gorm"
type LocalProxy struct {
gorm.Model
Port uint `json:"port"`
ProxyType string `json:"proxy_type"`
Domain string `json:"domain"`
}
func (*LocalProxy) TableName() string {
return "local_proxy"
}

16
godo/model/server_user.go

@ -0,0 +1,16 @@
package model
import "gorm.io/gorm"
type ServerUser struct {
gorm.Model
DiskId string `json:"disk_id"`
AuthType string `json:"auth_type"`
Username string `json:"username"`
Password string `json:"password"`
Nickname string `json:"nickname"`
}
func (*ServerUser) TableName() string {
return "server_user"
}

59
godo/model/sys_disk.go

@ -0,0 +1,59 @@
package model
import (
"fmt"
"godo/libs"
"os"
"path/filepath"
"gorm.io/gorm"
)
type SysDisk struct {
gorm.Model
Name string `json:"name"`
Disk string `json:"disk" gorm:"unique"`
Size int64 `json:"size"`
Type uint `json:"type"` //0C-E本地 1nasserver 2nasclient 3webdavserver 4webdavclient 5F分享 6B回收站
Path string `json:"path"`
Status uint `json:"status"`
}
func (*SysDisk) TableName() string {
return "sys_disk"
}
func initSysDisk(db *gorm.DB) {
var count int64
db.Model(&SysDisk{}).Count(&count)
basePath, err := libs.GetOsDir()
if err != nil {
basePath, _ = os.Getwd()
}
if count == 0 {
disks := []SysDisk{
{Disk: "B", Name: "回收站", Size: 0, Path: filepath.Join(basePath, "B"), Type: 6, Status: 1},
{Disk: "C", Name: "系统", Size: 0, Path: filepath.Join(basePath, "C"), Type: 0, Status: 1},
{Disk: "D", Name: "文档", Size: 0, Path: filepath.Join(basePath, "D"), Type: 0, Status: 1},
{Disk: "E", Name: "办公", Size: 0, Path: filepath.Join(basePath, "E"), Type: 0, Status: 1},
}
db.Create(&disks)
fmt.Println("Initialized A-Z disks")
}
}
// BeforeDelete 钩子
func (sd *SysDisk) BeforeDelete(tx *gorm.DB) (err error) {
// 不允许删除的磁盘列表
nonDeletableDisks := map[string]struct{}{
"B": {},
"C": {},
"D": {},
"E": {},
"F": {},
}
if _, exists := nonDeletableDisks[sd.Disk]; exists {
return fmt.Errorf("disk %s cannot be deleted", sd.Disk)
}
return nil
}

13
godo/model/sys_user.go

@ -0,0 +1,13 @@
package model
import "gorm.io/gorm"
type SysUser struct {
gorm.Model
Username string `json:"username"`
Password string `json:"password"`
}
func (*SysUser) TableName() string {
return "sys_user"
}

285
godo/sys/frpc.go

@ -0,0 +1,285 @@
package sys
import (
"fmt"
"strings"
)
// FrpConfig 结构体用于存储 FRP 配置
type FrpConfig struct {
ServerAddr string
ServerPort int
AuthMethod string
AuthToken string
User string
MetaToken string
TransportHeartbeatInterval int
TransportHeartbeatTimeout int
LogLevel string
LogMaxDays int
WebPort int
TlsConfigEnable bool
TlsConfigCertFile string
TlsConfigKeyFile string
TlsConfigTrustedCaFile string
TlsConfigServerName string
ProxyConfigEnable bool
ProxyConfigProxyUrl string
}
// Proxy 结构体用于存储代理配置
type Proxy struct {
Name string
Type string
LocalIp string
LocalPort int
RemotePort int
CustomDomains []string
Subdomain string
BasicAuth bool
HttpUser string
HttpPassword string
StcpModel string
ServerName string
BindAddr string
BindPort int
FallbackTo string
FallbackTimeoutMs int
SecretKey string
}
// isRangePort 检查端口是否为范围端口
func isRangePort(proxy Proxy) bool {
// 这里假设范围端口的判断逻辑
// 你可以根据实际情况调整
return strings.Contains(proxy.Name, ":")
}
// GenFrpcIniConfig 生成 FRP 配置文件内容
func GenFrpcIniConfig(config FrpConfig, proxys []Proxy) string {
var proxyIni []string
for _, m := range proxys {
rangePort := isRangePort(m)
ini := fmt.Sprintf("[%s%s]\ntype = \"%s\"\n",
func() string {
if rangePort {
return "range:"
}
return ""
}(),
m.Name,
m.Type,
)
switch m.Type {
case "tcp", "udp":
ini += fmt.Sprintf(`
localIP = "%s"
localPort = %d
remotePort = %d
`,
m.LocalIp,
m.LocalPort,
m.RemotePort,
)
case "http", "https":
ini += fmt.Sprintf(`
localIP = "%s"
localPort = %d
`,
m.LocalIp,
m.LocalPort,
)
if len(m.CustomDomains) > 0 {
ini += fmt.Sprintf(`custom_domains = [%s]
`,
strings.Join(m.CustomDomains, ","),
)
}
if m.Subdomain != "" {
ini += fmt.Sprintf(`subdomain="%s"
`,
m.Subdomain,
)
}
if m.BasicAuth {
ini += fmt.Sprintf(`
httpUser = "%s"
httpPassword = "%s"
`,
m.HttpUser,
m.HttpPassword,
)
}
case "stcp", "xtcp", "sudp":
if m.StcpModel == "visitors" {
// 访问者
ini += fmt.Sprintf(`
role = visitor
serverName = "%s"
bindAddr = "%s"
bindPort = %d
`,
m.ServerName,
m.BindAddr,
m.BindPort,
)
if m.FallbackTo != "" {
ini += fmt.Sprintf(`
fallbackTo = %s
fallbackTimeoutMs = %d
`,
m.FallbackTo,
m.FallbackTimeoutMs,
)
}
} else if m.StcpModel == "visited" {
// 被访问者
ini += fmt.Sprintf(`
localIP = "%s"
localPort = %d
`,
m.LocalIp,
m.LocalPort,
)
}
ini += fmt.Sprintf(`
sk="%s"
`,
m.SecretKey,
)
default:
// 默认情况不做处理
}
proxyIni = append(proxyIni, ini)
}
ini := fmt.Sprintf(`[common]
serverAddr = %s
serverPort = %d
%s
%s
%s
%s
logFile = "frpc.log"
logLevel = %s
logMaxDays = %d
adminAddr = 127.0.0.1
adminPort = %d
tlsEnable = %t
%s
%s
%s
%s
%s
`,
config.ServerAddr,
config.ServerPort,
func() string {
if config.AuthMethod == "token" {
return fmt.Sprintf(`
authenticationMethod = %s
token = %s
`,
config.AuthMethod,
config.AuthToken,
)
}
return ""
}(),
func() string {
if config.AuthMethod == "multiuser" {
return fmt.Sprintf(`
user = %s
metaToken = %s
`,
config.User,
config.MetaToken,
)
}
return ""
}(),
func() string {
if config.TransportHeartbeatInterval > 0 {
return fmt.Sprintf(`
heartbeatInterval = %d
`,
config.TransportHeartbeatInterval,
)
}
return ""
}(),
func() string {
if config.TransportHeartbeatTimeout > 0 {
return fmt.Sprintf(`
heartbeatTimeout = %d
`,
config.TransportHeartbeatTimeout,
)
}
return ""
}(),
config.LogLevel,
config.LogMaxDays,
config.WebPort,
config.TlsConfigEnable,
func() string {
if config.TlsConfigEnable && config.TlsConfigCertFile != "" {
return fmt.Sprintf(`
tlsCertFile = %s
`,
config.TlsConfigCertFile,
)
}
return ""
}(),
func() string {
if config.TlsConfigEnable && config.TlsConfigKeyFile != "" {
return fmt.Sprintf(`
tlsKeyFile = %s
`,
config.TlsConfigKeyFile,
)
}
return ""
}(),
func() string {
if config.TlsConfigEnable && config.TlsConfigTrustedCaFile != "" {
return fmt.Sprintf(`
tlsTrustedCaFile = %s
`,
config.TlsConfigTrustedCaFile,
)
}
return ""
}(),
func() string {
if config.TlsConfigEnable && config.TlsConfigServerName != "" {
return fmt.Sprintf(`
tlsServerName = %s
`,
config.TlsConfigServerName,
)
}
return ""
}(),
func() string {
if config.ProxyConfigEnable {
return fmt.Sprintf(`
httpProxy = "%s"
`,
config.ProxyConfigProxyUrl,
)
}
return ""
}(),
)
ini += strings.Join(proxyIni, "")
return ini
}
Loading…
Cancel
Save