Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "nonebot-plugin-mimo-console"
version = "0.1.2"
version = "0.1.3"
description = "A glass-style WebUI management console for NoneBot2"
readme = "README.md"
requires-python = ">=3.10"
Expand Down Expand Up @@ -54,7 +54,7 @@ requires = ["uv_build>=0.10.0,<0.12.0"]
build-backend = "uv_build"

[tool.bumpversion]
current_version = "0.1.2"
current_version = "0.1.3"
commit = true
message = "chore: 发布 v{new_version}"
tag = true
Expand Down
74 changes: 71 additions & 3 deletions src/nonebot_plugin_mimo_console/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from collections import defaultdict, deque
from typing import Annotated, Any, Literal

import httpx
from fastapi import APIRouter, Depends, HTTPException, Query, Request, UploadFile, status
from fastapi.responses import FileResponse, HTMLResponse, RedirectResponse
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
Expand All @@ -19,7 +20,14 @@
from .security import AuthError, Session
from .state import ConsoleState
from .store import StoreError, _clean_output, build_self_update_command
from .version import PACKAGE_GIT_URL, PACKAGE_NAME, get_installed_version
from .version import (
GITHUB_PROXY_PRESETS,
PACKAGE_NAME,
get_installed_version,
normalize_github_proxy,
resolve_git_url,
resolve_version_url,
)


class SetupBody(BaseModel):
Expand All @@ -45,6 +53,10 @@ class PluginActionBody(BaseModel):
action: Literal["install", "update", "uninstall"]


class GithubProxyBody(BaseModel):
proxy: str = Field(default="", max_length=512)


class AttemptLimiter:
def __init__(self, limit: int = 10, window: int = 300) -> None:
self.limit = limit
Expand Down Expand Up @@ -312,9 +324,65 @@ async def system_version(
session: Annotated[Session, Depends(require_session)],
force: bool = Query(default=False),
) -> dict[str, Any]:
await state.release_cache.fetch(force=force)
await state.release_cache.fetch(force=force, proxy=state.config.mimo_console_github_proxy)
return state.release_cache.snapshot(get_installed_version())

@router.get("/api/system/github-proxy")
async def get_github_proxy(
session: Annotated[Session, Depends(require_session)],
) -> dict[str, Any]:
return {
"proxy": state.config.mimo_console_github_proxy,
"presets": list(GITHUB_PROXY_PRESETS),
}

@router.put("/api/system/github-proxy")
async def set_github_proxy(
body: GithubProxyBody,
session: Annotated[Session, Depends(require_session)],
) -> dict[str, Any]:
try:
proxy = normalize_github_proxy(body.proxy)
except ValueError as exc:
raise HTTPException(status_code=400, detail=str(exc)) from exc
environment = str(getattr(get_driver().config, "environment", "prod"))
path = locate_env_file(state.config.project_root(), environment)
try:
await asyncio.to_thread(
update_env, path, {"MIMO_CONSOLE_GITHUB_PROXY": proxy}, state.backup_dir
)
except (OSError, ValueError) as exc:
raise HTTPException(status_code=400, detail=str(exc)) from exc
# 热更新内存配置,无需重启即可生效
state.config.mimo_console_github_proxy = proxy
logger.info(f"[Mimo Console] GitHub 加速已设置为:{proxy or '直连'}")
return {"ok": True, "proxy": proxy}

@router.post("/api/system/github-proxy/test")
async def test_github_proxy(
body: GithubProxyBody,
session: Annotated[Session, Depends(require_session)],
) -> dict[str, Any]:
try:
proxy = normalize_github_proxy(body.proxy)
except ValueError as exc:
raise HTTPException(status_code=400, detail=str(exc)) from exc
url = resolve_version_url(proxy)
started = time.perf_counter()
try:
async with httpx.AsyncClient(timeout=10, follow_redirects=True) as client:
response = await client.get(url, headers={"User-Agent": PACKAGE_NAME})
latency = int((time.perf_counter() - started) * 1000)
except (httpx.HTTPError, OSError) as exc:
return {"ok": False, "latency_ms": None, "detail": f"连接失败:{exc}"}
if response.status_code != 200:
return {
"ok": False,
"latency_ms": latency,
"detail": f"加速地址返回 HTTP {response.status_code}",
}
return {"ok": True, "latency_ms": latency, "detail": ""}

