Browse Source

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

master
godo 4 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>
<div
v-if="!sys._options.noPassword"
class="lockscreen"
:class="lockClassName"
>
<el-card
v-if="sys._rootState.state !== SystemStateEnum.lock"
class="login-box"
shadow="never"
>
@ -322,6 +324,34 @@
</el-row>
</div>
</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>
</template>
@ -330,8 +360,9 @@
import { useSystem } from "@/system";
import { getSystemConfig, setSystemConfig } from "@/system/config";
import router from "@/system/router";
import { SystemStateEnum } from "@/system/type/enum";
import { RestartApp } from "@/util/goutil";
import { notifyError } from "@/util/msg";
import { notifyError, notifySuccess } from "@/util/msg";
import { computed, onMounted, ref, watchEffect } from "vue";
import { useRoute } from "vue-router";
const route = useRoute();
@ -346,6 +377,12 @@
localStorage.removeItem("ThirdPartyPlatform");
};
// /screen/unlock
const unlockForm = ref({
username: "123",
password: "123",
});
const phoneForm = ref({
phone: "",
code: "",
@ -356,6 +393,20 @@
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 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>

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

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

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

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

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

@ -1,199 +1,341 @@
<template>
<div class="container">
<div class="nav">
<ul>
<li v-for="(item, index) in items" :key="index" @click="selectItem(index)"
:class="{ active: index === activeIndex }">
{{ item }}
</li>
</ul>
</div>
<div class="setting">
<div v-if="0 === activeIndex">
<div class="setting-item">
<h1 class="setting-title">{{ t("background") }}</h1>
</div>
<div class="setting-item">
<el-select v-model="config.background.type">
<el-option v-for="(item, key) in desktopOptions" :key="key" :label="item.label" :value="item.value" />
</el-select>
</div>
<template v-if="config.background.type === 'color'">
<div class="setting-item">
<label> </label>
<ColorPicker v-model:modelValue="config.background.color" @update:modelValue="onColorChange"></ColorPicker>
</div>
</template>
<template v-if="config.background.type === 'image'">
<div class="setting-item">
<ul class="image-gallery">
<li v-for="(item, index) in config.background.imageList" :key="index"
:class="config.background.url === item ? 'selected' : ''" @click="setBg(item)">
<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="container">
<div class="nav">
<ul>
<li
v-for="(item, index) in items"
:key="index"
@click="selectItem(index)"
:class="{ active: index === activeIndex }"
>
{{ item }}
</li>
</ul>
</div>
<div class="setting">
<div v-if="0 === activeIndex">
<div class="setting-item">
<h1 class="setting-title">{{ t("background") }}</h1>
</div>
<div class="setting-item">
<el-select v-model="config.background.type">
<el-option
v-for="(item, key) in desktopOptions"
:key="key"
:label="item.label"
:value="item.value"
/>
</el-select>
</div>
<template v-if="config.background.type === 'color'">
<div class="setting-item">
<label> </label>
<ColorPicker
v-model:modelValue="config.background.color"
@update:modelValue="onColorChange"
></ColorPicker>
</div>
</template>
<template v-if="config.background.type === 'image'">
<div class="setting-item">
<ul class="image-gallery">
<li
v-for="(item, index) in config.background
.imageList"
:key="index"
:class="
config.background.url === item
? 'selected'
: ''
"
@click="setBg(item)"
>
<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">
<label></label>
<el-button @click="submit" type="primary">
{{ t('confirm') }}
</el-button>
</div>
</div>
<div v-if="2 === activeIndex">
<div class="setting-item">
<h1 class="setting-title">广告与更新提示</h1>
<div class="setting-item">
<label></label>
<el-button
@click="submit"
type="primary"
>
<span v-if="loginOrRegister">锁屏</span>
<span v-else>立即注册</span>
</el-button>
</div>
</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">
<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 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">
<label></label>
<el-button @click="submitLang" type="primary">
{{ t("confirm") }}
</el-button>
</div>
</div>
</div>
</div>
<div class="setting-item">
<label></label>
<el-button
@click="submitLang"
type="primary"
>
{{ t("confirm") }}
</el-button>
</div>
</div>
</div>
</div>
</template>
<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';
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 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)
})
const aiUrl = config.value.aiUrl;
if (!loginOrRegister.value) {
console.log(data);
}, 1000)
})
} else {
data.ad = true
setSystemKey('account', data);
return Promise.resolve(true)
}
try {
const response = await fetch(aiUrl + "/user/register", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(data),
});
}
function setBg(item: any) {
config.value.background.url = item
config.value.background.type = "image";
setSystemConfig(config.value);
sys.initBackground();
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),
});
}
function onColorChange(color: string) {
config.value.background.color = color;
config.value.background.type = "color";
setSystemConfig(config.value);
sys.initBackground();
}
async function submitLang() {
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() {
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);
locale.value = modelvalue.value;
ElMessageBox.alert(t("save.success"), t("language"), {
confirmButtonText: "OK",
});
}
watch(loginOrRegister, () => {
account.value.username = "";
account.value.password = "";
});
</script>
<style scoped>
@import "./setStyle.css";
@import "@/assets/imglist.scss";
@import "./setStyle.css";
@import "@/assets/imglist.scss";
</style>

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

