Browse Source

Merge branch 'master' of https://gitee.com/godoos/godoos

master
godo 5 months ago
parent
commit
e41d7aab1a
  1. 66
      frontend/src/components/desktop/LockDesktop.vue
  2. 14
      frontend/src/components/setting/LocalProxy.vue
  3. 6
      frontend/src/components/setting/NetProxy.vue
  4. 244
      frontend/src/components/setting/SetAccount.vue
  5. 310
      frontend/src/components/setting/Setting.vue
  6. 29
      frontend/src/system/index.ts
  7. 8
      godo/cmd/main.go
  8. 2
      godo/cmd/serve.go
  9. 1
      godo/model/local_proxy.go
  10. 60
      godo/proxy/local.go
  11. 43
      godo/user/user.go

66
frontend/src/components/desktop/LockDesktop.vue

@ -1,9 +1,11 @@
<template> <template>
<div <div
v-if="!sys._options.noPassword"
class="lockscreen" class="lockscreen"
:class="lockClassName" :class="lockClassName"
> >
<el-card <el-card
v-if="sys._rootState.state !== SystemStateEnum.lock"
class="login-box" class="login-box"
shadow="never" shadow="never"
> >
@ -322,6 +324,34 @@
</el-row> </el-row>
</div> </div>
</el-card> </el-card>
<el-card
v-else
class="lock-card"
>
<div class="avatar-container">
<el-avatar size="large">
<img
src="/logo.png"
alt="Logo"
/>
</el-avatar>
</div>
<el-form v-model="unlockForm">
<el-form-item>
<el-input
v-model="unlockForm.username"
placeholder="请输入锁屏用户名"
/>
</el-form-item>
<el-form-item>
<el-input
v-model="unlockForm.password"
placeholder="请输入锁屏密码"
/>
</el-form-item>
</el-form>
<el-button @click="lockScreen">锁屏</el-button>
</el-card>
</div> </div>
</template> </template>
@ -330,8 +360,9 @@
import { useSystem } from "@/system"; import { useSystem } from "@/system";
import { getSystemConfig, setSystemConfig } from "@/system/config"; import { getSystemConfig, setSystemConfig } from "@/system/config";
import router from "@/system/router"; import router from "@/system/router";
import { SystemStateEnum } from "@/system/type/enum";
import { RestartApp } from "@/util/goutil"; import { RestartApp } from "@/util/goutil";
import { notifyError } from "@/util/msg"; import { notifyError, notifySuccess } from "@/util/msg";
import { computed, onMounted, ref, watchEffect } from "vue"; import { computed, onMounted, ref, watchEffect } from "vue";
import { useRoute } from "vue-router"; import { useRoute } from "vue-router";
const route = useRoute(); const route = useRoute();
@ -346,6 +377,12 @@
localStorage.removeItem("ThirdPartyPlatform"); localStorage.removeItem("ThirdPartyPlatform");
}; };
// /screen/unlock
const unlockForm = ref({
username: "123",
password: "123",
});
const phoneForm = ref({ const phoneForm = ref({
phone: "", phone: "",
code: "", code: "",
@ -356,6 +393,20 @@
code: "", code: "",
}); });
const lockScreen = async () => {
const res = await fetch(config.apiUrl + "/user/screen/unlock", {
method: "POST",
body: JSON.stringify(unlockForm.value),
});
const data = await res.json();
if (data.code == 0) {
sys._rootState.state = SystemStateEnum.open;
notifySuccess("解锁成功");
} else {
notifyError("解锁失败");
}
};
const thirdpartyCode = ref(""); const thirdpartyCode = ref("");
const validateAndLoginByThirdparty = () => { const validateAndLoginByThirdparty = () => {
@ -1210,4 +1261,17 @@
} }
} }
} }
.lock-card {
border-radius: 10px;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
width: 300px;
height: 300px;
background-color: #ffffff;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
padding: 20px;
}
</style> </style>

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

