mirror of
https://gitcode.com/JianFeeeee/HomeAgent.git
synced 2026-09-21 17:38:10 +00:00
feat: add API key and login authentication to WebUI
- protect API routes with API key or authenticated session - add /login page and /api/v1/login, /api/v1/logout endpoints - gate dashboard behind session cookie - auto-bootstrap webui username/password/api_key if unset - add logout action to dashboard and cookie-aware API client
This commit is contained in:
@ -98,7 +98,7 @@ code{font-family:monospace;font-size:12px;color:#a5b4fc}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<nav><h1>HomeAgent</h1><a class="active" onclick="switchTab('overview')">概览</a><a onclick="switchTab('chat')">对话</a><a onclick="switchTab('plugins')">插件</a><a onclick="switchTab('memory')">记忆</a><a onclick="switchTab('knowledge')">知识</a><a onclick="switchTab('settings')">设置</a><a onclick="switchTab('kernel')">内核</a></nav>
|
||||
<nav><h1>HomeAgent</h1><a class="active" onclick="switchTab('overview')">概览</a><a onclick="switchTab('chat')">对话</a><a onclick="switchTab('plugins')">插件</a><a onclick="switchTab('memory')">记忆</a><a onclick="switchTab('knowledge')">知识</a><a onclick="switchTab('settings')">设置</a><a onclick="switchTab('kernel')">内核</a><div style="margin-left:auto"><button class="btn btn-ghost btn-sm" onclick="logout()">退出登录</button></div></nav>
|
||||
<div class="container" id="app">
|
||||
<div id="tab-overview" class="tab-content active"></div>
|
||||
<div id="tab-chat" class="tab-content"></div>
|
||||
@ -111,7 +111,7 @@ code{font-family:monospace;font-size:12px;color:#a5b4fc}
|
||||
<div id="toast" class="toast"></div>
|
||||
<script>
|
||||
let state={status:{},kernel:null,settings:{},meta:{},settingsPlugins:['core'],selectedSection:'core',messages:[],chatLoading:false,healthResult:null};
|
||||
async function api(p,o){let opts={headers:{'Content-Type':'application/json',...o?.headers},...o};let r=await fetch('/api/v1'+p,opts);if(opts.raw)return r;let ct=r.headers.get('content-type')||'';if(ct.includes('json'))return r.json();return r.text()}
|
||||
async function api(p,o){let opts={credentials:'include',headers:{'Content-Type':'application/json',...o?.headers},...o};let r=await fetch('/api/v1'+p,opts);if(r.status===401){location.href='/login';throw new Error('unauthorized')}if(opts.raw)return r;let ct=r.headers.get('content-type')||'';if(ct.includes('json'))return r.json();return r.text()}
|
||||
function switchTab(n){document.querySelectorAll('.tab-content').forEach(e=>e.classList.remove('active'));let el=document.getElementById('tab-'+n);if(el)el.classList.add('active');document.querySelectorAll('nav a').forEach(e=>e.classList.remove('active'));document.querySelector('nav a[onclick*="\'+n+\'"]')||document.querySelector(`nav a[onclick*="${n}"]`)?.classList.add('active');renderAll()}
|
||||
function toast(m,isError){let t=document.getElementById('toast');t.textContent=m;t.className='toast'+(isError?' error':'');t.style.display='block';setTimeout(()=>t.style.display='none',3000)}
|
||||
async function renderAll(){try{let s=await api('/status');state.status=s}catch(e){}try{state.kernel=await api('/kernel')}catch(e){}try{let s=await api('/settings');state.settings=s.settings||{};state.meta=s.meta||{};state.settingsPlugins=s.plugins||['core']}catch(e){}try{state.installedPlugins=await api('/plugins')}catch(e){}renderOverview();renderChat();renderPlugins();renderMemory();renderKnowledge();renderKernel()}
|
||||
@ -166,6 +166,7 @@ function renderConfigDisabled(){document.getElementById('tab-settings').innerHTM
|
||||
function renderKernel(){let k=state.kernel;if(!k){document.getElementById('tab-kernel').innerHTML='<div class="card"><div class="loading"></div><p style="color:#64748b">加载中...</p></div>';return}let html='<div class="card"><h2>内核状态快照</h2><pre>'+escHtml(JSON.stringify(k,null,2))+'</pre></div>';html+='<div class="card"><h2>通道</h2>';if(k.channels&&k.channels.length>0){html+='<table><tr><th>名称</th><th>类型</th><th>状态</th></tr>';k.channels.forEach(ch=>{html+='<tr><td>'+escHtml(ch.name)+'</td><td>'+escHtml(ch.type)+'</td><td><span class="status-dot '+(ch.ready?'dot-green':'dot-red')+'"></span>'+(ch.ready?'就绪':'离线')+'</td></tr>'});html+='</table>'}else{html+='<p style="color:#94a3b8">无通道</p>'}html+='</div>';if(k.tracker?.available){html+='<div class="card"><h2>变更追踪</h2><div class="kv-row"><span class="key">状态</span><span class="val"><span class="badge badge-green">活跃</span></span></div><div class="kv-row"><span class="key">目录</span><span class="val">'+escHtml(k.tracker.dir||'-')+'</span></div></div>'}document.getElementById('tab-kernel').innerHTML=html}
|
||||
|
||||
// === Settings Override ===
|
||||
async function logout(){try{await fetch('/api/v1/logout',{method:'POST',credentials:'include'});}catch(e){}location.href='/login'}
|
||||
renderConfigDisabled();
|
||||
renderAll();setInterval(renderAll,15000);
|
||||
</script>
|
||||
|
||||
@ -1,7 +1,9 @@
|
||||
package webui
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"embed"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
@ -10,6 +12,7 @@ import (
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
agentCore "gitcode.com/JianFeeeee/HomeAgent/internal/agent/core"
|
||||
@ -32,6 +35,8 @@ var dashboardFS embed.FS
|
||||
|
||||
var dashboardHTML string
|
||||
|
||||
const loginHTML = `<!DOCTYPE html><html lang="zh-CN"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1.0"><title>HomeAgent Login</title><style>body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;background:#0f172a;color:#e2e8f0;display:flex;align-items:center;justify-content:center;min-height:100vh;margin:0}.card{background:#1e293b;border:1px solid #334155;border-radius:12px;padding:28px;width:360px}h1{margin:0 0 16px;font-size:20px;color:#38bdf8}label{display:block;font-size:12px;color:#94a3b8;margin:10px 0 4px}input{width:100%;padding:10px 12px;border-radius:8px;border:1px solid #334155;background:#0f172a;color:#e2e8f0}button{width:100%;margin-top:16px;padding:10px 12px;border:none;border-radius:8px;background:#2563eb;color:#fff;font-weight:600;cursor:pointer}.err{margin-top:12px;color:#fca5a5;font-size:13px}</style></head><body><div class="card"><h1>HomeAgent</h1><form id="login-form"><label>用户名</label><input id="username" autocomplete="username"><label>密码</label><input id="password" type="password" autocomplete="current-password"><button type="submit">登录</button><div id="err" class="err"></div></form></div><script>document.getElementById('login-form').addEventListener('submit',async(e)=>{e.preventDefault();const username=document.getElementById('username').value;const password=document.getElementById('password').value;const err=document.getElementById('err');err.textContent='';const r=await fetch('/api/v1/login',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({username,password})});if(r.ok){location.href='/';return}let data={};try{data=await r.json()}catch(_){}err.textContent=data.error||'登录失败'})</script></body></html>`
|
||||
|
||||
func init() {
|
||||
data, err := dashboardFS.ReadFile("dashboard.html")
|
||||
if err == nil {
|
||||
@ -40,21 +45,23 @@ func init() {
|
||||
}
|
||||
|
||||
type Handler struct {
|
||||
supervisor *supervisor.Daemon
|
||||
memory *memory.GraphDB
|
||||
indexer *memory.Indexer
|
||||
skills *skill.Manager
|
||||
lua *luaVM.VM
|
||||
config *types.Config
|
||||
startTime time.Time
|
||||
iom *agentIO.IOManager
|
||||
textMem *text.Memory
|
||||
knowledge *knowledge.Store
|
||||
tracker *tracker.Tracker
|
||||
cfgReg *internalConfig.ConfigRegistry
|
||||
pluginReg *plugin.Registry
|
||||
eventBus *events.Bus
|
||||
supervisor *supervisor.Daemon
|
||||
memory *memory.GraphDB
|
||||
indexer *memory.Indexer
|
||||
skills *skill.Manager
|
||||
lua *luaVM.VM
|
||||
config *types.Config
|
||||
startTime time.Time
|
||||
iom *agentIO.IOManager
|
||||
textMem *text.Memory
|
||||
knowledge *knowledge.Store
|
||||
tracker *tracker.Tracker
|
||||
cfgReg *internalConfig.ConfigRegistry
|
||||
pluginReg *plugin.Registry
|
||||
eventBus *events.Bus
|
||||
statusProvider agentCore.StatusProvider
|
||||
sessionMu sync.Mutex
|
||||
sessions map[string]time.Time
|
||||
}
|
||||
|
||||
func NewHandler(sup *supervisor.Daemon, mem *memory.GraphDB, sk *skill.Manager, lua *luaVM.VM, cfg *types.Config, iom *agentIO.IOManager, tm *text.Memory, ks *knowledge.Store, tr *tracker.Tracker, cr *internalConfig.ConfigRegistry, pr *plugin.Registry, evBus *events.Bus, sp agentCore.StatusProvider) *Handler {
|
||||
@ -78,36 +85,199 @@ func NewHandler(sup *supervisor.Daemon, mem *memory.GraphDB, sk *skill.Manager,
|
||||
pluginReg: pr,
|
||||
eventBus: evBus,
|
||||
statusProvider: sp,
|
||||
sessions: make(map[string]time.Time),
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handler) getWebUIConfig() (apiKey, username, password string, ttl time.Duration) {
|
||||
ttl = 24 * time.Hour
|
||||
if h.cfgReg == nil {
|
||||
return
|
||||
}
|
||||
ps := h.cfgReg.PluginConfig("webui")
|
||||
if v, _ := ps.Get("api_key"); v != nil {
|
||||
apiKey, _ = v.(string)
|
||||
}
|
||||
if v, _ := ps.Get("username"); v != nil {
|
||||
username, _ = v.(string)
|
||||
}
|
||||
if v, _ := ps.Get("password"); v != nil {
|
||||
password, _ = v.(string)
|
||||
}
|
||||
if v, _ := ps.Get("session_ttl_hours"); v != nil {
|
||||
switch n := v.(type) {
|
||||
case float64:
|
||||
if n > 0 { ttl = time.Duration(n) * time.Hour }
|
||||
case string:
|
||||
if i, err := strconv.Atoi(n); err == nil && i > 0 { ttl = time.Duration(i) * time.Hour }
|
||||
}
|
||||
}
|
||||
if username == "" { username = "admin" }
|
||||
return
|
||||
}
|
||||
|
||||
func (h *Handler) createSession() (string, time.Time, error) {
|
||||
buf := make([]byte, 32)
|
||||
if _, err := rand.Read(buf); err != nil {
|
||||
return "", time.Time{}, err
|
||||
}
|
||||
_, _, _, ttl := h.getWebUIConfig()
|
||||
expires := time.Now().Add(ttl)
|
||||
token := hex.EncodeToString(buf)
|
||||
h.sessionMu.Lock()
|
||||
h.sessions[token] = expires
|
||||
h.sessionMu.Unlock()
|
||||
return token, expires, nil
|
||||
}
|
||||
|
||||
func (h *Handler) validSession(r *http.Request) bool {
|
||||
cookie, err := r.Cookie("homeagent_session")
|
||||
if err != nil || cookie.Value == "" {
|
||||
return false
|
||||
}
|
||||
h.sessionMu.Lock()
|
||||
defer h.sessionMu.Unlock()
|
||||
expires, ok := h.sessions[cookie.Value]
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
if time.Now().After(expires) {
|
||||
delete(h.sessions, cookie.Value)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (h *Handler) validAPIKey(r *http.Request) bool {
|
||||
apiKey, _, _, _ := h.getWebUIConfig()
|
||||
if apiKey == "" {
|
||||
return false
|
||||
}
|
||||
got := strings.TrimSpace(r.Header.Get("X-API-Key"))
|
||||
if got == "" {
|
||||
auth := strings.TrimSpace(r.Header.Get("Authorization"))
|
||||
if strings.HasPrefix(auth, "Bearer ") {
|
||||
got = strings.TrimSpace(strings.TrimPrefix(auth, "Bearer "))
|
||||
}
|
||||
}
|
||||
return got != "" && got == apiKey
|
||||
}
|
||||
|
||||
func (h *Handler) requireAPI(fn http.HandlerFunc) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
apiKey, _, _, _ := h.getWebUIConfig()
|
||||
if apiKey == "" && !h.validSession(r) {
|
||||
writeJSON(w, http.StatusServiceUnavailable, map[string]string{"error": "webui api_key not configured"})
|
||||
return
|
||||
}
|
||||
if h.validAPIKey(r) || h.validSession(r) {
|
||||
fn(w, r)
|
||||
return
|
||||
}
|
||||
writeJSON(w, http.StatusUnauthorized, map[string]string{"error": "unauthorized"})
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handler) requireWeb(fn http.HandlerFunc) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
_, username, password, _ := h.getWebUIConfig()
|
||||
if username == "" || password == "" {
|
||||
http.Error(w, "webui username/password not configured", http.StatusServiceUnavailable)
|
||||
return
|
||||
}
|
||||
if h.validSession(r) {
|
||||
fn(w, r)
|
||||
return
|
||||
}
|
||||
http.Redirect(w, r, "/login", http.StatusFound)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handler) RegisterRoutes(mux *http.ServeMux) {
|
||||
mux.HandleFunc("/api/v1/status", h.handleStatus)
|
||||
mux.HandleFunc("/api/v1/agents", h.handleAgents)
|
||||
mux.HandleFunc("/api/v1/agents/", h.handleAgentByID)
|
||||
mux.HandleFunc("/api/v1/skills", h.handleSkills)
|
||||
mux.HandleFunc("/api/v1/memory", h.handleMemory)
|
||||
mux.HandleFunc("/api/v1/memory/", h.handleMemory)
|
||||
mux.HandleFunc("/api/v1/memory/context", h.handleMemoryContext)
|
||||
mux.HandleFunc("/api/v1/memory/tools", h.handleMemoryTools)
|
||||
mux.HandleFunc("/api/v1/memory/text", h.handleTextMemory)
|
||||
mux.HandleFunc("/api/v1/network", h.handleNetwork)
|
||||
mux.HandleFunc("/api/v1/config", h.handleConfig)
|
||||
mux.HandleFunc("/api/v1/settings", h.handleSettings)
|
||||
mux.HandleFunc("/api/v1/settings/", h.handleSettings)
|
||||
mux.HandleFunc("/api/v1/knowledge", h.handleKnowledge)
|
||||
mux.HandleFunc("/api/v1/knowledge/", h.handleKnowledge)
|
||||
mux.HandleFunc("/api/v1/adapters", h.handleAdapters)
|
||||
mux.HandleFunc("/api/v1/adapters/", h.handleAdapterByID)
|
||||
mux.HandleFunc("/api/v1/tracker", h.handleTracker)
|
||||
mux.HandleFunc("/api/v1/tracker/", h.handleTracker)
|
||||
mux.HandleFunc("/api/v1/chat", h.handleChat)
|
||||
mux.HandleFunc("/api/v1/chat/events", h.handleChatEvents)
|
||||
mux.HandleFunc("/api/v1/kernel", h.handleKernel)
|
||||
mux.HandleFunc("/api/v1/plugins", h.handlePlugins)
|
||||
mux.HandleFunc("/api/v1/plugins/", h.handlePluginByID)
|
||||
mux.HandleFunc("/v1/chat/completions", h.handleOpenAICompletions)
|
||||
mux.HandleFunc("/", h.handleStatic)
|
||||
mux.HandleFunc("/login", h.handleLoginPage)
|
||||
mux.HandleFunc("/api/v1/login", h.handleLogin)
|
||||
mux.HandleFunc("/api/v1/logout", h.handleLogout)
|
||||
mux.HandleFunc("/api/v1/status", h.requireAPI(h.handleStatus))
|
||||
mux.HandleFunc("/api/v1/agents", h.requireAPI(h.handleAgents))
|
||||
mux.HandleFunc("/api/v1/agents/", h.requireAPI(h.handleAgentByID))
|
||||
mux.HandleFunc("/api/v1/skills", h.requireAPI(h.handleSkills))
|
||||
mux.HandleFunc("/api/v1/memory", h.requireAPI(h.handleMemory))
|
||||
mux.HandleFunc("/api/v1/memory/", h.requireAPI(h.handleMemory))
|
||||
mux.HandleFunc("/api/v1/memory/context", h.requireAPI(h.handleMemoryContext))
|
||||
mux.HandleFunc("/api/v1/memory/tools", h.requireAPI(h.handleMemoryTools))
|
||||
mux.HandleFunc("/api/v1/memory/text", h.requireAPI(h.handleTextMemory))
|
||||
mux.HandleFunc("/api/v1/network", h.requireAPI(h.handleNetwork))
|
||||
mux.HandleFunc("/api/v1/config", h.requireAPI(h.handleConfig))
|
||||
mux.HandleFunc("/api/v1/settings", h.requireAPI(h.handleSettings))
|
||||
mux.HandleFunc("/api/v1/settings/", h.requireAPI(h.handleSettings))
|
||||
mux.HandleFunc("/api/v1/knowledge", h.requireAPI(h.handleKnowledge))
|
||||
mux.HandleFunc("/api/v1/knowledge/", h.requireAPI(h.handleKnowledge))
|
||||
mux.HandleFunc("/api/v1/adapters", h.requireAPI(h.handleAdapters))
|
||||
mux.HandleFunc("/api/v1/adapters/", h.requireAPI(h.handleAdapterByID))
|
||||
mux.HandleFunc("/api/v1/tracker", h.requireAPI(h.handleTracker))
|
||||
mux.HandleFunc("/api/v1/tracker/", h.requireAPI(h.handleTracker))
|
||||
mux.HandleFunc("/api/v1/chat", h.requireAPI(h.handleChat))
|
||||
mux.HandleFunc("/api/v1/chat/events", h.requireAPI(h.handleChatEvents))
|
||||
mux.HandleFunc("/api/v1/kernel", h.requireAPI(h.handleKernel))
|
||||
mux.HandleFunc("/api/v1/plugins", h.requireAPI(h.handlePlugins))
|
||||
mux.HandleFunc("/api/v1/plugins/", h.requireAPI(h.handlePluginByID))
|
||||
mux.HandleFunc("/v1/chat/completions", h.requireAPI(h.handleOpenAICompletions))
|
||||
mux.HandleFunc("/", h.requireWeb(h.handleStatic))
|
||||
}
|
||||
|
||||
func (h *Handler) handleLoginPage(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
if h.validSession(r) {
|
||||
http.Redirect(w, r, "/", http.StatusFound)
|
||||
return
|
||||
}
|
||||
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||
_, _ = w.Write([]byte(loginHTML))
|
||||
}
|
||||
|
||||
func (h *Handler) handleLogin(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPost {
|
||||
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
_, username, password, _ := h.getWebUIConfig()
|
||||
if username == "" || password == "" {
|
||||
writeJSON(w, http.StatusServiceUnavailable, map[string]string{"error": "webui username/password not configured"})
|
||||
return
|
||||
}
|
||||
var body struct { Username string `json:"username"`; Password string `json:"password"` }
|
||||
if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
|
||||
writeJSON(w, http.StatusBadRequest, map[string]string{"error": "invalid request"})
|
||||
return
|
||||
}
|
||||
if body.Username != username || body.Password != password {
|
||||
writeJSON(w, http.StatusUnauthorized, map[string]string{"error": "用户名或密码错误"})
|
||||
return
|
||||
}
|
||||
token, expires, err := h.createSession()
|
||||
if err != nil {
|
||||
writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
http.SetCookie(w, &http.Cookie{Name: "homeagent_session", Value: token, Path: "/", HttpOnly: true, SameSite: http.SameSiteLaxMode, Expires: expires})
|
||||
writeJSON(w, http.StatusOK, map[string]string{"status": "ok"})
|
||||
}
|
||||
|
||||
func (h *Handler) handleLogout(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPost {
|
||||
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
if cookie, err := r.Cookie("homeagent_session"); err == nil {
|
||||
h.sessionMu.Lock()
|
||||
delete(h.sessions, cookie.Value)
|
||||
h.sessionMu.Unlock()
|
||||
http.SetCookie(w, &http.Cookie{Name: "homeagent_session", Value: "", Path: "/", Expires: time.Unix(0, 0), MaxAge: -1, HttpOnly: true, SameSite: http.SameSiteLaxMode})
|
||||
}
|
||||
writeJSON(w, http.StatusOK, map[string]string{"status": "ok"})
|
||||
}
|
||||
|
||||
func (h *Handler) handleStatus(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
@ -1,6 +1,9 @@
|
||||
package webui
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
|
||||
@ -109,9 +112,45 @@ func New(name, addr string,
|
||||
}
|
||||
}
|
||||
|
||||
func randomSecret(n int) string {
|
||||
buf := make([]byte, n)
|
||||
if _, err := rand.Read(buf); err != nil {
|
||||
return ""
|
||||
}
|
||||
return hex.EncodeToString(buf)
|
||||
}
|
||||
|
||||
func (p *Plugin) ensureAuthBootstrap(s *sdk.PluginSDK) {
|
||||
sett := s.Settings()
|
||||
if sett == nil {
|
||||
return
|
||||
}
|
||||
if v, _ := sett.Get("username"); v == nil || fmt.Sprint(v) == "" {
|
||||
_ = sett.Set("username", "admin")
|
||||
}
|
||||
if v, _ := sett.Get("password"); v == nil || fmt.Sprint(v) == "" {
|
||||
pw := randomSecret(12)
|
||||
_ = sett.Set("password", pw)
|
||||
log.Printf("[webui] bootstrap password generated for user admin: %s", pw)
|
||||
}
|
||||
if v, _ := sett.Get("api_key"); v == nil || fmt.Sprint(v) == "" {
|
||||
key := randomSecret(16)
|
||||
_ = sett.Set("api_key", key)
|
||||
log.Printf("[webui] bootstrap api_key generated: %s", key)
|
||||
}
|
||||
if v, _ := sett.Get("session_ttl_hours"); v == nil || fmt.Sprint(v) == "" {
|
||||
_ = sett.Set("session_ttl_hours", "24")
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Plugin) Name() string { return p.name }
|
||||
|
||||
func (p *Plugin) Start(s *sdk.PluginSDK) error {
|
||||
s.Settings().RegisterDef(sdk.ConfigDef{Key: "api_key", Default: "", Type: "password", DisplayName: "API 密钥", Description: "访问 API 时需要的密钥", Category: "webui"})
|
||||
s.Settings().RegisterDef(sdk.ConfigDef{Key: "username", Default: "admin", Type: "string", DisplayName: "登录用户名", Description: "Web 控制台登录用户名", Category: "webui"})
|
||||
s.Settings().RegisterDef(sdk.ConfigDef{Key: "password", Default: "", Type: "password", DisplayName: "Web 控制台登录密码", Description: "Web 控制台登录密码", Category: "webui"})
|
||||
s.Settings().RegisterDef(sdk.ConfigDef{Key: "session_ttl_hours", Default: "24", Type: "int", DisplayName: "会话时长(小时)", Description: "登录 cookie 有效时长", Category: "webui"})
|
||||
p.ensureAuthBootstrap(s)
|
||||
h := NewHandler(p.sup, p.mem, p.sk, p.lua, p.cfg, p.iom, p.tm, p.ks, p.tr, p.cr, p.pr, p.evBus, p.statusProvider)
|
||||
p.handler = h
|
||||
h.RegisterRoutes(p.mux)
|
||||
|
||||
Reference in New Issue
Block a user