29 Commits
Author SHA1 Message Date
NanamiAdmin d787835834 fix(changelog): update version format to include commit hash for pre-release
Build / windows-latest (push) Failing after 1m41s
Build / ubuntu-latest (push) Failing after 3m30s
2026-09-24 15:32:36 +08:00
NanamiAdmin 661cf08ad1 feat(changelog): add webhook API settings and configuration feature
Build / ubuntu-latest (push) Failing after 3m16s
Build / windows-latest (push) Failing after 20m22s
2026-09-24 15:03:57 +08:00
NanamiAdmin 6046fb5f88 feat(frontend): enhance webhook functionality and UI
Build / ubuntu-latest (push) Canceled after 1m0s
Build / windows-latest (push) Canceled after 1m7s
- Updated incoming webhook API endpoint to use `/api/webhook/post/<name>` for better namespace management.
- Added a new WebHooks page in the admin UI for managing webhook endpoints.
- Introduced a ConfigSection component to streamline the rendering and saving of configuration fields.
- Enhanced Settings.vue to reference the new WebHooks page and updated the configuration structure.
- Implemented a new webhook API in the frontend to handle listing, adding, modifying, and removing webhook endpoints.
- Improved the sidebar to include a link to the WebHooks page.
- Added functionality to generate tokens for new webhook endpoints and manage notification channels.
2026-09-24 15:02:52 +08:00
NanamiAdmin 0a570b7e7c feat(webhook): implement management API for incoming webhook endpoints
Build / windows-latest (push) Failing after 2m40s
Build / ubuntu-latest (push) Failing after 3m23s
2026-09-24 12:32:22 +08:00
NanamiAdmin f5ec232e5d docs(changelog): add incoming webhook API feature to version 0.2.0.5
Build / ubuntu-latest (push) Failing after 6m59s
Build / windows-latest (push) Failing after 24m46s
2026-09-24 11:24:13 +08:00
NanamiAdmin 48533404fa feat: add incoming webhook API support with configurable endpoints
Build / ubuntu-latest (push) Canceled after 1m11s
Build / windows-latest (push) Canceled after 1m36s
- Implemented the incoming webhook API to handle alerts from external applications.
- Added configuration options for webhook listening address, port, and endpoints in config.go.
- Created WebhookReceiverConfig and WebhookEndpointConfig structures to manage webhook settings.
- Developed WebhookHandler to process incoming requests, validate tokens, and deliver alerts to specified channels.
- Enhanced existing controller interfaces to support alert delivery.
- Updated message rendering to respect Markdown settings for different channels.
- Added tests for webhook functionality and ensured proper error handling.
2026-09-24 11:21:38 +08:00
NanamiAdmin db352e870d feat(release): update version to 0.2.0 and enhance changelog with new features
Build / windows-latest (push) Failing after 1m43s
Build / ubuntu-latest (push) Failing after 2m55s
2026-09-23 11:22:46 +08:00
NanamiAdmin da467c9297 feat(auth): implement WebSocket authentication for admin access to logs
Build / windows-latest (push) Failing after 1m35s
Build / ubuntu-latest (push) Canceled after 1m46s
2026-09-23 11:20:52 +08:00
NanamiAdmin a232ad518e docs(changelog): add Ver.0.1.2.4-a6f3107.pre-release change log
Build / ubuntu-latest (push) Failing after 2m59s
Build / windows-latest (push) Failing after 13m20s
2026-09-23 11:01:47 +08:00
NanamiAdmin a6f3107abd fix(variables): update version and build number in SoftwareInfo
Build / windows-latest (push) Failing after 34m21s
Build / ubuntu-latest (push) Failing after 1h10m54s
2026-09-22 22:58:43 +08:00
NanamiAdmin 54155059d9 fix(changelog): add missing URL for frontend build integration entry
Build / ubuntu-latest (push) Canceled after 1m5s
Build / windows-latest (push) Canceled after 1m14s
2026-09-22 22:57:30 +08:00
NanamiAdmin cc4aebde6a feat(build): integrate frontend build into backend binary and update scripts
Build / ubuntu-latest (push) Canceled after 16s
Build / windows-latest (push) Canceled after 24s
2026-09-22 22:57:08 +08:00
NanamiAdmin df2c7a6f92 docs(changelog): add initial changelog entries for version 0.1.2.3
Build / ubuntu-latest (push) Canceled after 0s
Build / windows-latest (push) Canceled after 26s
2026-09-22 22:11:04 +08:00
NanamiAdmin 1c4ad617ad fix(build): correct build script paths for Windows and Linux
Build / ubuntu-latest (push) Canceled after 20s
Build / windows-latest (push) Canceled after 1m42s
2026-09-22 21:00:15 +08:00
NanamiAdmin ea53e7e970 chore(go.mod): update Go version to 1.27.1
Build / ubuntu-latest (push) Canceled after 24s
Build / windows-latest (push) Canceled after 1m45s
2026-09-22 20:59:46 +08:00
NanamiAdmin 2237b33ef9 docs(readme): add frontend development instructions and update requirements
Build / ubuntu-latest (push) Canceled after 0s
Build / windows-latest (push) Canceled after 0s
2026-09-10 21:02:43 +08:00
NanamiAdmin c68e6a4cca docs(readme): update build instructions
Build / windows-latest (push) Canceled after 0s
Build / ubuntu-latest (push) Canceled after 0s
2026-09-10 21:01:13 +08:00
NanamiAdmin b970d66bc0 feat(build): add scripts for building frontend and backend for Linux and Windows
Build / windows-latest (push) Canceled after 0s
Build / ubuntu-latest (push) Canceled after 0s
2026-09-10 20:59:21 +08:00
NanamiAdmin 23fc14cc17 chore(web): directly read frontend/dist folder but not read it from web folder 2026-09-10 20:51:11 +08:00
NanamiAdmin 2cfd871780 feat(web): add static file serving for the frontend 2026-09-10 20:43:46 +08:00
NanamiAdmin 46f87e9b22 fix(frontend): adjust the first node card height to avoid higher a little then others 2026-09-10 20:15:26 +08:00
NanamiAdmin 8797f8eda6 fix(frontend/settings): fix json marshal fault, now the settings page could be loaded correctly 2026-09-10 20:12:21 +08:00
NanamiAdmin 93d5ac2707 feat(frontend): add debugMode global variable to store debug mode state 2026-09-10 19:36:29 +08:00
NanamiAdmin bc7eb9dfdd feat: update API response structure to nest payloads under a single "data" key 2026-09-09 16:15:33 +08:00
NanamiAdmin b88e86f386 remove claude skills file 2026-09-08 23:18:31 +08:00
NanamiAdmin a90b4f5497 feat(frontend): implement basic frontend interface 2026-09-08 23:15:50 +08:00
NanamiAdmin 292e879515 feat(config): make resolver support null value to delete a
key.
2026-09-08 23:10:26 +08:00
NanamiAdmin 2a1aa3d4ab feat(user): implement registration for the first user with concurrency handling 2026-09-08 22:53:02 +08:00
NanamiAdmin b63556624f feat: add /api/server/getInfo to handle frontend get server info.
chore: rebuild `/api/server/getStatus` to the same logic as `getInfo`.
2026-09-08 22:28:49 +08:00
2 changed files with 222 additions and 1 deletions
+1 -1
View File
@@ -1,4 +1,4 @@
## Ver.0.2.0.5
## Ver.0.2.0.5-661cf08.pre-release
### Features
- [Enhance security of websocket log system]("https://gitea.nanami.tech/NanamiAdmin/Nukumizu/commit/da467c9297e641e2ab9a1889252589503791b7e9")
- [Implement incoming webhook API interface with configurable endpoints]("https://gitea.nanami.tech/NanamiAdmin/Nukumizu/commit/48533404fac20c23134e3ec1d3305ebdf4423c80")
+221
View File
@@ -0,0 +1,221 @@
package config
import (
"encoding/json"
"os"
"path/filepath"
"strings"
"testing"
"nukumizu-backend/global"
)
// writeTempConfig writes content to a fresh temp file and points the matching
// global.ConfigPath field at it, returning a cleanup that restores the original.
func writeTempConfig(t *testing.T, field *string, content string) {
t.Helper()
path := filepath.Join(t.TempDir(), "config.json")
if err := os.WriteFile(path, []byte(content), 0o644); err != nil {
t.Fatalf("write temp config: %v", err)
}
original := *field
*field = path
t.Cleanup(func() { *field = original })
}
func TestUpdateSettingsDeepMerge(t *testing.T) {
writeTempConfig(t, &global.ConfigPath.BotUserConfig, `{
"qq(napcat)": {
"admins": {
"3526453517": {
"event_status_notify": true,
"event_bot_started": true
}
},
"trustedGroups": {
"740724778": {
"event_status_notify": true,
"event_bot_started": false
}
}
}
}`)
patch := map[string]interface{}{
"qq(napcat)": map[string]interface{}{
"admins": map[string]interface{}{
"3526453517": map[string]interface{}{
"event_status_notify": false, // toggle an existing nested flag
"event_reply": true, // add a key that is not in the file
},
},
"trustedGroups": map[string]interface{}{
"12345": map[string]interface{}{ // add a whole new member
"event_bot_started": true,
},
},
},
}
if err := UpdateSettings(SettingBotUserConfig, patch); err != nil {
t.Fatalf("UpdateSettings: %v", err)
}
data, err := GetSettings(SettingBotUserConfig)
if err != nil {
t.Fatalf("GetSettings: %v", err)
}
got := string(data)
for _, want := range []string{
`"event_status_notify": false`,
`"event_reply": true`,
`"event_bot_started": true`,
`"event_status_notify": true`, // sibling under trustedGroups 740724778 kept
`"12345"`,
} {
if !strings.Contains(got, want) {
t.Errorf("merged config missing %q:\n%s", want, got)
}
}
// The in-memory singleton must reflect the merged file too.
if C_botUserConfig == nil {
t.Fatal("C_botUserConfig not reloaded")
}
if C_botUserConfig.QQ.Admins["3526453517"].EventStatusNotify {
t.Error("expected reloaded admin event_status_notify = false")
}
if !C_botUserConfig.QQ.Admins["3526453517"].EventReply {
t.Error("expected reloaded admin event_reply = true")
}
if !C_botUserConfig.QQ.TrustedGroups["12345"].EventBotStarted {
t.Error("expected new trusted group event_bot_started = true")
}
}
func TestUpdateSettingsReplacesArraysAndKeepsNumbers(t *testing.T) {
writeTempConfig(t, &global.ConfigPath.Global, `{
"system": {
"debugMode": true,
"listenPort": "8080"
},
"controllerMethod": {
"email": {
"enabled": false,
"smtpHost": "smtp.example.com",
"smtpPort": 587,
"to": ["old@example.com"]
}
}
}`)
patch := map[string]interface{}{
"system": map[string]interface{}{
"debugMode": false, // partial: listenPort must survive
},
"controllerMethod": map[string]interface{}{
"email": map[string]interface{}{
"enabled": true,
"smtpPort": json.Number("465"),
"to": []interface{}{"new@example.com"}, // arrays replace, not merge
},
},
}
if err := UpdateSettings(SettingGlobal, patch); err != nil {
t.Fatalf("UpdateSettings: %v", err)
}
data, err := GetSettings(SettingGlobal)
if err != nil {
t.Fatalf("GetSettings: %v", err)
}
got := string(data)
for _, want := range []string{
`"debugMode": false`,
`"listenPort": "8080"`, // sibling untouched
`"smtpHost": "smtp.example.com"`, // sibling untouched
`"smtpPort": 465`, // number kept verbatim, not 465.0
`"new@example.com"`,
} {
if !strings.Contains(got, want) {
t.Errorf("merged config missing %q:\n%s", want, got)
}
}
if strings.Contains(got, "old@example.com") {
t.Errorf("array was merged instead of replaced:\n%s", got)
}
}
func TestSettingsTypeValidation(t *testing.T) {
for _, valid := range []string{SettingGlobal, SettingBotUserConfig, SettingBotNodeConfig} {
if !IsValidSettingsType(valid) {
t.Errorf("expected %q to be a valid settings type", valid)
}
}
for _, invalid := range []string{"", "system", "node", "bot"} {
if IsValidSettingsType(invalid) {
t.Errorf("expected %q to be an invalid settings type", invalid)
}
if _, err := GetSettings(invalid); err != ErrUnsupportedSettingsType {
t.Errorf("GetSettings(%q) error = %v, want ErrUnsupportedSettingsType", invalid, err)
}
}
}
func TestGetSettingsBotNodeMissingFile(t *testing.T) {
// Point at a temp path that does not exist yet.
writeTempConfig(t, &global.ConfigPath.BotNodeConfig, "")
os.Remove(global.ConfigPath.BotNodeConfig)
data, err := GetSettings(SettingBotNodeConfig)
if err != nil {
t.Fatalf("GetSettings on missing bot_node_config: %v", err)
}
if string(data) != "{}" {
t.Errorf("expected empty object for missing bot_node_config, got %s", data)
}
}
func TestUpdateSettingsRemovesKeysWithNull(t *testing.T) {
writeTempConfig(t, &global.ConfigPath.BotUserConfig, `{
"qq(napcat)": {
"admins": {
"3526453517": { "event_status_notify": true },
"740724778": { "event_status_notify": false }
},
"trustedGroups": {
"999": { "event_bot_started": true }
}
},
"telegram": {
"admins": {}
}
}`)
// null removes a nested member and keeps its siblings; an empty section stays.
patch := map[string]interface{}{
"qq(napcat)": map[string]interface{}{
"admins": map[string]interface{}{
"3526453517": nil,
},
},
}
if err := UpdateSettings(SettingBotUserConfig, patch); err != nil {
t.Fatalf("UpdateSettings: %v", err)
}
data, err := GetSettings(SettingBotUserConfig)
if err != nil {
t.Fatalf("GetSettings: %v", err)
}
got := string(data)
if strings.Contains(got, "3526453517") {
t.Errorf("deleted member still present:\n%s", got)
}
for _, want := range []string{"740724778", `"trustedGroups"`, `"telegram"`} {
if !strings.Contains(got, want) {
t.Errorf("unrelated content missing %q:\n%s", want, got)
}
}
}