Add 9Router control plugin (#291)

* Add 9Router panel plugin

This commit introduces the initial version of the 9Router panel plugin.
It provides a Noctalia panel to manage 9Router model combos directly
from the bar, without requiring the web dashboard.

Key features:
- List, create, rename, and delete model combos.
- Reorder models within a combo via drag-and-drop.
- Change combo routing kind (fallback, round-robin, fusion).
- Search and filter combos.
- Supports 9Router dashboard authentication via CLI token or password.

Includes a bar widget showing connection status, a full UI panel,
and a background service to interact with the 9Router REST API.
Translations for English and Vietnamese are included.

* Rename 9Router Panel to 9Router Control

Update plugin IDs, IPC commands, and internal references.

* Quote shell args in service commands

Add shell_quote() for URLs, bodies, and file paths passed to
noctalia.runAsync. Declare required external commands in plugin.toml
and document them in the README.
This commit is contained in:
weinguyen
2026-08-08 15:13:03 -04:00
committed by GitHub
parent 220bdb0773
commit 2f760f6103
9 changed files with 2032 additions and 0 deletions
+120
View File
@@ -0,0 +1,120 @@
{
"state": {
"tip": {
"online": "9Router: connected",
"offline": "9Router: offline",
"auth_required": "9Router: login required"
},
"combos": "{count} combo(s)"
},
"login": {
"title": "9Router Login",
"hint": "The dashboard requires authentication. Enter your 9Router password to continue.",
"password_placeholder": "Password…",
"submit": "Log in"
},
"list": {
"title": "9Router — Combos",
"refresh": "Refresh",
"new_combo": "New Combo",
"search": "Search combos…",
"empty": "No combos yet. Create one to get started.",
"no_match": "No combos match your search.",
"open": "Open combo",
"delete": "Delete combo",
"delete_confirm": "Confirm delete",
"delete_cancel": "Cancel",
"kind": "Kind: {kind}"
},
"detail": {
"back": "Back to combos",
"models_count": "{count} model(s)",
"refresh": "Refresh",
"delete": "Delete combo",
"delete_confirm": "Delete this combo?",
"delete_yes": "Yes, delete",
"delete_no": "Cancel",
"kind_label": "Kind",
"rename": "Rename",
"rename_placeholder": "New name…",
"rename_save": "Save",
"rename_cancel": "Cancel",
"models_title": "Model Chain",
"no_models": "This combo has no models yet.",
"drag_hint": "Drag a model to reorder",
"add_model": "Add model",
"remove_model": "Remove model",
"move_up": "Move up",
"move_down": "Move down"
},
"add_model": {
"title": "Add Model",
"back": "Back to combo",
"search": "Search models…",
"empty": "No models available.",
"no_match": "No models match your search."
},
"create": {
"title": "New Combo",
"back": "Back",
"name_label": "Name",
"name_placeholder": "e.g. my-fallback",
"models_label": "Models",
"models_hint": "One model per line, in fallback order (e.g. cc/claude-opus-4-7).",
"models_placeholder": "cc/claude-opus-4-7\nglm/glm-5.1",
"kind_label": "Kind",
"cancel": "Cancel",
"submit": "Create"
},
"kind": {
"none": "None",
"fallback": "Fallback",
"round_robin": "Round-robin",
"fusion": "Fusion"
},
"error": {
"dismiss": "Dismiss",
"connection_failed": "Could not connect to 9Router",
"http_status": "HTTP {status}",
"bad_response": "Unexpected response from 9Router",
"auth_required": "Authentication required",
"login_failed": "Login failed",
"need_password": "Please enter your password.",
"reorder_failed": "Failed to reorder models",
"add_model_failed": "Failed to add model",
"remove_model_failed": "Failed to remove model",
"invalid_name": "Name can only contain letters, numbers, -, _ and .",
"create_failed": "Failed to create combo",
"need_name_models": "A name and at least one model are required.",
"delete_failed": "Failed to delete combo",
"rename_failed": "Failed to rename combo",
"kind_failed": "Failed to update kind"
},
"settings": {
"server_host": {
"label": "Server Host",
"description": "Hostname of the 9Router dashboard. Default: 127.0.0.1."
},
"server_port": {
"label": "Server Port",
"description": "Port of the 9Router dashboard. Default: 20128."
},
"data_dir": {
"label": "Data Directory",
"description": "9Router data directory (used to compute the CLI token when the dashboard requires login). Default: ~/.9router."
},
"language": {
"label": "Language",
"description": "Language for this plugin's UI. 'Auto' follows the Noctalia shell language.",
"options": {
"auto": "Auto (follow shell)",
"en": "English",
"vi": "Tiếng Việt"
}
},
"debug_logging": {
"label": "Debug Logging",
"description": "Print debug messages to the Noctalia log."
}
}
}
+120
View File
@@ -0,0 +1,120 @@
{
"state": {
"tip": {
"online": "9Router: đã kết nối",
"offline": "9Router: ngoại tuyến",
"auth_required": "9Router: cần đăng nhập"
},
"combos": "{count} combo"
},
"login": {
"title": "Đăng nhập 9Router",
"hint": "Dashboard yêu cầu xác thực. Nhập mật khẩu 9Router của bạn để tiếp tục.",
"password_placeholder": "Mật khẩu…",
"submit": "Đăng nhập"
},
"list": {
"title": "9Router — Combos",
"refresh": "Làm mới",
"new_combo": "Combo mới",
"search": "Tìm combo…",
"empty": "Chưa có combo nào. Tạo một combo để bắt đầu.",
"no_match": "Không có combo nào khớp tìm kiếm.",
"open": "Mở combo",
"delete": "Xóa combo",
"delete_confirm": "Xác nhận xóa",
"delete_cancel": "Hủy",
"kind": "Loại: {kind}"
},
"detail": {
"back": "Quay lại danh sách",
"models_count": "{count} model",
"refresh": "Làm mới",
"delete": "Xóa combo",
"delete_confirm": "Xóa combo này?",
"delete_yes": "Có, xóa",
"delete_no": "Hủy",
"kind_label": "Loại",
"rename": "Đổi tên",
"rename_placeholder": "Tên mới…",
"rename_save": "Lưu",
"rename_cancel": "Hủy",
"models_title": "Chuỗi Model",
"no_models": "Combo này chưa có model nào.",
"drag_hint": "Kéo model để sắp xếp lại",
"add_model": "Thêm model",
"remove_model": "Xóa model",
"move_up": "Di chuyển lên",
"move_down": "Di chuyển xuống"
},
"add_model": {
"title": "Thêm Model",
"back": "Quay lại combo",
"search": "Tìm model…",
"empty": "Không có model nào.",
"no_match": "Không có model khớp tìm kiếm."
},
"create": {
"title": "Combo mới",
"back": "Quay lại",
"name_label": "Tên",
"name_placeholder": "vd: my-fallback",
"models_label": "Models",
"models_hint": "Mỗi model một dòng, theo thứ tự fallback (vd: cc/claude-opus-4-7).",
"models_placeholder": "cc/claude-opus-4-7\nglm/glm-5.1",
"kind_label": "Loại",
"cancel": "Hủy",
"submit": "Tạo"
},
"kind": {
"none": "Không",
"fallback": "Fallback",
"round_robin": "Round-robin",
"fusion": "Fusion"
},
"error": {
"dismiss": "Đóng",
"connection_failed": "Không kết nối được 9Router",
"http_status": "HTTP {status}",
"bad_response": "Phản hồi không mong đợi từ 9Router",
"auth_required": "Cần xác thực",
"login_failed": "Đăng nhập thất bại",
"need_password": "Vui lòng nhập mật khẩu.",
"reorder_failed": "Không thể sắp xếp lại model",
"add_model_failed": "Không thể thêm model",
"remove_model_failed": "Không thể xóa model",
"invalid_name": "Tên chỉ được chứa chữ, số, -, _ và .",
"create_failed": "Không thể tạo combo",
"need_name_models": "Cần tên và ít nhất một model.",
"delete_failed": "Không xóa được combo",
"rename_failed": "Không đổi tên được combo",
"kind_failed": "Không cập nhật được loại"
},
"settings": {
"server_host": {
"label": "Máy chủ",
"description": "Hostname của dashboard 9Router. Mặc định: 127.0.0.1."
},
"server_port": {
"label": "Cổng",
"description": "Cổng của dashboard 9Router. Mặc định: 20128."
},
"data_dir": {
"label": "Thư mục dữ liệu",
"description": "Thư mục dữ liệu 9Router (dùng để tính CLI token khi dashboard yêu cầu đăng nhập). Mặc định: ~/.9router."
},
"language": {
"label": "Ngôn ngữ",
"description": "Ngôn ngữ giao diện của plugin. 'Tự động' theo ngôn ngữ shell Noctalia.",
"options": {
"auto": "Tự động (theo shell)",
"en": "English",
"vi": "Tiếng Việt"
}
},
"debug_logging": {
"label": "Ghi log gỡ lỗi",
"description": "In thông điệp gỡ lỗi vào log Noctalia."
}
}
}