@ -11,7 +11,7 @@
domain: string; domain: string;
path?: string; path?: string;
status: boolean; status: boolean;
listenPort: number; // listenPort: number;
} }
const proxyInit = { const proxyInit = {
id: Date.now(), id: Date.now(),
@ -20,7 +20,7 @@
domain: "", // domain: "", //
path: "", // path: "", //
status: true, status: true,
listenPort: 80, // listenPort: 80,
}; };
const config = getSystemConfig(); const config = getSystemConfig();
const proxies = ref<ProxyItem[]>([]); const proxies = ref<ProxyItem[]>([]);
@ -143,7 +143,7 @@
pwdRef.value.validate((valid: boolean) => { pwdRef.value.validate((valid: boolean) => {
if (valid) { if (valid) {
proxyData.value.port = Number(proxyData.value.port); proxyData.value.port = Number(proxyData.value.port);
proxyData.value.listenPort = Number(proxyData.value.listenPort); // proxyData.value.listenPort = Number(proxyData.value.listenPort);
if (isEditing.value) { if (isEditing.value) {
updateProxies(proxyData.value); updateProxies(proxyData.value);
} else { } else {
@ -169,8 +169,8 @@
{ required: true, message: "代理域名不能为空", trigger: "blur" }, { required: true, message: "代理域名不能为空", trigger: "blur" },
{ {
pattern: pattern:
/^(https?:\/\/)?((?:[a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}|localhost)(:\d{1,5})?(\/[^\s]*)?$/, /^(https?:\/\/)?((?:[a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}|localhost|(\d{1,3}\.){3}\d{1,3})(:\d{1,5})?(\/[^\s]*)?$/,
message: "请输入有效的域名格式", message: "请输入有效的域名、IP或端口格式",
trigger: "blur", trigger: "blur",
}, },
], ],
@ -289,12 +289,12 @@
> >
<el-input v-model="proxyData.domain" /> <el-input v-model="proxyData.domain" />
</el-form-item> </el-form-item>
<el-form-item <!-- <el-form-item
label="代理端口" label="代理端口"
prop="listenPort" prop="listenPort"
> >
<el-input v-model="proxyData.listenPort" /> <el-input v-model="proxyData.listenPort" />
</el-form-item> </el-form-item> -->
</div> </div>
<el-form-item <el-form-item
label="文件路径" label="文件路径"

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

@ -75,12 +75,12 @@
</el-row> </el-row>
<el-table <el-table
:data="proxyStore.proxies" :data="proxyStore.proxies"
style="width: 98%; border: none" style="width: 100%; border: none"
> >
<el-table-column <el-table-column
prop="name" prop="name"
label="名称" label="名称"
width="100" width="80"
/> />
<el-table-column <el-table-column
prop="type" prop="type"
@ -96,7 +96,7 @@
prop="localIp" prop="localIp"
label="本地Ip" label="本地Ip"
/> />
<el-table-column label="操作"> <el-table-column fixed="right" width="120" label="操作">
<template #default="scope"> <template #default="scope">
<el-row <el-row
:gutter="24" :gutter="24"

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

@ -2,8 +2,12 @@
<div class="container"> <div class="container">
<div class="nav"> <div class="nav">
<ul> <ul>
<li v-for="(item, index) in items" :key="index" @click="selectItem(index)" <li
:class="{ active: index === activeIndex }"> v-for="(item, index) in items"
:key="index"
@click="selectItem(index)"
:class="{ active: index === activeIndex }"
>
{{ item }} {{ item }}
</li> </li>
</ul> </ul>
@ -15,20 +19,37 @@
</div> </div>
<div class="setting-item"> <div class="setting-item">
<el-select v-model="config.background.type"> <el-select v-model="config.background.type">
<el-option v-for="(item, key) in desktopOptions" :key="key" :label="item.label" :value="item.value" /> <el-option
v-for="(item, key) in desktopOptions"
:key="key"
:label="item.label"
:value="item.value"
/>
</el-select> </el-select>
</div> </div>
<template v-if="config.background.type === 'color'"> <template v-if="config.background.type === 'color'">
<div class="setting-item"> <div class="setting-item">
<label> </label> <label> </label>
<ColorPicker v-model:modelValue="config.background.color" @update:modelValue="onColorChange"></ColorPicker> <ColorPicker
v-model:modelValue="config.background.color"
@update:modelValue="onColorChange"
></ColorPicker>
</div> </div>
</template> </template>
<template v-if="config.background.type === 'image'"> <template v-if="config.background.type === 'image'">
<div class="setting-item"> <div class="setting-item">
<ul class="image-gallery"> <ul class="image-gallery">
<li v-for="(item, index) in config.background.imageList" :key="index" <li
:class="config.background.url === item ? 'selected' : ''" @click="setBg(item)"> v-for="(item, index) in config.background
.imageList"
:key="index"
:class="
config.background.url === item
? 'selected'
: ''
"
@click="setBg(item)"
>
<img :src="item" /> <img :src="item" />
</li> </li>
</ul> </ul>
@ -43,29 +64,71 @@
<h1 class="setting-title">锁屏</h1> <h1 class="setting-title">锁屏</h1>
</div> </div>
<div class="setting-item"> <div class="setting-item">
<label> {{ t('account') }} </label> <label> {{ t("account") }} </label>
<el-input v-model="account.username" :placeholder="t('account')" clearable /> <el-input
v-model="account.username"
:placeholder="t('account')"
clearable
/>
</div> </div>
<div class="setting-item"> <div class="setting-item">
<label> {{ t('password') }} </label> <label> {{ t("password") }} </label>
<el-input v-model="account.password" type="password" :placeholder="t('password')" clearable /> <el-input
v-model="account.password"
type="password"
:placeholder="t('password')"
clearable
/>
</div> </div>
<div class="setting-item"> <div class="setting-item">
<label></label> <label></label>
<el-button @click="submit" type="primary"> <el-button
{{ t('confirm') }} @click="submit"
type="primary"
>
<span v-if="loginOrRegister">锁屏</span>
<span v-else>立即注册</span>
</el-button> </el-button>
</div> </div>
<div class="setting-item">
<label></label>
<small
v-if="loginOrRegister"
style="font-size: 12px"
>还没有账号<a
href="#"
@click.prevent="loginOrRegister = false"
><span>立即注册</span></a
></small
>
<span
v-else
style="font-size: 12px"
>
<span>注册完成</span>
<a
href="#"
@click.prevent="loginOrRegister = true"
><span>点我返回</span></a
>
</span>
</div>
</div> </div>
<div v-if="2 === activeIndex"> <div v-if="2 === activeIndex">
<div class="setting-item"> <div class="setting-item">
<h1 class="setting-title">广告与更新提示</h1> <h1 class="setting-title">广告与更新提示</h1>
</div> </div>
<div class="setting-item"> <div class="setting-item">
<label></label> <label></label>
<el-switch v-model="ad" active-text="开启" inactive-text="关闭" size="large" :before-change="setAd"></el-switch> <el-switch
v-model="ad"
active-text="开启"
inactive-text="关闭"
size="large"
:before-change="setAd"
></el-switch>
</div> </div>
</div> </div>
<div v-if="3 === activeIndex"> <div v-if="3 === activeIndex">
@ -75,13 +138,21 @@
<div class="setting-item"> <div class="setting-item">
<label></label> <label></label>
<el-select v-model="modelvalue"> <el-select v-model="modelvalue">
<el-option v-for="(item, key) in langList" :key="key" :label="item.label" :value="item.value" /> <el-option
v-for="(item, key) in langList"
:key="key"
:label="item.label"
:value="item.value"
/>
</el-select> </el-select>
</div> </div>
<div class="setting-item"> <div class="setting-item">
<label></label> <label></label>
<el-button @click="submitLang" type="primary"> <el-button
@click="submitLang"
type="primary"
>
{{ t("confirm") }} {{ t("confirm") }}
</el-button> </el-button>
</div> </div>
@ -91,20 +162,27 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { ref } from 'vue';
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 { getLang, setLang, t } from "@/i18n";
import {
getSystemConfig,
getSystemKey,
setSystemConfig,
setSystemKey,
} from "@/system/config";
import { useSystem } from "@/system/index.ts";
import { SystemStateEnum } from "@/system/type/enum";
import { notifyError, notifySuccess } from "@/util/msg";
import { ElMessageBox } from "element-plus";
import { ref, watch } from "vue";
import { useI18n } from "vue-i18n"; import { useI18n } from "vue-i18n";
const { locale } = useI18n(); const { locale } = useI18n();
const sys = useSystem(); const sys = useSystem();
const items = [t("background"), '锁屏设置', '广告设置', '语言']; const items = [t("background"), "锁屏设置", "广告设置", "语言"];
const activeIndex = ref(0); const activeIndex = ref(0);
const account = ref(getSystemKey('account')); const account = ref(getSystemKey("account"));
const ad = ref(account.value.ad) const ad = ref(account.value.ad);
const config: any = ref(getSystemConfig()); const config: any = ref(getSystemConfig());
const loginOrRegister = ref(true);
const langList = [ const langList = [
{ {
label: "中文", label: "中文",
@ -131,53 +209,112 @@ const desktopOptions = [
}, },
]; ];
async function submit() { async function submit() {
setSystemKey('account', account.value); // setSystemKey("account", account.value);
Dialog.showMessageBox({ // Dialog.showMessageBox({
message: t('save.success'), // message: t("save.success"),
title: t('account'), // title: t("account"),
type: 'info', // type: "info",
}).then(() => { // }).then(() => {
location.reload(); // location.reload();
// });
const data = {
username: account.value.username,
password: account.value.password,
};
const aiUrl = config.value.aiUrl;
if (!loginOrRegister.value) {
console.log(data);
try {
const response = await fetch(aiUrl + "/user/register", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(data),
}); });
if (response.ok) {
const result = await response.json();
if (result.code == 0) {
notifySuccess("注册成功");
loginOrRegister.value = true;
} else {
notifyError("注册失败");
}
} else {
notifyError("注册失败");
}
} catch (error) {
notifyError("注册失败");
console.error("请求错误:", error);
}
} else {
const response = await fetch(aiUrl + "/user/screen/lock", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(data),
});
if (response.ok) {
const result = await response.json();
if (result.code == 0) {
notifySuccess("锁屏成功");
loginOrRegister.value = true;
// sysuser
const data = {
sysuser: result.data,
lock: true,
};
localStorage.setItem("syslock", JSON.stringify(data));
sys._rootState.state = SystemStateEnum.lock;
} else {
notifyError("锁屏失败");
}
} else {
notifyError("锁屏失败");
}
}
setSystemKey("account", account.value);
} }
function setAd() { function setAd() {
const data = toRaw(account.value) const data = toRaw(account.value);
if (ad.value) { if (ad.value) {
return new Promise((resolve) => { return new Promise((resolve) => {
setTimeout(() => { setTimeout(() => {
ElMessageBox.confirm( ElMessageBox.confirm(
'广告关闭后您将收不到任何系统通知和更新提示!', "广告关闭后您将收不到任何系统通知和更新提示!",
'Warning', "Warning",
{ {
confirmButtonText: '确定关闭', confirmButtonText: "确定关闭",
cancelButtonText: '取消', cancelButtonText: "取消",
type: 'warning', type: "warning",
} }
) )
.then(() => { .then(() => {
data.ad = false data.ad = false;
setSystemKey('account', data); setSystemKey("account", data);
return resolve(true) return resolve(true);
}) })
.catch(() => { .catch(() => {
return resolve(false) return resolve(false);
}) });
}, 1000);
}, 1000) });
})
} else { } else {
data.ad = true data.ad = true;
setSystemKey('account', data); setSystemKey("account", data);
return Promise.resolve(true) return Promise.resolve(true);
} }
} }
function setBg(item: any) { function setBg(item: any) {
config.value.background.url = item config.value.background.url = item;
config.value.background.type = "image"; config.value.background.type = "image";
setSystemConfig(config.value); setSystemConfig(config.value);
sys.initBackground(); sys.initBackground();
} }
function onColorChange(color: string) { function onColorChange(color: string) {
config.value.background.color = color; config.value.background.color = color;
@ -192,6 +329,11 @@ async function submitLang() {
confirmButtonText: "OK", confirmButtonText: "OK",
}); });
} }
watch(loginOrRegister, () => {
account.value.username = "";
account.value.password = "";
});
</script> </script>
<style scoped> <style scoped>
@import "./setStyle.css"; @import "./setStyle.css";

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

@ -1,64 +1,95 @@
<template> <template>
<div class="window-outer" :class="{ <div
class="window-outer"
:class="{
focus: focusState && currentRouter !== 'main', focus: focusState && currentRouter !== 'main',
}"> }"
<div class="upbar" v-dragable> >
<div
class="upbar"
v-dragable
v-if="!isMobileDevice()"
>
<div class="upbar-left"> <div class="upbar-left">
<div class="back-arr" v-if="currentRouter !== 'main'" @click="back"></div> <div
class="back-arr"
v-if="currentRouter !== 'main'"
@click="back"
>
</div>
<div class="upbar-text"> <div class="upbar-text">
{{ t("setting") }} {{ t("setting") }}
</div> </div>
</div> </div>
<div class="upbar-right" v-if="!isMobileDevice()"> <div class="upbar-right">
<WinUpButtonGroup :browser-window="browserWindow"></WinUpButtonGroup> <WinUpButtonGroup
</div> :browser-window="browserWindow"
</div> ></WinUpButtonGroup>
<Transition v-for="item in setList" :key="item.key" name="fade" appear>
<component :is="stepComponent(item.content)" v-if="currentRouter === item.key" />
</Transition>
<Transition name="fade" appear>
<div class="outer" v-if="currentRouter === 'main'">
<div class="uper_tab">
<div class="tab">
{{ t("windows.setting") }}
</div> </div>
</div> </div>
<div class="outer_main">
<div class="main_uper"> <div class="settings-container">
<div class="set_item" v-for="item in setList" :key="item.title" @click="openSet(item.key)" v-glowing="{ <aside class="sidebar">
color: '#3c3c3ce4', <ul>
scale: 0.6, <li
}"> v-for="item in setList"
<div class="set_item-img"> :key="item.key"
<!-- <img class="set_item-img-img" :src="item.icon" /> --> @click="openSet(item.key)"
<svg class="icon" aria-hidden="true" style="font-size: 2em"> >
<ElTooltip
v-if="!isMobileDevice()"
:content="item.title"
placement="right"
>
<svg
class="icon"
aria-hidden="true"
>
<use :xlink:href="'#icon-' + item.icon"></use> <use :xlink:href="'#icon-' + item.icon"></use>
</svg> </svg>
</ElTooltip>
<div v-else class="icon-container">
<svg
class="icon"
aria-hidden="true"
>
<use :xlink:href="'#icon-' + item.icon"></use>
</svg>
<div class="icon-title">{{ item.title }}</div>
</div> </div>
<div class="set_item-right"> </li>
<div class="set_item-title">{{ item.title }}</div> </ul>
<div class="set_item-desc">{{ item.desc }}</div> </aside>
</div> <main class="content">
</div> <Transition
</div> name="fade"
</div> appear
</div> >
<component
:is="stepComponent(currentContent)"
v-if="currentContent"
/>
</Transition> </Transition>
</main>
</div>
</div> </div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { inject, ref } from "vue"; import { computed, inject, ref } from "vue";
import { BrowserWindow } from "@/system/window/BrowserWindow";
import { t } from "@/i18n"; import { t } from "@/i18n";
import { useSystem } from "@/system"; import { useSystem } from "@/system";
import { BrowserWindow } from "@/system/window/BrowserWindow";
import { vDragable } from "@/system/window/MakeDragable"; import { vDragable } from "@/system/window/MakeDragable";
import { vGlowing } from "@/util/glowingBorder";
import { stepComponent } from "@/util/stepComponent";
import { isMobileDevice } from "@/util/device"; import { isMobileDevice } from "@/util/device";
import { stepComponent } from "@/util/stepComponent";
import { ElTooltip } from "element-plus";
const browserWindow = inject<BrowserWindow>("browserWindow")!; const browserWindow = inject<BrowserWindow>("browserWindow")!;
const sys = useSystem(); const sys = useSystem();
const currentRouter = ref(browserWindow.config?.router || "main"); const currentRouter = ref(browserWindow.config?.router || "system");
const focusState = ref(false); const focusState = ref(false);
browserWindow?.on("focus", () => { browserWindow?.on("focus", () => {
@ -81,39 +112,34 @@ const setList = ref([
icon: "system", icon: "system",
content: "SetSystem", content: "SetSystem",
}, },
{ {
key: "custom", key: "custom",
title: "代理", title: "代理",
desc: '本地代理、远程代理', desc: "本地代理、远程代理",
icon: "personal", icon: "personal",
content: "SetCustom", content: "SetCustom",
}, },
{ {
key: "nas", key: "nas",
title: "NAS服务", title: "NAS服务",
desc: 'NAS/webdav服务', desc: "NAS/webdav服务",
icon: "disk", icon: "disk",
content: "SetNas", content: "SetNas",
}, },
// {
// key: "language",
// title: '',
// desc: t("language"),
// icon: "language",
// content: "SetLang",
// },
{ {
key: "account", key: "account",
title: "屏幕", title: "屏幕",
desc: '壁纸/语言/锁屏/广告', desc: "壁纸/语言/锁屏/广告",
icon: "account", icon: "account",
content: "SetAccount", content: "SetAccount",
}, },
...(sys._rootState.settings ? sys._rootState.settings : []), ...(sys._rootState.settings ? sys._rootState.settings : []),
]); ]);
const currentContent = computed(() => {
return setList.value.find((item) => item.key === currentRouter.value)
?.content;
});
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@ -125,7 +151,6 @@ const setList = ref([
height: 100%; height: 100%;
border: #0076d795 1px solid; border: #0076d795 1px solid;
box-sizing: border-box; box-sizing: border-box;
// box-shadow: inset -599px 0px 0px 0px #ffffff;
transition: background-color 0.1s; transition: background-color 0.1s;
overflow: hidden; overflow: hidden;
} }
@ -180,162 +205,101 @@ const setList = ref([
} }
} }
.outer { .settings-container {
display: flex; display: flex;
flex-direction: column; height: calc(100% - 40px); //
height: 100%;
width: 100%;
background-color: #ffffff;
user-select: none;
/* background-color: rgb(241, 241, 241); */
} }
.outer_main { .sidebar {
height: 100%; box-sizing: border-box;
margin: 0px 10px 00px 10px;
display: flex;
flex-direction: column;
align-items: center;
}
.main_uper { ul {
display: flex; list-style: none;
flex-direction: row; padding: 0;
justify-content: center;
flex-wrap: wrap;
align-items: center;
height: 50px;
width: 90%;
}
.set_item { li {
margin: 10px 20px;
padding: 2px 8px 10px 2px;
width: 200px;
display: flex; display: flex;
align-items: center; align-items: center;
background-color: white; justify-content: center;
border: 1px solid #99999900; padding: 10px;
transition: all 0.1s; cursor: pointer;
position: relative; transition: background-color 0.2s;
z-index: 2;
.set_item-img {
width: 30px;
margin: 10px 16px;
flex-shrink: 0;
img { &:hover {
width: 100%; // background-color: #e0e0e0;
}
} }
.set_item-right { .icon {
flex: 1; font-size: 1.5em;
} }
.set_item-title {
padding: 4px 0px;
} }
.set_item-desc {
font-size: 10px;
color: #999999;
} }
} }
.set_item:hover { .content {
border: 1px solid #99999954; flex: 1;
box-shadow: 0 0 0 1px #999999 inset; padding: 20px;
overflow-y: auto;
box-sizing: border-box;
} }
.uper_tab { @media screen and (max-width: 768px) {
/* width: 90%; */ .window-outer {
margin: 10px 10px -1px 10px; width: 100%;
font-size: 12px; height: 100%;
border: none;
box-sizing: border-box;
transition: background-color 0.1s;
overflow: hidden;
}
z-index: 1; .upbar {
display: flex; flex-direction: column;
align-items: flex-start;
padding: 10px;
} }
.tab { .upbar-right {
text-align: center;
width: 100%; width: 100%;
height: 40px; justify-content: space-between;
font-size: 20px;
font-weight: 200;
/* border: 1px solid #d9d9d9; */
border-bottom: none;
} }
.tab_unactive { .settings-container {
text-align: center; flex-direction: column-reverse;
width: 40px;
background-color: rgb(241, 241, 241);
border: 1px solid #d9d9d9;
/* border-bottom:none; */
} }
.bottom { .sidebar {
flex-shrink: 0; width: 100%;
display: flex; display: flex;
flex-direction: row; overflow-x: auto;
justify-content: flex-end; border-right: none;
flex-shrink: 0;
width: 90%;
margin: 6px auto;
// background-color: #f0f0f0; // background-color: #f0f0f0;
// border-bottom: 1px solid #dcdcdc;
justify-content: center;
} }
.bottom_button { .sidebar ul {
width: 80px; display: flex;
height: 26px; flex-direction: row;
line-height: 26px; width: 100%;
text-align: center; justify-content: space-around;
font-size: 13px;
background-color: #e1e1e1;
border: 1px solid #999999;
transition: all 0.2s;
box-sizing: border-box;
margin: 0 0 0 10px;
}
.bottom_button:first-child {
border: 1px solid #0078d7;
box-shadow: 0 0 0px 1px #0078d7 inset;
}
.bottom_button:hover {
border: 1px solid #0078d7;
background-color: #e5f1fb;
box-shadow: 0 0 0 0px #0078d7 inset;
} }
.fade-enter-active, .content {
.fade-leave-active { padding: 10px;
transition: all 0.2s ease; flex: 1;
} }
.fade-enter-to,
.fade-leave-from {
opacity: 1;
} }
.fade-enter-from, .icon-container {
.fade-leave-to { display: flex;
opacity: 0; flex-direction: column;
align-items: center;
} }
@media screen and (max-width: 768px) { .icon-title {
.window-outer { // margin-top: 5px;
width: 100%; font-size: 0.8em;
height: 100%; color: #333;
border: none;
box-sizing: border-box;
// box-shadow: inset -599px 0px 0px 0px #ffffff;
transition: background-color 0.1s;
overflow: hidden;
}
} }
</style> </style>