@ -1,341 +1,305 @@
<template>
<div class="window-outer" :class="{
focus: focusState && currentRouter !== 'main',
}">
<div class="upbar" v-dragable>
<div class="upbar-left">
<div class="back-arr" v-if="currentRouter !== 'main'" @click="back"></div>
<div class="upbar-text">
{{ t("setting") }}
</div>
</div>
<div class="upbar-right" v-if="!isMobileDevice()">
<WinUpButtonGroup :browser-window="browserWindow"></WinUpButtonGroup>
</div>
</div>
<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 class="outer_main">
<div class="main_uper">
<div class="set_item" v-for="item in setList" :key="item.title" @click="openSet(item.key)" v-glowing="{
color: '#3c3c3ce4',
scale: 0.6,
}">
<div class="set_item-img">
<!-- <img class="set_item-img-img" :src="item.icon" /> -->
<svg class="icon" aria-hidden="true" style="font-size: 2em">
<use :xlink:href="'#icon-' + item.icon"></use>
</svg>
</div>
<div class="set_item-right">
<div class="set_item-title">{{ item.title }}</div>
<div class="set_item-desc">{{ item.desc }}</div>
</div>
</div>
</div>
</div>
</div>
</Transition>
</div>
<div
class="window-outer"
:class="{
focus: focusState && currentRouter !== 'main',
}"
>
<div
class="upbar"
v-dragable
v-if="!isMobileDevice()"
>
<div class="upbar-left">
<div
class="back-arr"
v-if="currentRouter !== 'main'"
@click="back"
>
</div>
<div class="upbar-text">
{{ t("setting") }}
</div>
</div>
<div class="upbar-right">
<WinUpButtonGroup
:browser-window="browserWindow"
></WinUpButtonGroup>
</div>
</div>
<div class="settings-container">
<aside class="sidebar">
<ul>
<li
v-for="item in setList"
:key="item.key"
@click="openSet(item.key)"
>
<ElTooltip
v-if="!isMobileDevice()"
:content="item.title"
placement="right"
>
<svg
class="icon"
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>
<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>
<style lang="scss" scoped>
@import "@/assets/main.scss";
.window-outer {
background-color: white;
width: 100%;
height: 100%;
border: #0076d795 1px solid;
box-sizing: border-box;
// box-shadow: inset -599px 0px 0px 0px #ffffff;
transition: background-color 0.1s;
overflow: hidden;
}
.window-outer.focus {
background-color: rgba(255, 255, 255, 0.704);
backdrop-filter: blur(10px);
}
.upbar {
height: 40px;
width: 100%;
display: flex;
justify-content: space-between;
align-items: center;
user-select: none;
.upbar-left {
display: flex;
align-items: center;
font-size: 12px;
height: 100%;
.back-arr {
padding: 0 6px;
margin-right: 3px;
font-size: 14px;
width: 40px;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
}
.back-arr:hover {
color: var(--color-ui-gray);
background-color: var(--color-dark);
}
.upbar-text {
font-size: 14px;
margin-left: 10px;
}
}
.upbar-right {
width: calc(100% - 200px);
display: flex;
justify-content: flex-end;
height: 100%;
background-color: white;
}
}
.outer {
display: flex;
flex-direction: column;
height: 100%;
width: 100%;
background-color: #ffffff;
user-select: none;
/* background-color: rgb(241, 241, 241); */
}
.outer_main {
height: 100%;
margin: 0px 10px 00px 10px;
display: flex;
flex-direction: column;
align-items: center;
}
.main_uper {
display: flex;
flex-direction: row;
justify-content: center;
flex-wrap: wrap;
align-items: center;
height: 50px;
width: 90%;
}
.set_item {
margin: 10px 20px;
padding: 2px 8px 10px 2px;
width: 200px;
display: flex;
align-items: center;
background-color: white;
border: 1px solid #99999900;
transition: all 0.1s;
position: relative;
z-index: 2;
.set_item-img {
width: 30px;
margin: 10px 16px;
flex-shrink: 0;
img {
width: 100%;
}
}
.set_item-right {
flex: 1;
}
.set_item-title {
padding: 4px 0px;
}
.set_item-desc {
font-size: 10px;
color: #999999;
}
}
.set_item:hover {
border: 1px solid #99999954;
box-shadow: 0 0 0 1px #999999 inset;
}
.uper_tab {
/* width: 90%; */
margin: 10px 10px -1px 10px;
font-size: 12px;
z-index: 1;
display: flex;
}
.tab {
text-align: center;
width: 100%;
height: 40px;
font-size: 20px;
font-weight: 200;
/* border: 1px solid #d9d9d9; */
border-bottom: none;
}
.tab_unactive {
text-align: center;
width: 40px;
background-color: rgb(241, 241, 241);
border: 1px solid #d9d9d9;
/* border-bottom:none; */
}
.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;
}
}
@import "@/assets/main.scss";
.window-outer {
background-color: white;
width: 100%;
height: 100%;
border: #0076d795 1px solid;
box-sizing: border-box;
transition: background-color 0.1s;
overflow: hidden;
}
.window-outer.focus {
background-color: rgba(255, 255, 255, 0.704);
backdrop-filter: blur(10px);
}
.upbar {
height: 40px;
width: 100%;
display: flex;
justify-content: space-between;
align-items: center;
user-select: none;
.upbar-left {
display: flex;
align-items: center;
font-size: 12px;
height: 100%;
.back-arr {
padding: 0 6px;
margin-right: 3px;
font-size: 14px;
width: 40px;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
}
.back-arr:hover {
color: var(--color-ui-gray);
background-color: var(--color-dark);
}
.upbar-text {
font-size: 14px;
margin-left: 10px;
}
}
.upbar-right {
width: calc(100% - 200px);
display: flex;
justify-content: flex-end;
height: 100%;
background-color: white;
}
}
.settings-container {
display: flex;
height: calc(100% - 40px); //
}
.sidebar {
box-sizing: border-box;
ul {
list-style: none;
padding: 0;
li {
display: flex;
align-items: center;
justify-content: center;
padding: 10px;
cursor: pointer;
transition: background-color 0.2s;
&:hover {
// background-color: #e0e0e0;
}
.icon {
font-size: 1.5em;
}
}
}
}
.content {
flex: 1;
padding: 20px;
overflow-y: auto;
box-sizing: border-box;
}
@media screen and (max-width: 768px) {
.window-outer {
width: 100%;
height: 100%;
border: none;
box-sizing: border-box;
transition: background-color 0.1s;
overflow: hidden;
}
.upbar {
flex-direction: column;
align-items: flex-start;
padding: 10px;
}
.upbar-right {
width: 100%;
justify-content: space-between;
}
.settings-container {
flex-direction: column-reverse;
}
.sidebar {
width: 100%;
display: flex;
overflow-x: auto;
border-right: none;
// background-color: #f0f0f0;
// border-bottom: 1px solid #dcdcdc;
justify-content: center;
}
.sidebar ul {
display: flex;
flex-direction: row;
width: 100%;
justify-content: space-around;
}
.content {
padding: 10px;
flex: 1;
}
}
.icon-container {
display: flex;
flex-direction: column;
align-items: center;
}
.icon-title {
// margin-top: 5px;
font-size: 0.8em;
color: #333;
}
</style>