@router.post("/api/system/update")
async def system_update(
session: Annotated[Session, Depends(require_session)],
Expand All @@ -324,7 +392,7 @@ async def system_update(
command = build_self_update_command(
state.config.project_root(),
PACKAGE_NAME,
PACKAGE_GIT_URL,
resolve_git_url(state.config.mimo_console_github_proxy),
)
env = os.environ.copy()
env.update({"NO_COLOR": "1", "PYTHONUTF8": "1", "PYTHONIOENCODING": "utf-8"})
Expand Down
8 changes: 8 additions & 0 deletions src/nonebot_plugin_mimo_console/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

from pydantic import BaseModel, Field, field_validator

from .version import normalize_github_proxy


class ConsoleConfig(BaseModel):
"""NoneBot 环境变量中的控制台配置。"""
Expand All @@ -16,12 +18,18 @@ class ConsoleConfig(BaseModel):
mimo_console_store_cache_seconds: int = Field(default=600, ge=60, le=86400)
mimo_console_package_timeout: int = Field(default=300, ge=60, le=1800)
mimo_console_background_url: str | None = None
mimo_console_github_proxy: str = ""

@field_validator("mimo_console_path")
@classmethod
def normalize_path(cls, value: str) -> str:
path = "/" + value.strip().strip("/")
return path if path != "/" else "/mimo-console"

@field_validator("mimo_console_github_proxy")
@classmethod
def normalize_proxy(cls, value: str) -> str:
return normalize_github_proxy(value)

def project_root(self) -> Path:
return (self.mimo_console_project_root or Path.cwd()).expanduser().resolve()
105 changes: 104 additions & 1 deletion src/nonebot_plugin_mimo_console/static/assets/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ const state = {
background: { source: "default", url: "" },
theme: null,
version: null,
proxy: { proxy: "", presets: [] },
};

const $ = (selector, context = document) => context.querySelector(selector);
Expand Down Expand Up @@ -186,6 +187,8 @@ function bindEvents() {
$("#theme-reset").addEventListener("click", resetTheme);
$("#restart-button").addEventListener("click", restartNonebot);
$("#check-update-btn").addEventListener("click", checkUpdate);
$("#proxy-save").addEventListener("click", saveProxySettings);
$("#proxy-test-btn").addEventListener("click", testProxySettings);
$$(".view-toggle button").forEach((button) => button.addEventListener("click", () => {
$$(".view-toggle button").forEach((item) => item.classList.remove("active"));
button.classList.add("active");
Expand Down Expand Up @@ -242,7 +245,7 @@ function enterApp(username) {
clearTimers();
state.timers.push(setInterval(() => loadDashboard(false), 5000));
state.timers.push(setInterval(loadLogs, 2000));
Promise.allSettled([loadDashboard(), loadPlugins(), loadLogs(), loadBackground(), loadVersion()]);
Promise.allSettled([loadDashboard(), loadPlugins(), loadLogs(), loadBackground(), loadVersion(), loadProxySettings()]);
}

async function signOut(requestLogout) {
Expand Down Expand Up @@ -1146,6 +1149,106 @@ function renderVersion(data) {
$("#version-current").textContent = data.current ? `v${data.current}` : "--";
}

const PROXY_CUSTOM_VALUE = "__custom__";

function presetLabel(url) {
return url.includes("cnb.cool") ? "CNB 镜像仓库" : "GitHub 加速代理";
}

async function loadProxySettings() {
try {
state.proxy = await api("/system/github-proxy");
renderProxySettings();
} catch (_) { /* 读取失败不影响使用 */ }
}

function renderProxySettings() {
const container = $("#proxy-modes");
if (!container) return;
const presets = state.proxy.presets || [];
const current = state.proxy.proxy || "";
const options = [
{ value: "", title: "不使用 GitHub 加速", desc: "直连 GitHub" },
...presets.map((url) => ({ value: url, title: url, desc: presetLabel(url) })),
{ value: PROXY_CUSTOM_VALUE, title: "自定义", desc: "填入自定义加速前缀或镜像仓库地址" },
];
container.innerHTML = options
.map(
(opt) => `<label class="bg-mode">
<input type="radio" name="gh-proxy" value="${escapeHtml(opt.value)}"${opt.value === "" ? " data-proxy-off" : ""}>
<span class="bg-mode-text"><strong>${escapeHtml(opt.title)}</strong><small>${escapeHtml(opt.desc)}</small></span>
</label>`,
)
.join("");
const mode = current === "" ? "" : presets.includes(current) ? current : PROXY_CUSTOM_VALUE;
$$('input[name="gh-proxy"]', container).forEach((input) => {
input.checked = input.value === mode;
});
$("#proxy-custom-field").classList.toggle("hidden", mode !== PROXY_CUSTOM_VALUE);
if (mode === PROXY_CUSTOM_VALUE) $("#proxy-custom-input").value = current;
container.onchange = () => {
const checked = $('input[name="gh-proxy"]:checked', container);
$("#proxy-custom-field").classList.toggle(
"hidden",
!checked || checked.value !== PROXY_CUSTOM_VALUE,
);
};
updateProxyStatus();
}

function selectedProxyValue() {
const checked = $('input[name="gh-proxy"]:checked');
if (!checked) return "";
if (checked.value === PROXY_CUSTOM_VALUE) return ($("#proxy-custom-input").value || "").trim();
return checked.value;
}

function updateProxyStatus() {
const current = state.proxy.proxy || "";
$("#proxy-status").textContent = current ? `当前:${current}` : "当前:直连";
}

async function saveProxySettings() {
const button = $("#proxy-save");
button.disabled = true;
try {
const data = await api("/system/github-proxy", {
method: "PUT",
body: JSON.stringify({ proxy: selectedProxyValue() }),
});
state.proxy.proxy = data.proxy || "";
renderProxySettings();
toast("GitHub 加速设置已保存,立即生效");
} catch (error) {
toast(error.message, "error");
} finally {
button.disabled = false;
}
}

async function testProxySettings() {
const button = $("#proxy-test-btn");
const original = button.textContent;
button.disabled = true;
button.textContent = "测试中…";
try {
const data = await api("/system/github-proxy/test", {
method: "POST",
body: JSON.stringify({ proxy: selectedProxyValue() }),
});
if (data.ok) {
toast(`连通正常(${data.latency_ms}ms)`);
} else {
toast(data.detail || "连接失败", "error");
}
} catch (error) {
toast(error.message, "error");
} finally {
button.disabled = false;
button.textContent = original;
}
}

async function checkUpdate() {
const button = $("#check-update-btn");
const original = button.textContent;
Expand Down
8 changes: 8 additions & 0 deletions src/nonebot_plugin_mimo_console/static/assets/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -770,6 +770,13 @@ a { color: inherit; text-decoration: none; }
.bot-chip strong { display: block; font-size: 12px; }
.bot-chip span, .empty-mini { color: var(--muted); font-size: 11px; }
.recent-panel { margin-top: 12px; }
.proxy-panel { margin-top: 12px; padding: 22px; }
.proxy-panel .proxy-hint { margin: -8px 0 16px; font-size: 13px; line-height: 1.6; }
.proxy-panel .bg-modes { grid-template-columns: repeat(auto-fill, minmax(230px, 1fr)); }
.proxy-panel .bg-mode-text { min-width: 0; }
.proxy-panel .bg-mode-text strong { overflow-wrap: anywhere; word-break: break-all; line-height: 1.45; }
.proxy-panel .bg-actions { align-items: center; }
.proxy-panel #proxy-status { font-size: 12px; overflow-wrap: anywhere; }
.recent-logs {
overflow: hidden;
border: 1px solid var(--line);
Expand Down Expand Up @@ -1719,6 +1726,7 @@ a { color: inherit; text-decoration: none; }
.metric,
.dash-grid > .panel,
.recent-panel,
.proxy-panel,
.toolbar,
.tabs,
.plugin-card,
Expand Down
25 changes: 22 additions & 3 deletions src/nonebot_plugin_mimo_console/static/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600&family=Noto+Sans+SC:wght@400;500;600;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="./assets/styles.css?v=0.6.1">
<link rel="stylesheet" href="./assets/styles.css?v=0.6.3">
</head>
<body>
<div class="bg-glow glow-a"></div>
Expand Down Expand Up @@ -115,7 +115,7 @@ <h2 id="auth-title">欢迎回来</h2>
</div>
<button id="logout-button" class="icon-btn logout-btn" title="退出登录"></button>
</div>
<div class="version">Mimo Console · v0.1.2</div>
<div class="version">Mimo Console · v0.1.3</div>
</div>
</aside>

Expand Down Expand Up @@ -231,6 +231,25 @@ <h2>最近日志</h2>
<div class="empty-state compact">正在等待日志…</div>
</div>
</article>

<article class="panel proxy-panel">
<div class="panel-head">
<h2>GitHub 加速</h2>
<button class="btn btn-secondary btn-sm" id="proxy-test-btn" type="button">测试代理连通性</button>
</div>
<p class="muted proxy-hint">用于控制台自身的版本检测与在线更新,保存后立即生效。</p>
<div class="bg-modes" id="proxy-modes"></div>
<div id="proxy-custom-field" class="bg-field hidden">
<label class="field">
<span>自定义加速地址</span>
<input id="proxy-custom-input" type="text" autocomplete="off" placeholder="https://gh-proxy.com 或镜像仓库地址">
</label>
</div>
<div class="bg-actions">
<button id="proxy-save" class="btn btn-primary" type="button">保存设置</button>
<span id="proxy-status" class="muted"></span>
</div>
</article>
</section>

<!-- Plugins -->
Expand Down Expand Up @@ -498,6 +517,6 @@ <h2 id="detail-title">插件详情</h2>
</div>

<div id="toast-stack" class="toast-stack" aria-live="polite"></div>
<script src="./assets/app.js?v=0.6.1" defer></script>
<script src="./assets/app.js?v=0.6.2" defer></script>
</body>
</html>
Loading
Loading