diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..9b1ee42
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,175 @@
+# Based on https://raw.githubusercontent.com/github/gitignore/main/Node.gitignore
+
+# Logs
+
+logs
+_.log
+npm-debug.log_
+yarn-debug.log*
+yarn-error.log*
+lerna-debug.log*
+.pnpm-debug.log*
+
+# Caches
+
+.cache
+
+# Diagnostic reports (https://nodejs.org/api/report.html)
+
+report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json
+
+# Runtime data
+
+pids
+_.pid
+_.seed
+*.pid.lock
+
+# Directory for instrumented libs generated by jscoverage/JSCover
+
+lib-cov
+
+# Coverage directory used by tools like istanbul
+
+coverage
+*.lcov
+
+# nyc test coverage
+
+.nyc_output
+
+# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
+
+.grunt
+
+# Bower dependency directory (https://bower.io/)
+
+bower_components
+
+# node-waf configuration
+
+.lock-wscript
+
+# Compiled binary addons (https://nodejs.org/api/addons.html)
+
+build/Release
+
+# Dependency directories
+
+node_modules/
+jspm_packages/
+
+# Snowpack dependency directory (https://snowpack.dev/)
+
+web_modules/
+
+# TypeScript cache
+
+*.tsbuildinfo
+
+# Optional npm cache directory
+
+.npm
+
+# Optional eslint cache
+
+.eslintcache
+
+# Optional stylelint cache
+
+.stylelintcache
+
+# Microbundle cache
+
+.rpt2_cache/
+.rts2_cache_cjs/
+.rts2_cache_es/
+.rts2_cache_umd/
+
+# Optional REPL history
+
+.node_repl_history
+
+# Output of 'npm pack'
+
+*.tgz
+
+# Yarn Integrity file
+
+.yarn-integrity
+
+# dotenv environment variable files
+
+.env
+.env.development.local
+.env.test.local
+.env.production.local
+.env.local
+
+# parcel-bundler cache (https://parceljs.org/)
+
+.parcel-cache
+
+# Next.js build output
+
+.next
+out
+
+# Nuxt.js build / generate output
+
+.nuxt
+dist
+
+# Gatsby files
+
+# Comment in the public line in if your project uses Gatsby and not Next.js
+
+# https://nextjs.org/blog/next-9-1#public-directory-support
+
+# public
+
+# vuepress build output
+
+.vuepress/dist
+
+# vuepress v2.x temp and cache directory
+
+.temp
+
+# Docusaurus cache and generated files
+
+.docusaurus
+
+# Serverless directories
+
+.serverless/
+
+# FuseBox cache
+
+.fusebox/
+
+# DynamoDB Local files
+
+.dynamodb/
+
+# TernJS port file
+
+.tern-port
+
+# Stores VSCode versions used for testing VSCode extensions
+
+.vscode-test
+
+# yarn v2
+
+.yarn/cache
+.yarn/unplugged
+.yarn/build-state.yml
+.yarn/install-state.gz
+.pnp.*
+
+# IntelliJ based IDEs
+.idea
+
+# Finder (MacOS) folder config
+.DS_Store
diff --git a/README.md b/README.md
index 2765629..be94997 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,15 @@
# netgate-switch
+To install dependencies:
+
+```bash
+bun install
+```
+
+To run:
+
+```bash
+bun run index.ts
+```
+
+This project was created using `bun init` in bun v1.1.18. [Bun](https://bun.sh) is a fast all-in-one JavaScript runtime.
diff --git a/bun.lockb b/bun.lockb
new file mode 100644
index 0000000..0cee092
Binary files /dev/null and b/bun.lockb differ
diff --git a/config.json b/config.json
new file mode 100644
index 0000000..153ec70
--- /dev/null
+++ b/config.json
@@ -0,0 +1 @@
+{"login":true,"host":"10.225.0.1","user":"lan","password":"xp258147","maincidr":"10.225.1.0/24","proxycidr":"10.225.2.0/24"}
\ No newline at end of file
diff --git a/index.html b/index.html
new file mode 100644
index 0000000..a279e2e
--- /dev/null
+++ b/index.html
@@ -0,0 +1,110 @@
+
+
+
+
+
+
+
+ NetgateSwitch
+
+
+
+
+
+
+
+ NetgateSwitch
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/index.ts b/index.ts
new file mode 100644
index 0000000..470dc23
--- /dev/null
+++ b/index.ts
@@ -0,0 +1,123 @@
+import { serve } from "bun";
+import { RouterOSAPI } from 'node-routeros';
+import cfg from "./config.json";
+
+let api = new RouterOSAPI({
+ host: cfg.host,
+ user: cfg.user,
+ password: cfg.password
+});
+let haslogin = cfg.login;
+const maincidr = cfg.maincidr;
+const proxycidr = cfg.proxycidr;
+
+if (haslogin) {
+ if (!await connectAPI()) {
+ haslogin = false;
+ }
+}
+
+const server = serve({
+ port: 3000, async fetch(request) {
+ const url = new URL(request.url);
+ if (url.pathname === "/") {
+ let file = haslogin ? Bun.file("./index.html") : Bun.file("./login.html");
+ return new Response(file);
+ }
+
+ if (url.pathname === "/core/login/") {
+ let data = await request.json();
+ if (data.user === "" || data.password === "" || data.host === "") {
+ return new Response("参数错误", { status: 400 });
+ }
+ api = new RouterOSAPI({
+ host: data.host,
+ user: data.user,
+ password: data.password
+ });
+ if (await connectAPI()) {
+ writeConfig(data.host, data.user, data.password, data.main, data.proxy);
+ console.log("配置文件已更新");
+ return new Response("登入成功");
+ } else {
+ return new Response("ROS登入失败", { status: 400 });
+ }
+ }
+
+ if (url.pathname === "/core/get/") {
+ if (!haslogin) return new Response("ROS配置未设置", { status: 401 });
+ return new Response(JSON.stringify(await getDHCPList()));
+ }
+
+ if (url.pathname === "/core/switch/") {
+ if (!haslogin) return new Response("ROS配置未设置", { status: 401 });
+ let data = await request.json();
+ let id = data.id;
+ let mac = data.mac;
+ let group = data.group;
+ if ((id === "" && mac === "") || group === "") {
+ return new Response("400 Bad Request", { status: 400 });
+ }
+ switchNetgate('*458',group);
+ }
+
+ if (url.pathname === "/core/logout/") {
+ cleanConfig();
+ return new Response("已登出");
+ }
+
+ return new Response("Page not found", { status: 404 });
+ }
+});
+console.log(`Listening on http://localhost:${server.port} ...`);
+
+async function connectAPI() {
+ try {
+ await api.connect();
+ console.log("API登入成功");
+ return true;
+ } catch (err) {
+ console.log(err);
+ console.log("API登入失败");
+ return false;
+ }
+}
+
+async function getDHCPList() {
+ const result = await api.write('/ip/dhcp-server/lease/print');
+ let list = [];
+ for (let eq in result) {
+ list.push({
+ 'id': result[eq]['.id'],
+ 'address': result[eq]['address'],
+ 'mac-address': result[eq]['mac-address'],
+ 'status': result[eq]['status'],
+ 'host-name': result[eq]['host-name'],
+ 'comment': result[eq]['comment'],
+ });
+ }
+ console.log(list);
+ return list;
+}
+
+async function switchNetgate(id:string,group:string) {
+ const result = await api.write('/ip/dhcp-server/lease/set',['=.id='+id,'=address='+(group==='proxy'?'10.225.1.100':'10.225.0.100')]);
+}
+
+async function writeConfig(host: string, user: string, password: string, mcidr: string, pcidr: string) {
+ cfg.login = true;
+ cfg.host = host;
+ cfg.user = user;
+ cfg.password = password;
+ cfg.maincidr = mcidr;
+ cfg.proxycidr = pcidr;
+
+ haslogin = true;
+ await Bun.write("./config.json", JSON.stringify(cfg));
+}
+
+async function cleanConfig() {
+ cfg.login = false;
+ haslogin = false;
+ await Bun.write("./config.json", JSON.stringify(cfg));
+}
\ No newline at end of file
diff --git a/login.html b/login.html
new file mode 100644
index 0000000..98c07c1
--- /dev/null
+++ b/login.html
@@ -0,0 +1,119 @@
+
+
+
+
+
+
+
+ NetgateSwitch-Login
+
+
+
+
+
+
+
+ NetgateSwitch
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..845ed7a
--- /dev/null
+++ b/package.json
@@ -0,0 +1,17 @@
+{
+ "name": "netgate-switch",
+ "module": "index.ts",
+ "devDependencies": {
+ "@types/bun": "latest"
+ },
+ "peerDependencies": {
+ "typescript": "^5.0.0"
+ },
+ "scripts": {
+ "dev": "bun run index.ts"
+ },
+ "type": "module",
+ "dependencies": {
+ "node-routeros": "^1.6.9"
+ }
+}
\ No newline at end of file
diff --git a/tsconfig.json b/tsconfig.json
new file mode 100644
index 0000000..238655f
--- /dev/null
+++ b/tsconfig.json
@@ -0,0 +1,27 @@
+{
+ "compilerOptions": {
+ // Enable latest features
+ "lib": ["ESNext", "DOM"],
+ "target": "ESNext",
+ "module": "ESNext",
+ "moduleDetection": "force",
+ "jsx": "react-jsx",
+ "allowJs": true,
+
+ // Bundler mode
+ "moduleResolution": "bundler",
+ "allowImportingTsExtensions": true,
+ "verbatimModuleSyntax": true,
+ "noEmit": true,
+
+ // Best practices
+ "strict": true,
+ "skipLibCheck": true,
+ "noFallthroughCasesInSwitch": true,
+
+ // Some stricter flags (disabled by default)
+ "noUnusedLocals": false,
+ "noUnusedParameters": false,
+ "noPropertyAccessFromIndexSignature": false
+ }
+}