158 lines
7.0 KiB
Go
158 lines
7.0 KiB
Go
package adminpanel
|
|||
|
|
|
||
|
|
import (
|
||
|
|
"encoding/json"
|
||
|
|
"fmt"
|
||
|
|
"net/http"
|
||
|
|
"strings"
|
||
|
|
|
||
|
|
"writebot/internal/helpers"
|
||
|
|
"writebot/internal/writeserver"
|
||
|
|
)
|
||
|
|
|
||
|
|
var (
|
||
|
|
writePort int
|
||
|
|
controlPort int
|
||
|
|
)
|
||
|
|
|
||
|
|
func Init(wp, cp int) {
|
||
|
|
writePort = wp
|
||
|
|
controlPort = cp
|
||
|
|
}
|
||
|
|
|
||
|
|
const adminHTML = `<!DOCTYPE html>
|
||
|
|
<html lang="zh-CN">
|
||
|
|
<head>
|
||
|
|
<meta charset="UTF-8">
|
||
|
|
<meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1">
|
||
|
|
<title>WriteBot</title>
|
||
|
|
<style>
|
||
|
|
:root{color-scheme:dark;--bg:#111827;--card:#1f2937;--text:#f3f4f6;--muted:#9ca3af;--accent:#60a5fa;--accent-hover:#93bbfd;--danger:#fca5a5;--success:#86efac;--line:#374151}
|
||
|
|
*{box-sizing:border-box;margin:0;padding:0}
|
||
|
|
body{background:var(--bg);color:var(--text);font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;display:flex;align-items:center;justify-content:center;min-height:100vh;padding:20px}
|
||
|
|
.card{background:var(--card);border:1px solid var(--line);border-radius:12px;padding:28px;max-width:420px;width:100%}
|
||
|
|
h1{font-size:18px;font-weight:700;margin-bottom:6px}
|
||
|
|
.sub{font-size:13px;color:var(--muted);margin-bottom:20px}
|
||
|
|
.status-row{display:flex;align-items:center;gap:10px;padding:12px 16px;background:rgba(255,255,255,0.03);border-radius:8px;margin-bottom:16px}
|
||
|
|
.dot{width:10px;height:10px;border-radius:50%;flex-shrink:0}
|
||
|
|
.dot.running{background:var(--success)}
|
||
|
|
.dot.starting{background:#fbbf24}
|
||
|
|
.dot.stopped{background:var(--muted)}
|
||
|
|
.status-text{font-size:14px;font-weight:500}
|
||
|
|
.status-ip{font-size:12px;color:var(--muted);margin-top:2px}
|
||
|
|
.actions{display:flex;gap:8px;flex-wrap:wrap}
|
||
|
|
.btn{padding:10px 20px;border-radius:8px;font-size:14px;font-weight:600;border:none;cursor:pointer;transition:all 0.15s;color:#fff}
|
||
|
|
.btn-primary{background:var(--accent)}
|
||
|
|
.btn-primary:hover{background:var(--accent-hover)}
|
||
|
|
.btn-primary:disabled{opacity:0.4;cursor:not-allowed}
|
||
|
|
.btn-secondary{background:transparent;border:1px solid var(--line);color:var(--text)}
|
||
|
|
.btn-secondary:hover{background:rgba(255,255,255,0.05)}
|
||
|
|
.btn-danger{background:transparent;border:1px solid var(--danger);color:var(--danger)}
|
||
|
|
.btn-danger:hover{background:rgba(252,165,165,0.1)}
|
||
|
|
.link-row{margin-top:16px;padding:10px 14px;background:rgba(255,255,255,0.03);border-radius:8px;font-size:13px;color:var(--muted);word-break:break-all}
|
||
|
|
.link-row a{color:var(--accent);text-decoration:none}
|
||
|
|
.quick-links{display:flex;gap:8px;margin-bottom:12px;flex-wrap:wrap}
|
||
|
|
.ql-btn{padding:6px 14px;border-radius:6px;background:rgba(255,255,255,0.05);border:1px solid var(--line);color:var(--accent);text-decoration:none;font-size:13px;font-weight:500;transition:all 0.15s}
|
||
|
|
.ql-btn:hover{background:rgba(255,255,255,0.1)}
|
||
|
|
.footer{margin-top:20px;font-size:11px;color:var(--muted);text-align:center}
|
||
|
|
</style>
|
||
|
|
</head>
|
||
|
|
<body>
|
||
|
|
<div class="card">
|
||
|
|
<h1>WriteBot</h1>
|
||
|
|
<div class="sub">Ctrl+D bookmark this page</div>
|
||
|
|
<div class="quick-links">
|
||
|
|
<a href="http://localhost:1313/posts/" class="ql-btn" target="_blank">Posts</a>
|
||
|
|
<a href="http://localhost:WRITE_PORT/" class="ql-btn" target="_blank">Write</a>
|
||
|
|
</div>
|
||
|
|
<div class="sub" id="sub">Loading...</div>
|
||
|
|
<div class="status-row">
|
||
|
|
<div class="dot" id="dot"></div>
|
||
|
|
<div>
|
||
|
|
<div class="status-text" id="statusText">-</div>
|
||
|
|
<div class="status-ip" id="statusIp"></div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
<div class="actions">
|
||
|
|
<button class="btn btn-primary" id="btnStart" onclick="action('start')">Start Write</button>
|
||
|
|
<button class="btn btn-secondary" id="btnOpen" onclick="openWrite()">Open</button>
|
||
|
|
<button class="btn btn-danger" id="btnStop" onclick="action('stop')">Stop</button>
|
||
|
|
</div>
|
||
|
|
<div class="link-row" id="linkRow" style="display:none">
|
||
|
|
Copy: <a href="#" id="lanLink" onclick="copyLink(event)"></a>
|
||
|
|
</div>
|
||
|
|
<div class="footer"><a href="https://usj.cc" style="color:var(--muted)">usj.cc</a></div>
|
||
|
|
</div>
|
||
|
|
<script>
|
||
|
|
var port=WRITE_PORT;
|
||
|
|
var ipCache="";
|
||
|
|
function updateUI(s){var d=document.getElementById("dot"),st=document.getElementById("statusText"),si=document.getElementById("statusIp"),bs=document.getElementById("btnStart"),bo=document.getElementById("btnOpen"),lr=document.getElementById("linkRow"),ll=document.getElementById("lanLink");d.className="dot";if(s.running){d.classList.add("running");st.textContent="Running";if(s.ip)ipCache=s.ip;si.textContent="http://"+ipCache+":"+s.port;bs.disabled=true;bo.disabled=false;lr.style.display="block";ll.href="http://"+ipCache+":"+s.port;ll.textContent="http://"+ipCache+":"+s.port;document.getElementById("sub").textContent="Write is serving at"}else if(s.starting){d.classList.add("starting");st.textContent="Starting...";si.textContent="";bs.disabled=true;bo.disabled=true;lr.style.display="none";document.getElementById("sub").textContent="Please wait..."}else{d.classList.add("stopped");st.textContent="Stopped";si.textContent="";bs.disabled=false;bo.disabled=true;lr.style.display="none";document.getElementById("sub").textContent="Click Start to launch Write"}}
|
||
|
|
async function fetchStatus(){try{var r=await fetch("/status");var s=await r.json();updateUI(s)}catch(e){document.getElementById("sub").textContent="Cannot reach API"}}
|
||
|
|
async function action(a){document.getElementById("sub").textContent="Working...";await fetch("/"+a,{method:"POST"});fetchStatus()}
|
||
|
|
function openWrite(){window.open("http://"+(ipCache||"localhost:WRITE_PORT"))}
|
||
|
|
function copyLink(e){e.preventDefault();navigator.clipboard.writeText(document.getElementById("lanLink").textContent).catch(function(){})}
|
||
|
|
fetchStatus();
|
||
|
|
setInterval(fetchStatus,5000);
|
||
|
|
</script>
|
||
|
|
</body>
|
||
|
|
</html>`
|
||
|
|
|
||
|
|
func CreateControlServer() *http.Server {
|
||
|
|
html := strings.ReplaceAll(adminHTML, "WRITE_PORT", fmt.Sprintf("%d", writePort))
|
||
|
|
|
||
|
|
mux := http.NewServeMux()
|
||
|
|
|
||
|
|
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||
|
|
path := r.URL.Path
|
||
|
|
w.Header().Set("Access-Control-Allow-Origin", "*")
|
||
|
|
|
||
|
|
switch path {
|
||
|
|
case "/":
|
||
|
|
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||
|
|
w.Write([]byte(html))
|
||
|
|
case "/status":
|
||
|
|
w.Header().Set("Content-Type", "application/json")
|
||
|
|
s := writeserver.GetWriteStatus()
|
||
|
|
s["ip"] = helpers.GetLanIP("WLAN")
|
||
|
|
s["port"] = writePort
|
||
|
|
json.NewEncoder(w).Encode(s)
|
||
|
|
case "/start":
|
||
|
|
if r.Method == "POST" {
|
||
|
|
w.Header().Set("Content-Type", "application/json")
|
||
|
|
json.NewEncoder(w).Encode(writeserver.StartWriteServer())
|
||
|
|
} else {
|
||
|
|
http.NotFound(w, r)
|
||
|
|
}
|
||
|
|
case "/stop":
|
||
|
|
if r.Method == "POST" {
|
||
|
|
w.Header().Set("Content-Type", "application/json")
|
||
|
|
json.NewEncoder(w).Encode(map[string]bool{"ok": writeserver.StopWriteServer()})
|
||
|
|
} else {
|
||
|
|
http.NotFound(w, r)
|
||
|
|
}
|
||
|
|
case "/ip":
|
||
|
|
w.Header().Set("Content-Type", "application/json")
|
||
|
|
json.NewEncoder(w).Encode(map[string]interface{}{
|
||
|
|
"ip": helpers.GetLanIP("WLAN"),
|
||
|
|
"port": writePort,
|
||
|
|
})
|
||
|
|
case "/health":
|
||
|
|
w.Header().Set("Content-Type", "application/json")
|
||
|
|
json.NewEncoder(w).Encode(map[string]bool{"ok": true})
|
||
|
|
default:
|
||
|
|
w.WriteHeader(http.StatusNotFound)
|
||
|
|
w.Header().Set("Content-Type", "application/json")
|
||
|
|
json.NewEncoder(w).Encode(map[string]string{"error": "not found"})
|
||
|
|
}
|
||
|
|
})
|
||
|
|
|
||
|
|
addr := fmt.Sprintf("127.0.0.1:%d", controlPort)
|
||
|
|
srv := &http.Server{
|
||
|
|
Addr: addr,
|
||
|
|
Handler: mux,
|
||
|
|
}
|
||
|
|
|
||
|
|
fmt.Printf("[control] http://%s\n", addr)
|
||
|
|
|
||
|
|
return srv
|
||
|
|
}
|