mirror of
https://github.com/binwiederhier/ntfy.git
synced 2026-01-19 00:27:25 +01:00
Compare commits
10 Commits
predefined
...
v2.14.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
eac523dcf9 | ||
|
|
4225ce2f42 | ||
|
|
d35dfc14d1 | ||
|
|
cef228f880 | ||
|
|
bcfb50b35a | ||
|
|
c4c4916bc8 | ||
|
|
81463614c9 | ||
|
|
15a7f86344 | ||
|
|
3c1da90f47 | ||
|
|
1b394e9bb8 |
@@ -60,6 +60,9 @@ func TestCLI_User_Add_Password_Mismatch(t *testing.T) {
|
||||
|
||||
func TestCLI_User_ChangePass(t *testing.T) {
|
||||
s, conf, port := newTestServerWithAuth(t)
|
||||
conf.AuthUsers = []*user.User{
|
||||
{Name: "philuser", Hash: "$2a$10$U4WSIYY6evyGmZaraavM2e2JeVG6EMGUKN1uUwufUeeRd4Jpg6cGC", Role: user.RoleUser}, // philuser:philpass
|
||||
}
|
||||
defer test.StopServer(t, s, port)
|
||||
|
||||
// Add user
|
||||
@@ -73,6 +76,11 @@ func TestCLI_User_ChangePass(t *testing.T) {
|
||||
stdin.WriteString("newpass\nnewpass")
|
||||
require.Nil(t, runUserCommand(app, conf, "change-pass", "phil"))
|
||||
require.Contains(t, stdout.String(), "changed password for user phil")
|
||||
|
||||
// Cannot change provisioned user's pass
|
||||
app, stdin, _, _ = newTestApp()
|
||||
stdin.WriteString("newpass\nnewpass")
|
||||
require.Error(t, runUserCommand(app, conf, "change-pass", "philuser"))
|
||||
}
|
||||
|
||||
func TestCLI_User_ChangeRole(t *testing.T) {
|
||||
|
||||
@@ -30,37 +30,37 @@ deb/rpm packages.
|
||||
|
||||
=== "x86_64/amd64"
|
||||
```bash
|
||||
wget https://github.com/binwiederhier/ntfy/releases/download/v2.13.0/ntfy_2.13.0_linux_amd64.tar.gz
|
||||
tar zxvf ntfy_2.13.0_linux_amd64.tar.gz
|
||||
sudo cp -a ntfy_2.13.0_linux_amd64/ntfy /usr/local/bin/ntfy
|
||||
sudo mkdir /etc/ntfy && sudo cp ntfy_2.13.0_linux_amd64/{client,server}/*.yml /etc/ntfy
|
||||
wget https://github.com/binwiederhier/ntfy/releases/download/v2.14.0/ntfy_2.14.0_linux_amd64.tar.gz
|
||||
tar zxvf ntfy_2.14.0_linux_amd64.tar.gz
|
||||
sudo cp -a ntfy_2.14.0_linux_amd64/ntfy /usr/local/bin/ntfy
|
||||
sudo mkdir /etc/ntfy && sudo cp ntfy_2.14.0_linux_amd64/{client,server}/*.yml /etc/ntfy
|
||||
sudo ntfy serve
|
||||
```
|
||||
|
||||
=== "armv6"
|
||||
```bash
|
||||
wget https://github.com/binwiederhier/ntfy/releases/download/v2.13.0/ntfy_2.13.0_linux_armv6.tar.gz
|
||||
tar zxvf ntfy_2.13.0_linux_armv6.tar.gz
|
||||
sudo cp -a ntfy_2.13.0_linux_armv6/ntfy /usr/bin/ntfy
|
||||
sudo mkdir /etc/ntfy && sudo cp ntfy_2.13.0_linux_armv6/{client,server}/*.yml /etc/ntfy
|
||||
wget https://github.com/binwiederhier/ntfy/releases/download/v2.14.0/ntfy_2.14.0_linux_armv6.tar.gz
|
||||
tar zxvf ntfy_2.14.0_linux_armv6.tar.gz
|
||||
sudo cp -a ntfy_2.14.0_linux_armv6/ntfy /usr/bin/ntfy
|
||||
sudo mkdir /etc/ntfy && sudo cp ntfy_2.14.0_linux_armv6/{client,server}/*.yml /etc/ntfy
|
||||
sudo ntfy serve
|
||||
```
|
||||
|
||||
=== "armv7/armhf"
|
||||
```bash
|
||||
wget https://github.com/binwiederhier/ntfy/releases/download/v2.13.0/ntfy_2.13.0_linux_armv7.tar.gz
|
||||
tar zxvf ntfy_2.13.0_linux_armv7.tar.gz
|
||||
sudo cp -a ntfy_2.13.0_linux_armv7/ntfy /usr/bin/ntfy
|
||||
sudo mkdir /etc/ntfy && sudo cp ntfy_2.13.0_linux_armv7/{client,server}/*.yml /etc/ntfy
|
||||
wget https://github.com/binwiederhier/ntfy/releases/download/v2.14.0/ntfy_2.14.0_linux_armv7.tar.gz
|
||||
tar zxvf ntfy_2.14.0_linux_armv7.tar.gz
|
||||
sudo cp -a ntfy_2.14.0_linux_armv7/ntfy /usr/bin/ntfy
|
||||
sudo mkdir /etc/ntfy && sudo cp ntfy_2.14.0_linux_armv7/{client,server}/*.yml /etc/ntfy
|
||||
sudo ntfy serve
|
||||
```
|
||||
|
||||
=== "arm64"
|
||||
```bash
|
||||
wget https://github.com/binwiederhier/ntfy/releases/download/v2.13.0/ntfy_2.13.0_linux_arm64.tar.gz
|
||||
tar zxvf ntfy_2.13.0_linux_arm64.tar.gz
|
||||
sudo cp -a ntfy_2.13.0_linux_arm64/ntfy /usr/bin/ntfy
|
||||
sudo mkdir /etc/ntfy && sudo cp ntfy_2.13.0_linux_arm64/{client,server}/*.yml /etc/ntfy
|
||||
wget https://github.com/binwiederhier/ntfy/releases/download/v2.14.0/ntfy_2.14.0_linux_arm64.tar.gz
|
||||
tar zxvf ntfy_2.14.0_linux_arm64.tar.gz
|
||||
sudo cp -a ntfy_2.14.0_linux_arm64/ntfy /usr/bin/ntfy
|
||||
sudo mkdir /etc/ntfy && sudo cp ntfy_2.14.0_linux_arm64/{client,server}/*.yml /etc/ntfy
|
||||
sudo ntfy serve
|
||||
```
|
||||
|
||||
@@ -110,7 +110,7 @@ Manually installing the .deb file:
|
||||
|
||||
=== "x86_64/amd64"
|
||||
```bash
|
||||
wget https://github.com/binwiederhier/ntfy/releases/download/v2.13.0/ntfy_2.13.0_linux_amd64.deb
|
||||
wget https://github.com/binwiederhier/ntfy/releases/download/v2.14.0/ntfy_2.14.0_linux_amd64.deb
|
||||
sudo dpkg -i ntfy_*.deb
|
||||
sudo systemctl enable ntfy
|
||||
sudo systemctl start ntfy
|
||||
@@ -118,7 +118,7 @@ Manually installing the .deb file:
|
||||
|
||||
=== "armv6"
|
||||
```bash
|
||||
wget https://github.com/binwiederhier/ntfy/releases/download/v2.13.0/ntfy_2.13.0_linux_armv6.deb
|
||||
wget https://github.com/binwiederhier/ntfy/releases/download/v2.14.0/ntfy_2.14.0_linux_armv6.deb
|
||||
sudo dpkg -i ntfy_*.deb
|
||||
sudo systemctl enable ntfy
|
||||
sudo systemctl start ntfy
|
||||
@@ -126,7 +126,7 @@ Manually installing the .deb file:
|
||||
|
||||
=== "armv7/armhf"
|
||||
```bash
|
||||
wget https://github.com/binwiederhier/ntfy/releases/download/v2.13.0/ntfy_2.13.0_linux_armv7.deb
|
||||
wget https://github.com/binwiederhier/ntfy/releases/download/v2.14.0/ntfy_2.14.0_linux_armv7.deb
|
||||
sudo dpkg -i ntfy_*.deb
|
||||
sudo systemctl enable ntfy
|
||||
sudo systemctl start ntfy
|
||||
@@ -134,7 +134,7 @@ Manually installing the .deb file:
|
||||
|
||||
=== "arm64"
|
||||
```bash
|
||||
wget https://github.com/binwiederhier/ntfy/releases/download/v2.13.0/ntfy_2.13.0_linux_arm64.deb
|
||||
wget https://github.com/binwiederhier/ntfy/releases/download/v2.14.0/ntfy_2.14.0_linux_arm64.deb
|
||||
sudo dpkg -i ntfy_*.deb
|
||||
sudo systemctl enable ntfy
|
||||
sudo systemctl start ntfy
|
||||
@@ -144,28 +144,28 @@ Manually installing the .deb file:
|
||||
|
||||
=== "x86_64/amd64"
|
||||
```bash
|
||||
sudo rpm -ivh https://github.com/binwiederhier/ntfy/releases/download/v2.13.0/ntfy_2.13.0_linux_amd64.rpm
|
||||
sudo rpm -ivh https://github.com/binwiederhier/ntfy/releases/download/v2.14.0/ntfy_2.14.0_linux_amd64.rpm
|
||||
sudo systemctl enable ntfy
|
||||
sudo systemctl start ntfy
|
||||
```
|
||||
|
||||
=== "armv6"
|
||||
```bash
|
||||
sudo rpm -ivh https://github.com/binwiederhier/ntfy/releases/download/v2.13.0/ntfy_2.13.0_linux_armv6.rpm
|
||||
sudo rpm -ivh https://github.com/binwiederhier/ntfy/releases/download/v2.14.0/ntfy_2.14.0_linux_armv6.rpm
|
||||
sudo systemctl enable ntfy
|
||||
sudo systemctl start ntfy
|
||||
```
|
||||
|
||||
=== "armv7/armhf"
|
||||
```bash
|
||||
sudo rpm -ivh https://github.com/binwiederhier/ntfy/releases/download/v2.13.0/ntfy_2.13.0_linux_armv7.rpm
|
||||
sudo rpm -ivh https://github.com/binwiederhier/ntfy/releases/download/v2.14.0/ntfy_2.14.0_linux_armv7.rpm
|
||||
sudo systemctl enable ntfy
|
||||
sudo systemctl start ntfy
|
||||
```
|
||||
|
||||
=== "arm64"
|
||||
```bash
|
||||
sudo rpm -ivh https://github.com/binwiederhier/ntfy/releases/download/v2.13.0/ntfy_2.13.0_linux_arm64.rpm
|
||||
sudo rpm -ivh https://github.com/binwiederhier/ntfy/releases/download/v2.14.0/ntfy_2.14.0_linux_arm64.rpm
|
||||
sudo systemctl enable ntfy
|
||||
sudo systemctl start ntfy
|
||||
```
|
||||
@@ -195,18 +195,18 @@ NixOS also supports [declarative setup of the ntfy server](https://search.nixos.
|
||||
|
||||
## macOS
|
||||
The [ntfy CLI](subscribe/cli.md) (`ntfy publish` and `ntfy subscribe` only) is supported on macOS as well.
|
||||
To install, please [download the tarball](https://github.com/binwiederhier/ntfy/releases/download/v2.13.0/ntfy_2.13.0_darwin_all.tar.gz),
|
||||
To install, please [download the tarball](https://github.com/binwiederhier/ntfy/releases/download/v2.14.0/ntfy_2.14.0_darwin_all.tar.gz),
|
||||
extract it and place it somewhere in your `PATH` (e.g. `/usr/local/bin/ntfy`).
|
||||
|
||||
If run as `root`, ntfy will look for its config at `/etc/ntfy/client.yml`. For all other users, it'll look for it at
|
||||
`~/Library/Application Support/ntfy/client.yml` (sample included in the tarball).
|
||||
|
||||
```bash
|
||||
curl -L https://github.com/binwiederhier/ntfy/releases/download/v2.13.0/ntfy_2.13.0_darwin_all.tar.gz > ntfy_2.13.0_darwin_all.tar.gz
|
||||
tar zxvf ntfy_2.13.0_darwin_all.tar.gz
|
||||
sudo cp -a ntfy_2.13.0_darwin_all/ntfy /usr/local/bin/ntfy
|
||||
curl -L https://github.com/binwiederhier/ntfy/releases/download/v2.14.0/ntfy_2.14.0_darwin_all.tar.gz > ntfy_2.14.0_darwin_all.tar.gz
|
||||
tar zxvf ntfy_2.14.0_darwin_all.tar.gz
|
||||
sudo cp -a ntfy_2.14.0_darwin_all/ntfy /usr/local/bin/ntfy
|
||||
mkdir ~/Library/Application\ Support/ntfy
|
||||
cp ntfy_2.13.0_darwin_all/client/client.yml ~/Library/Application\ Support/ntfy/client.yml
|
||||
cp ntfy_2.14.0_darwin_all/client/client.yml ~/Library/Application\ Support/ntfy/client.yml
|
||||
ntfy --help
|
||||
```
|
||||
|
||||
@@ -224,7 +224,7 @@ brew install ntfy
|
||||
|
||||
## Windows
|
||||
The [ntfy CLI](subscribe/cli.md) (`ntfy publish` and `ntfy subscribe` only) is supported on Windows as well.
|
||||
To install, please [download the latest ZIP](https://github.com/binwiederhier/ntfy/releases/download/v2.13.0/ntfy_2.13.0_windows_amd64.zip),
|
||||
To install, please [download the latest ZIP](https://github.com/binwiederhier/ntfy/releases/download/v2.14.0/ntfy_2.14.0_windows_amd64.zip),
|
||||
extract it and place the `ntfy.exe` binary somewhere in your `%Path%`.
|
||||
|
||||
The default path for the client config file is at `%AppData%\ntfy\client.yml` (not created automatically, sample in the ZIP file).
|
||||
|
||||
@@ -2,6 +2,22 @@
|
||||
Binaries for all releases can be found on the GitHub releases pages for the [ntfy server](https://github.com/binwiederhier/ntfy/releases)
|
||||
and the [ntfy Android app](https://github.com/binwiederhier/ntfy-android/releases).
|
||||
|
||||
### ntfy server v2.14.0
|
||||
Released August 5, 2025
|
||||
|
||||
This release adds support for [declarative users](config.md#users-via-the-config), [declarative ACL entries](config.md#acl-entries-via-the-config) and [declarative tokens](config.md#tokens-via-the-config). This allows you to define users, ACL entries and tokens in the config file, which is useful for static deployments or deployments that use a configuration management system.
|
||||
|
||||
It also adds support for [pre-defined templates](publish.md#pre-defined-templates) and [custom templates](publish.md#custom-templates) for enhanced JSON webhook support, as well as advanced [template functions](publish.md#template-functions) based on the [Sprig](https://github.com/Masterminds/sprig) functions.
|
||||
|
||||
❤️ If you like ntfy, **please consider sponsoring me** via [GitHub Sponsors](https://github.com/sponsors/binwiederhier), [Liberapay](https://en.liberapay.com/ntfy/), Bitcoin (`1626wjrw3uWk9adyjCfYwafw4sQWujyjn8`), or by buying a [paid plan via the web app](https://ntfy.sh/app). ntfy
|
||||
will always remain open source.
|
||||
|
||||
**Features:**
|
||||
|
||||
* [Declarative users](config.md#users-via-the-config), [declarative ACL entries](config.md#acl-entries-via-the-config) and [declarative tokens](config.md#tokens-via-the-config) ([#464](https://github.com/binwiederhier/ntfy/issues/464), [#1384](https://github.com/binwiederhier/ntfy/pull/1384), [#1413](https://github.com/binwiederhier/ntfy/pull/1413), thanks to [pinpox](https://github.com/pinpox) for reporting, to [@wunter8](https://github.com/wunter8) for reviewing and implementing parts of it)
|
||||
* [Pre-defined templates](publish.md#pre-defined-templates) and [custom templates](publish.md#custom-templates) for enhanced JSON webhook support ([#1390](https://github.com/binwiederhier/ntfy/pull/1390))
|
||||
* Support of advanced [template functions](publish.md#template-functions) based on the [Sprig](https://github.com/Masterminds/sprig) library ([#1121](https://github.com/binwiederhier/ntfy/issues/1121), thanks to [@davidatkinsondoyle](https://github.com/davidatkinsondoyle) for reporting, to [@wunter8](https://github.com/wunter8) for implementing, and to the Sprig team for their work)
|
||||
|
||||
### ntfy server v2.13.0
|
||||
Released July 10, 2025
|
||||
|
||||
@@ -1452,14 +1468,6 @@ and the [ntfy Android app](https://github.com/binwiederhier/ntfy-android/release
|
||||
|
||||
## Not released yet
|
||||
|
||||
### ntfy server v2.14.0 (UNRELEASED)
|
||||
|
||||
**Features:**
|
||||
|
||||
* [Declarative users](config.md#users-via-the-config), [declarative ACL entries](config.md#acl-entries-via-the-config) and [declarative tokens](config.md#tokens-via-the-config) ([#464](https://github.com/binwiederhier/ntfy/issues/464), [#1384](https://github.com/binwiederhier/ntfy/pull/1384), thanks to [pinpox](https://github.com/pinpox) for reporting, to [@wunter8](https://github.com/wunter8) for reviewing)
|
||||
* [Pre-defined templates](publish.md#pre-defined-templates) and [custom templates](publish.md#custom-templates) for enhanced JSON webhook support ([#1390](https://github.com/binwiederhier/ntfy/pull/1390))
|
||||
* Support of advanced [template functions](publish.md#template-functions) based on the [Sprig](https://github.com/Masterminds/sprig) library ([#1121](https://github.com/binwiederhier/ntfy/issues/1121), thanks to [@davidatkinsondoyle](https://github.com/davidatkinsondoyle) for reporting, to [@wunter8](https://github.com/wunter8) for implementing, and to the Sprig team for their work)
|
||||
|
||||
### ntfy Android app v1.16.1 (UNRELEASED)
|
||||
|
||||
**Features:**
|
||||
|
||||
12
go.mod
12
go.mod
@@ -30,10 +30,10 @@ replace github.com/emersion/go-smtp => github.com/emersion/go-smtp v0.17.0 // Pi
|
||||
require github.com/pkg/errors v0.9.1 // indirect
|
||||
|
||||
require (
|
||||
firebase.google.com/go/v4 v4.17.0
|
||||
firebase.google.com/go/v4 v4.18.0
|
||||
github.com/SherClockHolmes/webpush-go v1.4.0
|
||||
github.com/microcosm-cc/bluemonday v1.0.27
|
||||
github.com/prometheus/client_golang v1.22.0
|
||||
github.com/prometheus/client_golang v1.23.0
|
||||
github.com/stripe/stripe-go/v74 v74.30.0
|
||||
golang.org/x/text v0.27.0
|
||||
)
|
||||
@@ -61,7 +61,7 @@ require (
|
||||
github.com/envoyproxy/go-control-plane/envoy v1.32.4 // indirect
|
||||
github.com/envoyproxy/protoc-gen-validate v1.2.1 // indirect
|
||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||
github.com/go-jose/go-jose/v4 v4.1.1 // indirect
|
||||
github.com/go-jose/go-jose/v4 v4.1.2 // indirect
|
||||
github.com/go-logr/logr v1.4.3 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/golang-jwt/jwt/v4 v4.5.2 // indirect
|
||||
@@ -95,9 +95,9 @@ require (
|
||||
golang.org/x/net v0.42.0 // indirect
|
||||
golang.org/x/sys v0.34.0 // indirect
|
||||
google.golang.org/appengine/v2 v2.0.6 // indirect
|
||||
google.golang.org/genproto v0.0.0-20250728155136-f173205681a0 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250728155136-f173205681a0 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250728155136-f173205681a0 // indirect
|
||||
google.golang.org/genproto v0.0.0-20250804133106-a7a43d27e69b // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250804133106-a7a43d27e69b // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250804133106-a7a43d27e69b // indirect
|
||||
google.golang.org/grpc v1.74.2 // indirect
|
||||
google.golang.org/protobuf v1.36.6 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
|
||||
24
go.sum
24
go.sum
@@ -22,8 +22,8 @@ cloud.google.com/go/storage v1.56.0 h1:iixmq2Fse2tqxMbWhLWC9HfBj1qdxqAmiK8/eqtsL
|
||||
cloud.google.com/go/storage v1.56.0/go.mod h1:Tpuj6t4NweCLzlNbw9Z9iwxEkrSem20AetIeH/shgVU=
|
||||
cloud.google.com/go/trace v1.11.6 h1:2O2zjPzqPYAHrn3OKl029qlqG6W8ZdYaOWRyr8NgMT4=
|
||||
cloud.google.com/go/trace v1.11.6/go.mod h1:GA855OeDEBiBMzcckLPE2kDunIpC72N+Pq8WFieFjnI=
|
||||
firebase.google.com/go/v4 v4.17.0 h1:Bih69QV/k0YKPA1qUX04ln0aPT9IERrAo2ezibcngzE=
|
||||
firebase.google.com/go/v4 v4.17.0/go.mod h1:aAPJq/bOyb23tBlc1K6GR+2E8sOGAeJSc8wIJVgl9SM=
|
||||
firebase.google.com/go/v4 v4.18.0 h1:S+g0P72oDGqOaG4wlLErX3zQmU9plVdu7j+Bc3R1qFw=
|
||||
firebase.google.com/go/v4 v4.18.0/go.mod h1:P7UfBpzc8+Z3MckX79+zsWzKVfpGryr6HLbAe7gCWfs=
|
||||
github.com/AlekSi/pointer v1.2.0 h1:glcy/gc4h8HnG2Z3ZECSzZ1IX1x2JxRVuDzaJwQE0+w=
|
||||
github.com/AlekSi/pointer v1.2.0/go.mod h1:gZGfd3dpW4vEc/UlyfKKi1roIqcCgwOIvb0tSNSBle0=
|
||||
github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg=
|
||||
@@ -70,8 +70,8 @@ github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2
|
||||
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||
github.com/gabriel-vasile/mimetype v1.4.9 h1:5k+WDwEsD9eTLL8Tz3L0VnmVh9QxGjRmjBvAG7U/oYY=
|
||||
github.com/gabriel-vasile/mimetype v1.4.9/go.mod h1:WnSQhFKJuBlRyLiKohA/2DtIlPFAbguNaG7QCHcyGok=
|
||||
github.com/go-jose/go-jose/v4 v4.1.1 h1:JYhSgy4mXXzAdF3nUx3ygx347LRXJRrpgyU3adRmkAI=
|
||||
github.com/go-jose/go-jose/v4 v4.1.1/go.mod h1:BdsZGqgdO3b6tTc6LSE56wcDbMMLuPsw5d4ZD5f94kA=
|
||||
github.com/go-jose/go-jose/v4 v4.1.2 h1:TK/7NqRQZfgAh+Td8AlsrvtPoUyiHh0LqVvokh+1vHI=
|
||||
github.com/go-jose/go-jose/v4 v4.1.2/go.mod h1:22cg9HWM1pOlnRiY+9cQYJ9XHmya1bYW8OeDM6Ku6Oo=
|
||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
|
||||
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
@@ -127,8 +127,8 @@ github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q=
|
||||
github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0=
|
||||
github.com/prometheus/client_golang v1.23.0 h1:ust4zpdl9r4trLY/gSjlm07PuiBq2ynaXXlptpfy8Uc=
|
||||
github.com/prometheus/client_golang v1.23.0/go.mod h1:i/o0R9ByOnHX0McrTMTyhYvKE4haaf2mW08I+jGAjEE=
|
||||
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
|
||||
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
|
||||
github.com/prometheus/common v0.65.0 h1:QDwzd+G1twt//Kwj/Ww6E9FQq1iVMmODnILtW1t2VzE=
|
||||
@@ -265,12 +265,12 @@ google.golang.org/api v0.244.0 h1:lpkP8wVibSKr++NCD36XzTk/IzeKJ3klj7vbj+XU5pE=
|
||||
google.golang.org/api v0.244.0/go.mod h1:dMVhVcylamkirHdzEBAIQWUCgqY885ivNeZYd7VAVr8=
|
||||
google.golang.org/appengine/v2 v2.0.6 h1:LvPZLGuchSBslPBp+LAhihBeGSiRh1myRoYK4NtuBIw=
|
||||
google.golang.org/appengine/v2 v2.0.6/go.mod h1:WoEXGoXNfa0mLvaH5sV3ZSGXwVmy8yf7Z1JKf3J3wLI=
|
||||
google.golang.org/genproto v0.0.0-20250728155136-f173205681a0 h1:btBcgujH2+KIWEfz0s7Cdtt9R7hpwM4SAEXAdXf/ddw=
|
||||
google.golang.org/genproto v0.0.0-20250728155136-f173205681a0/go.mod h1:Q4yZQ3kmmIyg6HsMjCGx2vQ8gzN+dntaPmFWz6Zj0fo=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250728155136-f173205681a0 h1:0UOBWO4dC+e51ui0NFKSPbkHHiQ4TmrEfEZMLDyRmY8=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250728155136-f173205681a0/go.mod h1:8ytArBbtOy2xfht+y2fqKd5DRDJRUQhqbyEnQ4bDChs=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250728155136-f173205681a0 h1:MAKi5q709QWfnkkpNQ0M12hYJ1+e8qYVDyowc4U1XZM=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250728155136-f173205681a0/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
|
||||
google.golang.org/genproto v0.0.0-20250804133106-a7a43d27e69b h1:eZTgydvqZO44zyTZAvMaSyAxccZZdraiSAGvqOczVvk=
|
||||
google.golang.org/genproto v0.0.0-20250804133106-a7a43d27e69b/go.mod h1:suyz2QBHQKlGIF92HEEsCfO1SwxXdk7PFLz+Zd9Uah4=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250804133106-a7a43d27e69b h1:ULiyYQ0FdsJhwwZUwbaXpZF5yUE3h+RA+gxvBu37ucc=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250804133106-a7a43d27e69b/go.mod h1:oDOGiMSXHL4sDTJvFvIB9nRQCGdLP1o/iVaqQK8zB+M=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250804133106-a7a43d27e69b h1:zPKJod4w6F1+nRGDI9ubnXYhU9NSWoFAijkHkUXeTK8=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250804133106-a7a43d27e69b/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
|
||||
google.golang.org/grpc v1.74.2 h1:WoosgB65DlWVC9FqI82dGsZhWFNBSLjQ84bjROOpMu4=
|
||||
google.golang.org/grpc v1.74.2/go.mod h1:CtQ+BGjaAIXHs/5YS3i473GqwBBa1zGQNevxdeBEXrM=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
|
||||
@@ -132,6 +132,8 @@ var (
|
||||
errHTTPConflictTopicReserved = &errHTTP{40902, http.StatusConflict, "conflict: access control entry for topic or topic pattern already exists", "", nil}
|
||||
errHTTPConflictSubscriptionExists = &errHTTP{40903, http.StatusConflict, "conflict: topic subscription already exists", "", nil}
|
||||
errHTTPConflictPhoneNumberExists = &errHTTP{40904, http.StatusConflict, "conflict: phone number already exists", "", nil}
|
||||
errHTTPConflictProvisionedUserChange = &errHTTP{40905, http.StatusConflict, "conflict: cannot change or delete provisioned user", "", nil}
|
||||
errHTTPConflictProvisionedTokenChange = &errHTTP{40906, http.StatusConflict, "conflict: cannot change or delete provisioned token", "", nil}
|
||||
errHTTPGonePhoneVerificationExpired = &errHTTP{41001, http.StatusGone, "phone number verification expired or does not exist", "", nil}
|
||||
errHTTPEntityTooLargeAttachment = &errHTTP{41301, http.StatusRequestEntityTooLarge, "attachment too large, or bandwidth limit reached", "https://ntfy.sh/docs/publish/#limitations", nil}
|
||||
errHTTPEntityTooLargeMatrixRequest = &errHTTP{41302, http.StatusRequestEntityTooLarge, "Matrix request is larger than the max allowed length", "", nil}
|
||||
|
||||
@@ -85,6 +85,7 @@ func (s *Server) handleAccountGet(w http.ResponseWriter, r *http.Request, v *vis
|
||||
response.Username = u.Name
|
||||
response.Role = string(u.Role)
|
||||
response.SyncTopic = u.SyncTopic
|
||||
response.Provisioned = u.Provisioned
|
||||
if u.Prefs != nil {
|
||||
if u.Prefs.Language != nil {
|
||||
response.Language = *u.Prefs.Language
|
||||
@@ -139,11 +140,12 @@ func (s *Server) handleAccountGet(w http.ResponseWriter, r *http.Request, v *vis
|
||||
lastOrigin = t.LastOrigin.String()
|
||||
}
|
||||
response.Tokens = append(response.Tokens, &apiAccountTokenResponse{
|
||||
Token: t.Value,
|
||||
Label: t.Label,
|
||||
LastAccess: t.LastAccess.Unix(),
|
||||
LastOrigin: lastOrigin,
|
||||
Expires: t.Expires.Unix(),
|
||||
Token: t.Value,
|
||||
Label: t.Label,
|
||||
LastAccess: t.LastAccess.Unix(),
|
||||
LastOrigin: lastOrigin,
|
||||
Expires: t.Expires.Unix(),
|
||||
Provisioned: t.Provisioned,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -174,6 +176,12 @@ func (s *Server) handleAccountDelete(w http.ResponseWriter, r *http.Request, v *
|
||||
if _, err := s.userManager.Authenticate(u.Name, req.Password); err != nil {
|
||||
return errHTTPBadRequestIncorrectPasswordConfirmation
|
||||
}
|
||||
if err := s.userManager.CanChangeUser(u.Name); err != nil {
|
||||
if errors.Is(err, user.ErrProvisionedUserChange) {
|
||||
return errHTTPConflictProvisionedUserChange
|
||||
}
|
||||
return err
|
||||
}
|
||||
if s.webPush != nil && u.ID != "" {
|
||||
if err := s.webPush.RemoveSubscriptionsByUserID(u.ID); err != nil {
|
||||
logvr(v, r).Err(err).Warn("Error removing web push subscriptions for %s", u.Name)
|
||||
@@ -208,6 +216,9 @@ func (s *Server) handleAccountPasswordChange(w http.ResponseWriter, r *http.Requ
|
||||
}
|
||||
logvr(v, r).Tag(tagAccount).Debug("Changing password for user %s", u.Name)
|
||||
if err := s.userManager.ChangePassword(u.Name, req.NewPassword, false); err != nil {
|
||||
if errors.Is(err, user.ErrProvisionedUserChange) {
|
||||
return errHTTPConflictProvisionedUserChange
|
||||
}
|
||||
return err
|
||||
}
|
||||
return s.writeJSON(w, newSuccessResponse())
|
||||
@@ -274,6 +285,9 @@ func (s *Server) handleAccountTokenUpdate(w http.ResponseWriter, r *http.Request
|
||||
Debug("Updating token for user %s as deleted", u.Name)
|
||||
token, err := s.userManager.ChangeToken(u.ID, req.Token, req.Label, expires)
|
||||
if err != nil {
|
||||
if errors.Is(err, user.ErrProvisionedTokenChange) {
|
||||
return errHTTPConflictProvisionedTokenChange
|
||||
}
|
||||
return err
|
||||
}
|
||||
response := &apiAccountTokenResponse{
|
||||
@@ -296,6 +310,9 @@ func (s *Server) handleAccountTokenDelete(w http.ResponseWriter, r *http.Request
|
||||
}
|
||||
}
|
||||
if err := s.userManager.RemoveToken(u.ID, token); err != nil {
|
||||
if errors.Is(err, user.ErrProvisionedTokenChange) {
|
||||
return errHTTPConflictProvisionedTokenChange
|
||||
}
|
||||
return err
|
||||
}
|
||||
logvr(v, r).
|
||||
|
||||
@@ -251,7 +251,11 @@ func TestAccount_Subscription_AddUpdateDelete(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestAccount_ChangePassword(t *testing.T) {
|
||||
s := newTestServer(t, newTestConfigWithAuthFile(t))
|
||||
conf := newTestConfigWithAuthFile(t)
|
||||
conf.AuthUsers = []*user.User{
|
||||
{Name: "philuser", Hash: "$2a$10$U4WSIYY6evyGmZaraavM2e2JeVG6EMGUKN1uUwufUeeRd4Jpg6cGC", Role: user.RoleUser}, // philuser:philpass
|
||||
}
|
||||
s := newTestServer(t, conf)
|
||||
defer s.closeDatabases()
|
||||
|
||||
require.Nil(t, s.userManager.AddUser("phil", "phil", user.RoleUser, false))
|
||||
@@ -281,6 +285,12 @@ func TestAccount_ChangePassword(t *testing.T) {
|
||||
"Authorization": util.BasicAuth("phil", "new password"),
|
||||
})
|
||||
require.Equal(t, 200, rr.Code)
|
||||
|
||||
// Cannot change password of provisioned user
|
||||
rr = request(t, s, "POST", "/v1/account/password", `{"password": "philpass", "new_password": "new password"}`, map[string]string{
|
||||
"Authorization": util.BasicAuth("philuser", "philpass"),
|
||||
})
|
||||
require.Equal(t, 409, rr.Code)
|
||||
}
|
||||
|
||||
func TestAccount_ChangePassword_NoAccount(t *testing.T) {
|
||||
|
||||
@@ -360,11 +360,12 @@ type apiAccountTokenUpdateRequest struct {
|
||||
}
|
||||
|
||||
type apiAccountTokenResponse struct {
|
||||
Token string `json:"token"`
|
||||
Label string `json:"label,omitempty"`
|
||||
LastAccess int64 `json:"last_access,omitempty"`
|
||||
LastOrigin string `json:"last_origin,omitempty"`
|
||||
Expires int64 `json:"expires,omitempty"` // Unix timestamp
|
||||
Token string `json:"token"`
|
||||
Label string `json:"label,omitempty"`
|
||||
LastAccess int64 `json:"last_access,omitempty"`
|
||||
LastOrigin string `json:"last_origin,omitempty"`
|
||||
Expires int64 `json:"expires,omitempty"` // Unix timestamp
|
||||
Provisioned bool `json:"provisioned,omitempty"` // True if this token was provisioned by the server config
|
||||
}
|
||||
|
||||
type apiAccountPhoneNumberVerifyRequest struct {
|
||||
@@ -426,6 +427,7 @@ type apiAccountResponse struct {
|
||||
Username string `json:"username"`
|
||||
Role string `json:"role,omitempty"`
|
||||
SyncTopic string `json:"sync_topic,omitempty"`
|
||||
Provisioned bool `json:"provisioned,omitempty"`
|
||||
Language string `json:"language,omitempty"`
|
||||
Notification *user.NotificationPrefs `json:"notification,omitempty"`
|
||||
Subscriptions []*user.Subscription `json:"subscriptions,omitempty"`
|
||||
|
||||
@@ -773,6 +773,9 @@ func (a *Manager) ChangeToken(userID, token string, label *string, expires *time
|
||||
if token == "" {
|
||||
return nil, errNoTokenProvided
|
||||
}
|
||||
if err := a.CanChangeToken(userID, token); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tx, err := a.db.Begin()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -796,6 +799,9 @@ func (a *Manager) ChangeToken(userID, token string, label *string, expires *time
|
||||
|
||||
// RemoveToken deletes the token defined in User.Token
|
||||
func (a *Manager) RemoveToken(userID, token string) error {
|
||||
if err := a.CanChangeToken(userID, token); err != nil {
|
||||
return err
|
||||
}
|
||||
return execTx(a.db, func(tx *sql.Tx) error {
|
||||
return a.removeTokenTx(tx, userID, token)
|
||||
})
|
||||
@@ -811,6 +817,17 @@ func (a *Manager) removeTokenTx(tx *sql.Tx, userID, token string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// CanChangeToken checks if the token can be changed. If the token is provisioned, it cannot be changed.
|
||||
func (a *Manager) CanChangeToken(userID, token string) error {
|
||||
t, err := a.Token(userID, token)
|
||||
if err != nil {
|
||||
return err
|
||||
} else if t.Provisioned {
|
||||
return ErrProvisionedTokenChange
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// RemoveExpiredTokens deletes all expired tokens from the database
|
||||
func (a *Manager) RemoveExpiredTokens() error {
|
||||
if _, err := a.db.Exec(deleteExpiredTokensQuery, time.Now().Unix()); err != nil {
|
||||
@@ -1072,6 +1089,9 @@ func (a *Manager) addUserTx(tx *sql.Tx, username, password string, role Role, ha
|
||||
// RemoveUser deletes the user with the given username. The function returns nil on success, even
|
||||
// if the user did not exist in the first place.
|
||||
func (a *Manager) RemoveUser(username string) error {
|
||||
if err := a.CanChangeUser(username); err != nil {
|
||||
return err
|
||||
}
|
||||
return execTx(a.db, func(tx *sql.Tx) error {
|
||||
return a.removeUserTx(tx, username)
|
||||
})
|
||||
@@ -1389,11 +1409,26 @@ func (a *Manager) ReservationOwner(topic string) (string, error) {
|
||||
|
||||
// ChangePassword changes a user's password
|
||||
func (a *Manager) ChangePassword(username, password string, hashed bool) error {
|
||||
if err := a.CanChangeUser(username); err != nil {
|
||||
return err
|
||||
}
|
||||
return execTx(a.db, func(tx *sql.Tx) error {
|
||||
return a.changePasswordTx(tx, username, password, hashed)
|
||||
})
|
||||
}
|
||||
|
||||
// CanChangeUser checks if the user with the given username can be changed.
|
||||
// This is used to prevent changes to provisioned users, which are defined in the config file.
|
||||
func (a *Manager) CanChangeUser(username string) error {
|
||||
user, err := a.User(username)
|
||||
if err != nil {
|
||||
return err
|
||||
} else if user.Provisioned {
|
||||
return ErrProvisionedUserChange
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *Manager) changePasswordTx(tx *sql.Tx, username, password string, hashed bool) error {
|
||||
var hash string
|
||||
var err error
|
||||
@@ -1417,6 +1452,9 @@ func (a *Manager) changePasswordTx(tx *sql.Tx, username, password string, hashed
|
||||
// ChangeRole changes a user's role. When a role is changed from RoleUser to RoleAdmin,
|
||||
// all existing access control entries (Grant) are removed, since they are no longer needed.
|
||||
func (a *Manager) ChangeRole(username string, role Role) error {
|
||||
if err := a.CanChangeUser(username); err != nil {
|
||||
return err
|
||||
}
|
||||
return execTx(a.db, func(tx *sql.Tx) error {
|
||||
return a.changeRoleTx(tx, username, role)
|
||||
})
|
||||
@@ -1437,14 +1475,8 @@ func (a *Manager) changeRoleTx(tx *sql.Tx, username string, role Role) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// ChangeProvisioned changes the provisioned status of a user. This is used to mark users as
|
||||
// changeProvisionedTx changes the provisioned status of a user. This is used to mark users as
|
||||
// provisioned. A provisioned user is a user defined in the config file.
|
||||
func (a *Manager) ChangeProvisioned(username string, provisioned bool) error {
|
||||
return execTx(a.db, func(tx *sql.Tx) error {
|
||||
return a.changeProvisionedTx(tx, username, provisioned)
|
||||
})
|
||||
}
|
||||
|
||||
func (a *Manager) changeProvisionedTx(tx *sql.Tx, username string, provisioned bool) error {
|
||||
if _, err := tx.Exec(updateUserProvisionedQuery, provisioned, username); err != nil {
|
||||
return err
|
||||
@@ -1670,7 +1702,7 @@ func (a *Manager) Tiers() ([]*Tier, error) {
|
||||
tiers := make([]*Tier, 0)
|
||||
for {
|
||||
tier, err := a.readTier(rows)
|
||||
if err == ErrTierNotFound {
|
||||
if errors.Is(err, ErrTierNotFound) {
|
||||
break
|
||||
} else if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -1209,6 +1209,9 @@ func TestManager_WithProvisionedUsers(t *testing.T) {
|
||||
require.Equal(t, "tk_u48wqendnkx9er21pqqcadlytbutx", tokens[1].Value)
|
||||
require.Equal(t, "Another token", tokens[1].Label)
|
||||
|
||||
// Try changing provisioned user's password
|
||||
require.Error(t, a.ChangePassword("philuser", "new-pass", false))
|
||||
|
||||
// Re-open the DB again (third app start)
|
||||
require.Nil(t, a.db.Close())
|
||||
conf.Users = []*User{}
|
||||
|
||||
@@ -244,15 +244,17 @@ const (
|
||||
|
||||
// Error constants used by the package
|
||||
var (
|
||||
ErrUnauthenticated = errors.New("unauthenticated")
|
||||
ErrUnauthorized = errors.New("unauthorized")
|
||||
ErrInvalidArgument = errors.New("invalid argument")
|
||||
ErrUserNotFound = errors.New("user not found")
|
||||
ErrUserExists = errors.New("user already exists")
|
||||
ErrPasswordHashInvalid = errors.New("password hash but be a bcrypt hash, use 'ntfy user hash' to generate")
|
||||
ErrTierNotFound = errors.New("tier not found")
|
||||
ErrTokenNotFound = errors.New("token not found")
|
||||
ErrPhoneNumberNotFound = errors.New("phone number not found")
|
||||
ErrTooManyReservations = errors.New("new tier has lower reservation limit")
|
||||
ErrPhoneNumberExists = errors.New("phone number already exists")
|
||||
ErrUnauthenticated = errors.New("unauthenticated")
|
||||
ErrUnauthorized = errors.New("unauthorized")
|
||||
ErrInvalidArgument = errors.New("invalid argument")
|
||||
ErrUserNotFound = errors.New("user not found")
|
||||
ErrUserExists = errors.New("user already exists")
|
||||
ErrPasswordHashInvalid = errors.New("password hash but be a bcrypt hash, use 'ntfy user hash' to generate")
|
||||
ErrTierNotFound = errors.New("tier not found")
|
||||
ErrTokenNotFound = errors.New("token not found")
|
||||
ErrPhoneNumberNotFound = errors.New("phone number not found")
|
||||
ErrTooManyReservations = errors.New("new tier has lower reservation limit")
|
||||
ErrPhoneNumberExists = errors.New("phone number already exists")
|
||||
ErrProvisionedUserChange = errors.New("cannot change or delete provisioned user")
|
||||
ErrProvisionedTokenChange = errors.New("cannot change or delete provisioned token")
|
||||
)
|
||||
|
||||
31
web/package-lock.json
generated
31
web/package-lock.json
generated
@@ -3066,13 +3066,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@types/babel__traverse": {
|
||||
"version": "7.20.7",
|
||||
"resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.7.tgz",
|
||||
"integrity": "sha512-dkO5fhS7+/oos4ciWxyEyjWe48zmG6wbCheo/G2ZnHx4fs3EU6YC6UM8rk56gAjNJ9P3MTH2jo5jb92/K6wbng==",
|
||||
"version": "7.28.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz",
|
||||
"integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/types": "^7.20.7"
|
||||
"@babel/types": "^7.28.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/estree": {
|
||||
@@ -3819,9 +3819,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/core-js-compat": {
|
||||
"version": "3.44.0",
|
||||
"resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.44.0.tgz",
|
||||
"integrity": "sha512-JepmAj2zfl6ogy34qfWtcE7nHKAJnKsQFRn++scjVS2bZFllwptzw61BZcZFYBPpUznLfAvh0LGhxKppk04ClA==",
|
||||
"version": "3.45.0",
|
||||
"resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.45.0.tgz",
|
||||
"integrity": "sha512-gRoVMBawZg0OnxaVv3zpqLLxaHmsubEGyTnqdpI/CEBvX4JadI1dMSHxagThprYRtSVbuQxvi6iUatdPxohHpA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -4112,9 +4112,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/electron-to-chromium": {
|
||||
"version": "1.5.193",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.193.tgz",
|
||||
"integrity": "sha512-eePuBZXM9OVCwfYUhd2OzESeNGnWmLyeu0XAEjf7xjijNjHFdeJSzuRUGN4ueT2tEYo5YqjHramKEFxz67p3XA==",
|
||||
"version": "1.5.195",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.195.tgz",
|
||||
"integrity": "sha512-URclP0iIaDUzqcAyV1v2PgduJ9N0IdXmWsnPzPfelvBmjmZzEy6xJcjb1cXj+TbYqXgtLrjHEoaSIdTYhw4ezg==",
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
@@ -6045,16 +6045,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/jake": {
|
||||
"version": "10.9.2",
|
||||
"resolved": "https://registry.npmjs.org/jake/-/jake-10.9.2.tgz",
|
||||
"integrity": "sha512-2P4SQ0HrLQ+fw6llpLnOaGAvN2Zu6778SJMrCUwns4fOoG9ayrTiZk3VV8sCPkVZF8ab0zksVpS8FDY5pRCNBA==",
|
||||
"version": "10.9.4",
|
||||
"resolved": "https://registry.npmjs.org/jake/-/jake-10.9.4.tgz",
|
||||
"integrity": "sha512-wpHYzhxiVQL+IV05BLE2Xn34zW1S223hvjtqk0+gsPrwd/8JNLXJgZZM/iPFsYc1xyphF+6M6EvdE5E9MBGkDA==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"async": "^3.2.3",
|
||||
"chalk": "^4.0.2",
|
||||
"async": "^3.2.6",
|
||||
"filelist": "^1.0.4",
|
||||
"minimatch": "^3.1.2"
|
||||
"picocolors": "^1.1.1"
|
||||
},
|
||||
"bin": {
|
||||
"jake": "bin/cli.js"
|
||||
|
||||
@@ -212,6 +212,7 @@
|
||||
"account_basics_phone_numbers_dialog_check_verification_button": "Confirm code",
|
||||
"account_basics_phone_numbers_dialog_channel_sms": "SMS",
|
||||
"account_basics_phone_numbers_dialog_channel_call": "Call",
|
||||
"account_basics_cannot_edit_or_delete_provisioned_user": "A provisioned user cannot be edited or deleted",
|
||||
"account_usage_title": "Usage",
|
||||
"account_usage_of_limit": "of {{limit}}",
|
||||
"account_usage_unlimited": "Unlimited",
|
||||
@@ -291,6 +292,7 @@
|
||||
"account_tokens_table_current_session": "Current browser session",
|
||||
"account_tokens_table_copied_to_clipboard": "Access token copied",
|
||||
"account_tokens_table_cannot_delete_or_edit": "Cannot edit or delete current session token",
|
||||
"account_tokens_table_cannot_delete_or_edit_provisioned_token": "Cannot edit or delete provisioned token",
|
||||
"account_tokens_table_create_token_button": "Create access token",
|
||||
"account_tokens_table_last_origin_tooltip": "From IP address {{ip}}, click to lookup",
|
||||
"account_tokens_dialog_title_create": "Create access token",
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
"nav_button_documentation": "ஆவணப்படுத்துதல்",
|
||||
"nav_button_publish_message": "அறிவிப்பை வெளியிடுங்கள்",
|
||||
"alert_not_supported_description": "உங்கள் உலாவியில் அறிவிப்புகள் ஆதரிக்கப்படவில்லை",
|
||||
"alert_not_supported_context_description": "அறிவிப்புகள் HTTP களில் மட்டுமே ஆதரிக்கப்படுகின்றன. இது <mdnlink> அறிவிப்புகள் பநிஇ </mdnlink> இன் வரம்பு.",
|
||||
"alert_not_supported_context_description": "அறிவிப்புகள் HTTP களில் மட்டுமே ஆதரிக்கப்படுகின்றன. இது<mdnLink>அறிவிப்புகள் பநிஇ</mdnLink> இன் வரம்பு.",
|
||||
"notifications_list": "அறிவிப்புகள் பட்டியல்",
|
||||
"notifications_delete": "நீக்கு",
|
||||
"notifications_copied_to_clipboard": "இடைநிலைப்பலகைக்கு நகலெடுக்கப்பட்டது",
|
||||
@@ -76,7 +76,7 @@
|
||||
"publish_dialog_chip_email_label": "மின்னஞ்சலுக்கு அனுப்பவும்",
|
||||
"publish_dialog_chip_call_no_verified_numbers_tooltip": "சரிபார்க்கப்பட்ட தொலைபேசி எண்கள் இல்லை",
|
||||
"publish_dialog_chip_attach_url_label": "முகவரி மூலம் கோப்பை இணைக்கவும்",
|
||||
"publish_dialog_details_examples_description": "எடுத்துக்காட்டுகள் மற்றும் அனைத்து அனுப்பும் அம்சங்களின் விரிவான விளக்கத்திற்கு, தயவுசெய்து <ock இணைப்பு> ஆவணங்கள் </டாக்ச் இணைப்பு> ஐப் பார்க்கவும்.",
|
||||
"publish_dialog_details_examples_description": "எடுத்துக்காட்டுகள் மற்றும் அனைத்து அனுப்பும் அம்சங்களின் விரிவான விளக்கத்திற்கு, தயவுசெய்து <docsLink>ஆவணங்கள் </docsLink> ஐப் பார்க்கவும்.",
|
||||
"publish_dialog_chip_attach_file_label": "உள்ளக கோப்பை இணைக்கவும்",
|
||||
"publish_dialog_chip_delay_label": "நேரந்தவறுகை வழங்கல்",
|
||||
"publish_dialog_chip_topic_label": "தலைப்பை மாற்றவும்",
|
||||
@@ -133,10 +133,10 @@
|
||||
"account_usage_cannot_create_portal_session": "பட்டியலிடல் போர்ட்டலைத் திறக்க முடியவில்லை",
|
||||
"account_delete_title": "கணக்கை நீக்கு",
|
||||
"account_delete_description": "உங்கள் கணக்கை நிரந்தரமாக நீக்கவும்",
|
||||
"account_upgrade_dialog_cancel_warning": "இது <strong> உங்கள் சந்தாவை ரத்துசெய்யும் </strong>, மேலும் உங்கள் கணக்கை {{date} at இல் தரமிறக்குகிறது. அந்த தேதியில், தலைப்பு முன்பதிவு மற்றும் சேவையகத்தில் தற்காலிகமாக சேமிக்கப்பட்ட செய்திகளும் நீக்கப்படும் </strong>.",
|
||||
"account_upgrade_dialog_cancel_warning": "இது <strong> உங்கள் சந்தாவை ரத்துசெய்யும் </strong>, மேலும் உங்கள் கணக்கை {{date}} இல் தரமிறக்குகிறது. அந்தத் தேதியில், தலைப்பு முன்பதிவு மற்றும் சேவையகத்தில் தற்காலிகமாகச் சேமிக்கப்பட்ட செய்திகளும் <strong>நீக்கப்படும் </strong>.",
|
||||
"account_upgrade_dialog_proration_info": "<strong> புரோரேசன் </strong>: கட்டணத் திட்டங்களுக்கு இடையில் மேம்படுத்தும்போது, விலை வேறுபாடு <strong> உடனடியாக கட்டணம் வசூலிக்கப்படும் </strong>. குறைந்த அடுக்குக்கு தரமிறக்கும்போது, எதிர்கால பட்டியலிடல் காலங்களுக்கு செலுத்த இருப்பு பயன்படுத்தப்படும்.",
|
||||
"account_upgrade_dialog_reservations_warning_one": "தேர்ந்தெடுக்கப்பட்ட அடுக்கு உங்கள் தற்போதைய அடுக்கை விட குறைவான ஒதுக்கப்பட்ட தலைப்புகளை அனுமதிக்கிறது. உங்கள் அடுக்கை மாற்றுவதற்கு முன், <strong> தயவுசெய்து குறைந்தது ஒரு முன்பதிவை நீக்கு </strong>. <இணைப்பு> அமைப்புகள் </இணைப்பு> இல் முன்பதிவுகளை அகற்றலாம்.",
|
||||
"account_upgrade_dialog_reservations_warning_other": "தேர்ந்தெடுக்கப்பட்ட அடுக்கு உங்கள் தற்போதைய அடுக்கை விட குறைவான ஒதுக்கப்பட்ட தலைப்புகளை அனுமதிக்கிறது. உங்கள் அடுக்கை மாற்றுவதற்கு முன், <strong> தயவுசெய்து குறைந்தபட்சம் {{count}} முன்பதிவு </strong> ஐ நீக்கவும். <இணைப்பு> அமைப்புகள் </இணைப்பு> இல் முன்பதிவுகளை அகற்றலாம்.",
|
||||
"account_upgrade_dialog_reservations_warning_one": "தேர்ந்தெடுக்கப்பட்ட அடுக்கு உங்கள் தற்போதைய அடுக்கைவிடக் குறைவான ஒதுக்கப்பட்ட தலைப்புகளை அனுமதிக்கிறது. உங்கள் அடுக்கை மாற்றுவதற்கு முன், <strong> தயவுசெய்து குறைந்தது ஒரு முன்பதிவை நீக்கு </strong>. <Link>அமைப்புகள்</Link> இல் முன்பதிவுகளை அகற்றலாம்.",
|
||||
"account_upgrade_dialog_reservations_warning_other": "தேர்ந்தெடுக்கப்பட்ட அடுக்கு உங்கள் தற்போதைய அடுக்கைவிடக் குறைவான ஒதுக்கப்பட்ட தலைப்புகளை அனுமதிக்கிறது. உங்கள் அடுக்கை மாற்றுவதற்கு முன், <strong> தயவுசெய்து குறைந்தபட்சம் {{count}} முன்பதிவு </strong> ஐ நீக்கவும். <Link>அமைப்புகள்</Link> இல் முன்பதிவுகளை அகற்றலாம்.",
|
||||
"account_upgrade_dialog_tier_features_reservations_other": "{{reservations}} ஒதுக்கப்பட்ட தலைப்புகள்",
|
||||
"account_upgrade_dialog_tier_features_no_reservations": "ஒதுக்கப்பட்ட தலைப்புகள் இல்லை",
|
||||
"account_upgrade_dialog_tier_features_messages_one": "{{messages}} நாள்தோறும் செய்தி",
|
||||
@@ -153,14 +153,14 @@
|
||||
"account_upgrade_dialog_tier_price_billed_yearly": "{{price}} ஆண்டுதோறும் கட்டணம் செலுத்தப்படுகிறது. {{save}} சேமி.",
|
||||
"account_upgrade_dialog_tier_selected_label": "தேர்ந்தெடுக்கப்பட்டது",
|
||||
"account_upgrade_dialog_tier_current_label": "மின்னோட்ட்ம், ஓட்டம்",
|
||||
"account_upgrade_dialog_billing_contact_email": "பட்டியலிடல் கேள்விகளுக்கு, தயவுசெய்து <இணைப்பு> எங்களை தொடர்பு கொள்ளவும் </இணைப்பு> நேரடியாக.",
|
||||
"account_upgrade_dialog_billing_contact_email": "பட்டியலிடல் கேள்விகளுக்கு, தயவுசெய்து <Link>எங்களைத் தொடர்பு கொள்ளவும் </Link>நேரடியாக.",
|
||||
"account_upgrade_dialog_button_cancel": "ரத்துசெய்",
|
||||
"account_upgrade_dialog_billing_contact_website": "பட்டியலிடல் கேள்விகளுக்கு, தயவுசெய்து எங்கள் <இணைப்பு> வலைத்தளம் </இணைப்பு> ஐப் பார்க்கவும்.",
|
||||
"account_upgrade_dialog_billing_contact_website": "பட்டியலிடல் கேள்விகளுக்கு, தயவுசெய்து எங்கள் <Link>வலைத்தளம்</Link> ஐப் பார்க்கவும்.",
|
||||
"account_upgrade_dialog_button_redirect_signup": "இப்போது பதிவுபெறுக",
|
||||
"account_upgrade_dialog_button_pay_now": "இப்போது பணம் செலுத்தி குழுசேரவும்",
|
||||
"account_upgrade_dialog_button_cancel_subscription": "சந்தாவை ரத்துசெய்",
|
||||
"account_tokens_title": "டோக்கன்களை அணுகவும்",
|
||||
"account_tokens_description": "NTFY பநிஇ வழியாக வெளியிடும் மற்றும் சந்தா செலுத்தும் போது அணுகல் டோக்கன்களைப் பயன்படுத்தவும், எனவே உங்கள் கணக்கு நற்சான்றிதழ்களை அனுப்ப வேண்டியதில்லை. மேலும் அறிய <இணைப்பு> ஆவணங்கள் </இணைப்பு> ஐப் பாருங்கள்.",
|
||||
"account_tokens_description": "NTFY பநிஇ வழியாக வெளியிடும் மற்றும் சந்தா செலுத்தும்போது அணுகல் டோக்கன்களைப் பயன்படுத்தவும், எனவே உங்கள் கணக்கு நற்சான்றிதழ்களை அனுப்ப வேண்டியதில்லை. மேலும் அறிய <Link> ஆவணங்கள்</Link> ஐப் பாருங்கள்.",
|
||||
"account_upgrade_dialog_button_update_subscription": "சந்தாவைப் புதுப்பிக்கவும்",
|
||||
"account_tokens_table_token_header": "கிள்ளாக்கு",
|
||||
"account_tokens_table_label_header": "சிட்டை",
|
||||
@@ -216,7 +216,7 @@
|
||||
"prefs_notifications_web_push_title": "பின்னணி அறிவிப்புகள்",
|
||||
"prefs_notifications_web_push_enabled_description": "வலை பயன்பாடு இயங்காதபோது கூட அறிவிப்புகள் பெறப்படுகின்றன (வலை புச் வழியாக)",
|
||||
"prefs_notifications_web_push_disabled_description": "வலை பயன்பாடு இயங்கும்போது அறிவிப்பு பெறப்படுகிறது (வெப்சாக்கெட் வழியாக)",
|
||||
"prefs_notifications_web_push_enabled": "{{server} க்கு க்கு இயக்கப்பட்டது",
|
||||
"prefs_notifications_web_push_enabled": "{{server}} க்கு இயக்கப்பட்டது",
|
||||
"prefs_notifications_web_push_disabled": "முடக்கப்பட்டது",
|
||||
"prefs_users_title": "பயனர்களை நிர்வகிக்கவும்",
|
||||
"prefs_users_description": "உங்கள் பாதுகாக்கப்பட்ட தலைப்புகளுக்கு பயனர்களை இங்கே சேர்க்கவும்/அகற்றவும். பயனர்பெயர் மற்றும் கடவுச்சொல் உலாவியின் உள்ளக சேமிப்பகத்தில் சேமிக்கப்பட்டுள்ளன என்பதை நினைவில் கொள்க.",
|
||||
@@ -271,7 +271,7 @@
|
||||
"priority_max": "அதிகபட்சம்",
|
||||
"priority_default": "இயல்புநிலை",
|
||||
"error_boundary_title": "ஓ, NTFY செயலிழந்தது",
|
||||
"error_boundary_description": "இது வெளிப்படையாக நடக்கக்கூடாது. இதைப் பற்றி மிகவும் வருந்துகிறேன். .",
|
||||
"error_boundary_description": "இது நிச்சயமாக நடக்கக் கூடாது. இதுகுறித்து மிகவும் வருந்துகிறேன்.<br/>உங்களிடம் ஒரு நிமிடம் இருந்தால், தயவுசெய்து <githubLink>இதை GitHub இல் புகாரளிக்கவும்</githubLink>, அல்லது <discordLink>Discord</discordLink> அல்லது <matrixLink>Matrix</matrixLink> வழியாக எங்களுக்குத் தெரியப்படுத்தவும்.",
|
||||
"error_boundary_button_copy_stack_trace": "அடுக்கு சுவடு நகலெடுக்கவும்",
|
||||
"error_boundary_button_reload_ntfy": "Ntfy ஐ மீண்டும் ஏற்றவும்",
|
||||
"error_boundary_stack_trace": "ச்டாக் சுவடு",
|
||||
@@ -349,7 +349,7 @@
|
||||
"notifications_no_subscriptions_title": "உங்களிடம் இன்னும் சந்தாக்கள் இல்லை என்று தெரிகிறது.",
|
||||
"notifications_no_subscriptions_description": "ஒரு தலைப்பை உருவாக்க அல்லது குழுசேர \"{{linktext}}\" இணைப்பைக் சொடுக்கு செய்க. அதன்பிறகு, நீங்கள் புட் அல்லது இடுகை வழியாக செய்திகளை அனுப்பலாம், மேலும் நீங்கள் இங்கே அறிவிப்புகளைப் பெறுவீர்கள்.",
|
||||
"notifications_example": "எடுத்துக்காட்டு",
|
||||
"notifications_more_details": "மேலும் தகவலுக்கு, </webititeLink> வலைத்தளம் </websiteLink> அல்லது <ockslink> ஆவணங்கள் </docslink> ஐப் பாருங்கள்.",
|
||||
"notifications_more_details": "மேலும் தகவலுக்கு, <websiteLink>வலைத்தளம் </websiteLink> அல்லது <docsLink> ஆவணங்கள் </docsLink> ஐப் பாருங்கள்.",
|
||||
"display_name_dialog_title": "காட்சி பெயரை மாற்றவும்",
|
||||
"display_name_dialog_description": "சந்தா பட்டியலில் காட்டப்படும் தலைப்புக்கு மாற்று பெயரை அமைக்கவும். சிக்கலான பெயர்களைக் கொண்ட தலைப்புகளை மிக எளிதாக அடையாளம் காண இது உதவுகிறது.",
|
||||
"display_name_dialog_placeholder": "காட்சி பெயர்",
|
||||
@@ -399,7 +399,7 @@
|
||||
"account_upgrade_dialog_interval_yearly_discount_save_up_to": "{{discount}}% வரை சேமிக்கவும்",
|
||||
"account_upgrade_dialog_tier_features_reservations_one": "{{reservations}} முன்பதிவு செய்யப்பட்ட தலைப்பு",
|
||||
"prefs_users_add_button": "பயனரைச் சேர்க்கவும்",
|
||||
"error_boundary_unsupported_indexeddb_description": "NTFY வலை பயன்பாட்டிற்கு செயல்பட குறியீட்டு தேவை, மற்றும் உங்கள் உலாவி தனிப்பட்ட உலாவல் பயன்முறையில் IndexEDDB ஐ ஆதரிக்காது. எப்படியிருந்தாலும் தனிப்பட்ட உலாவல் பயன்முறையில் பயன்பாடு, ஏனென்றால் அனைத்தும் உலாவி சேமிப்பகத்தில் சேமிக்கப்படுகின்றன. இந்த அறிவிலிமையம் இதழில் </githublink> இல் <githublink> பற்றி நீங்கள் மேலும் படிக்கலாம் அல்லது <scordlink> டிச்கார்ட் </disordlink> அல்லது <agadgaglelink> மேட்ரிக்ச் </மேட்ரிக்ச்லிங்க்> இல் எங்களுடன் பேசலாம்.",
|
||||
"error_boundary_unsupported_indexeddb_description": "ntfy வலை பயன்பாடு செயல்பட IndexedDB தேவை, மேலும் உங்கள் உலாவித் தனிப்பட்ட உலாவல் பயன்முறையில் IndexedDB ஐ ஆதரிக்காது.<br/><br/>இது துரதிர்ஷ்டவசமானது என்றாலும், ntfy வலை பயன்பாட்டைத் தனிப்பட்ட உலாவல் பயன்முறையில் பயன்படுத்துவது உண்மையில் அர்த்தமற்றது, ஏனெனில் அனைத்தும் உலாவிச் சேமிப்பகத்தில் சேமிக்கப்படுகின்றன. இதைப் பற்றி நீங்கள் <githubLink>இந்த GitHub சிக்கலில் மேலும் படிக்கலாம்</githubLink>, அல்லது <discordLink>Discord</discordLink> அல்லது <matrixLink>Matrix</matrixLink> இல் எங்களுடன் பேசலாம்.",
|
||||
"web_push_subscription_expiring_title": "அறிவிப்புகள் இடைநிறுத்தப்படும்",
|
||||
"web_push_subscription_expiring_body": "தொடர்ந்து அறிவிப்புகளைப் பெற NTFY ஐத் திறக்கவும்",
|
||||
"web_push_unknown_notification_title": "சேவையகத்திலிருந்து அறியப்படாத அறிவிப்பு பெறப்பட்டது",
|
||||
|
||||
@@ -100,15 +100,13 @@ const Username = () => {
|
||||
<Pref labelId={labelId} title={t("account_basics_username_title")} description={t("account_basics_username_description")}>
|
||||
<div aria-labelledby={labelId}>
|
||||
{session.username()}
|
||||
{account?.role === Role.ADMIN ? (
|
||||
{account?.role === Role.ADMIN && (
|
||||
<>
|
||||
{" "}
|
||||
<Tooltip title={t("account_basics_username_admin_tooltip")}>
|
||||
<span style={{ cursor: "default" }}>👑</span>
|
||||
</Tooltip>
|
||||
</>
|
||||
) : (
|
||||
""
|
||||
)}
|
||||
</div>
|
||||
</Pref>
|
||||
@@ -119,6 +117,7 @@ const ChangePassword = () => {
|
||||
const { t } = useTranslation();
|
||||
const [dialogKey, setDialogKey] = useState(0);
|
||||
const [dialogOpen, setDialogOpen] = useState(false);
|
||||
const { account } = useContext(AccountContext);
|
||||
const labelId = "prefChangePassword";
|
||||
|
||||
const handleDialogOpen = () => {
|
||||
@@ -136,9 +135,19 @@ const ChangePassword = () => {
|
||||
<Typography color="gray" sx={{ float: "left", fontSize: "0.7rem", lineHeight: "3.5" }}>
|
||||
⬤⬤⬤⬤⬤⬤⬤⬤⬤⬤
|
||||
</Typography>
|
||||
<IconButton onClick={handleDialogOpen} aria-label={t("account_basics_password_description")}>
|
||||
<EditIcon />
|
||||
</IconButton>
|
||||
{!account?.provisioned ? (
|
||||
<IconButton onClick={handleDialogOpen} aria-label={t("account_basics_password_description")}>
|
||||
<EditIcon />
|
||||
</IconButton>
|
||||
) : (
|
||||
<Tooltip title={t("account_basics_cannot_edit_or_delete_provisioned_user")}>
|
||||
<span>
|
||||
<IconButton disabled>
|
||||
<EditIcon />
|
||||
</IconButton>
|
||||
</span>
|
||||
</Tooltip>
|
||||
)}
|
||||
</div>
|
||||
<ChangePasswordDialog key={`changePasswordDialog${dialogKey}`} open={dialogOpen} onClose={handleDialogClose} />
|
||||
</Pref>
|
||||
@@ -888,7 +897,7 @@ const TokensTable = (props) => {
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell align="right" sx={{ whiteSpace: "nowrap" }}>
|
||||
{token.token !== session.token() && (
|
||||
{token.token !== session.token() && !token.provisioned && (
|
||||
<>
|
||||
<IconButton onClick={() => handleEditClick(token)} aria-label={t("account_tokens_dialog_title_edit")}>
|
||||
<EditIcon />
|
||||
@@ -910,6 +919,18 @@ const TokensTable = (props) => {
|
||||
</span>
|
||||
</Tooltip>
|
||||
)}
|
||||
{token.provisioned && (
|
||||
<Tooltip title={t("account_tokens_table_cannot_delete_or_edit_provisioned_token")}>
|
||||
<span>
|
||||
<IconButton disabled>
|
||||
<EditIcon />
|
||||
</IconButton>
|
||||
<IconButton disabled>
|
||||
<CloseIcon />
|
||||
</IconButton>
|
||||
</span>
|
||||
</Tooltip>
|
||||
)}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
@@ -1048,6 +1069,7 @@ const DeleteAccount = () => {
|
||||
const { t } = useTranslation();
|
||||
const [dialogKey, setDialogKey] = useState(0);
|
||||
const [dialogOpen, setDialogOpen] = useState(false);
|
||||
const { account } = useContext(AccountContext);
|
||||
|
||||
const handleDialogOpen = () => {
|
||||
setDialogKey((prev) => prev + 1);
|
||||
@@ -1061,9 +1083,19 @@ const DeleteAccount = () => {
|
||||
return (
|
||||
<Pref title={t("account_delete_title")} description={t("account_delete_description")}>
|
||||
<div>
|
||||
<Button fullWidth={false} variant="outlined" color="error" startIcon={<DeleteOutlineIcon />} onClick={handleDialogOpen}>
|
||||
{t("account_delete_title")}
|
||||
</Button>
|
||||
{!account?.provisioned ? (
|
||||
<Button fullWidth={false} variant="outlined" color="error" startIcon={<DeleteOutlineIcon />} onClick={handleDialogOpen}>
|
||||
{t("account_delete_title")}
|
||||
</Button>
|
||||
) : (
|
||||
<Tooltip title={t("account_basics_cannot_edit_or_delete_provisioned_user")}>
|
||||
<span>
|
||||
<Button fullWidth={false} variant="outlined" color="error" startIcon={<DeleteOutlineIcon />} disabled>
|
||||
{t("account_delete_title")}
|
||||
</Button>
|
||||
</span>
|
||||
</Tooltip>
|
||||
)}
|
||||
</div>
|
||||
<DeleteAccountDialog key={`deleteAccountDialog${dialogKey}`} open={dialogOpen} onClose={handleDialogClose} />
|
||||
</Pref>
|
||||
|
||||
Reference in New Issue
Block a user