diff --git a/go.mod b/go.mod index 3c98e46..8d260b6 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/flashcatcloud/flashduty-cli go 1.26.0 require ( - github.com/flashcatcloud/go-flashduty v0.15.17 + github.com/flashcatcloud/go-flashduty v0.15.18-0.20261015103612-f84d9ddaca1a github.com/mattn/go-runewidth v0.0.30 github.com/spf13/cobra v1.10.2 github.com/spf13/pflag v1.0.10 diff --git a/go.sum b/go.sum index 81128d3..c952806 100644 --- a/go.sum +++ b/go.sum @@ -1,8 +1,8 @@ github.com/clipperhouse/uax29/v2 v2.2.0 h1:ChwIKnQN3kcZteTXMgb1wztSgaU+ZemkgWdohwgs8tY= github.com/clipperhouse/uax29/v2 v2.2.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM= github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= -github.com/flashcatcloud/go-flashduty v0.15.17 h1:AkWSRYEUdMMYAI7WJbzHIHBnZ+FqcbMAwK8aInOOLpI= -github.com/flashcatcloud/go-flashduty v0.15.17/go.mod h1:YpHiTYXR5NXBI/rGRZfUy537XMkhdCkwA8NW1QoRHwk= +github.com/flashcatcloud/go-flashduty v0.15.18-0.20261015103612-f84d9ddaca1a h1:+oqnFVWaZlC7yGMu+wl0QpBwEOGdOG+a6hFagXSHYuM= +github.com/flashcatcloud/go-flashduty v0.15.18-0.20261015103612-f84d9ddaca1a/go.mod h1:YpHiTYXR5NXBI/rGRZfUy537XMkhdCkwA8NW1QoRHwk= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/mattn/go-runewidth v0.0.30 h1:+KUuiDA4fF0R1p5FeueHefjDm+GIM+kWfFnDjybOPgk= diff --git a/internal/cli/folders_test.go b/internal/cli/folders_test.go new file mode 100644 index 0000000..5d22fd5 --- /dev/null +++ b/internal/cli/folders_test.go @@ -0,0 +1,55 @@ +package cli + +import ( + "reflect" + "strings" + "testing" +) + +func TestFolderCommands(t *testing.T) { + for _, tt := range []struct { + action string + args []string + body map[string]any + }{ + {"list", nil, nil}, + {"health", nil, nil}, + {"create", []string{"--name", "Checkout", "--parent-id", "10"}, map[string]any{"name": "Checkout", "parent_id": float64(10)}}, + {"update", []string{"--id", "12", "--name", "Checkout", "--note", "", "--team-id", "0"}, map[string]any{"id": float64(12), "name": "Checkout"}}, + {"move", []string{"--id", "12", "--dest-parent-id", "0"}, map[string]any{"id": float64(12), "dest_parent_id": float64(0)}}, + {"delete", []string{"--id", "12"}, map[string]any{"id": float64(12)}}, + } { + t.Run(tt.action, func(t *testing.T) { + saveAndResetGlobals(t) + stub := newGFStub(t) + stub.dataForPath = func(_ string, _ map[string]any) any { + if tt.action == "list" { + return []any{} + } + return map[string]any{} + } + args := append([]string{"monit", "folder-" + tt.action}, tt.args...) + if _, err := execCommand(args...); err != nil { + t.Fatal(err) + } + if stub.lastPath != "/monit/folder/"+tt.action { + t.Errorf("path = %q", stub.lastPath) + } + if tt.body != nil && !reflect.DeepEqual(stub.lastBody, tt.body) { + t.Errorf("body = %#v, want %#v", stub.lastBody, tt.body) + } + }) + } +} + +func TestFolderMoveRequiresDestination(t *testing.T) { + saveAndResetGlobals(t) + stub := newGFStub(t) + _, err := execCommand("monit", "folder-move", "--id", "12") + if err == nil || !strings.Contains(err.Error(), "dest-parent-id") { + t.Fatalf("expected missing destination error, got %v", err) + } + if stub.lastPath != "" { + t.Fatalf("unexpected request to %q", stub.lastPath) + } +} diff --git a/internal/cli/zz_generated_manifest.go b/internal/cli/zz_generated_manifest.go index 1bb4a0e..f92b040 100644 --- a/internal/cli/zz_generated_manifest.go +++ b/internal/cli/zz_generated_manifest.go @@ -219,7 +219,12 @@ var generatedOpIDs = []string{ "monit-datasource-write-create", "monit-datasource-write-delete", "monit-datasource-write-update", + "monit-folder-read-health", "monit-folder-read-list", + "monit-folder-write-create", + "monit-folder-write-delete", + "monit-folder-write-move", + "monit-folder-write-update", "monit-read-query-data", "monit-read-query-explore", "monit-rule-read-audit-detail", diff --git a/internal/cli/zz_generated_response_help.go b/internal/cli/zz_generated_response_help.go index fd2434a..e8d0859 100644 --- a/internal/cli/zz_generated_response_help.go +++ b/internal/cli/zz_generated_response_help.go @@ -199,7 +199,10 @@ var responseHelpBySDKMethod = map[string]string{ "RolesPermissions.ReadListPermission": "Response fields (this command's `--json` is a TOP-LEVEL array of these row objects — pipe `jq '.[]'`, NOT `.items[]`):\n - account_id (integer) — Owning account ID. Omitted when 0, i.e. for system-level permissions.\n - class (string) (required) — Permission class (e.g., 'On-call', 'Organization').\n - description (string) (required) — Human-readable permission description.\n - id (integer) (required) — Unique permission ID.\n - is_granted (boolean) (required) — Whether this permission is granted to the roles given in `role_ids`. Always present in this endpoint's response; `false` entries only appear when `with_all` is true.\n - permission_name (string) (required) — Permission display name.\n - permission_type (string) (required) — Whether this is a read or manage permission. `read`: view-only permission (read/list/query); `manage`: administrative permission covering mutations (create, update, delete, configure). [read, manage]\n - scope (string) (required) — Functional scope the permission applies to. | value | meaning | | --- | --- | | `account` | Account settings and API keys | | `organization` | Members, teams, roles, audit | | `on-call` | On-call incident management | | `monit` | Monitoring | | `rum` | Real user monitoring | | `ai-sre` | AI SRE features | | `custom_menu` | Account-defined custom menu pages (on-premises only) | [account, organization, on-call, monit, rum, ai-sre, custom_menu]\n - source (string) (required) — Origin of the permission. `system` — seeded built-in permission; `account` — dynamic permission created for this account (e.g. custom menus). [system, account]\n - source_ref (string) — Primary key of the source object (e.g. the custom menu ID) for account-scoped permissions. Omitted when empty.\n - status (string) (required) — Permission status. `enabled` — active; `deleted` — removed (deleted permissions are filtered out and never returned). [enabled, deleted]\n", "RolesPermissions.ReadListPermissionFactor": "Response fields (`data` is a TOP-LEVEL array of these row objects — pipe `jq '.[]'`, NOT `.items[]`):\n - factor_name (string) (required) — Factor identifier (e.g., 'template:read:info').\n - factor_type (string) (required) — Factor type. `api`: backend API factor — `factor_name` is the API name (e.g. `skill:write:upload`), enforced at the gateway; `button`: UI action factor, used by the role-config page to render action toggles; `visit`: page-visit factor (custom menu pages use this type); `menu`: menu-visibility factor (legacy, no current seed data); `url`: page route-path factor (legacy, no current seed data). [api, button, visit, menu, url]\n - source (string) — Origin of the factor. `system` — seeded built-in factor; `account` — dynamic factor created for this account (e.g. custom menus). [system, account]\n - source_ref (string) — Primary key of the source object (e.g. the custom menu ID) for account-scoped factors. Omitted when empty.\n", "RolesPermissions.WriteUpsert": "Response fields (`data` envelope is unwrapped — these fields are at the top level):\n - role_id (integer) (required) — Created or updated role ID.\n - role_name (string) (required) — Role name echoed from the request.\n", - "RuleFolders.List": "Response fields (`data` is a TOP-LEVEL array of these row objects — pipe `jq '.[]'`, NOT `.items[]`):\n - account_id (integer) (required) — Owning account ID.\n - created_at (string) (required) — Unix timestamp in seconds. CLI `--json` renders this as an RFC3339 string in the process's local timezone (NOT UTC, and NOT the wire integer); an unset value renders as null.\n - creator_id (integer) (required) — Member ID of the creator.\n - creator_name (string) (required) — Name of the creator.\n - id (integer) (required) — Folder ID.\n - name (string) (required) — Folder name.\n - note (string) (required) — Free-form note.\n - parent_id (integer) (required) — Parent folder ID; 0 at the root.\n - parent_path (string) (required) — Comma-separated ancestor IDs from the root, excluding this folder; empty at the root.\n - team_id (integer) (required) — Team the folder is scoped to; 0 when account-wide.\n - updated_at (string) (required) — Unix timestamp in seconds. CLI `--json` renders this as an RFC3339 string in the process's local timezone (NOT UTC, and NOT the wire integer); an unset value renders as null.\n - updater_id (integer) (required) — Member ID of the last updater.\n - updater_name (string) (required) — Name of the last updater.\n", + "RuleFolders.ReadHealth": "Response fields (this command's `--json` is a TOP-LEVEL array of these row objects — pipe `jq '.[]'`, NOT `.items[]`):\n - folder_id (integer) (required) — Folder ID. (min 0)\n - has_issue (boolean) (required) — Whether this folder or a visible descendant has active alerts or rule runtime issues.\n", + "RuleFolders.ReadList": "Response fields (`data` is a TOP-LEVEL array of these row objects — pipe `jq '.[]'`, NOT `.items[]`):\n - account_id (integer) (required) — Owning account ID.\n - created_at (string) (required) — Unix timestamp in seconds. CLI `--json` renders this as an RFC3339 string in the process's local timezone (NOT UTC, and NOT the wire integer); an unset value renders as null.\n - creator_id (integer) (required) — Member ID of the creator.\n - creator_name (string) (required) — Name of the creator.\n - id (integer) (required) — Folder ID.\n - name (string) (required) — Folder name.\n - note (string) (required) — Free-form note.\n - parent_id (integer) (required) — Parent folder ID; 0 at the root.\n - parent_path (string) (required) — Comma-separated ancestor IDs from the root, excluding this folder; empty at the root.\n - team_id (integer) (required) — Team the folder is scoped to; 0 when account-wide.\n - updated_at (string) (required) — Unix timestamp in seconds. CLI `--json` renders this as an RFC3339 string in the process's local timezone (NOT UTC, and NOT the wire integer); an unset value renders as null.\n - updater_id (integer) (required) — Member ID of the last updater.\n - updater_name (string) (required) — Name of the last updater.\n", + "RuleFolders.WriteCreate": "Response fields (`data` envelope is unwrapped — these fields are at the top level):\n - account_id (integer) (required) — Owning account ID.\n - created_at (string) (required) — Unix timestamp in seconds. CLI `--json` renders this as an RFC3339 string in the process's local timezone (NOT UTC, and NOT the wire integer); an unset value renders as null.\n - creator_id (integer) (required) — Member ID of the creator.\n - creator_name (string) (required) — Name of the creator.\n - id (integer) (required) — Folder ID.\n - name (string) (required) — Folder name.\n - note (string) (required) — Free-form note.\n - parent_id (integer) (required) — Parent folder ID; 0 at the root.\n - parent_path (string) (required) — Comma-separated ancestor IDs from the root, excluding this folder; empty at the root.\n - team_id (integer) (required) — Team the folder is scoped to; 0 when account-wide.\n - updated_at (string) (required) — Unix timestamp in seconds. CLI `--json` renders this as an RFC3339 string in the process's local timezone (NOT UTC, and NOT the wire integer); an unset value renders as null.\n - updater_id (integer) (required) — Member ID of the last updater.\n - updater_name (string) (required) — Name of the last updater.\n", + "RuleFolders.WriteUpdate": "Response fields (`data` envelope is unwrapped — these fields are at the top level):\n - account_id (integer) (required) — Owning account ID.\n - created_at (string) (required) — Unix timestamp in seconds. CLI `--json` renders this as an RFC3339 string in the process's local timezone (NOT UTC, and NOT the wire integer); an unset value renders as null.\n - creator_id (integer) (required) — Member ID of the creator.\n - creator_name (string) (required) — Name of the creator.\n - id (integer) (required) — Folder ID.\n - name (string) (required) — Folder name.\n - note (string) (required) — Free-form note.\n - parent_id (integer) (required) — Parent folder ID; 0 at the root.\n - parent_path (string) (required) — Comma-separated ancestor IDs from the root, excluding this folder; empty at the root.\n - team_id (integer) (required) — Team the folder is scoped to; 0 when account-wide.\n - updated_at (string) (required) — Unix timestamp in seconds. CLI `--json` renders this as an RFC3339 string in the process's local timezone (NOT UTC, and NOT the wire integer); an unset value renders as null.\n - updater_id (integer) (required) — Member ID of the last updater.\n - updater_name (string) (required) — Name of the last updater.\n", "Schedules.ByPerson": "Response fields (`data` envelope is unwrapped — these fields are at the top level):\n - current (object) — One on-call shift interval of a member.\n - end_at (string) — Unix timestamp in seconds - when the shift ends. Absent while the shift is ongoing. CLI `--json` renders this as an RFC3339 string in the process's local timezone (NOT UTC, and NOT the wire integer); an unset value renders as null.\n - schedule_id (integer) — Owning schedule ID.\n - schedule_name (string) — Owning schedule name.\n - start_at (string) — Unix timestamp in seconds - when the shift starts. CLI `--json` renders this as an RFC3339 string in the process's local timezone (NOT UTC, and NOT the wire integer); an unset value renders as null.\n - next (object) — One on-call shift interval of a member.\n - end_at (string) — Unix timestamp in seconds - when the shift ends. Absent while the shift is ongoing. CLI `--json` renders this as an RFC3339 string in the process's local timezone (NOT UTC, and NOT the wire integer); an unset value renders as null.\n - schedule_id (integer) — Owning schedule ID.\n - schedule_name (string) — Owning schedule name.\n - start_at (string) — Unix timestamp in seconds - when the shift starts. CLI `--json` renders this as an RFC3339 string in the process's local timezone (NOT UTC, and NOT the wire integer); an unset value renders as null.\n - schedules (array) (required) — All enabled schedules the member participates in.\n - schedule_id (integer) — Schedule ID.\n - schedule_name (string) — Schedule name.\n", "Schedules.Create": "Response fields (`data` envelope is unwrapped — these fields are at the top level):\n - schedule_id (integer) (required) — ID of the newly created schedule.\n", "Schedules.Info": "Response fields (`data` envelope is unwrapped — these fields are at the top level):\n - account_id (integer) (required) — Account ID.\n - create_at (string) (required) — Creation timestamp (Unix seconds). CLI `--json` renders this as an RFC3339 string in the process's local timezone (NOT UTC, and NOT the wire integer); an unset value renders as null.\n - create_by (integer) (required) — Creator person ID.\n - cur_oncall (object) (required) — Current on-call group, or null when nobody is on-call.\n - end (string) (required) — Shift end timestamp (Unix seconds). CLI `--json` renders this as an RFC3339 string in the process's local timezone (NOT UTC, and NOT the wire integer); an unset value renders as null.\n - group (object) (required) — Oncall group on duty for the shift.\n - end (integer) (required) — Group end timestamp (Unix seconds).\n - group_name (string) (required) — Group display name. Null when only the legacy name is set.\n - members (array) (required) — Members of this group.\n - person_ids (array) (required) — Person IDs in this slot.\n - role_id (integer) (required) — Oncall role ID. (min 0)\n - name (string) (required) — Legacy group name.\n - start (integer) (required) — Group start timestamp (Unix seconds).\n - index (integer) (required) — Index inside the rotation.\n - start (string) (required) — Shift start timestamp (Unix seconds). CLI `--json` renders this as an RFC3339 string in the process's local timezone (NOT UTC, and NOT the wire integer); an unset value renders as null.\n - update_at (string) (required) — Update timestamp (Unix seconds). CLI `--json` renders this as an RFC3339 string in the process's local timezone (NOT UTC, and NOT the wire integer); an unset value renders as null.\n - weight (integer) (required) — Layer weight the shift comes from.\n - description (string) (required) — Schedule description. null when returned from /schedule/preview.\n - disabled (integer) (required) — Disabled flag (0 = enabled, 1 = disabled). Deprecated. null when returned from /schedule/preview.\n - end (string) — Window end (Unix seconds). Omitted when 0 (no window requested). CLI `--json` renders this as an RFC3339 string in the process's local timezone (NOT UTC, and NOT the wire integer); an unset value renders as null.\n - field (string) — Field name used by the legacy update-field endpoint.\n - final_schedule (object) (required) — Collapsed final schedule across all layers.\n - layer_name (string) (required) — Layer display name.\n - mode (integer) (required) — Layer mode: 0 = common rotation, 1 = override.\n - name (string) (required) — Layer internal name.\n - schedules (array) (required) — Computed shifts; null when the layer produces none.\n - end (string) (required) — Shift end timestamp (Unix seconds). CLI `--json` renders this as an RFC3339 string in the process's local timezone (NOT UTC, and NOT the wire integer); an unset value renders as null.\n - group (object) (required) — Oncall group covering the shift; null marks a coverage gap.\n - end (integer) (required) — Group end timestamp (Unix seconds).\n - group_name (string) (required) — Group display name. Null when only the legacy name is set.\n - members (array) (required) — Members of this group.\n - name (string) (required) — Legacy group name.\n - start (integer) (required) — Group start timestamp (Unix seconds).\n - index (integer) (required) — Index inside the rotation.\n - start (string) (required) — Shift start timestamp (Unix seconds). CLI `--json` renders this as an RFC3339 string in the process's local timezone (NOT UTC, and NOT the wire integer); an unset value renders as null.\n - group_id (integer) (required) — Legacy team/group ID. null when returned from /schedule/preview.\n - id (integer) (required) — Schedule ID. null when returned from /schedule/preview.\n - layer_schedules (array) (required) — Alias of schedule_layers returned for compatibility. Null when not computed.\n - layer_name (string) (required) — Layer display name.\n - mode (integer) (required) — Layer mode: 0 = common rotation, 1 = override.\n - name (string) (required) — Layer internal name.\n - schedules (array) (required) — Computed shifts; null when the layer produces none.\n - end (string) (required) — Shift end timestamp (Unix seconds). CLI `--json` renders this as an RFC3339 string in the process's local timezone (NOT UTC, and NOT the wire integer); an unset value renders as null.\n - group (object) (required) — Oncall group covering the shift; null marks a coverage gap.\n - end (integer) (required) — Group end timestamp (Unix seconds).\n - group_name (string) (required) — Group display name. Null when only the legacy name is set.\n - members (array) (required) — Members of this group.\n - name (string) (required) — Legacy group name.\n - start (integer) (required) — Group start timestamp (Unix seconds).\n - index (integer) (required) — Index inside the rotation.\n - start (string) (required) — Shift start timestamp (Unix seconds). CLI `--json` renders this as an RFC3339 string in the process's local timezone (NOT UTC, and NOT the wire integer); an unset value renders as null.\n - layers (array) (required) — Rotation layers defined on the schedule. Null when layers were not loaded (for example by `/schedule/infos`, or by `/schedule/list` without start/end).\n - account_id (integer) (required) — Account ID.\n - create_at (integer) (required) — Creation timestamp (Unix seconds).\n - create_by (integer) (required) — Creator person ID.\n - day_mask (object) (required) — Day-of-week mask.\n - repeat (array) — Weekday numbers (0 = Sunday) included in the rotation. Conflicts with restrict_mode = 2 (week).\n - enable_time (integer) (required) — When the layer becomes effective (Unix seconds).\n - expire_time (integer) (required) — When the layer expires (Unix seconds, 0 means never).\n - fair_rotation (boolean) (required) — Whether fair rotation is enabled.\n - groups (array) (required) — Oncall groups participating in the rotation. Null when not set.\n - end (integer) (required) — Group end timestamp (Unix seconds).\n - group_name (string) (required) — Group display name. Null when only the legacy name is set.\n - members (array) (required) — Members of this group.\n - person_ids (array) (required) — Person IDs in this slot.\n - role_id (integer) (required) — Oncall role ID. (min 0)\n - name (string) (required) — Legacy group name.\n - start (integer) (required) — Group start timestamp (Unix seconds).\n - handoff_time (integer) (required) — Rotation handoff time as a weekly offset in seconds (weekday x 86400 + seconds since midnight), not an absolute Unix timestamp.\n - hidden (integer) (required) — Whether the layer is hidden in the UI (0 = no, 1 = yes).\n - layer_end (integer) — Layer end timestamp (Unix seconds). null means open-ended.\n - layer_name (string) — User-facing layer name. Null when not set.\n - layer_start (integer) — Layer effective start (Unix seconds). Null when not set.\n - mask_continuous_enabled (boolean) (required) — Whether continuous masking is enabled.\n - mode (integer) (required) — Layer mode: 0 = common rotation, 1 = override.\n - name (string) (required) — Layer internal name.\n - restrict_end (integer) (required) — Legacy end offset inside the restriction window (seconds).\n - restrict_mode (integer) (required) — Restriction mode: 0 = none, 1 = day, 2 = week.\n - restrict_periods (array) (required) — Restriction windows inside each rotation cycle. Null when not set.\n - restrict_end (integer) (required) — End offset inside the rotation cycle.\n - restrict_start (integer) (required) — Start offset inside the rotation cycle.\n - restrict_start (integer) (required) — Legacy start offset inside the restriction window (seconds).\n - rotation_duration (integer) (required) — Rotation duration in seconds.\n - rotation_unit (string) (required) — Rotation unit. On-call assignees rotate in turn by this unit. | Value | Meaning | |---|---| | `hour` | Rotates hourly. | | `day` | Rotates daily. | | `week` | Rotates weekly. | | `month` | Rotates monthly. | [hour, day, week, month]\n - rotation_value (integer) (required) — Rotation quantity (number of rotation_unit per cycle). (min 0)\n - schedule_id (integer) (required) — Parent schedule ID.\n - update_at (integer) (required) — Last update timestamp (Unix seconds).\n - update_by (integer) (required) — Last updater person ID.\n - weight (integer) (required) — Layer weight for ordering.\n - name (string) (required) — Schedule name (legacy field; mirrors schedule_name). null when returned from /schedule/preview.\n - next_oncall (object) (required) — Next on-call group, or null when unknown.\n - end (string) (required) — Shift end timestamp (Unix seconds). CLI `--json` renders this as an RFC3339 string in the process's local timezone (NOT UTC, and NOT the wire integer); an unset value renders as null.\n - group (object) (required) — Oncall group on duty for the shift.\n - end (integer) (required) — Group end timestamp (Unix seconds).\n - group_name (string) (required) — Group display name. Null when only the legacy name is set.\n - members (array) (required) — Members of this group.\n - person_ids (array) (required) — Person IDs in this slot.\n - role_id (integer) (required) — Oncall role ID. (min 0)\n - name (string) (required) — Legacy group name.\n - start (integer) (required) — Group start timestamp (Unix seconds).\n - index (integer) (required) — Index inside the rotation.\n - start (string) (required) — Shift start timestamp (Unix seconds). CLI `--json` renders this as an RFC3339 string in the process's local timezone (NOT UTC, and NOT the wire integer); an unset value renders as null.\n - update_at (string) (required) — Update timestamp (Unix seconds). CLI `--json` renders this as an RFC3339 string in the process's local timezone (NOT UTC, and NOT the wire integer); an unset value renders as null.\n - weight (integer) (required) — Layer weight the shift comes from.\n - notify (object) (required) — Notification configuration. Null when the schedule has none.\n - advance_in_time (integer) — Advance notification lead time in seconds. `0` notifies exactly at shift start; omitting disables advance notification.\n - by (object) (required) — Recipient notification preference; null when not configured.\n - follow_preference (boolean) (required) — Whether to follow each responder's personal notification preference.\n - personal_channels (array) (required) — Personal notification channel keys.\n - fixed_time (object) (required) — Fixed-time notification config; null when not configured.\n - cycle (string) (required) — Notification cycle: `day` sends every day; `week` sends on the weekdays listed in `days`; `month` sends on the day-of-month values listed in `days`. [day, week, month]\n - days (array) — Days the notification fires on, read in the schedule's timezone. For `week`, weekday numbers 0-6 (0 = Sunday); for `month`, day-of-month numbers 1-31, and a month without that date is skipped. Required when `cycle` is `week` or `month`; ignored when `cycle` is `day`.\n - start (string) (required) — Time of day to send, format `HH:MM` (24-hour).\n - im (object) — Legacy IM-type to token map.\n - webhooks (array) (required) — IM webhook notification channels; null when not configured.\n - settings (object) (required) — Webhook channel settings.\n - alias (string) (required) — Channel alias.\n - chat_ids (array) (required) — Chat IDs.\n - data_source_id (integer) (required) — Data source ID.\n - sign_secret (string) (required) — Signature secret.\n - token (string) (required) — Webhook token.\n - verify_token (string) (required) — Verification token.\n - type (string) (required) — IM provider type (for example feishu_app, dingtalk_app, wecom_app, teams_app, slack_app).\n - schedule_id (integer) (required) — Schedule ID. Null when returned from `/schedule/preview`.\n - schedule_layers (array) (required) — Computed per-layer schedules for the requested window. Null when not computed.\n - layer_name (string) (required) — Layer display name.\n - mode (integer) (required) — Layer mode: 0 = common rotation, 1 = override.\n - name (string) (required) — Layer internal name.\n - schedules (array) (required) — Computed shifts; null when the layer produces none.\n - end (string) (required) — Shift end timestamp (Unix seconds). CLI `--json` renders this as an RFC3339 string in the process's local timezone (NOT UTC, and NOT the wire integer); an unset value renders as null.\n - group (object) (required) — Oncall group covering the shift; null marks a coverage gap.\n - end (integer) (required) — Group end timestamp (Unix seconds).\n - group_name (string) (required) — Group display name. Null when only the legacy name is set.\n - members (array) (required) — Members of this group.\n - name (string) (required) — Legacy group name.\n - start (integer) (required) — Group start timestamp (Unix seconds).\n - index (integer) (required) — Index inside the rotation.\n - start (string) (required) — Shift start timestamp (Unix seconds). CLI `--json` renders this as an RFC3339 string in the process's local timezone (NOT UTC, and NOT the wire integer); an unset value renders as null.\n - schedule_name (string) (required) — Schedule display name. null when returned from /schedule/preview.\n - start (string) — Window start (Unix seconds). Omitted when 0 (no window requested). CLI `--json` renders this as an RFC3339 string in the process's local timezone (NOT UTC, and NOT the wire integer); an unset value renders as null.\n - status (integer) (required) — Legacy status flag. Deprecated. null when returned from /schedule/preview.\n - team_id (integer) (required) — Owning team ID. null when returned from /schedule/preview.\n - update_at (string) (required) — Last update timestamp (Unix seconds). CLI `--json` renders this as an RFC3339 string in the process's local timezone (NOT UTC, and NOT the wire integer); an unset value renders as null.\n - update_by (integer) (required) — Last updater person ID.\n", diff --git a/internal/cli/zz_generated_rule_folders.go b/internal/cli/zz_generated_rule_folders.go index d47eac6..98734a5 100644 --- a/internal/cli/zz_generated_rule_folders.go +++ b/internal/cli/zz_generated_rule_folders.go @@ -2,9 +2,50 @@ package cli -import "github.com/spf13/cobra" +import ( + "github.com/spf13/cobra" -func genRuleFoldersListCmd() *cobra.Command { + flashduty "github.com/flashcatcloud/go-flashduty" +) + +func genRuleFoldersReadHealthCmd() *cobra.Command { + var dataJSON string + cmd := &cobra.Command{ + Use: "folder-health", + Short: "Get monitor folder health", + Long: `Get monitor folder health. + +Get health summaries for visible folders, including issues in their descendants. + +API: POST /monit/folder/health (monit-folder-read-health) + +Response fields ('data' envelope is unwrapped — rows are nested under items[]; pipe 'jq '.items[]'', NOT '.data.items[]'): + - items (array) (required) — Folder health summaries. + - folder_id (integer) (required) — Folder ID. (min 0) + - has_issue (boolean) (required) — Whether this folder or a visible descendant has active alerts or rule runtime issues. +`, + RunE: func(cmd *cobra.Command, args []string) error { + return runCommand(cmd, args, func(ctx *RunContext) error { + body, err := genAssembleBody(dataJSON, func(body map[string]any) error { + return nil + }) + if err != nil { + return err + } + _ = body + out, _, err := ctx.Client.RuleFolders.ReadHealth(cmdContext(ctx.Cmd)) + if err != nil { + return err + } + return printGenericResult(ctx, out, body, listDefaultsFor("RuleFolders", "ReadHealth")) + }) + }, + } + cmd.Flags().StringVar(&dataJSON, "data", "", "Full request body as JSON; positional arguments and typed flags override its fields. Accepts inline JSON, or - to read stdin.") + return cmd +} + +func genRuleFoldersReadListCmd() *cobra.Command { var dataJSON string cmd := &cobra.Command{ Use: "folder-list", @@ -39,19 +80,284 @@ Response fields ('data' is a TOP-LEVEL array of these row objects — pipe 'jq ' return err } _ = body - out, _, err := ctx.Client.RuleFolders.List(cmdContext(ctx.Cmd)) + out, _, err := ctx.Client.RuleFolders.ReadList(cmdContext(ctx.Cmd)) + if err != nil { + return err + } + return printGenericResult(ctx, out, body, listDefaultsFor("RuleFolders", "ReadList")) + }) + }, + } + cmd.Flags().StringVar(&dataJSON, "data", "", "Full request body as JSON; positional arguments and typed flags override its fields. Accepts inline JSON, or - to read stdin.") + return cmd +} + +func genRuleFoldersWriteCreateCmd() *cobra.Command { + var dataJSON string + var fName string + var fNote string + var fParentID int64 + var fTeamID int64 + cmd := &cobra.Command{ + Use: "folder-create", + Short: "Create monitor folder", + Long: `Create monitor folder. + +Create a monitor folder at the root or under an accessible parent folder. + +API: POST /monit/folder/create (monit-folder-write-create) + +Request fields: + --name string (required) — Folder name; cannot contain a period, slash, or comma. (≥1 chars) + --note string — Free-form note; omitted or empty clears the note on update. + --parent-id int — Parent folder ID; 0 or omitted creates a root folder. (min 0) + --team-id int — Team ID; 0 means account-wide. Omitted on update resets to 0. (min 0) + +Response fields ('data' envelope is unwrapped — these fields are at the top level): + - account_id (integer) (required) — Owning account ID. + - created_at (string) (required) — Unix timestamp in seconds. CLI '--json' renders this as an RFC3339 string in the process's local timezone (NOT UTC, and NOT the wire integer); an unset value renders as null. + - creator_id (integer) (required) — Member ID of the creator. + - creator_name (string) (required) — Name of the creator. + - id (integer) (required) — Folder ID. + - name (string) (required) — Folder name. + - note (string) (required) — Free-form note. + - parent_id (integer) (required) — Parent folder ID; 0 at the root. + - parent_path (string) (required) — Comma-separated ancestor IDs from the root, excluding this folder; empty at the root. + - team_id (integer) (required) — Team the folder is scoped to; 0 when account-wide. + - updated_at (string) (required) — Unix timestamp in seconds. CLI '--json' renders this as an RFC3339 string in the process's local timezone (NOT UTC, and NOT the wire integer); an unset value renders as null. + - updater_id (integer) (required) — Member ID of the last updater. + - updater_name (string) (required) — Name of the last updater. +`, + Example: ` flashduty monit folder-create --data '{"name":"Checkout","note":"Checkout service folders","parent_id":10,"team_id":0}'`, + RunE: func(cmd *cobra.Command, args []string) error { + return runCommand(cmd, args, func(ctx *RunContext) error { + body, err := genAssembleBody(dataJSON, func(body map[string]any) error { + if cmd.Flags().Changed("name") { + body["name"] = fName + } + if cmd.Flags().Changed("note") { + body["note"] = fNote + } + if cmd.Flags().Changed("parent-id") { + body["parent_id"] = fParentID + } + if cmd.Flags().Changed("team-id") { + body["team_id"] = fTeamID + } + return nil + }) + if err != nil { + return err + } + req := new(flashduty.FolderCreateRequest) + if err := genBindBody(ctx.Cmd.ErrOrStderr(), body, req); err != nil { + return err + } + out, _, err := ctx.Client.RuleFolders.WriteCreate(cmdContext(ctx.Cmd), req) + if err != nil { + return err + } + return printGenericResult(ctx, out, body, listDefaultsFor("RuleFolders", "WriteCreate")) + }) + }, + } + cmd.Flags().StringVar(&fName, "name", "", "Folder name; cannot contain a period, slash, or comma. (required) (≥1 chars)") + cmd.Flags().StringVar(&fNote, "note", "", "Free-form note; omitted or empty clears the note on update.") + cmd.Flags().Int64Var(&fParentID, "parent-id", 0, "Parent folder ID; 0 or omitted creates a root folder. (min 0)") + cmd.Flags().Int64Var(&fTeamID, "team-id", 0, "Team ID; 0 means account-wide. Omitted on update resets to 0. (min 0)") + cmd.Flags().StringVar(&dataJSON, "data", "", "Full request body as JSON; positional arguments and typed flags override its fields. Accepts inline JSON, or - to read stdin.") + return cmd +} + +func genRuleFoldersWriteDeleteCmd() *cobra.Command { + var dataJSON string + var fID int64 + cmd := &cobra.Command{ + Use: "folder-delete", + Short: "Delete monitor folder", + Long: `Delete monitor folder. + +Delete an accessible monitor folder after removing its children, rules, and dashboards. + +API: POST /monit/folder/delete (monit-folder-write-delete) + +Request fields: + --id int (required) — Folder ID. (min 1) +`, + Example: ` flashduty monit folder-delete --data '{"id":12}'`, + RunE: func(cmd *cobra.Command, args []string) error { + return runCommand(cmd, args, func(ctx *RunContext) error { + body, err := genAssembleBody(dataJSON, func(body map[string]any) error { + if cmd.Flags().Changed("id") { + body["id"] = fID + } + return nil + }) + if err != nil { + return err + } + req := new(flashduty.FolderDeleteRequest) + if err := genBindBody(ctx.Cmd.ErrOrStderr(), body, req); err != nil { + return err + } + resp, err := ctx.Client.RuleFolders.WriteDelete(cmdContext(ctx.Cmd), req) + if err != nil { + return err + } + if resp != nil && len(resp.Raw) > 0 { + return ctx.WriteRaw(resp.Raw) + } + ctx.WriteResult("OK: POST /monit/folder/delete") + return nil + }) + }, + } + cmd.Flags().Int64Var(&fID, "id", 0, "Folder ID. (required) (min 1)") + cmd.Flags().StringVar(&dataJSON, "data", "", "Full request body as JSON; positional arguments and typed flags override its fields. Accepts inline JSON, or - to read stdin.") + return cmd +} + +func genRuleFoldersWriteMoveCmd() *cobra.Command { + var dataJSON string + var fDestParentID int64 + var fID int64 + cmd := &cobra.Command{ + Use: "folder-move ", + Short: "Move monitor folder", + Long: `Move monitor folder. + +Move a monitor folder and its descendants under another folder or to the root. + +API: POST /monit/folder/move (monit-folder-write-move) + +Request fields: + --dest-parent-id int (required) — Destination parent folder ID; required even when 0 to move to the root. (min 0) + --id int (required) — Folder ID. (min 1) +`, + Args: requireBodyFieldOrExactArg("dest_parent_id", "dest-parent-id"), + Example: ` flashduty monit folder-move --data '{"dest_parent_id":0,"id":12}'`, + RunE: func(cmd *cobra.Command, args []string) error { + return runCommand(cmd, args, func(ctx *RunContext) error { + body, err := genAssembleBody(dataJSON, func(body map[string]any) error { + if err := genFoldPositional(args, body, "dest_parent_id", "int"); err != nil { + return err + } + if cmd.Flags().Changed("dest-parent-id") { + body["dest_parent_id"] = fDestParentID + } + if cmd.Flags().Changed("id") { + body["id"] = fID + } + return nil + }) + if err != nil { + return err + } + req := new(flashduty.FolderMoveRequest) + if err := genBindBody(ctx.Cmd.ErrOrStderr(), body, req); err != nil { + return err + } + resp, err := ctx.Client.RuleFolders.WriteMove(cmdContext(ctx.Cmd), req) + if err != nil { + return err + } + if resp != nil && len(resp.Raw) > 0 { + return ctx.WriteRaw(resp.Raw) + } + ctx.WriteResult("OK: POST /monit/folder/move") + return nil + }) + }, + } + cmd.Flags().Int64Var(&fDestParentID, "dest-parent-id", 0, "Destination parent folder ID; required even when 0 to move to the root. (required) (min 0)") + cmd.Flags().Int64Var(&fID, "id", 0, "Folder ID. (required) (min 1)") + cmd.Flags().StringVar(&dataJSON, "data", "", "Full request body as JSON; positional arguments and typed flags override its fields. Accepts inline JSON, or - to read stdin.") + return cmd +} + +func genRuleFoldersWriteUpdateCmd() *cobra.Command { + var dataJSON string + var fID int64 + var fName string + var fNote string + var fTeamID int64 + cmd := &cobra.Command{ + Use: "folder-update", + Short: "Update monitor folder", + Long: `Update monitor folder. + +Replace the name, note, and team assignment of an accessible monitor folder. + +API: POST /monit/folder/update (monit-folder-write-update) + +Request fields: + --id int (required) — Folder ID. (min 1) + --name string (required) — Folder name; cannot contain a period, slash, or comma. (≥1 chars) + --note string — Free-form note; omitted or empty clears the note on update. + --team-id int — Team ID; 0 means account-wide. Omitted on update resets to 0. (min 0) + +Response fields ('data' envelope is unwrapped — these fields are at the top level): + - account_id (integer) (required) — Owning account ID. + - created_at (string) (required) — Unix timestamp in seconds. CLI '--json' renders this as an RFC3339 string in the process's local timezone (NOT UTC, and NOT the wire integer); an unset value renders as null. + - creator_id (integer) (required) — Member ID of the creator. + - creator_name (string) (required) — Name of the creator. + - id (integer) (required) — Folder ID. + - name (string) (required) — Folder name. + - note (string) (required) — Free-form note. + - parent_id (integer) (required) — Parent folder ID; 0 at the root. + - parent_path (string) (required) — Comma-separated ancestor IDs from the root, excluding this folder; empty at the root. + - team_id (integer) (required) — Team the folder is scoped to; 0 when account-wide. + - updated_at (string) (required) — Unix timestamp in seconds. CLI '--json' renders this as an RFC3339 string in the process's local timezone (NOT UTC, and NOT the wire integer); an unset value renders as null. + - updater_id (integer) (required) — Member ID of the last updater. + - updater_name (string) (required) — Name of the last updater. +`, + Example: ` flashduty monit folder-update --data '{"id":12,"name":"Checkout production","note":"Production services","team_id":0}'`, + RunE: func(cmd *cobra.Command, args []string) error { + return runCommand(cmd, args, func(ctx *RunContext) error { + body, err := genAssembleBody(dataJSON, func(body map[string]any) error { + if cmd.Flags().Changed("id") { + body["id"] = fID + } + if cmd.Flags().Changed("name") { + body["name"] = fName + } + if cmd.Flags().Changed("note") { + body["note"] = fNote + } + if cmd.Flags().Changed("team-id") { + body["team_id"] = fTeamID + } + return nil + }) + if err != nil { + return err + } + req := new(flashduty.FolderUpdateRequest) + if err := genBindBody(ctx.Cmd.ErrOrStderr(), body, req); err != nil { + return err + } + out, _, err := ctx.Client.RuleFolders.WriteUpdate(cmdContext(ctx.Cmd), req) if err != nil { return err } - return printGenericResult(ctx, out, body, listDefaultsFor("RuleFolders", "List")) + return printGenericResult(ctx, out, body, listDefaultsFor("RuleFolders", "WriteUpdate")) }) }, } + cmd.Flags().Int64Var(&fID, "id", 0, "Folder ID. (required) (min 1)") + cmd.Flags().StringVar(&fName, "name", "", "Folder name; cannot contain a period, slash, or comma. (required) (≥1 chars)") + cmd.Flags().StringVar(&fNote, "note", "", "Free-form note; omitted or empty clears the note on update.") + cmd.Flags().Int64Var(&fTeamID, "team-id", 0, "Team ID; 0 means account-wide. Omitted on update resets to 0. (min 0)") cmd.Flags().StringVar(&dataJSON, "data", "", "Full request body as JSON; positional arguments and typed flags override its fields. Accepts inline JSON, or - to read stdin.") return cmd } func registerGeneratedRuleFolders(root *cobra.Command) { gMonit := genGroup(root, "monit", "Monitors API") - genAddLeaf(gMonit, genRuleFoldersListCmd()) + genAddLeaf(gMonit, genRuleFoldersReadHealthCmd()) + genAddLeaf(gMonit, genRuleFoldersReadListCmd()) + genAddLeaf(gMonit, genRuleFoldersWriteCreateCmd()) + genAddLeaf(gMonit, genRuleFoldersWriteDeleteCmd()) + genAddLeaf(gMonit, genRuleFoldersWriteMoveCmd()) + genAddLeaf(gMonit, genRuleFoldersWriteUpdateCmd()) } diff --git a/skills/flashduty/reference/monit.md b/skills/flashduty/reference/monit.md index 900f476..d2d56af 100644 --- a/skills/flashduty/reference/monit.md +++ b/skills/flashduty/reference/monit.md @@ -141,10 +141,39 @@ Update dashboard - body-only (`--data`): definition (object) (required) - response: same shape as `dashboard-move` above +### folder-create +Create monitor folder +- `--name` string (required) — Folder name; cannot contain a period, slash, or comma. (≥1 chars) +- `--note` string — Free-form note; omitted or empty clears the note on update. +- `--parent-id` int64 — Parent folder ID; 0 or omitted creates a root folder. (min 0) +- `--team-id` int64 — Team ID; 0 means account-wide. Omitted on update resets to 0. (min 0) +- response: single object (`data` unwrapped to the top level) — fields: account_id (integer); created_at (string); creator_id (integer); creator_name (string); id (integer); name (string); note (string); parent_id (integer); parent_path (string); team_id (integer); updated_at (string); updater_id (integer); updater_name (string) + +### folder-delete +Delete monitor folder +- `--id` int64 (required) — Folder ID. (min 1) + +### folder-health +Get monitor folder health +- response: `{items: [...]}` page wrapper — pipe `--json | jq '.items[]'` (NOT top-level `.[]`) — items fields: folder_id (integer); has_issue (boolean) + ### folder-list List monitor folders - response: TOP-LEVEL array — pipe `--json | jq '.[]'` (NOT `.items[]`) — fields: account_id (integer); created_at (string); creator_id (integer); creator_name (string); id (integer); name (string); note (string); parent_id (integer); parent_path (string); team_id (integer); updated_at (string); updater_id (integer); updater_name (string) +### folder-move +Move monitor folder +- `` (positional, required) int64 — Destination parent folder ID; required even when 0 to move to the root. (min 0) +- `--id` int64 (required) — Folder ID. (min 1) + +### folder-update +Update monitor folder +- `--id` int64 (required) — Folder ID. (min 1) +- `--name` string (required) — Folder name; cannot contain a period, slash, or comma. (≥1 chars) +- `--note` string — Free-form note; omitted or empty clears the note on update. +- `--team-id` int64 — Team ID; 0 means account-wide. Omitted on update resets to 0. (min 0) +- response: same shape as `folder-create` above + ### prometheus-api-v1-label-{label_name}-values List Prometheus label values - `--data-source-id` int64