29
frontend/src/system/index.ts

@ -103,6 +103,28 @@ export class System {
initBuiltinApp(this);
initBuiltinFileOpener(this); // 注册内建文件打开器
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");
@ -177,6 +199,13 @@ export class System {
}
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;
if (!tempCallBack) {
throw new Error('没有设置登录回调函数');

8
godo/cmd/main.go

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

2
godo/cmd/serve.go

@ -115,7 +115,7 @@ func recoverMiddleware(next http.Handler) http.Handler {
// CORS 中间件
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"
return func(next http.Handler) http.Handler {

11
godo/model/local_proxy.go

@ -2,12 +2,11 @@ package model
type LocalProxy struct {
BaseModel
Port uint `json:"port"` // 本地端口
ProxyType string `json:"proxyType"` // 代理类型
Domain string `json:"domain"` // 代理域名
Path string `json:"path"` // 代理路径
Status bool `json:"status"` // 状态
ListenPort uint `json:"listenPort"` // 代理端口
Port uint `json:"port"` // 本地端口
ProxyType string `json:"proxyType"` // 代理类型
Domain string `json:"domain"` // 代理域名
Path string `json:"path"` // 代理路径
Status bool `json:"status"` // 状态
}
func (*LocalProxy) TableName() string {

74
godo/proxy/local.go

@ -3,13 +3,16 @@ package proxy
import (
"context"
"encoding/json"
"errors"
"fmt"
"godo/libs"
"godo/model"
"log"
"net"
"net/http"
"net/http/httputil"
"net/url"
"os"
"strconv"
"sync"
"time"
@ -116,11 +119,10 @@ func UpdateLocalProxyHandler(w http.ResponseWriter, r *http.Request) {
return
}
updata := map[string]interface{}{
"port": lp.Port,
"proxy_type": lp.ProxyType,
"domain": lp.Domain,
"status": lp.Status,
"listen_port": lp.ListenPort,
"port": lp.Port,
"proxy_type": lp.ProxyType,
"domain": lp.Domain,
"status": lp.Status,
// path
}
@ -133,8 +135,10 @@ func UpdateLocalProxyHandler(w http.ResponseWriter, r *http.Request) {
// 停止旧的代理服务
stopProxy(lp.ID)
// 启动新的代理服务
go startProxy(lp)
if !lp.Status {
// 启动新的代理服务
go startProxy(lp)
}
libs.SuccessMsg(w, lp, "")
}
@ -235,11 +239,13 @@ func stopProxy(id uint) {
switch proxyServer.Type {
case "http":
fmt.Println("closing http proxy...")
httpServer, ok := proxyServer.Server.(*http.Server)
if ok {
// 创建一个上下文,用于传递给 server.Shutdown(ctx)
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
defer httpServer.Close()
if err := httpServer.Shutdown(ctx); err != nil {
fmt.Printf("Failed to shutdown HTTP server on port %s: %v\n", httpServer.Addr, err)
} else {
@ -267,58 +273,56 @@ func stopProxy(id uint) {
default:
fmt.Printf("Unknown proxy type: %s\n", proxyServer.Type)
}
proxyServers.Delete(id)
}
}
// HTTP 代理处理函数
func httpProxyHandler(proxy model.LocalProxy) {
// 如果 ListenPort 没有传递,默认为 80
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))
remote, err := url.Parse(proxy.Domain)
if err != nil {
fmt.Printf("Failed to parse remote URL for port %d: %v\n", proxy.Port, err)
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.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.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) {
fmt.Printf("Error during proxying: %v\n", err)
http.Error(rw, "Proxy error", http.StatusBadGateway)
}
reverseProxy.ErrorLog = log.New(os.Stderr, "proxy-debug: ", log.LstdFlags)
// 监听配置中指定的域名和端口
// 启动 HTTP 服务器并监听指定端口
bindAddress := "127.0.0.1"
serverAddr := fmt.Sprintf("%s:%d", bindAddress, proxy.Port)
server := &http.Server{
Addr: fmt.Sprintf(":%d", proxy.ListenPort),
Addr: serverAddr,
Handler: reverseProxy,
}
proxyServers.Store(proxy.ID, ProxyServer{Type: "http", Server: server})
if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
fmt.Printf("Failed to start HTTP proxy on domain %s and listen port %d: %v\n", proxy.Domain, proxy.ListenPort, err)
} else {
fmt.Printf("HTTP proxy on domain %s and listen port %d started successfully\n", proxy.Domain, proxy.ListenPort)
fmt.Printf("Starting HTTP proxy on LocalHost port %d and forwarding to %s \n", proxy.Port, proxy.Domain)
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 {
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 (
"encoding/json"
"fmt"
"godo/libs"
"godo/model"
"io"
"log"
"net/http"
"strconv"
"sync"
@ -21,20 +24,35 @@ func RegisterSysUserHandler(w http.ResponseWriter, r *http.Request) {
}
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")
return
}
// 检查用户名和密码是否为空
if user.Username == "" || user.Password == "" {
libs.ErrorMsg(w, "username or password is empty")
return
}
// 创建用户
if err := model.Db.Create(&user).Error; err != nil {
libs.ErrorMsg(w, "failed to create user")
return
}
// 返回成功消息
libs.SuccessMsg(w, user.ID, "")
}
@ -57,14 +75,6 @@ func LockedScreenHandler(w http.ResponseWriter, r *http.Request) {
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
mu.Lock()
if userLockedScreen == nil {
@ -73,7 +83,7 @@ func LockedScreenHandler(w http.ResponseWriter, r *http.Request) {
userLockedScreen[user.ID] = true
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
}
decoderUid := r.Header.Get("sysuser")
if decoderUid == "" {
libs.ErrorMsg(w, "invalid uid")
return
}
uidStr, err := libs.DecodeFile("godoos", decoderUid)
if err != nil {
libs.ErrorMsg(w, "invalid uid")
uidStr := r.Header.Get("sysuser")
if uidStr == "" {
//获取锁屏状态
libs.SuccessMsg(w, false, "")
return
}
uid, err := strconv.Atoi(uidStr)
if err != nil {
libs.ErrorMsg(w, "parse uid fail")

Loading…
Cancel
Save