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. 500
      frontend/src/components/setting/SetAccount.vue
  5. 632
      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. 11
      godo/model/local_proxy.go
  10. 74
      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"

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

@ -1,199 +1,341 @@
<template> <template>
<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"
{{ item }} :key="index"
</li> @click="selectItem(index)"
</ul> :class="{ active: index === activeIndex }"
</div> >
<div class="setting"> {{ item }}
<div v-if="0 === activeIndex"> </li>
<div class="setting-item"> </ul>
<h1 class="setting-title">{{ t("background") }}</h1> </div>
</div> <div class="setting">
<div class="setting-item"> <div v-if="0 === activeIndex">
<el-select v-model="config.background.type"> <div class="setting-item">
<el-option v-for="(item, key) in desktopOptions" :key="key" :label="item.label" :value="item.value" /> <h1 class="setting-title">{{ t("background") }}</h1>
</el-select> </div>
</div> <div class="setting-item">
<template v-if="config.background.type === 'color'"> <el-select v-model="config.background.type">
<div class="setting-item"> <el-option
<label> </label> v-for="(item, key) in desktopOptions"
<ColorPicker v-model:modelValue="config.background.color" @update:modelValue="onColorChange"></ColorPicker> :key="key"
</div> :label="item.label"
</template> :value="item.value"
<template v-if="config.background.type === 'image'"> />
<div class="setting-item"> </el-select>
<ul class="image-gallery"> </div>
<li v-for="(item, index) in config.background.imageList" :key="index" <template v-if="config.background.type === 'color'">
:class="config.background.url === item ? 'selected' : ''" @click="setBg(item)"> <div class="setting-item">
<img :src="item" /> <label> </label>
</li> <ColorPicker
</ul> v-model:modelValue="config.background.color"
</div> @update:modelValue="onColorChange"
<div class="setting-item"> ></ColorPicker>
<label> </label> </div>
</div> </template>
</template> <template v-if="config.background.type === 'image'">
</div> <div class="setting-item">
<div v-if="1 === activeIndex"> <ul class="image-gallery">
<div class="setting-item"> <li
<h1 class="setting-title">锁屏</h1> v-for="(item, index) in config.background
</div> .imageList"
<div class="setting-item"> :key="index"
<label> {{ t('account') }} </label> :class="
<el-input v-model="account.username" :placeholder="t('account')" clearable /> config.background.url === item
</div> ? 'selected'
<div class="setting-item"> : ''
<label> {{ t('password') }} </label> "
<el-input v-model="account.password" type="password" :placeholder="t('password')" clearable /> @click="setBg(item)"
</div> >
<img :src="item" />
</li>
</ul>
</div>
<div class="setting-item">
<label> </label>
</div>
</template>
</div>
<div v-if="1 === activeIndex">
<div class="setting-item">
<h1 class="setting-title">锁屏</h1>
</div>
<div class="setting-item">
<label> {{ t("account") }} </label>
<el-input
v-model="account.username"
:placeholder="t('account')"
clearable
/>
</div>
<div class="setting-item">
<label> {{ t("password") }} </label>
<el-input
v-model="account.password"
type="password"
:placeholder="t('password')"
clearable
/>
</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"
</el-button> type="primary"
</div> >
</div> <span v-if="loginOrRegister">锁屏</span>
<div v-if="2 === activeIndex"> <span v-else>立即注册</span>
<div class="setting-item"> </el-button>
<h1 class="setting-title">广告与更新提示</h1> </div>
</div> <div class="setting-item">
<div class="setting-item"> <label></label>
<label></label> <small
<el-switch v-model="ad" active-text="开启" inactive-text="关闭" size="large" :before-change="setAd"></el-switch> v-if="loginOrRegister"
</div> style="font-size: 12px"
</div> >还没有账号<a
<div v-if="3 === activeIndex"> href="#"
<div class="setting-item"> @click.prevent="loginOrRegister = false"
<h1 class="setting-title">{{ t("language") }}</h1> ><span>立即注册</span></a
</div> ></small
<div class="setting-item"> >
<label></label> <span
<el-select v-model="modelvalue"> v-else
<el-option v-for="(item, key) in langList" :key="key" :label="item.label" :value="item.value" /> style="font-size: 12px"
</el-select> >
</div> <span>注册完成</span>
<a
href="#"
@click.prevent="loginOrRegister = true"
><span>点我返回</span></a
>
</span>
</div>
</div>
<div v-if="2 === activeIndex">
<div class="setting-item">
<h1 class="setting-title">广告与更新提示</h1>
</div>
<div class="setting-item">
<label></label>
<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"> <div class="setting-item">
<label></label> <label></label>
<el-button @click="submitLang" type="primary"> <el-button
{{ t("confirm") }} @click="submitLang"
</el-button> type="primary"
</div> >
</div> {{ t("confirm") }}
</div> </el-button>
</div> </div>
</div>
</div>
</div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
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";
const { locale } = useI18n();
const sys = useSystem();
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 loginOrRegister = ref(true);
const langList = [
{
label: "中文",
value: "zh-cn",
},
{
label: "English",
value: "en",
},
];
const currentLang = getLang();
const modelvalue = ref(currentLang);
const selectItem = (index: number) => {
activeIndex.value = index;
};
const desktopOptions = [
{
label: t("image"),
value: "image",
},
{
label: t("color"),
value: "color",
},
];
async function submit() {
// setSystemKey("account", account.value);
// Dialog.showMessageBox({
// message: t("save.success"),
// title: t("account"),
// type: "info",
// }).then(() => {
// location.reload();
// });
const data = {
username: account.value.username,
password: account.value.password,
};
import { ref } from 'vue'; const aiUrl = config.value.aiUrl;
import { Dialog, useSystem } from '@/system/index.ts'; if (!loginOrRegister.value) {
import { getSystemKey, setSystemKey, getSystemConfig, setSystemConfig } from '@/system/config' console.log(data);
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 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;
};
const desktopOptions = [
{
label: t("image"),
value: "image",
},
{
label: t("color"),
value: "color",
},
];
async function submit() {
setSystemKey('account', account.value);
Dialog.showMessageBox({
message: t('save.success'),
title: t('account'),
type: 'info',
}).then(() => {
location.reload();
});
}
function setAd() {
const data = toRaw(account.value)
if (ad.value) {
return new Promise((resolve) => {
setTimeout(() => {
ElMessageBox.confirm(
'广告关闭后您将收不到任何系统通知和更新提示!',
'Warning',
{
confirmButtonText: '确定关闭',
cancelButtonText: '取消',
type: 'warning',
}
)
.then(() => {
data.ad = false
setSystemKey('account', data);
return resolve(true)
})
.catch(() => {
return resolve(false)
})
}, 1000) try {
}) const response = await fetch(aiUrl + "/user/register", {
} else { method: "POST",
data.ad = true headers: {
setSystemKey('account', data); "Content-Type": "application/json",
return Promise.resolve(true) },
} body: JSON.stringify(data),
});
} if (response.ok) {
function setBg(item: any) { const result = await response.json();
config.value.background.url = item if (result.code == 0) {
config.value.background.type = "image"; notifySuccess("注册成功");
setSystemConfig(config.value); loginOrRegister.value = true;
sys.initBackground(); } 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) {
function onColorChange(color: string) { const result = await response.json();
config.value.background.color = color; if (result.code == 0) {
config.value.background.type = "color"; notifySuccess("锁屏成功");
setSystemConfig(config.value); loginOrRegister.value = true;
sys.initBackground(); // sysuser
} const data = {
async function submitLang() { 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() {
const data = toRaw(account.value);
if (ad.value) {
return new Promise((resolve) => {
setTimeout(() => {
ElMessageBox.confirm(
"广告关闭后您将收不到任何系统通知和更新提示!",
"Warning",
{
confirmButtonText: "确定关闭",
cancelButtonText: "取消",
type: "warning",
}
)
.then(() => {
data.ad = false;
setSystemKey("account", data);
return resolve(true);
})
.catch(() => {
return resolve(false);
});
}, 1000);
});
} else {
data.ad = true;
setSystemKey("account", data);
return Promise.resolve(true);
}
}
function setBg(item: any) {
config.value.background.url = item;
config.value.background.type = "image";
setSystemConfig(config.value);
sys.initBackground();
}
function onColorChange(color: string) {
config.value.background.color = color;
config.value.background.type = "color";
setSystemConfig(config.value);
sys.initBackground();
}
async function submitLang() {
setLang(modelvalue.value); setLang(modelvalue.value);
locale.value = modelvalue.value; locale.value = modelvalue.value;
ElMessageBox.alert(t("save.success"), t("language"), { ElMessageBox.alert(t("save.success"), t("language"), {
confirmButtonText: "OK", confirmButtonText: "OK",
}); });
} }
watch(loginOrRegister, () => {
account.value.username = "";
account.value.password = "";
});
</script> </script>
<style scoped> <style scoped>
@import "./setStyle.css"; @import "./setStyle.css";
@import "@/assets/imglist.scss"; @import "@/assets/imglist.scss";
</style> </style>

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

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

11
godo/model/local_proxy.go

@ -2,12 +2,11 @@ package model
type LocalProxy struct { type LocalProxy struct {
BaseModel BaseModel
Port uint `json:"port"` // 本地端口 Port uint `json:"port"` // 本地端口
ProxyType string `json:"proxyType"` // 代理类型 ProxyType string `json:"proxyType"` // 代理类型
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 {

74
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"
@ -116,11 +119,10 @@ func UpdateLocalProxyHandler(w http.ResponseWriter, r *http.Request) {
return return
} }
updata := map[string]interface{}{ updata := map[string]interface{}{
"port": lp.Port, "port": lp.Port,
"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 { reverseProxy.ErrorLog = log.New(os.Stderr, "proxy-debug: ", log.LstdFlags)
fmt.Printf("Received response with status: %s\n", resp.Status)
return nil
}
reverseProxy.ErrorHandler = func(rw http.ResponseWriter, req *http.Request, err error) {
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 {
} else { if errors.Is(err, http.ErrServerClosed) {
fmt.Printf("HTTP proxy on domain %s and listen port %d started successfully\n", proxy.Domain, proxy.ListenPort) fmt.Printf("HTTP proxy on port %d stopped gracefully.\n", proxy.Port)
} else {
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")
return
}
// 解析请求体
if err := json.Unmarshal(body, &user); err != nil {
log.Println("解析请求体错误:", err)
libs.ErrorMsg(w, "invalid input") libs.ErrorMsg(w, "invalid input")
return 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