Manage notification channels instead of an ntfy topic
Mirrors the server's channels API: add/remove channels per user from the Users section. Requires the matching server release. Claude-Session: https://claude.ai/code/session_016mBLURvJoMuUEr9cB2RpUN
This commit is contained in:
+82
-20
@@ -413,12 +413,21 @@ func (m Model) routeKey(msg tea.KeyMsg) (Model, tea.Cmd) {
|
||||
m2, ourCmd := m.handleKey(msg)
|
||||
return m2, tea.Batch(inputCmd, ourCmd)
|
||||
|
||||
case modeUserNotifyEdit:
|
||||
case modeChannelAdd:
|
||||
var inputCmd tea.Cmd
|
||||
m.ntfyTopicInput, inputCmd = m.ntfyTopicInput.Update(msg)
|
||||
m.channelAddInput, inputCmd = m.channelAddInput.Update(msg)
|
||||
m2, ourCmd := m.handleKey(msg)
|
||||
return m2, tea.Batch(inputCmd, ourCmd)
|
||||
|
||||
case modeChannelRemoveByID:
|
||||
var inputCmd tea.Cmd
|
||||
m.channelRemoveIn, inputCmd = m.channelRemoveIn.Update(msg)
|
||||
m2, ourCmd := m.handleKey(msg)
|
||||
return m2, tea.Batch(inputCmd, ourCmd)
|
||||
|
||||
case modeChannelMenu:
|
||||
return m.handleKey(msg)
|
||||
|
||||
case modeAPIKeyCreate:
|
||||
var inputCmd tea.Cmd
|
||||
m.apiKeyNameInput, inputCmd = m.apiKeyNameInput.Update(msg)
|
||||
@@ -507,8 +516,12 @@ func (m Model) handleKey(msg tea.KeyMsg) (Model, tea.Cmd) {
|
||||
return m.handleTeamPickerKey(msg)
|
||||
case modeUserCreate:
|
||||
return m.handleUserCreateKey(msg)
|
||||
case modeUserNotifyEdit:
|
||||
return m.handleUserNotifyEditKey(msg)
|
||||
case modeChannelMenu:
|
||||
return m.handleChannelMenuKey(msg)
|
||||
case modeChannelAdd:
|
||||
return m.handleChannelAddKey(msg)
|
||||
case modeChannelRemoveByID:
|
||||
return m.handleChannelRemoveKey(msg)
|
||||
case modeAPIKeyMenu:
|
||||
return m.handleAPIKeyMenuKey(msg)
|
||||
case modePasswordSet:
|
||||
@@ -721,15 +734,10 @@ func (m Model) handleDashboardKey(msg tea.KeyMsg) (Model, tea.Cmd) {
|
||||
}
|
||||
m.selectedUser = m.users[cursor]
|
||||
if !m.canManageUser(m.selectedUser) {
|
||||
cmd := m.refuseUserAction("notification topic")
|
||||
cmd := m.refuseUserAction("notification channels")
|
||||
return m, cmd
|
||||
}
|
||||
// Prefilled with what they have, so editing a topic does not mean
|
||||
// retyping it, and clearing one is a deliberate wipe.
|
||||
m.ntfyTopicInput.SetValue(m.selectedUser.Topic())
|
||||
m.ntfyTopicInput.CursorEnd()
|
||||
m.ntfyTopicInput.Focus()
|
||||
m.mode = modeUserNotifyEdit
|
||||
m.mode = modeChannelMenu
|
||||
return m, nil
|
||||
|
||||
case "k":
|
||||
@@ -1369,23 +1377,77 @@ func (m Model) handleUserCreateKey(msg tea.KeyMsg) (Model, tea.Cmd) {
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// handleUserNotifyEditKey edits one user's ntfy topic.
|
||||
//
|
||||
// Unlike the other forms here, an empty value is not a mistake to reject: it is
|
||||
// how a topic is cleared, which the server accepts and treats as NULL.
|
||||
func (m Model) handleUserNotifyEditKey(msg tea.KeyMsg) (Model, tea.Cmd) {
|
||||
// handleChannelMenuKey is the menu for one user's notification channels: add
|
||||
// one, or remove one by its ID. The same shape as the API key menu.
|
||||
func (m Model) handleChannelMenuKey(msg tea.KeyMsg) (Model, tea.Cmd) {
|
||||
switch msg.String() {
|
||||
case "esc":
|
||||
m.ntfyTopicInput.Blur()
|
||||
m.mode = modeDashboard
|
||||
return m, nil
|
||||
|
||||
case "a":
|
||||
m.channelAddInput.Reset()
|
||||
m.channelAddInput.Focus()
|
||||
m.mode = modeChannelAdd
|
||||
return m, nil
|
||||
|
||||
case "r":
|
||||
m.channelRemoveIn.Reset()
|
||||
m.channelRemoveIn.Focus()
|
||||
m.mode = modeChannelRemoveByID
|
||||
return m, nil
|
||||
}
|
||||
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// parseChannelSpec splits "type:address" at the first colon. The address may
|
||||
// itself contain colons (a URL-ish topic, say), the type never does.
|
||||
func parseChannelSpec(spec string) (typ, address string, ok bool) {
|
||||
typ, address, found := strings.Cut(strings.TrimSpace(spec), ":")
|
||||
typ, address = strings.TrimSpace(typ), strings.TrimSpace(address)
|
||||
return typ, address, found && typ != "" && address != ""
|
||||
}
|
||||
|
||||
func (m Model) handleChannelAddKey(msg tea.KeyMsg) (Model, tea.Cmd) {
|
||||
switch msg.String() {
|
||||
case "esc":
|
||||
m.channelAddInput.Blur()
|
||||
m.mode = modeChannelMenu
|
||||
return m, nil
|
||||
|
||||
case "enter":
|
||||
topic := strings.TrimSpace(m.ntfyTopicInput.Value())
|
||||
m.ntfyTopicInput.Blur()
|
||||
typ, address, ok := parseChannelSpec(m.channelAddInput.Value())
|
||||
if !ok {
|
||||
m.statusMsg = "use type:address, e.g. ntfy:my-topic"
|
||||
return m, clearStatusCmd()
|
||||
}
|
||||
m.channelAddInput.Blur()
|
||||
m.mode = modeDashboard
|
||||
m.usersLoading = true
|
||||
return m, setUserNotifyTargetCmd(m.client, m.selectedUser.ID, topic)
|
||||
return m, addChannelCmd(m.client, m.selectedUser.ID, typ, address)
|
||||
}
|
||||
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func (m Model) handleChannelRemoveKey(msg tea.KeyMsg) (Model, tea.Cmd) {
|
||||
switch msg.String() {
|
||||
case "esc":
|
||||
m.channelRemoveIn.Blur()
|
||||
m.mode = modeChannelMenu
|
||||
return m, nil
|
||||
|
||||
case "enter":
|
||||
channelID, err := strconv.ParseInt(strings.TrimSpace(m.channelRemoveIn.Value()), 10, 64)
|
||||
if err != nil || channelID <= 0 {
|
||||
m.statusMsg = "invalid channel ID — must be a positive integer"
|
||||
return m, clearStatusCmd()
|
||||
}
|
||||
m.channelRemoveIn.Blur()
|
||||
m.mode = modeDashboard
|
||||
m.usersLoading = true
|
||||
return m, removeChannelCmd(m.client, m.selectedUser.ID, channelID)
|
||||
}
|
||||
|
||||
return m, nil
|
||||
|
||||
Reference in New Issue
Block a user