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:
+41
-20
@@ -48,7 +48,9 @@ const (
|
||||
modeUserPicker
|
||||
modeTeamPicker
|
||||
modeUserCreate
|
||||
modeUserNotifyEdit
|
||||
modeChannelMenu
|
||||
modeChannelAdd
|
||||
modeChannelRemoveByID
|
||||
modeAPIKeyMenu
|
||||
modeAPIKeyCreate
|
||||
modeAPIKeyReveal
|
||||
@@ -356,7 +358,8 @@ type Model struct {
|
||||
selectedUser api.User
|
||||
userFormInputs [2]textinput.Model
|
||||
userFormFocus int
|
||||
ntfyTopicInput textinput.Model
|
||||
channelAddInput textinput.Model
|
||||
channelRemoveIn textinput.Model
|
||||
apiKeyNameInput textinput.Model
|
||||
apiKeyRevokeInput textinput.Model
|
||||
revealedAPIKey api.APIKey
|
||||
@@ -424,9 +427,13 @@ func NewModel(client *api.Client, serverURL string, refreshInterval time.Duratio
|
||||
emailIn.Placeholder = "email"
|
||||
emailIn.CharLimit = 128
|
||||
|
||||
topicIn := textinput.New()
|
||||
topicIn.Placeholder = "ntfy topic — empty clears it"
|
||||
topicIn.CharLimit = 128
|
||||
channelIn := textinput.New()
|
||||
channelIn.Placeholder = "type:address, e.g. ntfy:my-topic"
|
||||
channelIn.CharLimit = 160
|
||||
|
||||
channelRemoveIn := textinput.New()
|
||||
channelRemoveIn.Placeholder = "integer channel ID"
|
||||
channelRemoveIn.CharLimit = 20
|
||||
|
||||
keyNameIn := textinput.New()
|
||||
keyNameIn.Placeholder = "key name (e.g. laptop)"
|
||||
@@ -458,7 +465,7 @@ func NewModel(client *api.Client, serverURL string, refreshInterval time.Duratio
|
||||
}
|
||||
|
||||
for _, in := range []*textinput.Model{
|
||||
¬eIn, &snoozeIn, &usernameIn, &emailIn, &topicIn, &keyNameIn, &revokeIn,
|
||||
¬eIn, &snoozeIn, &usernameIn, &emailIn, &channelIn, &channelRemoveIn, &keyNameIn, &revokeIn,
|
||||
} {
|
||||
*in = st.Input(*in)
|
||||
}
|
||||
@@ -507,7 +514,8 @@ func NewModel(client *api.Client, serverURL string, refreshInterval time.Duratio
|
||||
teamPickerTable: teamPickerT,
|
||||
userManageTable: manageT,
|
||||
userFormInputs: [2]textinput.Model{usernameIn, emailIn},
|
||||
ntfyTopicInput: topicIn,
|
||||
channelAddInput: channelIn,
|
||||
channelRemoveIn: channelRemoveIn,
|
||||
apiKeyNameInput: keyNameIn,
|
||||
apiKeyRevokeInput: revokeIn,
|
||||
pwInputs: pwIn,
|
||||
@@ -668,11 +676,11 @@ func (m *Model) rebuildUserManageTable() {
|
||||
m.userManageTable.SetColumns(userManageColumns(m.width))
|
||||
rows := make([]table.Row, len(m.users))
|
||||
for i, u := range m.users {
|
||||
topic := u.Topic()
|
||||
if topic == "" {
|
||||
topic = "—"
|
||||
channels := u.ChannelSummary()
|
||||
if channels == "" {
|
||||
channels = "—"
|
||||
}
|
||||
rows[i] = table.Row{u.Username, u.Email, topic, userFlags(u), u.CreatedAt.UTC().Format("2006-01-02")}
|
||||
rows[i] = table.Row{u.Username, u.Email, channels, userFlags(u), u.CreatedAt.UTC().Format("2006-01-02")}
|
||||
}
|
||||
setRows(&m.userManageTable, rows)
|
||||
m.userManageTable.SetHeight(tableHeight(m.height, 10))
|
||||
@@ -768,7 +776,7 @@ func (m Model) canEditSchedule(t api.Team) bool {
|
||||
}
|
||||
|
||||
// canManageUser mirrors the server's self-or-admin rule for a user's password,
|
||||
// ntfy topic and API keys.
|
||||
// notification channels and API keys.
|
||||
func (m Model) canManageUser(u api.User) bool {
|
||||
return m.isAdmin || u.ID == m.meID
|
||||
}
|
||||
@@ -940,17 +948,17 @@ func teamPickerColumns(width int) []table.Column {
|
||||
func userManageColumns(width int) []table.Column {
|
||||
createdW := 12
|
||||
usernameW := 25
|
||||
topicW := 22
|
||||
channelsW := 22
|
||||
flagsW := 14
|
||||
// 10 = bubbles' Padding(0, 1) on each of the five cells.
|
||||
emailW := width - usernameW - topicW - flagsW - createdW - 10
|
||||
emailW := width - usernameW - channelsW - flagsW - createdW - 10
|
||||
if emailW < 15 {
|
||||
emailW = 15
|
||||
}
|
||||
return []table.Column{
|
||||
{Title: "Username", Width: usernameW},
|
||||
{Title: "Email", Width: emailW},
|
||||
{Title: "Ntfy Topic", Width: topicW},
|
||||
{Title: "Channels", Width: channelsW},
|
||||
{Title: "Flags", Width: flagsW},
|
||||
{Title: "Created", Width: createdW},
|
||||
}
|
||||
@@ -1496,12 +1504,25 @@ func createUserCmd(client *api.Client, username, email string) tea.Cmd {
|
||||
}
|
||||
}
|
||||
|
||||
// setUserNotifyTargetCmd points a user's pages at a topic, or clears it when
|
||||
// topic is empty. It re-lists afterwards so the table shows what the server
|
||||
// stored rather than what was typed.
|
||||
func setUserNotifyTargetCmd(client *api.Client, userID int64, topic string) tea.Cmd {
|
||||
// addChannelCmd gives a user a notification channel. It re-lists afterwards so
|
||||
// the table shows what the server stored rather than what was typed.
|
||||
func addChannelCmd(client *api.Client, userID int64, typ, address string) tea.Cmd {
|
||||
return func() tea.Msg {
|
||||
if _, err := client.SetUserNotifyTarget(userID, topic); err != nil {
|
||||
if _, err := client.AddChannel(userID, typ, address); err != nil {
|
||||
return userActionErrMsg{err}
|
||||
}
|
||||
users, err := client.ListUsers()
|
||||
if err != nil {
|
||||
return userActionErrMsg{err}
|
||||
}
|
||||
return usersFetchedMsg{users: users}
|
||||
}
|
||||
}
|
||||
|
||||
// removeChannelCmd deletes one of a user's channels and re-lists.
|
||||
func removeChannelCmd(client *api.Client, userID, channelID int64) tea.Cmd {
|
||||
return func() tea.Msg {
|
||||
if err := client.RemoveChannel(userID, channelID); err != nil {
|
||||
return userActionErrMsg{err}
|
||||
}
|
||||
users, err := client.ListUsers()
|
||||
|
||||
Reference in New Issue
Block a user