29
frontend/src/system/index.ts

@ -103,6 +103,28 @@ export class System {
initBuiltinApp(this); initBuiltinApp(this);
initBuiltinFileOpener(this); // 注册内建文件打开器 initBuiltinFileOpener(this); // 注册内建文件打开器
await this.initSavedConfig(); // 初始化保存的配置 await this.initSavedConfig(); // 初始化保存的配置
const config = getSystemConfig();
const syslock = localStorage.getItem("syslock");
const lockData = JSON.parse(syslock || "{}");
// 判断是解锁
if (lockData.lock) {
const sysuser = lockData.sysuser;
const res = await fetch(config.apiUrl + "/user/screen/status", {
headers: {
"sysuser": sysuser,
},
});
const data = await res.json();
// data.data 为 true 表示锁屏
// 如果没锁屏,则删除 sysuser
if (!data.data) {
lockData.lock = false;
lockData.sysuser = "";
localStorage.setItem("syslock", JSON.stringify(lockData));
}
}
// 判断是否为钉钉工作台登录 // 判断是否为钉钉工作台登录
const login_type = new URLSearchParams(window.location.search).get("login_type"); const login_type = new URLSearchParams(window.location.search).get("login_type");
@ -177,6 +199,13 @@ export class System {
} }
this._rootState.state = SystemStateEnum.lock; this._rootState.state = SystemStateEnum.lock;
const syslock = localStorage.getItem("syslock");
const lockData = JSON.parse(syslock || "{}");
if (!lockData.lock) {
this._rootState.state = SystemStateEnum.open;
}
const tempCallBack = this._options.loginCallback; const tempCallBack = this._options.loginCallback;
if (!tempCallBack) { if (!tempCallBack) {
throw new Error('没有设置登录回调函数'); throw new Error('没有设置登录回调函数');

8
godo/cmd/main.go

@ -186,10 +186,10 @@ func OsStart() {
proxyRouter.HandleFunc("/frpc/status", proxy.StatusFrpcHandler).Methods(http.MethodGet) proxyRouter.HandleFunc("/frpc/status", proxy.StatusFrpcHandler).Methods(http.MethodGet)
userRouter := router.PathPrefix("/user").Subrouter() userRouter := router.PathPrefix("/user").Subrouter()
userRouter.HandleFunc("/register", user.RegisterSysUserHandler).Methods(http.MethodPost) userRouter.HandleFunc("/register", user.RegisterSysUserHandler).Methods(http.MethodPost) // 注册系统用户
userRouter.HandleFunc("/screen/lock", user.LockedScreenHandler).Methods(http.MethodPost) userRouter.HandleFunc("/screen/lock", user.LockedScreenHandler).Methods(http.MethodPost) // 锁屏
userRouter.HandleFunc("/screen/unlock", user.UnLockScreenHandler).Methods(http.MethodPost) userRouter.HandleFunc("/screen/unlock", user.UnLockScreenHandler).Methods(http.MethodPost) // 解锁
userRouter.HandleFunc("/screen/status", user.CheckLockedScreenHandler).Methods(http.MethodGet) userRouter.HandleFunc("/screen/status", user.CheckLockedScreenHandler).Methods(http.MethodGet) // 检查锁屏状态
// 注册根路径的处理函数 // 注册根路径的处理函数
distFS, _ := fs.Sub(deps.Frontendassets, "dist") distFS, _ := fs.Sub(deps.Frontendassets, "dist")

2
godo/cmd/serve.go

@ -115,7 +115,7 @@ func recoverMiddleware(next http.Handler) http.Handler {
// CORS 中间件 // CORS 中间件
func corsMiddleware() mux.MiddlewareFunc { func corsMiddleware() mux.MiddlewareFunc {
allowHeaders := "Content-Type, Accept, Authorization, Origin, Pwd" allowHeaders := "Content-Type, Accept, Authorization, Origin, Pwd, sysuser"
allowMethods := "GET, POST, PUT, DELETE, OPTIONS" allowMethods := "GET, POST, PUT, DELETE, OPTIONS"
return func(next http.Handler) http.Handler { return func(next http.Handler) http.Handler {

1
godo/model/local_proxy.go

@ -7,7 +7,6 @@ type LocalProxy struct {
Domain string `json:"domain"` // 代理域名 Domain string `json:"domain"` // 代理域名
Path string `json:"path"` // 代理路径 Path string `json:"path"` // 代理路径
Status bool `json:"status"` // 状态 Status bool `json:"status"` // 状态
ListenPort uint `json:"listenPort"` // 代理端口
} }
func (*LocalProxy) TableName() string { func (*LocalProxy) TableName() string {

60
godo/proxy/local.go

@ -3,13 +3,16 @@ package proxy
import ( import (
"context" "context"
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
"godo/libs" "godo/libs"
"godo/model" "godo/model"
"log"
"net" "net"
"net/http" "net/http"
"net/http/httputil" "net/http/httputil"
"net/url" "net/url"
"os"
"strconv" "strconv"
"sync" "sync"
"time" "time"
@ -120,7 +123,6 @@ func UpdateLocalProxyHandler(w http.ResponseWriter, r *http.Request) {
"proxy_type": lp.ProxyType, "proxy_type": lp.ProxyType,
"domain": lp.Domain, "domain": lp.Domain,
"status": lp.Status, "status": lp.Status,
"listen_port": lp.ListenPort,
// path // path
} }
@ -133,8 +135,10 @@ func UpdateLocalProxyHandler(w http.ResponseWriter, r *http.Request) {
// 停止旧的代理服务 // 停止旧的代理服务
stopProxy(lp.ID) stopProxy(lp.ID)
if !lp.Status {
// 启动新的代理服务 // 启动新的代理服务
go startProxy(lp) go startProxy(lp)
}
libs.SuccessMsg(w, lp, "") libs.SuccessMsg(w, lp, "")
} }
@ -235,11 +239,13 @@ func stopProxy(id uint) {
switch proxyServer.Type { switch proxyServer.Type {
case "http": case "http":
fmt.Println("closing http proxy...")
httpServer, ok := proxyServer.Server.(*http.Server) httpServer, ok := proxyServer.Server.(*http.Server)
if ok { if ok {
// 创建一个上下文,用于传递给 server.Shutdown(ctx) // 创建一个上下文,用于传递给 server.Shutdown(ctx)
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel() defer cancel()
defer httpServer.Close()
if err := httpServer.Shutdown(ctx); err != nil { if err := httpServer.Shutdown(ctx); err != nil {
fmt.Printf("Failed to shutdown HTTP server on port %s: %v\n", httpServer.Addr, err) fmt.Printf("Failed to shutdown HTTP server on port %s: %v\n", httpServer.Addr, err)
} else { } else {
@ -267,58 +273,56 @@ func stopProxy(id uint) {
default: default:
fmt.Printf("Unknown proxy type: %s\n", proxyServer.Type) fmt.Printf("Unknown proxy type: %s\n", proxyServer.Type)
} }
proxyServers.Delete(id) proxyServers.Delete(id)
} }
} }
// HTTP 代理处理函数 // HTTP 代理处理函数
func httpProxyHandler(proxy model.LocalProxy) { func httpProxyHandler(proxy model.LocalProxy) {
// 如果 ListenPort 没有传递,默认为 80 remote, err := url.Parse(proxy.Domain)
if proxy.ListenPort == 0 {
proxy.ListenPort = 80
}
fmt.Printf("Initializing HTTP proxy for ID %d on domain %s and listen port %d\n", proxy.ID, proxy.Domain, proxy.ListenPort)
// 使用 proxy.Port 作为本地目标端口
remote, err := url.Parse(fmt.Sprintf("http://localhost:%d", proxy.Port))
if err != nil { if err != nil {
fmt.Printf("Failed to parse remote URL for port %d: %v\n", proxy.Port, err) fmt.Printf("Failed to parse remote URL for port %d: %v\n", proxy.Port, err)
return return
} }
fmt.Printf("Parsed remote URL: %s\n", remote.String())
if remote.Scheme == "" {
fmt.Printf("Remote URL for port %d does not contain a scheme (http/https): %s\n", proxy.Port, proxy.Domain)
return
}
// 创建反向代理
reverseProxy := httputil.NewSingleHostReverseProxy(remote) reverseProxy := httputil.NewSingleHostReverseProxy(remote)
// 设置请求头(如需要可以启用)
reverseProxy.Director = func(req *http.Request) { reverseProxy.Director = func(req *http.Request) {
req.Header.Set("X-Forwarded-Host", req.Host)
req.Header.Set("X-Origin-Host", remote.Host)
req.Header.Set("User-Agent", "Mozilla/5.0 (compatible; curl/7.68.0)") // 设置常见的 User-Agent
req.Host = remote.Host
req.URL.Scheme = remote.Scheme req.URL.Scheme = remote.Scheme
req.URL.Host = remote.Host req.URL.Host = remote.Host
req.Host = remote.Host
req.URL.Path = proxy.Path + req.URL.Path // 使用代理路径
fmt.Printf("Proxying request to: %s\n", req.URL.String())
}
reverseProxy.ModifyResponse = func(resp *http.Response) error {
fmt.Printf("Received response with status: %s\n", resp.Status)
return nil
} }
reverseProxy.ErrorHandler = func(rw http.ResponseWriter, req *http.Request, err error) { reverseProxy.ErrorLog = log.New(os.Stderr, "proxy-debug: ", log.LstdFlags)
fmt.Printf("Error during proxying: %v\n", err)
http.Error(rw, "Proxy error", http.StatusBadGateway)
}
// 监听配置中指定的域名和端口 // 启动 HTTP 服务器并监听指定端口
bindAddress := "127.0.0.1"
serverAddr := fmt.Sprintf("%s:%d", bindAddress, proxy.Port)
server := &http.Server{ server := &http.Server{
Addr: fmt.Sprintf(":%d", proxy.ListenPort), Addr: serverAddr,
Handler: reverseProxy, Handler: reverseProxy,
} }
proxyServers.Store(proxy.ID, ProxyServer{Type: "http", Server: server}) proxyServers.Store(proxy.ID, ProxyServer{Type: "http", Server: server})
if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed { fmt.Printf("Starting HTTP proxy on LocalHost port %d and forwarding to %s \n", proxy.Port, proxy.Domain)
fmt.Printf("Failed to start HTTP proxy on domain %s and listen port %d: %v\n", proxy.Domain, proxy.ListenPort, err) if err := server.ListenAndServe(); err != nil {
if errors.Is(err, http.ErrServerClosed) {
fmt.Printf("HTTP proxy on port %d stopped gracefully.\n", proxy.Port)
} else { } else {
fmt.Printf("HTTP proxy on domain %s and listen port %d started successfully\n", proxy.Domain, proxy.ListenPort) fmt.Printf("Error starting HTTP proxy on port %d: %v\n", proxy.Port, err)
}
} }
} }

43
godo/user/user.go

@ -2,8 +2,11 @@ package user
import ( import (
"encoding/json" "encoding/json"
"fmt"
"godo/libs" "godo/libs"
"godo/model" "godo/model"
"io"
"log"
"net/http" "net/http"
"strconv" "strconv"
"sync" "sync"
@ -21,20 +24,35 @@ func RegisterSysUserHandler(w http.ResponseWriter, r *http.Request) {
} }
var user model.SysUser var user model.SysUser
if err := json.NewDecoder(r.Body).Decode(&user); err != nil {
// 获取请求体
body, err := io.ReadAll(r.Body)
if err != nil {
log.Println("读取请求体错误:", err)
libs.ErrorMsg(w, "invalid input") libs.ErrorMsg(w, "invalid input")
return return
} }
// 解析请求体
if err := json.Unmarshal(body, &user); err != nil {
log.Println("解析请求体错误:", err)
libs.ErrorMsg(w, "invalid input")
return
}
// 检查用户名和密码是否为空
if user.Username == "" || user.Password == "" { if user.Username == "" || user.Password == "" {
libs.ErrorMsg(w, "username or password is empty") libs.ErrorMsg(w, "username or password is empty")
return return
} }
// 创建用户
if err := model.Db.Create(&user).Error; err != nil { if err := model.Db.Create(&user).Error; err != nil {
libs.ErrorMsg(w, "failed to create user") libs.ErrorMsg(w, "failed to create user")
return return
} }
// 返回成功消息
libs.SuccessMsg(w, user.ID, "") libs.SuccessMsg(w, user.ID, "")
} }
@ -57,14 +75,6 @@ func LockedScreenHandler(w http.ResponseWriter, r *http.Request) {
return return
} }
userEncodeStr, err := libs.EncodeFile("godoos", strconv.Itoa(int(user.ID)))
if err != nil {
libs.ErrorMsg(w, "failed to encode user")
return
}
w.Header().Set("sysuser", userEncodeStr)
// 登录成功,锁屏状态设置为 true // 登录成功,锁屏状态设置为 true
mu.Lock() mu.Lock()
if userLockedScreen == nil { if userLockedScreen == nil {
@ -73,7 +83,7 @@ func LockedScreenHandler(w http.ResponseWriter, r *http.Request) {
userLockedScreen[user.ID] = true userLockedScreen[user.ID] = true
mu.Unlock() mu.Unlock()
libs.SuccessMsg(w, nil, "system locked") libs.SuccessMsg(w, fmt.Sprint(user.ID), "system locked")
} }
// 系统用户登录(解锁) // 系统用户登录(解锁)
@ -113,16 +123,13 @@ func CheckLockedScreenHandler(w http.ResponseWriter, r *http.Request) {
return return
} }
decoderUid := r.Header.Get("sysuser") uidStr := r.Header.Get("sysuser")
if decoderUid == "" { if uidStr == "" {
libs.ErrorMsg(w, "invalid uid") //获取锁屏状态
return libs.SuccessMsg(w, false, "")
}
uidStr, err := libs.DecodeFile("godoos", decoderUid)
if err != nil {
libs.ErrorMsg(w, "invalid uid")
return return
} }
uid, err := strconv.Atoi(uidStr) uid, err := strconv.Atoi(uidStr)
if err != nil { if err != nil {
libs.ErrorMsg(w, "parse uid fail") libs.ErrorMsg(w, "parse uid fail")

Loading…
Cancel
Save