From b5d8e407d9dd19f43fb5a6d1b549c44eca6dd3c3 Mon Sep 17 00:00:00 2001 From: nashaofu Date: Fri, 16 Jun 2023 23:44:44 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=E4=BB=A3=E7=A0=81?= =?UTF-8?q?=EF=BC=8C=E7=BC=96=E5=86=99=20README?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Dockerfile | 2 +- README.md | 115 +++++++++++++++++++++++++++- src/api/device.rs | 7 ++ src/api/mod.rs | 16 ++-- src/api/wol.rs | 9 --- web/src/components/Device/index.tsx | 45 +++++------ web/src/components/Header/index.tsx | 10 +-- web/src/components/Home/index.tsx | 49 ++++++++---- web/src/hooks/useDevices.ts | 18 ++++- 9 files changed, 204 insertions(+), 67 deletions(-) delete mode 100644 src/api/wol.rs diff --git a/Dockerfile b/Dockerfile index bd65782..bebe695 100644 --- a/Dockerfile +++ b/Dockerfile @@ -16,7 +16,7 @@ WORKDIR /opt/wol COPY --from=builder /build/wol . EXPOSE 3000 -VOLUME ["/opt/wol/data"] +VOLUME ["/opt/wol"] ENV RUST_LOG=info \ RUST_BACKTRACE=1 \ diff --git a/README.md b/README.md index 4de822d..036fcb2 100644 --- a/README.md +++ b/README.md @@ -1 +1,114 @@ -# wol \ No newline at end of file +# wol + +Wol 是 wake on lan 的简写,是一个轻量、简介的 Wol 管理服务,支持检测设备是否开机成功。 + +## 功能特性 + +- 部署简单,且可私有部署。 +- 默认使用 yaml 作为配置文件,易于编辑与迁移。 +- 主题切换:支持浅色主题与暗黑主题。 +- 占用资源少,运行速度快。 +- 跨平台:可以在 Linux、macOS 和 Windows 操作系统上运行。 + +## 安装和使用 + +### Docker 中使用(推荐) + +推荐使用 Docker 安装方式,使用简单方便,只需运行如下命令: + +```sh +docker pull ghcr.io/nashaofu/wol:latest +docker run -d \ + --name wol \ + -p 3000:3000 \ + -v /path/to:/opt/wol \ + ghcr.io/nashaofu/wol:latest +``` + +然后在浏览器中访问 `http://127.0.0.1:3000` 即可使用。 + +如果需要自定义配置,可将项目根目录下的 `wol.example.yaml` 文件拷贝到 `/opt/wol` 目录下并重命名为 `wol.yaml`,具体配置参考配置章节。 + +### 系统中使用 + +1. 前往[release](https://github.com/nashaofu/wol/releases)页面下载`wol-client.zip`与`wol-xxxx.zip`,`xxxx`表示系统架构,请根据自己的情况选择 +2. 新建一个目录`wol`,解压`wol-client.zip`到`wol/www`,解压`wol-xxxx.zip`到`wol`目录下,最终目录结构如下 + + ```bash + . + ├── wol # wol-xxxx.zip + └── www # wol-client.zip + ├── ... # other files + └── index.html + ``` + +3. 在终端中运行`./wol`即可启动服务。同时也支持在启动时指定服务的端口号与配置文件。 + + ```bash + Usage: wol [OPTIONS] + + Options: + -p, --port App listen port [default: 3000] + -c, --config Config file path [default: ./wol.yaml] + -h, --help Print help + -V, --version Print version + ``` + +## 配置 + +项目配置文件为`wol.yaml`,配置内容如下: + +```yaml +# 账号密码 +user: null +# 设备列表 +devices: + - name: Windows # 设备名称 + mac: 00:00:00:00:00:00 # 设备 mac 地址 + ip: 192.168.1.1 # 设备 ipv4 地址 + port: 9 # wake on lan 唤醒端口号,一般为9、7 或者 0 +``` + +## 贡献指南 + +如果您想为 Wol 做出贡献,可以按照以下步骤进行: + +1. 克隆项目到本地: + + ```sh + git clone https://github.com/nashaofu/wol.git + ``` + +2. 创建新分支: + + ```sh + git checkout -b my-feature-branch + ``` + +3. 启动项目:你需要安装 rust、nodejs 与 yarn + + ```sh + # 启动服务端项目 + cargo run + # 启动前端项目 + cd client && yarn && yarn dev + ``` + +4. 修改并提交代码: + + ```sh + git add . + git commit -m "Add new feature" + ``` + +5. 推送代码到远程仓库: + + ```sh + git push origin my-feature-branch + ``` + +6. 创建 Pull Request:在 GitHub 上创建一个新的 Pull Request 并等待审核。 + +## 许可证 + +Wol 使用 MIT 许可证,详情请参阅 [LICENSE](LICENSE) 文件。 diff --git a/src/api/device.rs b/src/api/device.rs index b9d4f9a..7e1f44f 100644 --- a/src/api/device.rs +++ b/src/api/device.rs @@ -1,6 +1,7 @@ use crate::{ errors::Result, settings::{Device, SETTINGS}, + wol, }; use actix_web::{get, post, web, HttpResponse, Responder}; @@ -21,6 +22,12 @@ async fn save(data: web::Json>) -> Result { Ok(HttpResponse::Ok().json(&settings.devices)) } +#[post("/wake")] +async fn wake(data: web::Json) -> Result { + wol::wake(&data)?; + Ok(HttpResponse::Ok().json(&data)) +} + #[derive(Debug, Serialize, Deserialize)] pub struct PingData { ip: String, diff --git a/src/api/mod.rs b/src/api/mod.rs index 326da05..9d90361 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -1,15 +1,13 @@ mod device; -mod wol; use actix_web::web; pub fn init(cfg: &mut web::ServiceConfig) { - cfg - .service( - web::scope("/device") - .service(device::all) - .service(device::save) - .service(device::status) - ) - .service(web::scope("/wol").service(wol::wake)); + cfg.service( + web::scope("/device") + .service(device::all) + .service(device::save) + .service(device::wake) + .service(device::status), + ); } diff --git a/src/api/wol.rs b/src/api/wol.rs deleted file mode 100644 index ae4cfc1..0000000 --- a/src/api/wol.rs +++ /dev/null @@ -1,9 +0,0 @@ -use crate::{errors::Result, wol}; - -use actix_web::{post, web, HttpResponse, Responder}; - -#[post("/wake")] -async fn wake(data: web::Json) -> Result { - wol::wake(&data)?; - Ok(HttpResponse::Ok().json(&data)) -} diff --git a/web/src/components/Device/index.tsx b/web/src/components/Device/index.tsx index 6389517..873bfc4 100644 --- a/web/src/components/Device/index.tsx +++ b/web/src/components/Device/index.tsx @@ -8,16 +8,15 @@ import { PoweroffOutlined, } from '@ant-design/icons'; import useSWR from 'swr'; -import useSWRMutation from 'swr/mutation'; import { get } from 'lodash-es'; import { IDevice, IDeviceStatus } from '@/types/device'; -import fetcher from '@/utils/fetcher'; import useMessage from '@/hooks/useMessage'; import useModal from '@/hooks/useModal'; import DeviceEdit from '../DeviceEdit'; import useBoolean from '@/hooks/useBoolean'; -import { useDeleteDevice } from '@/hooks/useDevices'; +import { useDeleteDevice, useWakeDevice } from '@/hooks/useDevices'; import styles from './index.module.less'; +import fetcher from '@/utils/fetcher'; export interface IDeviceProps { device: IDevice; @@ -28,14 +27,6 @@ export default function Device({ device }: IDeviceProps) { const message = useMessage(); const modal = useModal(); const [open, actions] = useBoolean(false); - const { trigger: deleteDevice } = useDeleteDevice({ - onSuccess: () => { - message.success('删除成功'); - }, - onError: (err) => { - message.error(get(err, 'response.data.message', '删除失败')); - }, - }); const { data: status, @@ -45,32 +36,34 @@ export default function Device({ device }: IDeviceProps) { `/device/status/${device.ip}`, (url) => fetcher.get(url), { - refreshInterval: 5000, + refreshInterval: 7000, }, ); - const { isMutating: isWaking, trigger: wakeDevice } = useSWRMutation( - '/wol/wake', - async (url) => { - await fetcher.post(url, device); - await new Promise((resolve) => { - setTimeout(() => resolve(), 10000); - }); + const { isMutating: isWaking, trigger: wakeDevice } = useWakeDevice({ + onSuccess: () => { fetchDeviceStatus(); }, - { - onError: (err) => { - message.error(get(err, 'response.data.message', '开机失败')); - }, + onError: (err) => { + message.error(get(err, 'response.data.message', '开机失败')); }, - ); + }); + + const { trigger: deleteDevice } = useDeleteDevice({ + onSuccess: () => { + message.success('删除成功'); + }, + onError: (err) => { + message.error(get(err, 'response.data.message', '删除失败')); + }, + }); const onWake = useCallback(() => { if (isWaking) { return; } - wakeDevice(); - }, [isWaking, wakeDevice]); + wakeDevice(device); + }, [isWaking, device, wakeDevice]); const items: MenuProps['items'] = [ { diff --git a/web/src/components/Header/index.tsx b/web/src/components/Header/index.tsx index 24bc58a..e6a6fd6 100644 --- a/web/src/components/Header/index.tsx +++ b/web/src/components/Header/index.tsx @@ -29,10 +29,6 @@ export default function Header() { [setThemeValue], ); - const onCreateOk = useCallback(() => { - actions.setFalse(); - }, [actions]); - return ( <> @@ -56,7 +52,11 @@ export default function Header() { - + ); } diff --git a/web/src/components/Home/index.tsx b/web/src/components/Home/index.tsx index 8766b76..0c47e5b 100644 --- a/web/src/components/Home/index.tsx +++ b/web/src/components/Home/index.tsx @@ -1,25 +1,44 @@ -import { Col, Row, Spin } from 'antd'; +import { + Button, Col, Empty, Row, Spin, +} from 'antd'; import { useDevices } from '@/hooks/useDevices'; import Device from '@/components/Device'; +import DeviceEdit from '../DeviceEdit'; +import useBoolean from '@/hooks/useBoolean'; import styles from './index.module.less'; export default function Home() { const { data: devices = [], isLoading } = useDevices(); + const [open, actions] = useBoolean(false); return ( - -
- - {devices.map((item) => ( - -
- -
- - ))} -
-
-
- + <> + +
+ {!devices.length && ( + + + + )} + + {devices.map((item) => ( + +
+ +
+ + ))} +
+
+
+ + + ); } diff --git a/web/src/hooks/useDevices.ts b/web/src/hooks/useDevices.ts index 770b9fa..c8bc908 100644 --- a/web/src/hooks/useDevices.ts +++ b/web/src/hooks/useDevices.ts @@ -11,7 +11,7 @@ IDevice[], '/device/save' >; -export function useDevices(config?: SWRConfiguration) { +export function useDevices(config?: SWRConfiguration) { return useSWR( '/device/all', async (url) => { @@ -99,3 +99,19 @@ export function useDeleteDevice(config?: UseSaveDevicesConfig) { trigger: (device: IDevice) => saveDevices.trigger(devices.filter((item) => item.uid !== device.uid)), }; } + +export function useWakeDevice( + config?: SWRMutationConfiguration, +) { + return useSWRMutation( + '/device/wake', + async (url, { arg }: { arg: IDevice }) => { + await fetcher.post(url, arg); + // 延迟 10s, 等待机器开机 + await new Promise((resolve) => { + setTimeout(() => resolve(), 10000); + }); + }, + config, + ); +}