Compare commits
68 Commits
b2d5a1ffdf
...
b59262b7af
| Author | SHA1 | Date | |
|---|---|---|---|
| b59262b7af | |||
| 8093904d47 | |||
| 66c0ed5bf0 | |||
| 725603101b | |||
| 7f0264dec3 | |||
| 5e88862e29 | |||
| b3bc70875b | |||
| 34dc4c2d07 | |||
| 2ef7a709d3 | |||
| d7a9e1a517 | |||
| f938e9c3c0 | |||
| c8d560d78f | |||
| 3641969dd4 | |||
| 49b3163bbe | |||
| 1a36475afa | |||
| 335de04a4e | |||
| f38069623b | |||
| 0a70896080 | |||
| 5a09062070 | |||
| 47be3dbdf9 | |||
| 9f5f89c9eb | |||
| 2e8d170114 | |||
| bf1787acd5 | |||
| 78ff835ac9 | |||
| 3c005c9ab1 | |||
| 54adcd2c56 | |||
| 9a6408d28c | |||
| 2dad97cb6b | |||
| 0ac97ecd5e | |||
| 387049beac | |||
| c9240ecb84 | |||
| b87e71c557 | |||
| 866293aa5a | |||
| e142dd32b4 | |||
| 949786dab5 | |||
| 2dd142b0e9 | |||
| dfd16a6752 | |||
| f4454d4d48 | |||
| e7efc917f0 | |||
| 5ffe1ba07d | |||
| b56e2972c4 | |||
| ca1a084397 | |||
| a7a1f81e9d | |||
| 9c09f17dc5 | |||
| 21e2c022c7 | |||
| 222cbc886d | |||
| db2e76bd31 | |||
| bf32cc150d | |||
| 967de0b79f | |||
| 22231e6c45 | |||
| 20ed9966b9 | |||
| dddf060e1a | |||
| a569054e94 | |||
| 5885e8eb39 | |||
| 22964cff0f | |||
| e952c2d34a | |||
| 8324445895 | |||
| 796d14a9e4 | |||
| 707cc4b275 | |||
| 93400ace27 | |||
| 79c6fcac95 | |||
| 7d13ed79b2 | |||
| 9fe4f78269 | |||
| bd3d6994c1 | |||
| 5fd78817a8 | |||
| 72bc125f84 | |||
| b18ea66def | |||
| 41f541a531 |
@@ -1,17 +0,0 @@
|
||||
meta {
|
||||
name: Create API Key
|
||||
type: http
|
||||
seq: 1
|
||||
}
|
||||
|
||||
put {
|
||||
url: http://localhost:3000/api/v1/api-key
|
||||
body: json
|
||||
auth: inherit
|
||||
}
|
||||
|
||||
body:json {
|
||||
{
|
||||
"isRoot": true
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
meta {
|
||||
name: Delete API Key
|
||||
type: http
|
||||
seq: 2
|
||||
}
|
||||
|
||||
delete {
|
||||
url: http://localhost:3000/api/v1/api-key/dm47aacqxxn3ubj
|
||||
body: none
|
||||
auth: inherit
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
meta {
|
||||
name: List API Key Actions
|
||||
type: http
|
||||
seq: 6
|
||||
}
|
||||
|
||||
get {
|
||||
url: http://localhost:3000/api/v1/api-key/ex0izu2c37fjz9x/actions
|
||||
body: none
|
||||
auth: inherit
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
meta {
|
||||
name: List Org API Keys
|
||||
type: http
|
||||
seq: 4
|
||||
}
|
||||
|
||||
get {
|
||||
url: http://localhost:3000/api/v1/org/home-lab/api-keys
|
||||
body: none
|
||||
auth: inherit
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
meta {
|
||||
name: List Root API Keys
|
||||
type: http
|
||||
seq: 3
|
||||
}
|
||||
|
||||
get {
|
||||
url: http://localhost:3000/api/v1/root/api-keys
|
||||
body: none
|
||||
auth: inherit
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
meta {
|
||||
name: Set API Key Actions
|
||||
type: http
|
||||
seq: 5
|
||||
}
|
||||
|
||||
post {
|
||||
url: http://localhost:3000/api/v1/api-key/ex0izu2c37fjz9x/actions
|
||||
body: json
|
||||
auth: inherit
|
||||
}
|
||||
|
||||
body:json {
|
||||
{
|
||||
"actionIds": ["listSites"]
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
meta {
|
||||
name: Set API Key Orgs
|
||||
type: http
|
||||
seq: 7
|
||||
}
|
||||
|
||||
post {
|
||||
url: http://localhost:3000/api/v1/api-key/ex0izu2c37fjz9x/orgs
|
||||
body: json
|
||||
auth: inherit
|
||||
}
|
||||
|
||||
body:json {
|
||||
{
|
||||
"orgIds": ["home-lab"]
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
meta {
|
||||
name: API Keys
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
meta {
|
||||
name: 2fa-disable
|
||||
type: http
|
||||
seq: 6
|
||||
}
|
||||
|
||||
post {
|
||||
url: http://localhost:3000/api/v1/auth/2fa/disable
|
||||
body: json
|
||||
auth: none
|
||||
}
|
||||
|
||||
body:json {
|
||||
{
|
||||
"password": "aaaaa-1A",
|
||||
"code": "377289"
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
meta {
|
||||
name: 2fa-enable
|
||||
type: http
|
||||
seq: 4
|
||||
}
|
||||
|
||||
post {
|
||||
url: http://localhost:3000/api/v1/auth/2fa/enable
|
||||
body: json
|
||||
auth: none
|
||||
}
|
||||
|
||||
body:json {
|
||||
{
|
||||
"code": "374138"
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
meta {
|
||||
name: 2fa-request
|
||||
type: http
|
||||
seq: 5
|
||||
}
|
||||
|
||||
post {
|
||||
url: http://localhost:3000/api/v1/auth/2fa/request
|
||||
body: json
|
||||
auth: none
|
||||
}
|
||||
|
||||
body:json {
|
||||
{
|
||||
"password": "aaaaa-1A"
|
||||
}
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
meta {
|
||||
name: change-password
|
||||
type: http
|
||||
seq: 9
|
||||
}
|
||||
|
||||
post {
|
||||
url: http://localhost:3000/api/v1/auth/change-password
|
||||
body: json
|
||||
auth: none
|
||||
}
|
||||
|
||||
body:json {
|
||||
{
|
||||
"oldPassword": "",
|
||||
"newPassword": ""
|
||||
}
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
meta {
|
||||
name: login
|
||||
type: http
|
||||
seq: 1
|
||||
}
|
||||
|
||||
post {
|
||||
url: http://localhost:3000/api/v1/auth/login
|
||||
body: json
|
||||
auth: none
|
||||
}
|
||||
|
||||
body:json {
|
||||
{
|
||||
"email": "admin@fosrl.io",
|
||||
"password": "Password123!"
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
meta {
|
||||
name: logout
|
||||
type: http
|
||||
seq: 3
|
||||
}
|
||||
|
||||
post {
|
||||
url: http://localhost:4000/api/v1/auth/logout
|
||||
body: none
|
||||
auth: none
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
meta {
|
||||
name: reset-password-request
|
||||
type: http
|
||||
seq: 10
|
||||
}
|
||||
|
||||
post {
|
||||
url: http://localhost:3000/api/v1/auth/reset-password/request
|
||||
body: json
|
||||
auth: none
|
||||
}
|
||||
|
||||
body:json {
|
||||
{
|
||||
"email": "milo@pangolin.net"
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
meta {
|
||||
name: reset-password
|
||||
type: http
|
||||
seq: 11
|
||||
}
|
||||
|
||||
post {
|
||||
url: http://localhost:3000/api/v1/auth/reset-password
|
||||
body: json
|
||||
auth: none
|
||||
}
|
||||
|
||||
body:json {
|
||||
{
|
||||
"token": "3uhsbom72dwdhboctwrtntyd6jrlg4jtf5oaxy4k",
|
||||
"newPassword": "aaaaa-1A",
|
||||
"code": "6irqCGR3"
|
||||
}
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
meta {
|
||||
name: signup
|
||||
type: http
|
||||
seq: 2
|
||||
}
|
||||
|
||||
put {
|
||||
url: http://localhost:3000/api/v1/auth/signup
|
||||
body: json
|
||||
auth: none
|
||||
}
|
||||
|
||||
body:json {
|
||||
{
|
||||
"email": "numbat@pangolin.net",
|
||||
"password": "Password123!"
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
meta {
|
||||
name: verify-email-request
|
||||
type: http
|
||||
seq: 8
|
||||
}
|
||||
|
||||
post {
|
||||
url: http://localhost:3000/api/v1/auth/verify-email/request
|
||||
body: none
|
||||
auth: none
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
meta {
|
||||
name: verify-email
|
||||
type: http
|
||||
seq: 7
|
||||
}
|
||||
|
||||
post {
|
||||
url: http://localhost:3000/api/v1/auth/verify-email
|
||||
body: json
|
||||
auth: none
|
||||
}
|
||||
|
||||
body:json {
|
||||
{
|
||||
"code": "50317187"
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
meta {
|
||||
name: verify-user
|
||||
type: http
|
||||
seq: 4
|
||||
}
|
||||
|
||||
get {
|
||||
url: http://localhost:3001/api/v1/badger/verify-user?sessionId=mb52273jkb6t3oys2bx6ur5x7rcrkl26c7warg3e
|
||||
body: none
|
||||
auth: none
|
||||
}
|
||||
|
||||
params:query {
|
||||
sessionId: mb52273jkb6t3oys2bx6ur5x7rcrkl26c7warg3e
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
meta {
|
||||
name: createClient
|
||||
type: http
|
||||
seq: 1
|
||||
}
|
||||
|
||||
put {
|
||||
url: http://localhost:3000/api/v1/site/1/client
|
||||
body: json
|
||||
auth: none
|
||||
}
|
||||
|
||||
body:json {
|
||||
{
|
||||
"siteId": 1,
|
||||
"name": "test",
|
||||
"type": "olm",
|
||||
"subnet": "100.90.129.4/30",
|
||||
"olmId": "029yzunhx6nh3y5",
|
||||
"secret": "l0ymp075y3d4rccb25l6sqpgar52k09etunui970qq5gj7x6"
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
meta {
|
||||
name: pickClientDefaults
|
||||
type: http
|
||||
seq: 2
|
||||
}
|
||||
|
||||
get {
|
||||
url: http://localhost:3000/api/v1/site/1/pick-client-defaults
|
||||
body: none
|
||||
auth: none
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
meta {
|
||||
name: Create OIDC Provider
|
||||
type: http
|
||||
seq: 1
|
||||
}
|
||||
|
||||
put {
|
||||
url: http://localhost:3000/api/v1/org/home-lab/idp/oidc
|
||||
body: json
|
||||
auth: inherit
|
||||
}
|
||||
|
||||
body:json {
|
||||
{
|
||||
"clientId": "JJoSvHCZcxnXT2sn6CObj6a21MuKNRXs3kN5wbys",
|
||||
"clientSecret": "2SlGL2wOGgMEWLI9yUuMAeFxre7qSNJVnXMzyepdNzH1qlxYnC4lKhhQ6a157YQEkYH3vm40KK4RCqbYiF8QIweuPGagPX3oGxEj2exwutoXFfOhtq4hHybQKoFq01Z3",
|
||||
"authUrl": "http://localhost:9000/application/o/authorize/",
|
||||
"tokenUrl": "http://localhost:9000/application/o/token/",
|
||||
"scopes": ["email", "openid", "profile"],
|
||||
"userIdentifier": "email"
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
meta {
|
||||
name: Generate OIDC URL
|
||||
type: http
|
||||
seq: 2
|
||||
}
|
||||
|
||||
get {
|
||||
url: http://localhost:3000/api/v1
|
||||
body: none
|
||||
auth: inherit
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
meta {
|
||||
name: IDP
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
meta {
|
||||
name: Traefik Config
|
||||
type: http
|
||||
seq: 1
|
||||
}
|
||||
|
||||
get {
|
||||
url: http://localhost:3001/api/v1/traefik-config
|
||||
body: none
|
||||
auth: inherit
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
meta {
|
||||
name: Internal
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
meta {
|
||||
name: Create Newt
|
||||
type: http
|
||||
seq: 2
|
||||
}
|
||||
|
||||
get {
|
||||
url: http://localhost:3000/api/v1/newt
|
||||
body: none
|
||||
auth: none
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
meta {
|
||||
name: Get Token
|
||||
type: http
|
||||
seq: 1
|
||||
}
|
||||
|
||||
get {
|
||||
url: http://localhost:3000/api/v1/auth/newt/get-token
|
||||
body: json
|
||||
auth: none
|
||||
}
|
||||
|
||||
body:json {
|
||||
{
|
||||
"newtId": "o0d4rdxq3stnz7b",
|
||||
"secret": "sy7l09fnaesd03iwrfp9m3qf0ryn19g0zf3dqieaazb4k7vk"
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
meta {
|
||||
name: createOlm
|
||||
type: http
|
||||
seq: 1
|
||||
}
|
||||
|
||||
put {
|
||||
url: http://localhost:3000/api/v1/olm
|
||||
body: none
|
||||
auth: inherit
|
||||
}
|
||||
|
||||
settings {
|
||||
encodeUrl: true
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
meta {
|
||||
name: Olm
|
||||
seq: 15
|
||||
}
|
||||
|
||||
auth {
|
||||
mode: inherit
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
meta {
|
||||
name: Check Id
|
||||
type: http
|
||||
seq: 2
|
||||
}
|
||||
|
||||
get {
|
||||
url: http://localhost:3000/api/v1/org/checkId
|
||||
body: none
|
||||
auth: none
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
meta {
|
||||
name: listOrgs
|
||||
type: http
|
||||
seq: 1
|
||||
}
|
||||
|
||||
get {
|
||||
url:
|
||||
body: none
|
||||
auth: none
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
meta {
|
||||
name: createRemoteExitNode
|
||||
type: http
|
||||
seq: 1
|
||||
}
|
||||
|
||||
put {
|
||||
url: http://localhost:4000/api/v1/org/org_i21aifypnlyxur2/remote-exit-node
|
||||
body: none
|
||||
auth: none
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
meta {
|
||||
name: listResourcesByOrg
|
||||
type: http
|
||||
seq: 1
|
||||
}
|
||||
|
||||
get {
|
||||
url:
|
||||
body: none
|
||||
auth: none
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
meta {
|
||||
name: listResourcesBySite
|
||||
type: http
|
||||
seq: 2
|
||||
}
|
||||
|
||||
get {
|
||||
url: http://localhost:3000/api/v1/site/1/resources?limit=10&offset=0
|
||||
body: none
|
||||
auth: none
|
||||
}
|
||||
|
||||
params:query {
|
||||
limit: 10
|
||||
offset: 0
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
meta {
|
||||
name: Get Site
|
||||
type: http
|
||||
seq: 2
|
||||
}
|
||||
|
||||
get {
|
||||
url: http://localhost:3000/api/v1/org/test/sites/mexican-mole-lizard-windy
|
||||
body: none
|
||||
auth: none
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
meta {
|
||||
name: listSites
|
||||
type: http
|
||||
seq: 1
|
||||
}
|
||||
|
||||
get {
|
||||
url:
|
||||
body: none
|
||||
auth: none
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
meta {
|
||||
name: listTargets
|
||||
type: http
|
||||
seq: 1
|
||||
}
|
||||
|
||||
get {
|
||||
url: http://localhost:3000/api/v1/resource/web.main.localhost/targets?limit=10&offset=0
|
||||
body: none
|
||||
auth: none
|
||||
}
|
||||
|
||||
params:query {
|
||||
limit: 10
|
||||
offset: 0
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
meta {
|
||||
name: Test
|
||||
type: http
|
||||
seq: 2
|
||||
}
|
||||
|
||||
get {
|
||||
url: http://localhost:3000/api/v1
|
||||
body: none
|
||||
auth: inherit
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
meta {
|
||||
name: traefik-config
|
||||
type: http
|
||||
seq: 1
|
||||
}
|
||||
|
||||
get {
|
||||
url: http://localhost:3001/api/v1/traefik-config
|
||||
body: none
|
||||
auth: none
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
meta {
|
||||
name: adminListUsers
|
||||
type: http
|
||||
seq: 2
|
||||
}
|
||||
|
||||
get {
|
||||
url: http://localhost:3000/api/v1/users
|
||||
body: none
|
||||
auth: none
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
meta {
|
||||
name: adminRemoveUser
|
||||
type: http
|
||||
seq: 3
|
||||
}
|
||||
|
||||
delete {
|
||||
url: http://localhost:3000/api/v1/user/ky5r7ivqs8wc7u4
|
||||
body: none
|
||||
auth: none
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
meta {
|
||||
name: getUser
|
||||
type: http
|
||||
seq: 1
|
||||
}
|
||||
|
||||
get {
|
||||
url:
|
||||
body: none
|
||||
auth: none
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
{
|
||||
"version": "1",
|
||||
"name": "Pangolin",
|
||||
"type": "collection",
|
||||
"ignore": [
|
||||
"node_modules",
|
||||
".git"
|
||||
],
|
||||
"presets": {
|
||||
"requestType": "http",
|
||||
"requestUrl": "http://localhost:3000/api/v1"
|
||||
}
|
||||
}
|
||||
@@ -898,6 +898,7 @@
|
||||
"idpDisplayName": "Име за показване за този доставчик на идентичност",
|
||||
"idpAutoProvisionUsers": "Автоматично потребителско създаване",
|
||||
"idpAutoProvisionUsersDescription": "Когато е активирано, потребителите ще бъдат автоматично създадени в системата при първо влизане с възможност за свързване на потребителите с роли и организации.",
|
||||
"idpAutoProvisionConfigureAfterCreate": "Можете да конфигурирате настройките за автоматично предоставяне, след като дистрибуторът на самоличност бъде създаден.",
|
||||
"licenseBadge": "ЕЕ",
|
||||
"idpType": "Тип доставчик",
|
||||
"idpTypeDescription": "Изберете типа доставчик на идентичност, който искате да конфигурирате",
|
||||
@@ -949,7 +950,7 @@
|
||||
"defaultMappingsRole": "Карта на роля по подразбиране",
|
||||
"defaultMappingsRoleDescription": "Резултатът от този израз трябва да върне името на ролята, както е дефинирано в организацията, като стринг.",
|
||||
"defaultMappingsOrg": "Карта на организация по подразбиране",
|
||||
"defaultMappingsOrgDescription": "Този израз трябва да върне ID на организацията или 'true', за да бъде разрешен достъпът на потребителя до организацията.",
|
||||
"defaultMappingsOrgDescription": "При задаване, този израз трябва да върне идентификационния номер на организацията или true, за да се даде достъп на потребителя до тази организация. Ако не е зададено, дефинирането на роля е достатъчно: потребителят има право на достъп, стига валидно картографиране на роля да бъде разрешено за него в рамките на организацията.",
|
||||
"defaultMappingsSubmit": "Запазване на файловете по подразбиране",
|
||||
"orgPoliciesEdit": "Редактиране на Организационна Политика",
|
||||
"org": "Организация",
|
||||
@@ -2026,7 +2027,7 @@
|
||||
},
|
||||
"internationaldomaindetected": "Открит международен домейн",
|
||||
"willbestoredas": "Ще бъде съхранено като:",
|
||||
"roleMappingDescription": "Определете как се разпределят ролите на потребителите при вписване, когато е активирано автоматично предоставяне.",
|
||||
"roleMappingDescription": "Определете как ролите се присвояват на потребителите, когато се вписват с този доставчик на самоличност.",
|
||||
"selectRole": "Избор на роля",
|
||||
"roleMappingExpression": "Израз",
|
||||
"selectRolePlaceholder": "Избор на роля",
|
||||
@@ -2899,5 +2900,22 @@
|
||||
"httpDestUpdatedSuccess": "Дестинацията беше актуализирана успешно",
|
||||
"httpDestCreatedSuccess": "Дестинацията беше създадена успешно",
|
||||
"httpDestUpdateFailed": "Неуспешно актуализиране на дестинацията",
|
||||
"httpDestCreateFailed": "Неуспешно създаване на дестинацията"
|
||||
"httpDestCreateFailed": "Неуспешно създаване на дестинацията",
|
||||
"idpAddActionCreateNew": "Създайте нов доставчик на самоличност",
|
||||
"idpAddActionImportFromOrg": "Импортиране от друга организация",
|
||||
"idpImportDialogTitle": "Импортиране на доставчик на самоличност",
|
||||
"idpImportDialogDescription": "Изберете доставчик на самоличност от организация, в която сте администратор. Той ще бъде свързан с тази организация.",
|
||||
"idpImportSearchPlaceholder": "Търсене по име на организация или доставчик...",
|
||||
"idpImportEmpty": "Няма намерени доставчици на самоличност.",
|
||||
"idpImportedDescription": "Доставчикът на самоличност беше импортиран успешно.",
|
||||
"idpDeleteGlobalQuestion": "Сигурни ли сте, че искате да изтриете този доставчик на самоличност завинаги?",
|
||||
"idpDeleteGlobalDescription": "Това ще изтрие доставичка на самоличност завинаги от всички организации, с които е свързан.",
|
||||
"idpUnassociateTitle": "Отвързване на доставчик на самоличност",
|
||||
"idpUnassociateQuestion": "Сигурни ли сте, че искате да отвържете този доставчик на самоличност от тази организация?",
|
||||
"idpUnassociateDescription": "Всички потребители, свързани с този доставчик на самоличност, ще бъдат премахнати от тази организация, но доставчика на самоличност ще продължи да съществува за други свързани организации.",
|
||||
"idpUnassociateConfirm": "Потвърдете отвързване на доставчика на самоличност",
|
||||
"idpUnassociateWarning": "Това не може да бъде отменено за тази организация.",
|
||||
"idpUnassociatedDescription": "Доставчика на самоличност е успешно отвързан от тази организация",
|
||||
"idpUnassociateMenu": "Отвързване",
|
||||
"idpDeleteAllOrgsMenu": "Изтриване"
|
||||
}
|
||||
|
||||
@@ -898,6 +898,7 @@
|
||||
"idpDisplayName": "Zobrazované jméno tohoto poskytovatele identity",
|
||||
"idpAutoProvisionUsers": "Automatická úprava uživatelů",
|
||||
"idpAutoProvisionUsersDescription": "Pokud je povoleno, uživatelé budou automaticky vytvářeni v systému při prvním přihlášení, s možností namapovat uživatele na role a organizace.",
|
||||
"idpAutoProvisionConfigureAfterCreate": "Nastavení automatického poskytování lze nakonfigurovat, jakmile je vytvořen poskytovatel identity.",
|
||||
"licenseBadge": "PE",
|
||||
"idpType": "Typ poskytovatele",
|
||||
"idpTypeDescription": "Vyberte typ poskytovatele identity, který chcete nakonfigurovat",
|
||||
@@ -949,7 +950,7 @@
|
||||
"defaultMappingsRole": "Výchozí mapování rolí",
|
||||
"defaultMappingsRoleDescription": "Výsledek tohoto výrazu musí vrátit název role definovaný v organizaci jako řetězec.",
|
||||
"defaultMappingsOrg": "Výchozí mapování organizace",
|
||||
"defaultMappingsOrgDescription": "Tento výraz musí vrátit org ID nebo pravdu, aby měl uživatel přístup k organizaci.",
|
||||
"defaultMappingsOrgDescription": "Pokud je nastaven, musí tento výraz vracet ID organizace nebo pravda, aby k této organizaci měl uživatel přístup. Pokud není nastaveno, je dostačující definice mapování rolí: uživateli je umožněn přístup, pokud pro něj lze v rámci organizace vyřešit platné mapování rolí.",
|
||||
"defaultMappingsSubmit": "Uložit výchozí mapování",
|
||||
"orgPoliciesEdit": "Upravit zásady organizace",
|
||||
"org": "Organizace",
|
||||
@@ -2026,7 +2027,7 @@
|
||||
},
|
||||
"internationaldomaindetected": "Zjištěna mezinárodní doména",
|
||||
"willbestoredas": "Bude uloženo jako:",
|
||||
"roleMappingDescription": "Určete, jak jsou role přiřazeny uživatelům, když se přihlásí, když je povoleno automatické poskytnutí služby.",
|
||||
"roleMappingDescription": "Určete, jak jsou role přiřazeny uživatelům, když se přihlásí s tímto poskytovatelem identity.",
|
||||
"selectRole": "Vyberte roli",
|
||||
"roleMappingExpression": "Výraz",
|
||||
"selectRolePlaceholder": "Vyberte roli",
|
||||
@@ -2899,5 +2900,22 @@
|
||||
"httpDestUpdatedSuccess": "Cíl byl úspěšně aktualizován",
|
||||
"httpDestCreatedSuccess": "Cíl byl úspěšně vytvořen",
|
||||
"httpDestUpdateFailed": "Nepodařilo se aktualizovat cíl",
|
||||
"httpDestCreateFailed": "Nepodařilo se vytvořit cíl"
|
||||
"httpDestCreateFailed": "Nepodařilo se vytvořit cíl",
|
||||
"idpAddActionCreateNew": "Vytvořit nového poskytovatele identity",
|
||||
"idpAddActionImportFromOrg": "Importovat z jiné organizace",
|
||||
"idpImportDialogTitle": "Importovat poskytovatele identity",
|
||||
"idpImportDialogDescription": "Vyberte poskytovatele identity z organizace, v níž jste administrátor. Tento poskytovatel bude propojen s touto organizací.",
|
||||
"idpImportSearchPlaceholder": "Hledat podle názvu organizace nebo poskytovatele...",
|
||||
"idpImportEmpty": "Nebyli nalezeni žádní poskytovatelé identity.",
|
||||
"idpImportedDescription": "Poskytovatel identity byl úspěšně importován.",
|
||||
"idpDeleteGlobalQuestion": "Opravdu chcete trvale smazat tohoto poskytovatele identity?",
|
||||
"idpDeleteGlobalDescription": "Tímto bude poskytovatel identity trvale odstraněn ze všech organizací, se kterými je spojen.",
|
||||
"idpUnassociateTitle": "Odpojit poskytovatele identity",
|
||||
"idpUnassociateQuestion": "Opravdu chcete odpojit tohoto poskytovatele identity od této organizace?",
|
||||
"idpUnassociateDescription": "Všichni uživatelé spojení s tímto poskytovatelem identity budou odstraněni z této organizace, ale poskytovatel identity zůstane nadále existovat pro ostatní přidružené organizace.",
|
||||
"idpUnassociateConfirm": "Potvrdit odpojení poskytovatele identity",
|
||||
"idpUnassociateWarning": "Toto nelze pro tuto organizaci vrátit.",
|
||||
"idpUnassociatedDescription": "Poskytovatel identity byl úspěšně odpojen od této organizace",
|
||||
"idpUnassociateMenu": "Odpojit",
|
||||
"idpDeleteAllOrgsMenu": "Odstranit"
|
||||
}
|
||||
|
||||
@@ -898,6 +898,7 @@
|
||||
"idpDisplayName": "Ein Anzeigename für diesen Identitätsanbieter",
|
||||
"idpAutoProvisionUsers": "Automatische Benutzerbereitstellung",
|
||||
"idpAutoProvisionUsersDescription": "Wenn aktiviert, werden Benutzer beim ersten Login automatisch im System erstellt, mit der Möglichkeit, Benutzer Rollen und Organisationen zuzuordnen.",
|
||||
"idpAutoProvisionConfigureAfterCreate": "Sie können die automatische Bereitstellung einstellen, sobald der Identitätsanbieter erstellt ist.",
|
||||
"licenseBadge": "EE",
|
||||
"idpType": "Anbietertyp",
|
||||
"idpTypeDescription": "Wählen Sie den Typ des Identitätsanbieters, den Sie konfigurieren möchten",
|
||||
@@ -949,7 +950,7 @@
|
||||
"defaultMappingsRole": "Standard-Rollenzuordnung",
|
||||
"defaultMappingsRoleDescription": "JMESPath zur Extraktion von Rolleninformationen aus dem ID-Token. Das Ergebnis dieses Ausdrucks muss den Rollennamen als String zurückgeben, wie er in der Organisation definiert ist.",
|
||||
"defaultMappingsOrg": "Standard-Organisationszuordnung",
|
||||
"defaultMappingsOrgDescription": "JMESPath zur Extraktion von Organisationsinformationen aus dem ID-Token. Dieser Ausdruck muss die Organisations-ID oder true zurückgeben, damit der Benutzer Zugriff auf die Organisation erhält.",
|
||||
"defaultMappingsOrgDescription": "Wenn diese Einstellung festgelegt ist, muss dieser Ausdruck die Organisations-ID oder wahr zurückgeben, damit der Benutzer diese Organisation betreten kann. Ist sie nicht festgelegt, reicht die Definition einer Rollenzuordnung aus: Der Benutzer darf eintreten, solange eine gültige Rollenzuordnung innerhalb der Organisation für ihn aufgelöst werden kann.",
|
||||
"defaultMappingsSubmit": "Standardzuordnungen speichern",
|
||||
"orgPoliciesEdit": "Organisationsrichtlinie bearbeiten",
|
||||
"org": "Organisation",
|
||||
@@ -2026,7 +2027,7 @@
|
||||
},
|
||||
"internationaldomaindetected": "Internationale Domain erkannt",
|
||||
"willbestoredas": "Wird gespeichert als:",
|
||||
"roleMappingDescription": "Legen Sie fest, wie den Benutzern Rollen zugewiesen werden, wenn sie sich anmelden, wenn Auto Provision aktiviert ist.",
|
||||
"roleMappingDescription": "Bestimmen Sie, wie Rollen zugewiesen werden, wenn sich Benutzer mit diesem Identitätsanbieter anmelden.",
|
||||
"selectRole": "Wählen Sie eine Rolle",
|
||||
"roleMappingExpression": "Ausdruck",
|
||||
"selectRolePlaceholder": "Rolle auswählen",
|
||||
@@ -2899,5 +2900,22 @@
|
||||
"httpDestUpdatedSuccess": "Ziel erfolgreich aktualisiert",
|
||||
"httpDestCreatedSuccess": "Ziel erfolgreich erstellt",
|
||||
"httpDestUpdateFailed": "Fehler beim Aktualisieren des Ziels",
|
||||
"httpDestCreateFailed": "Fehler beim Erstellen des Ziels"
|
||||
"httpDestCreateFailed": "Fehler beim Erstellen des Ziels",
|
||||
"idpAddActionCreateNew": "Neuen Identitätsanbieter erstellen",
|
||||
"idpAddActionImportFromOrg": "Von einer anderen Organisation importieren",
|
||||
"idpImportDialogTitle": "Identitätsanbieter importieren",
|
||||
"idpImportDialogDescription": "Wählen Sie einen Identitätsanbieter aus einer Organisation, in der Sie Administrator sind. Er wird mit dieser Organisation verknüpft.",
|
||||
"idpImportSearchPlaceholder": "Nach Organisation oder Anbieternamen suchen...",
|
||||
"idpImportEmpty": "Keine Identitätsanbieter gefunden.",
|
||||
"idpImportedDescription": "Identitätsanbieter erfolgreich importiert.",
|
||||
"idpDeleteGlobalQuestion": "Sind Sie sicher, dass Sie diesen Identitätsanbieter dauerhaft löschen möchten?",
|
||||
"idpDeleteGlobalDescription": "Dies wird den Identitätsanbieter dauerhaft von allen Organisationen löschen, mit denen er verbunden ist.",
|
||||
"idpUnassociateTitle": "Verknüpfung mit Identitätsanbieter aufheben",
|
||||
"idpUnassociateQuestion": "Sind Sie sicher, dass Sie die Verknüpfung dieses Identitätsanbieters mit dieser Organisation aufheben möchten?",
|
||||
"idpUnassociateDescription": "Alle Benutzer, die mit diesem Identitätsanbieter verbunden sind, werden aus dieser Organisation entfernt, aber der Identitätsanbieter bleibt für andere verbundene Organisationen weiterhin bestehen.",
|
||||
"idpUnassociateConfirm": "Verknüpfung des Identitätsanbieters aufheben bestätigen",
|
||||
"idpUnassociateWarning": "Dies kann für diese Organisation nicht rückgängig gemacht werden.",
|
||||
"idpUnassociatedDescription": "Identitätsanbieter erfolgreich von dieser Organisation gelöst",
|
||||
"idpUnassociateMenu": "Verknüpfung aufheben",
|
||||
"idpDeleteAllOrgsMenu": "Löschen"
|
||||
}
|
||||
|
||||
@@ -167,7 +167,7 @@
|
||||
"proxyResourceTitle": "Manage Public Resources",
|
||||
"proxyResourceDescription": "Create and manage resources that are publicly accessible through a web browser",
|
||||
"proxyResourcesBannerTitle": "Web-based Public Access",
|
||||
"proxyResourcesBannerDescription": "Public resources are HTTPS or TCP/UDP proxies accessible to anyone on the internet through a web browser. Unlike private resources, they do not require client-side software and can include identity and context-aware access policies.",
|
||||
"proxyResourcesBannerDescription": "Public resources are HTTPS proxies accessible to anyone on the internet through a web browser. Unlike private resources, they do not require client-side software and can include identity and context-aware access policies.",
|
||||
"clientResourceTitle": "Manage Private Resources",
|
||||
"clientResourceDescription": "Create and manage resources that are only accessible through a connected client",
|
||||
"privateResourcesBannerTitle": "Zero-Trust Private Access",
|
||||
@@ -384,7 +384,7 @@
|
||||
"userTitle": "Manage All Users",
|
||||
"userDescription": "View and manage all users in the system",
|
||||
"userAbount": "About User Management",
|
||||
"userAbountDescription": "This table displays all root user objects in the system. Each user may belong to multiple organizations. Removing a user from an organization does not delete their root user object - they will remain in the system. To completely remove a user from the system, you must delete their root user object using the delete action in this table.",
|
||||
"userAbountDescription": "This table displays all base user objects in the system. Each user may belong to multiple organizations. Removing a user from an organization does not delete their base user object. They will remain in the system. To completely remove a user from the system, you must delete their base user object using the delete action in this table.",
|
||||
"userServer": "Server Users",
|
||||
"userSearch": "Search server users...",
|
||||
"userErrorDelete": "Error deleting user",
|
||||
@@ -527,7 +527,7 @@
|
||||
"userSettings": "User Information",
|
||||
"userSettingsDescription": "Enter the details for the new user",
|
||||
"inviteEmailSent": "Send invite email to user",
|
||||
"inviteValid": "Valid For",
|
||||
"inviteValid": "Invite Valid For (days)",
|
||||
"selectDuration": "Select duration",
|
||||
"selectResource": "Select Resource",
|
||||
"filterByResource": "Filter By Resource",
|
||||
@@ -902,6 +902,7 @@
|
||||
"idpDisplayName": "A display name for this identity provider",
|
||||
"idpAutoProvisionUsers": "Auto Provision Users",
|
||||
"idpAutoProvisionUsersDescription": "When enabled, users will be automatically created in the system upon first login with the ability to map users to roles and organizations.",
|
||||
"idpAutoProvisionConfigureAfterCreate": "You can configure auto provision settings once the identity provider is created.",
|
||||
"licenseBadge": "EE",
|
||||
"idpType": "Provider Type",
|
||||
"idpTypeDescription": "Select the type of identity provider you want to configure",
|
||||
@@ -953,7 +954,7 @@
|
||||
"defaultMappingsRole": "Default Role Mapping",
|
||||
"defaultMappingsRoleDescription": "The result of this expression must return the role name as defined in the organization as a string.",
|
||||
"defaultMappingsOrg": "Default Organization Mapping",
|
||||
"defaultMappingsOrgDescription": "When set, this expression must return the organization ID or true for the user to access that organization. When unset, defining an organization policy for that org is enough: the user is allowed in as long as a valid role mapping can be resolved for them within the organization.",
|
||||
"defaultMappingsOrgDescription": "When set, this expression must return the organization ID or true for the user to access that organization. When unset, defining a role mapping is enough: the user is allowed in as long as a valid role mapping can be resolved for them within the organization.",
|
||||
"defaultMappingsSubmit": "Save Default Mappings",
|
||||
"orgPoliciesEdit": "Edit Organization Policy",
|
||||
"org": "Organization",
|
||||
@@ -1351,7 +1352,7 @@
|
||||
"sidebarBillingAndLicenses": "Billing & Licenses",
|
||||
"sidebarLogsAnalytics": "Analytics",
|
||||
"alertingTitle": "Alerting",
|
||||
"alertingDescription": "Define sources, triggers, and actions for notifications.",
|
||||
"alertingDescription": "Define sources, triggers, and actions for notifications",
|
||||
"alertingRules": "Alert rules",
|
||||
"alertingSearchRules": "Search rules…",
|
||||
"alertingAddRule": "Create Rule",
|
||||
@@ -1375,6 +1376,18 @@
|
||||
"alertingPickSites": "Sites",
|
||||
"alertingPickHealthChecks": "Health checks",
|
||||
"alertingPickResources": "Resources",
|
||||
"alertingAllSites": "All Sites",
|
||||
"alertingAllSitesDescription": "Alert fires for any site",
|
||||
"alertingSpecificSites": "Specific Sites",
|
||||
"alertingSpecificSitesDescription": "Choose specific sites to watch",
|
||||
"alertingAllHealthChecks": "All Health Checks",
|
||||
"alertingAllHealthChecksDescription": "Alert fires for any health check",
|
||||
"alertingSpecificHealthChecks": "Specific Health Checks",
|
||||
"alertingSpecificHealthChecksDescription": "Choose specific health checks to watch",
|
||||
"alertingAllResources": "All Resources",
|
||||
"alertingAllResourcesDescription": "Alert fires for any resource",
|
||||
"alertingSpecificResources": "Specific resources",
|
||||
"alertingSpecificResourcesDescription": "Choose specific resources to watch",
|
||||
"alertingSelectResources": "Select resources…",
|
||||
"alertingResourcesSelected": "{count} resources selected",
|
||||
"alertingResourcesEmpty": "No resources with targets in the first 10 results.",
|
||||
@@ -1382,8 +1395,16 @@
|
||||
"alertingTrigger": "When to alert",
|
||||
"alertingTriggerSiteOnline": "Site online",
|
||||
"alertingTriggerSiteOffline": "Site offline",
|
||||
"alertingTriggerSiteToggle": "Site status changes",
|
||||
"alertingTriggerHcHealthy": "Health check healthy",
|
||||
"alertingTriggerHcUnhealthy": "Health check unhealthy",
|
||||
"alertingTriggerHcToggle": "Health check status changes",
|
||||
"alertingTriggerResourceHealthy": "Resource healthy",
|
||||
"alertingTriggerResourceUnhealthy": "Resource unhealthy",
|
||||
"alertingSearchHealthChecks": "Search health checks…",
|
||||
"alertingHealthChecksEmpty": "No health checks available.",
|
||||
"alertingTriggerResourceToggle": "Resource status changes",
|
||||
"alertingSourceResource": "Resource",
|
||||
"alertingSectionActions": "Actions",
|
||||
"alertingAddAction": "Add action",
|
||||
"alertingActionNotify": "Email",
|
||||
@@ -1410,12 +1431,15 @@
|
||||
"alertingRolesSelected": "{count} roles selected",
|
||||
"alertingSummarySites": "Sites ({count})",
|
||||
"alertingSummaryHealthChecks": "Health checks ({count})",
|
||||
"alertingSummaryResources": "Resources ({count})",
|
||||
"alertingErrorNameRequired": "Enter a name",
|
||||
"alertingErrorActionsMin": "Add at least one action",
|
||||
"alertingErrorPickSites": "Select at least one site",
|
||||
"alertingErrorPickHealthChecks": "Select at least one health check",
|
||||
"alertingErrorPickResources": "Select at least one resource",
|
||||
"alertingErrorTriggerSite": "Choose a site trigger",
|
||||
"alertingErrorTriggerHealth": "Choose a health check trigger",
|
||||
"alertingErrorTriggerResource": "Choose a resource trigger",
|
||||
"alertingErrorNotifyRecipients": "Pick users, roles, or at least one email",
|
||||
"alertingConfigureSource": "Configure Source",
|
||||
"alertingConfigureTrigger": "Configure Trigger",
|
||||
@@ -2152,7 +2176,7 @@
|
||||
},
|
||||
"internationaldomaindetected": "International Domain Detected",
|
||||
"willbestoredas": "Will be stored as:",
|
||||
"roleMappingDescription": "Determine how roles are assigned to users when they sign in when Auto Provision is enabled.",
|
||||
"roleMappingDescription": "Determine how roles are assigned to users when they sign in with this identity provider.",
|
||||
"selectRole": "Select a Role",
|
||||
"roleMappingExpression": "Expression",
|
||||
"selectRolePlaceholder": "Choose a role",
|
||||
@@ -2541,7 +2565,7 @@
|
||||
"action": "Action",
|
||||
"actor": "Actor",
|
||||
"timestamp": "Timestamp",
|
||||
"accessLogs": "Access Logs",
|
||||
"accessLogs": "Authentication Logs",
|
||||
"exportCsv": "Export CSV",
|
||||
"exportError": "Unknown error when exporting CSV",
|
||||
"exportCsvTooltip": "Within Time Range",
|
||||
@@ -2562,25 +2586,25 @@
|
||||
"noMoreAuthMethods": "No Valid Auth",
|
||||
"ip": "IP",
|
||||
"reason": "Reason",
|
||||
"requestLogs": "Request Logs",
|
||||
"requestLogs": "HTTPS Request Logs",
|
||||
"requestAnalytics": "Request Analytics",
|
||||
"host": "Host",
|
||||
"location": "Location",
|
||||
"actionLogs": "Action Logs",
|
||||
"sidebarLogsRequest": "Request Logs",
|
||||
"sidebarLogsAccess": "Access Logs",
|
||||
"sidebarLogsAction": "Action Logs",
|
||||
"actionLogs": "Admin Action Logs",
|
||||
"sidebarLogsRequest": "HTTPS Request Logs",
|
||||
"sidebarLogsAccess": "Authentication Logs",
|
||||
"sidebarLogsAction": "Admin Action Logs",
|
||||
"logRetention": "Log Retention",
|
||||
"logRetentionDescription": "Manage how long different types of logs are retained for this organization or disable them",
|
||||
"requestLogsDescription": "View detailed request logs for resources in this organization",
|
||||
"requestLogsDescription": "View detailed request logs for HTTPS resources in this organization",
|
||||
"requestAnalyticsDescription": "View detailed request analytics for resources in this organization",
|
||||
"logRetentionRequestLabel": "Request Log Retention",
|
||||
"logRetentionRequestLabel": "HTTPS Request Log Retention",
|
||||
"logRetentionRequestDescription": "How long to retain request logs",
|
||||
"logRetentionAccessLabel": "Access Log Retention",
|
||||
"logRetentionAccessLabel": "Authentication Log Retention",
|
||||
"logRetentionAccessDescription": "How long to retain access logs",
|
||||
"logRetentionActionLabel": "Action Log Retention",
|
||||
"logRetentionActionLabel": "Admin Action Log Retention",
|
||||
"logRetentionActionDescription": "How long to retain action logs",
|
||||
"logRetentionConnectionLabel": "Connection Log Retention",
|
||||
"logRetentionConnectionLabel": "Network Log Retention",
|
||||
"logRetentionConnectionDescription": "How long to retain connection logs",
|
||||
"logRetentionDisabled": "Disabled",
|
||||
"logRetention3Days": "3 days",
|
||||
@@ -2592,10 +2616,10 @@
|
||||
"logRetentionEndOfFollowingYear": "End of following year",
|
||||
"actionLogsDescription": "View a history of actions performed in this organization",
|
||||
"accessLogsDescription": "View access auth requests for resources in this organization",
|
||||
"connectionLogs": "Connection Logs",
|
||||
"connectionLogsDescription": "View connection logs for tunnels in this organization",
|
||||
"sidebarLogsConnection": "Connection Logs",
|
||||
"sidebarLogsStreaming": "Streaming",
|
||||
"connectionLogs": "Network Logs",
|
||||
"connectionLogsDescription": "View network session logs handled by sites in this organization",
|
||||
"sidebarLogsConnection": "Network Logs",
|
||||
"sidebarLogsStreaming": "Event Streaming",
|
||||
"sourceAddress": "Source Address",
|
||||
"destinationAddress": "Destination Address",
|
||||
"duration": "Duration",
|
||||
@@ -3027,13 +3051,13 @@
|
||||
"httpDestFormatSingleDescription": "Sends a separate HTTP POST for each individual event. Use only for endpoints that cannot handle batches.",
|
||||
"httpDestLogTypesTitle": "Log Types",
|
||||
"httpDestLogTypesDescription": "Choose which log types are forwarded to this destination. Only enabled log types will be streamed.",
|
||||
"httpDestAccessLogsTitle": "Access Logs",
|
||||
"httpDestAccessLogsTitle": "Authentication Logs",
|
||||
"httpDestAccessLogsDescription": "Resource access attempts, including authenticated and denied requests.",
|
||||
"httpDestActionLogsTitle": "Action Logs",
|
||||
"httpDestActionLogsTitle": "Admin Action Logs",
|
||||
"httpDestActionLogsDescription": "Administrative actions performed by users within the organization.",
|
||||
"httpDestConnectionLogsTitle": "Connection Logs",
|
||||
"httpDestConnectionLogsTitle": "Network Logs",
|
||||
"httpDestConnectionLogsDescription": "Site and tunnel connection events, including connects and disconnects.",
|
||||
"httpDestRequestLogsTitle": "Request Logs",
|
||||
"httpDestRequestLogsTitle": "HTTPS Request Logs",
|
||||
"httpDestRequestLogsDescription": "HTTP request logs for proxied resources, including method, path, and response code.",
|
||||
"httpDestSaveChanges": "Save Changes",
|
||||
"httpDestCreateDestination": "Create Destination",
|
||||
@@ -3052,5 +3076,23 @@
|
||||
"healthCheckTabConnection": "Connection",
|
||||
"healthCheckTabAdvanced": "Advanced",
|
||||
"healthCheckStrategyNotAvailable": "This strategy is not available. Please contact sales to enable this feature.",
|
||||
"uptime30d": "Uptime (30d)"
|
||||
"uptime30d": "Uptime (30d)",
|
||||
"idpAddActionCreateNew": "Create new identity provider",
|
||||
"idpAddActionImportFromOrg": "Import from another organization",
|
||||
"idpImportDialogTitle": "Import Identity Provider",
|
||||
"idpImportDialogDescription": "Choose an identity provider from an organization where you are an admin. It will be linked to this organization.",
|
||||
"idpImportSearchPlaceholder": "Search by organization or provider name...",
|
||||
"idpImportEmpty": "No identity providers found.",
|
||||
"idpImportedDescription": "Identity provider imported successfully.",
|
||||
"idpDeleteGlobalQuestion": "Are you sure you want to permanently delete this identity provider?",
|
||||
"idpDeleteGlobalDescription": "This will permanently delete the identity provider from all organizations it is associated with.",
|
||||
"idpUnassociateTitle": "Unassociate Identity Provider",
|
||||
"idpUnassociateQuestion": "Are you sure you want to unassociate this identity provider from this organization?",
|
||||
"idpUnassociateDescription": "All users associated with this identity provider will be removed from this organization, but the identity provider will still continue to exist for other associated organizations.",
|
||||
"idpUnassociateConfirm": "Confirm Unassociate Identity Provider",
|
||||
"idpUnassociateWarning": "This cannot be undone for this organization.",
|
||||
"idpUnassociatedDescription": "Identity provider unassociated from this organization successfully",
|
||||
"idpUnassociateMenu": "Unassociate",
|
||||
"idpDeleteAllOrgsMenu": "Delete",
|
||||
"publicIpEndpoint": "Endpoint"
|
||||
}
|
||||
|
||||
@@ -898,6 +898,7 @@
|
||||
"idpDisplayName": "Un nombre mostrado para este proveedor de identidad",
|
||||
"idpAutoProvisionUsers": "Auto-Provisión de Usuarios",
|
||||
"idpAutoProvisionUsersDescription": "Cuando está habilitado, los usuarios serán creados automáticamente en el sistema al iniciar sesión con la capacidad de asignar a los usuarios a roles y organizaciones.",
|
||||
"idpAutoProvisionConfigureAfterCreate": "Puede configurar las configuraciones de provisión automática una vez que se haya creado el proveedor de identidad.",
|
||||
"licenseBadge": "EE",
|
||||
"idpType": "Tipo de proveedor",
|
||||
"idpTypeDescription": "Seleccione el tipo de proveedor de identidad que desea configurar",
|
||||
@@ -949,7 +950,7 @@
|
||||
"defaultMappingsRole": "Mapeo de Rol por defecto",
|
||||
"defaultMappingsRoleDescription": "El resultado de esta expresión debe devolver el nombre del rol tal y como se define en la organización como una cadena.",
|
||||
"defaultMappingsOrg": "Mapeo de organización por defecto",
|
||||
"defaultMappingsOrgDescription": "Esta expresión debe devolver el ID de org o verdadero para que el usuario pueda acceder a la organización.",
|
||||
"defaultMappingsOrgDescription": "Cuando se establece, esta expresión debe devolver el ID de la organización o verdadero para que el usuario acceda a esa organización. Cuando no se establece, definir un mapeo de roles es suficiente: se permite la entrada del usuario siempre que se pueda resolver un mapeo de roles válido para él dentro de la organización.",
|
||||
"defaultMappingsSubmit": "Guardar asignaciones por defecto",
|
||||
"orgPoliciesEdit": "Editar Política de Organización",
|
||||
"org": "Organización",
|
||||
@@ -2026,7 +2027,7 @@
|
||||
},
|
||||
"internationaldomaindetected": "Dominio Internacional detectado",
|
||||
"willbestoredas": "Se almacenará como:",
|
||||
"roleMappingDescription": "Determinar cómo se asignan los roles a los usuarios cuando se registran cuando está habilitada la provisión automática.",
|
||||
"roleMappingDescription": "Determine cómo se asignan los roles a los usuarios cuando inician sesión con este proveedor de identidad.",
|
||||
"selectRole": "Seleccione un rol",
|
||||
"roleMappingExpression": "Expresión",
|
||||
"selectRolePlaceholder": "Elija un rol",
|
||||
@@ -2899,5 +2900,22 @@
|
||||
"httpDestUpdatedSuccess": "Destino actualizado correctamente",
|
||||
"httpDestCreatedSuccess": "Destino creado correctamente",
|
||||
"httpDestUpdateFailed": "Error al actualizar destino",
|
||||
"httpDestCreateFailed": "Error al crear el destino"
|
||||
"httpDestCreateFailed": "Error al crear el destino",
|
||||
"idpAddActionCreateNew": "Crear nuevo proveedor de identidad",
|
||||
"idpAddActionImportFromOrg": "Importar de otra organización",
|
||||
"idpImportDialogTitle": "Importar Proveedor de Identidad",
|
||||
"idpImportDialogDescription": "Elija un proveedor de identidad de una organización donde usted sea administrador. Se vinculará a esta organización.",
|
||||
"idpImportSearchPlaceholder": "Buscar por nombre de organización o proveedor...",
|
||||
"idpImportEmpty": "No se encontraron proveedores de identidad.",
|
||||
"idpImportedDescription": "Proveedor de identidad importado con éxito.",
|
||||
"idpDeleteGlobalQuestion": "¿Está seguro de que desea eliminar permanentemente este proveedor de identidad?",
|
||||
"idpDeleteGlobalDescription": "Esto eliminará permanentemente el proveedor de identidad de todas las organizaciones con las que está asociado.",
|
||||
"idpUnassociateTitle": "Desasociar Proveedor de Identidad",
|
||||
"idpUnassociateQuestion": "¿Está seguro de que desea desasociar este proveedor de identidad de esta organización?",
|
||||
"idpUnassociateDescription": "Todos los usuarios asociados con este proveedor de identidad serán eliminados de esta organización, pero el proveedor de identidad continuará existiendo para otras organizaciones asociadas.",
|
||||
"idpUnassociateConfirm": "Confirme Desasociar Proveedor de Identidad",
|
||||
"idpUnassociateWarning": "Esto no se puede deshacer para esta organización.",
|
||||
"idpUnassociatedDescription": "Proveedor de identidad desasociado de esta organización con éxito",
|
||||
"idpUnassociateMenu": "Desasociar",
|
||||
"idpDeleteAllOrgsMenu": "Eliminar"
|
||||
}
|
||||
|
||||
@@ -898,6 +898,7 @@
|
||||
"idpDisplayName": "Un nom d'affichage pour ce fournisseur d'identité",
|
||||
"idpAutoProvisionUsers": "Approvisionnement automatique des utilisateurs",
|
||||
"idpAutoProvisionUsersDescription": "Lorsque cette option est activée, les utilisateurs seront automatiquement créés dans le système lors de leur première connexion avec la possibilité de mapper les utilisateurs aux rôles et aux organisations.",
|
||||
"idpAutoProvisionConfigureAfterCreate": "Vous pouvez configurer les paramètres de provisionnement automatique une fois le fournisseur d'identités créé.",
|
||||
"licenseBadge": "EE",
|
||||
"idpType": "Type de fournisseur",
|
||||
"idpTypeDescription": "Sélectionnez le type de fournisseur d'identité que vous souhaitez configurer",
|
||||
@@ -949,7 +950,7 @@
|
||||
"defaultMappingsRole": "Mappage de rôle par défaut",
|
||||
"defaultMappingsRoleDescription": "JMESPath pour extraire les informations de rôle du jeton ID. Le résultat de cette expression doit renvoyer le nom du rôle tel que défini dans l'organisation sous forme de chaîne.",
|
||||
"defaultMappingsOrg": "Mappage d'organisation par défaut",
|
||||
"defaultMappingsOrgDescription": "JMESPath pour extraire les informations d'organisation du jeton ID. Cette expression doit renvoyer l'ID de l'organisation ou true pour que l'utilisateur soit autorisé à accéder à l'organisation.",
|
||||
"defaultMappingsOrgDescription": "Lorsque défini, cette expression doit renvoyer l'identifiant de l'organisation ou vrai pour que l'utilisateur accède à cette organisation. Lorsqu'indéfini, définir un mappage de rôle est suffisant : l'utilisateur est autorisé tant qu'un mappage de rôle valide peut être résolu pour lui au sein de l'organisation.",
|
||||
"defaultMappingsSubmit": "Enregistrer les mappages par défaut",
|
||||
"orgPoliciesEdit": "Modifier la politique d'organisation",
|
||||
"org": "Organisation",
|
||||
@@ -2026,7 +2027,7 @@
|
||||
},
|
||||
"internationaldomaindetected": "Domaine international détecté",
|
||||
"willbestoredas": "Sera stocké comme :",
|
||||
"roleMappingDescription": "Détermine comment les rôles sont assignés aux utilisateurs lorsqu'ils se connectent lorsque la fourniture automatique est activée.",
|
||||
"roleMappingDescription": "Déterminez comment les rôles sont attribués aux utilisateurs lorsqu'ils se connectent avec ce fournisseur d'identité.",
|
||||
"selectRole": "Sélectionnez un rôle",
|
||||
"roleMappingExpression": "Expression",
|
||||
"selectRolePlaceholder": "Choisir un rôle",
|
||||
@@ -2899,5 +2900,22 @@
|
||||
"httpDestUpdatedSuccess": "Destination mise à jour avec succès",
|
||||
"httpDestCreatedSuccess": "Destination créée avec succès",
|
||||
"httpDestUpdateFailed": "Impossible de mettre à jour la destination",
|
||||
"httpDestCreateFailed": "Impossible de créer la destination"
|
||||
"httpDestCreateFailed": "Impossible de créer la destination",
|
||||
"idpAddActionCreateNew": "Créer un nouveau fournisseur d'identité",
|
||||
"idpAddActionImportFromOrg": "Importer d'une autre organisation",
|
||||
"idpImportDialogTitle": "Importer le fournisseur d'identité",
|
||||
"idpImportDialogDescription": "Choisissez un fournisseur d'identités d'une organisation où vous êtes administrateur. Il sera lié à cette organisation.",
|
||||
"idpImportSearchPlaceholder": "Recherche par nom d'organisation ou de fournisseur...",
|
||||
"idpImportEmpty": "Aucun fournisseur d'identités trouvé.",
|
||||
"idpImportedDescription": "Fournisseur d'identités importé avec succès.",
|
||||
"idpDeleteGlobalQuestion": "Êtes-vous sûr de vouloir supprimer définitivement ce fournisseur d'identités?",
|
||||
"idpDeleteGlobalDescription": "Cela supprimera définitivement le fournisseur d'identités de toutes les organisations auxquelles il est associé.",
|
||||
"idpUnassociateTitle": "Dissocier le fournisseur d'identité",
|
||||
"idpUnassociateQuestion": "Êtes-vous sûr de vouloir dissocier ce fournisseur d'identités de cette organisation?",
|
||||
"idpUnassociateDescription": "Tous les utilisateurs associés à ce fournisseur d'identités seront retirés de cette organisation, mais le fournisseur d'identités continuera d'exister pour d'autres organisations associées.",
|
||||
"idpUnassociateConfirm": "Confirmer la dissociation du fournisseur d'identités",
|
||||
"idpUnassociateWarning": "Cela ne peut pas être annulé pour cette organisation.",
|
||||
"idpUnassociatedDescription": "Fournisseur d'identités dissocié de cette organisation avec succès",
|
||||
"idpUnassociateMenu": "Dissocier",
|
||||
"idpDeleteAllOrgsMenu": "Supprimer"
|
||||
}
|
||||
|
||||
@@ -898,6 +898,7 @@
|
||||
"idpDisplayName": "Un nome visualizzato per questo provider di identità",
|
||||
"idpAutoProvisionUsers": "Provisioning Automatico Utenti",
|
||||
"idpAutoProvisionUsersDescription": "Quando abilitato, gli utenti verranno creati automaticamente nel sistema al primo accesso con la possibilità di mappare gli utenti a ruoli e organizzazioni.",
|
||||
"idpAutoProvisionConfigureAfterCreate": "Puoi configurare le impostazioni di auto fornitura una volta creato il provider di identità.",
|
||||
"licenseBadge": "EE",
|
||||
"idpType": "Tipo di Provider",
|
||||
"idpTypeDescription": "Seleziona il tipo di provider di identità che desideri configurare",
|
||||
@@ -949,7 +950,7 @@
|
||||
"defaultMappingsRole": "Mappatura Ruolo Predefinito",
|
||||
"defaultMappingsRoleDescription": "JMESPath per estrarre informazioni sul ruolo dal token ID. Il risultato di questa espressione deve restituire il nome del ruolo come definito nell'organizzazione come stringa.",
|
||||
"defaultMappingsOrg": "Mappatura Organizzazione Predefinita",
|
||||
"defaultMappingsOrgDescription": "JMESPath per estrarre informazioni sull'organizzazione dal token ID. Questa espressione deve restituire l'ID dell'organizzazione o true affinché l'utente possa accedere all'organizzazione.",
|
||||
"defaultMappingsOrgDescription": "Quando impostata, questa espressione deve restituire l'ID dell'organizzazione o true affinché l'utente possa accedere a quell'organizzazione. Quando non impostata, è sufficiente definire una mappatura di ruoli: l'utente è autorizzato se esiste una mappatura di ruolo valida per loro all'interno dell'organizzazione.",
|
||||
"defaultMappingsSubmit": "Salva Mappature Predefinite",
|
||||
"orgPoliciesEdit": "Modifica Politica Organizzazione",
|
||||
"org": "Organizzazione",
|
||||
@@ -2026,7 +2027,7 @@
|
||||
},
|
||||
"internationaldomaindetected": "Dominio Internazionale Rilevato",
|
||||
"willbestoredas": "Verrà conservato come:",
|
||||
"roleMappingDescription": "Determinare come i ruoli sono assegnati agli utenti quando accedono quando è abilitata la fornitura automatica.",
|
||||
"roleMappingDescription": "Determina come i ruoli vengono assegnati agli utenti quando si accede con questo provider di identità.",
|
||||
"selectRole": "Seleziona un ruolo",
|
||||
"roleMappingExpression": "Espressione",
|
||||
"selectRolePlaceholder": "Scegli un ruolo",
|
||||
@@ -2899,5 +2900,22 @@
|
||||
"httpDestUpdatedSuccess": "Destinazione aggiornata con successo",
|
||||
"httpDestCreatedSuccess": "Destinazione creata con successo",
|
||||
"httpDestUpdateFailed": "Impossibile aggiornare la destinazione",
|
||||
"httpDestCreateFailed": "Impossibile creare la destinazione"
|
||||
"httpDestCreateFailed": "Impossibile creare la destinazione",
|
||||
"idpAddActionCreateNew": "Crea nuovo provider di identità",
|
||||
"idpAddActionImportFromOrg": "Importa da un'altra organizzazione",
|
||||
"idpImportDialogTitle": "Importa Provider di Identità",
|
||||
"idpImportDialogDescription": "Scegli un provider di identità da un'organizzazione di cui sei amministratore. Verrà collegato a questa organizzazione.",
|
||||
"idpImportSearchPlaceholder": "Cerca per nome organizzazione o provider...",
|
||||
"idpImportEmpty": "Nessun provider di identità trovato.",
|
||||
"idpImportedDescription": "Provider di identità importato con successo.",
|
||||
"idpDeleteGlobalQuestion": "Sei sicuro di voler eliminare definitivamente questo provider di identità?",
|
||||
"idpDeleteGlobalDescription": "Questo eliminerà definitivamente il provider di identità da tutte le organizzazioni con cui è associato.",
|
||||
"idpUnassociateTitle": "Disassociare Provider di Identità",
|
||||
"idpUnassociateQuestion": "Sei sicuro di voler disassociare questo provider di identità da questa organizzazione?",
|
||||
"idpUnassociateDescription": "Tutti gli utenti associati a questo provider di identità verranno rimossi da questa organizzazione, ma il provider di identità continuerà ad esistere per altre organizzazioni associate.",
|
||||
"idpUnassociateConfirm": "Conferma Disassociazione Provider di Identità",
|
||||
"idpUnassociateWarning": "Questo non può essere annullato per questa organizzazione.",
|
||||
"idpUnassociatedDescription": "Provider di identità disassociato con successo da questa organizzazione",
|
||||
"idpUnassociateMenu": "Disassocia",
|
||||
"idpDeleteAllOrgsMenu": "Elimina"
|
||||
}
|
||||
|
||||
@@ -898,6 +898,7 @@
|
||||
"idpDisplayName": "이 신원 공급자를 위한 표시 이름",
|
||||
"idpAutoProvisionUsers": "사용자 자동 프로비저닝",
|
||||
"idpAutoProvisionUsersDescription": "활성화되면 사용자가 첫 로그인 시 시스템에 자동으로 생성되며, 사용자와 역할 및 조직을 매핑할 수 있습니다.",
|
||||
"idpAutoProvisionConfigureAfterCreate": "아이덴티티 공급자가 생성되면 자동 프로비저닝 설정을 구성할 수 있습니다.",
|
||||
"licenseBadge": "EE",
|
||||
"idpType": "제공자 유형",
|
||||
"idpTypeDescription": "구성할 ID 공급자의 유형을 선택하십시오.",
|
||||
@@ -949,7 +950,7 @@
|
||||
"defaultMappingsRole": "기본 역할 매핑",
|
||||
"defaultMappingsRoleDescription": "이 표현식의 결과는 조직에서 정의된 역할 이름을 문자열로 반환해야 합니다.",
|
||||
"defaultMappingsOrg": "기본 조직 매핑",
|
||||
"defaultMappingsOrgDescription": "이 표현식은 사용자가 조직에 접근할 수 있도록 조직 ID 또는 true를 반환해야 합니다.",
|
||||
"defaultMappingsOrgDescription": "이 표현식은 사용자가 조직에 접근할 수 있도록 조직 ID 또는 true를 반환해야 합니다. 설정되지 않으면, 역할 매핑 정의가 충분합니다: 사용자는 유효한 역할 매핑이 해석되는 한 조직에 허용됩니다.",
|
||||
"defaultMappingsSubmit": "기본 매핑 저장",
|
||||
"orgPoliciesEdit": "조직 정책 편집",
|
||||
"org": "조직",
|
||||
@@ -2026,7 +2027,7 @@
|
||||
},
|
||||
"internationaldomaindetected": "국제 도메인 감지됨",
|
||||
"willbestoredas": "다음으로 저장됩니다:",
|
||||
"roleMappingDescription": "자동 프로비저닝이 활성화되면 사용자가 로그인할 때 역할이 할당되는 방법을 결정합니다.",
|
||||
"roleMappingDescription": "사용자가 이 아이덴티티 공급자로 로그인할 때 역할이 할당되는 방법을 결정합니다.",
|
||||
"selectRole": "역할 선택",
|
||||
"roleMappingExpression": "표현식",
|
||||
"selectRolePlaceholder": "역할 선택",
|
||||
@@ -2899,5 +2900,22 @@
|
||||
"httpDestUpdatedSuccess": "대상지가 성공적으로 업데이트되었습니다",
|
||||
"httpDestCreatedSuccess": "대상지가 성공적으로 생성되었습니다",
|
||||
"httpDestUpdateFailed": "대상지를 업데이트하는 데 실패했습니다",
|
||||
"httpDestCreateFailed": "대상지를 생성하는 데 실패했습니다"
|
||||
"httpDestCreateFailed": "대상지를 생성하는 데 실패했습니다",
|
||||
"idpAddActionCreateNew": "새로운 아이덴티티 공급자 생성",
|
||||
"idpAddActionImportFromOrg": "다른 조직에서 가져오기",
|
||||
"idpImportDialogTitle": "아이덴티티 공급자 가져오기",
|
||||
"idpImportDialogDescription": "관리자인 조직에서 아이덴티티 공급자를 선택하십시오. 이는 이 조직에 연결됩니다.",
|
||||
"idpImportSearchPlaceholder": "조직 또는 공급자 이름으로 검색...",
|
||||
"idpImportEmpty": "아이덴티티 공급자를 찾을 수 없습니다.",
|
||||
"idpImportedDescription": "아이덴티티 공급자가 성공적으로 가져왔습니다.",
|
||||
"idpDeleteGlobalQuestion": "정말로 이 아이덴티티 공급자를 영구적으로 삭제하시겠습니까?",
|
||||
"idpDeleteGlobalDescription": "이것은 연관된 모든 조직에서 아이덴티티 공급자를 영구적으로 삭제합니다.",
|
||||
"idpUnassociateTitle": "아이덴티티 공급자의 연관 해제",
|
||||
"idpUnassociateQuestion": "정말로 이 조직에서 이 아이덴티티 공급자의 연관을 해제하시겠습니까?",
|
||||
"idpUnassociateDescription": "이 아이덴티티 공급자와 연관된 모든 사용자는 이 조직에서 제거될 것이지만, 아이덴티티 공급자는 다른 연관된 조직에 계속해서 존재할 것입니다.",
|
||||
"idpUnassociateConfirm": "아이덴티티 공급자 연관 해제 확인",
|
||||
"idpUnassociateWarning": "이 조직에서 이것은 되돌릴 수 없습니다.",
|
||||
"idpUnassociatedDescription": "아이덴티티 공급자가 이 조직에서 성공적으로 연관 해제되었습니다",
|
||||
"idpUnassociateMenu": "연관 해제",
|
||||
"idpDeleteAllOrgsMenu": "삭제"
|
||||
}
|
||||
|
||||
@@ -898,6 +898,7 @@
|
||||
"idpDisplayName": "Et visningsnavn for denne identitetsleverandøren",
|
||||
"idpAutoProvisionUsers": "Automatisk brukerklargjøring",
|
||||
"idpAutoProvisionUsersDescription": "Når aktivert, opprettes brukere automatisk i systemet ved første innlogging, med mulighet til å tilordne brukere til roller og organisasjoner.",
|
||||
"idpAutoProvisionConfigureAfterCreate": "Du kan konfigurere autoprovisjonsinnstillingene når identitetsleverandøren er opprettet.",
|
||||
"licenseBadge": "EE",
|
||||
"idpType": "Leverandørtype",
|
||||
"idpTypeDescription": "Velg typen identitetsleverandør du ønsker å konfigurere",
|
||||
@@ -949,7 +950,7 @@
|
||||
"defaultMappingsRole": "Standard rolletilordning",
|
||||
"defaultMappingsRoleDescription": "Resultatet av dette uttrykket må returnere rollenavnet slik det er definert i organisasjonen som en streng.",
|
||||
"defaultMappingsOrg": "Standard organisasjonstilordning",
|
||||
"defaultMappingsOrgDescription": "Dette uttrykket må returnere organisasjons-ID-en eller «true» for å gi brukeren tilgang til organisasjonen.",
|
||||
"defaultMappingsOrgDescription": "Når denne er satt, må uttrykket returnere organisasjons-IDen eller «true» for at brukeren skal få tilgang til den organisasjonen. Når den ikke er satt, er det nok å definere en rolletilordning: brukeren gis tilgang så lenge en gyldig rolletilknytting kan løses for dem i organisasjonen.",
|
||||
"defaultMappingsSubmit": "Lagre standard tilordninger",
|
||||
"orgPoliciesEdit": "Rediger Organisasjonspolicy",
|
||||
"org": "Organisasjon",
|
||||
@@ -2026,7 +2027,7 @@
|
||||
},
|
||||
"internationaldomaindetected": "Internasjonalt domene oppdaget",
|
||||
"willbestoredas": "Vil bli lagret som:",
|
||||
"roleMappingDescription": "Bestem hvordan roller tilordnes brukere når innloggingen er aktivert når autog-rapportering er aktivert.",
|
||||
"roleMappingDescription": "Bestem hvordan roller tildeles brukere når de logger inn med denne identitetsleverandøren.",
|
||||
"selectRole": "Velg en rolle",
|
||||
"roleMappingExpression": "Uttrykk",
|
||||
"selectRolePlaceholder": "Velg en rolle",
|
||||
@@ -2899,5 +2900,22 @@
|
||||
"httpDestUpdatedSuccess": "Målet er oppdatert",
|
||||
"httpDestCreatedSuccess": "Målet er opprettet",
|
||||
"httpDestUpdateFailed": "Kunne ikke oppdatere destinasjon",
|
||||
"httpDestCreateFailed": "Kan ikke opprette mål"
|
||||
"httpDestCreateFailed": "Kan ikke opprette mål",
|
||||
"idpAddActionCreateNew": "Opprett ny identitetsleverandør",
|
||||
"idpAddActionImportFromOrg": "Importer fra en annen organisasjon",
|
||||
"idpImportDialogTitle": "Importer identitetsleverandør",
|
||||
"idpImportDialogDescription": "Velg en identitetsleverandør fra en organisasjon der du er admin. Den vil bli knyttet til denne organisasjonen.",
|
||||
"idpImportSearchPlaceholder": "Søk etter organisasjons- eller leverandørnavn...",
|
||||
"idpImportEmpty": "Ingen identitetsleverandører funnet.",
|
||||
"idpImportedDescription": "Identitetsleverandøren ble importert vellykket.",
|
||||
"idpDeleteGlobalQuestion": "Er du sikker på at du vil slette denne identitetsleverandøren permanent?",
|
||||
"idpDeleteGlobalDescription": "Dette vil slette identitetsleverandøren permanent fra alle organisasjoner den er tilknyttet.",
|
||||
"idpUnassociateTitle": "Frakoble identitetsleverandør",
|
||||
"idpUnassociateQuestion": "Er du sikker på at du vil frakoble denne identitetsleverandøren fra denne organisasjonen?",
|
||||
"idpUnassociateDescription": "Alle brukere knyttet til denne identitetsleverandøren vil bli fjernet fra denne organisasjonen, men identitetsleverandøren vil fortsatt eksistere for andre tilknyttede organisasjoner.",
|
||||
"idpUnassociateConfirm": "Bekreft frakobling av identitetsleverandør",
|
||||
"idpUnassociateWarning": "Dette kan ikke angres for denne organisasjonen.",
|
||||
"idpUnassociatedDescription": "Identitetsleverandør er vellykket frakoblet fra denne organisasjonen",
|
||||
"idpUnassociateMenu": "Frakoble",
|
||||
"idpDeleteAllOrgsMenu": "Slett"
|
||||
}
|
||||
|
||||
@@ -898,6 +898,7 @@
|
||||
"idpDisplayName": "Een weergavenaam voor deze identiteitsprovider",
|
||||
"idpAutoProvisionUsers": "Auto Provisie Gebruikers",
|
||||
"idpAutoProvisionUsersDescription": "Wanneer ingeschakeld, worden gebruikers automatisch in het systeem aangemaakt wanneer ze de eerste keer inloggen met de mogelijkheid om gebruikers toe te wijzen aan rollen en organisaties.",
|
||||
"idpAutoProvisionConfigureAfterCreate": "U kunt automatische voorzieningsinstellingen configureren zodra de identiteitsprovider is aangemaakt.",
|
||||
"licenseBadge": "EE",
|
||||
"idpType": "Type provider",
|
||||
"idpTypeDescription": "Selecteer het type identiteitsprovider dat u wilt configureren",
|
||||
@@ -949,7 +950,7 @@
|
||||
"defaultMappingsRole": "Standaard Rol Toewijzing",
|
||||
"defaultMappingsRoleDescription": "Het resultaat van deze uitdrukking moet de rolnaam zoals gedefinieerd in de organisatie als tekenreeks teruggeven.",
|
||||
"defaultMappingsOrg": "Standaard organisatie mapping",
|
||||
"defaultMappingsOrgDescription": "Deze expressie moet de org-ID teruggeven of waar om de gebruiker toegang te geven tot de organisatie.",
|
||||
"defaultMappingsOrgDescription": "Wanneer ingesteld, moet deze expressie de organisatie-ID of waar retourneren voor de gebruiker om toegang te krijgen tot die organisatie. Als het niet is ingesteld, is het definiëren van een roltoewijzing voldoende: de gebruiker is toegestaan zolang een geldige roltoewijzing voor hen binnen de organisatie kan worden opgelost.",
|
||||
"defaultMappingsSubmit": "Standaard toewijzingen opslaan",
|
||||
"orgPoliciesEdit": "Organisatie beleid bewerken",
|
||||
"org": "Organisatie",
|
||||
@@ -2026,7 +2027,7 @@
|
||||
},
|
||||
"internationaldomaindetected": "Internationaal Domein Gedetecteerd",
|
||||
"willbestoredas": "Zal worden opgeslagen als:",
|
||||
"roleMappingDescription": "Bepaal hoe rollen worden toegewezen aan gebruikers wanneer ze inloggen wanneer Auto Provision is ingeschakeld.",
|
||||
"roleMappingDescription": "Bepaal hoe rollen aan gebruikers worden toegewezen wanneer ze zich aanmelden met deze identiteitsprovider.",
|
||||
"selectRole": "Selecteer een rol",
|
||||
"roleMappingExpression": "Expressie",
|
||||
"selectRolePlaceholder": "Kies een rol",
|
||||
@@ -2899,5 +2900,22 @@
|
||||
"httpDestUpdatedSuccess": "Bestemming succesvol bijgewerkt",
|
||||
"httpDestCreatedSuccess": "Bestemming succesvol aangemaakt",
|
||||
"httpDestUpdateFailed": "Bijwerken bestemming mislukt",
|
||||
"httpDestCreateFailed": "Aanmaken bestemming mislukt"
|
||||
"httpDestCreateFailed": "Aanmaken bestemming mislukt",
|
||||
"idpAddActionCreateNew": "Nieuwe identiteitsprovider aanmaken",
|
||||
"idpAddActionImportFromOrg": "Importeer vanuit een andere organisatie",
|
||||
"idpImportDialogTitle": "Importeer Identiteitsprovider",
|
||||
"idpImportDialogDescription": "Kies een identiteitsprovider van een organisatie waar u beheerder bent. Het wordt gekoppeld aan deze organisatie.",
|
||||
"idpImportSearchPlaceholder": "Zoek op organisatie- of providernamen...",
|
||||
"idpImportEmpty": "Geen identiteitsproviders gevonden.",
|
||||
"idpImportedDescription": "Identiteitsprovider succesvol geïmporteerd.",
|
||||
"idpDeleteGlobalQuestion": "Weet u zeker dat u deze identiteitsprovider permanent wilt verwijderen?",
|
||||
"idpDeleteGlobalDescription": "Hiermee wordt de identiteitsprovider permanent verwijderd uit alle organisaties waarmee het is geassocieerd.",
|
||||
"idpUnassociateTitle": "Koppel Identiteitsprovider los",
|
||||
"idpUnassociateQuestion": "Weet u zeker dat u deze identiteitsprovider van deze organisatie wilt loskoppelen?",
|
||||
"idpUnassociateDescription": "Alle gebruikers die aan deze identiteitsprovider zijn gekoppeld, worden uit deze organisatie verwijderd, maar de identiteitsprovider blijft bestaan voor andere gerelateerde organisaties.",
|
||||
"idpUnassociateConfirm": "Bevestig ontkoppelen identiteitsprovider",
|
||||
"idpUnassociateWarning": "Dit kan niet ongedaan worden gemaakt voor deze organisatie.",
|
||||
"idpUnassociatedDescription": "Identiteitsprovider succesvol losgekoppeld van deze organisatie",
|
||||
"idpUnassociateMenu": "Ontkoppelen",
|
||||
"idpDeleteAllOrgsMenu": "Verwijderen"
|
||||
}
|
||||
|
||||
@@ -898,6 +898,7 @@
|
||||
"idpDisplayName": "Nazwa wyświetlana dla tego dostawcy tożsamości",
|
||||
"idpAutoProvisionUsers": "Automatyczne tworzenie użytkowników",
|
||||
"idpAutoProvisionUsersDescription": "Gdy włączone, użytkownicy będą automatycznie tworzeni w systemie przy pierwszym logowaniu z możliwością mapowania użytkowników do ról i organizacji.",
|
||||
"idpAutoProvisionConfigureAfterCreate": "Możesz skonfigurować automatyczne ustawienia provision, gdy dostawca tożsamości zostanie utworzony.",
|
||||
"licenseBadge": "EE",
|
||||
"idpType": "Typ dostawcy",
|
||||
"idpTypeDescription": "Wybierz typ dostawcy tożsamości, który chcesz skonfigurować",
|
||||
@@ -949,7 +950,7 @@
|
||||
"defaultMappingsRole": "Domyślne mapowanie roli",
|
||||
"defaultMappingsRoleDescription": "JMESPath do wydobycia informacji o roli z tokena ID. Wynik tego wyrażenia musi zwrócić nazwę roli zdefiniowaną w organizacji jako ciąg znaków.",
|
||||
"defaultMappingsOrg": "Domyślne mapowanie organizacji",
|
||||
"defaultMappingsOrgDescription": "JMESPath do wydobycia informacji o organizacji z tokena ID. To wyrażenie musi zwrócić ID organizacji lub true, aby użytkownik mógł uzyskać dostęp do organizacji.",
|
||||
"defaultMappingsOrgDescription": "Gdy jest ustawiona, ta wyrażenie musi zwrócić identyfikator organizacji lub true, aby użytkownik mógł uzyskać dostęp do tej organizacji. Gdy nie jest ustawiona, wystarczające jest zdefiniowanie mapowania ról: użytkownik jest dopuszczony, o ile można rozwiązać dla niego ważne mapowanie ról w organizacji.",
|
||||
"defaultMappingsSubmit": "Zapisz domyślne mapowania",
|
||||
"orgPoliciesEdit": "Edytuj politykę organizacji",
|
||||
"org": "Organizacja",
|
||||
@@ -2026,7 +2027,7 @@
|
||||
},
|
||||
"internationaldomaindetected": "Wykryto międzynarodową domenę",
|
||||
"willbestoredas": "Będą przechowywane jako:",
|
||||
"roleMappingDescription": "Określ jak role są przypisywane do użytkowników podczas logowania się, gdy automatyczne świadczenie jest włączone.",
|
||||
"roleMappingDescription": "Określ, jak role są przypisywane użytkownikom podczas logowania się z tym dostawcą tożsamości.",
|
||||
"selectRole": "Wybierz rolę",
|
||||
"roleMappingExpression": "Wyrażenie",
|
||||
"selectRolePlaceholder": "Wybierz rolę",
|
||||
@@ -2899,5 +2900,22 @@
|
||||
"httpDestUpdatedSuccess": "Cel został pomyślnie zaktualizowany",
|
||||
"httpDestCreatedSuccess": "Cel został utworzony pomyślnie",
|
||||
"httpDestUpdateFailed": "Nie udało się zaktualizować miejsca docelowego",
|
||||
"httpDestCreateFailed": "Nie udało się utworzyć miejsca docelowego"
|
||||
"httpDestCreateFailed": "Nie udało się utworzyć miejsca docelowego",
|
||||
"idpAddActionCreateNew": "Utwórz nowego dostawcę tożsamości",
|
||||
"idpAddActionImportFromOrg": "Importuj z innej organizacji",
|
||||
"idpImportDialogTitle": "Importuj dostawcę tożsamości",
|
||||
"idpImportDialogDescription": "Wybierz dostawcę tożsamości z organizacji, w której jesteś administratorem. Zostanie on powiązany z tą organizacją.",
|
||||
"idpImportSearchPlaceholder": "Szukaj według nazwy organizacji lub dostawcy...",
|
||||
"idpImportEmpty": "Nie znaleziono dostawców tożsamości.",
|
||||
"idpImportedDescription": "Dostawca tożsamości został pomyślnie zaimportowany.",
|
||||
"idpDeleteGlobalQuestion": "Czy na pewno chcesz trwale usunąć tego dostawcę tożsamości?",
|
||||
"idpDeleteGlobalDescription": "Spowoduje to trwałe usunięcie dostawcy tożsamości ze wszystkich organizacji, z którymi jest powiązany.",
|
||||
"idpUnassociateTitle": "Odłącz dostawcę tożsamości",
|
||||
"idpUnassociateQuestion": "Czy na pewno chcesz odłączyć tego dostawcę tożsamości od tej organizacji?",
|
||||
"idpUnassociateDescription": "Wszystkie użytkownicy powiązani z tym dostawcą tożsamości zostaną usunięci z tej organizacji, ale dostawca tożsamości będzie nadal istniał dla innych powiązanych organizacji.",
|
||||
"idpUnassociateConfirm": "Potwierdź odłączenie dostawcy tożsamości",
|
||||
"idpUnassociateWarning": "Tego nie można cofnąć dla tej organizacji.",
|
||||
"idpUnassociatedDescription": "Dostawca tożsamości pomyślnie odłączony od tej organizacji",
|
||||
"idpUnassociateMenu": "Odłącz",
|
||||
"idpDeleteAllOrgsMenu": "Usuń"
|
||||
}
|
||||
|
||||
@@ -898,6 +898,7 @@
|
||||
"idpDisplayName": "Um nome de exibição para este provedor de identidade",
|
||||
"idpAutoProvisionUsers": "Provisionamento Automático de Utilizadores",
|
||||
"idpAutoProvisionUsersDescription": "Quando ativado, os utilizadores serão criados automaticamente no sistema no primeiro login com a capacidade de mapear utilizadores para funções e organizações.",
|
||||
"idpAutoProvisionConfigureAfterCreate": "Você pode configurar as definições de auto provisão assim que o provedor de identidade for criado.",
|
||||
"licenseBadge": "EE",
|
||||
"idpType": "Tipo de Provedor",
|
||||
"idpTypeDescription": "Selecione o tipo de provedor de identidade que deseja configurar",
|
||||
@@ -949,7 +950,7 @@
|
||||
"defaultMappingsRole": "Mapeamento de Função Padrão",
|
||||
"defaultMappingsRoleDescription": "JMESPath para extrair informações de função do token ID. O resultado desta expressão deve retornar o nome da função como definido na organização como uma string.",
|
||||
"defaultMappingsOrg": "Mapeamento de Organização Padrão",
|
||||
"defaultMappingsOrgDescription": "JMESPath para extrair informações da organização do token ID. Esta expressão deve retornar o ID da organização ou verdadeiro para que o utilizador tenha permissão para aceder à organização.",
|
||||
"defaultMappingsOrgDescription": "Quando definida, esta expressão deve retornar o ID da organização ou verdadeiro para que o usuário acesse essa organização. Quando não definida, a definição de um mapeamento de papel é suficiente: o usuário é permitido desde que um mapeamento de papel válido possa ser resolvido para ele dentro da organização.",
|
||||
"defaultMappingsSubmit": "Guardar Mapeamentos Padrão",
|
||||
"orgPoliciesEdit": "Editar Política da Organização",
|
||||
"org": "Organização",
|
||||
@@ -2026,7 +2027,7 @@
|
||||
},
|
||||
"internationaldomaindetected": "Domínio Internacional Detectado",
|
||||
"willbestoredas": "Será armazenado como:",
|
||||
"roleMappingDescription": "Determinar como as funções são atribuídas aos usuários quando eles fazem login quando Auto Provisão está habilitada.",
|
||||
"roleMappingDescription": "Determine como os papéis são atribuídos aos usuários quando eles entram com este provedor de identidade.",
|
||||
"selectRole": "Selecione uma função",
|
||||
"roleMappingExpression": "Expressão",
|
||||
"selectRolePlaceholder": "Escolha uma função",
|
||||
@@ -2899,5 +2900,22 @@
|
||||
"httpDestUpdatedSuccess": "Destino atualizado com sucesso",
|
||||
"httpDestCreatedSuccess": "Destino criado com sucesso",
|
||||
"httpDestUpdateFailed": "Falha ao atualizar destino",
|
||||
"httpDestCreateFailed": "Falha ao criar destino"
|
||||
"httpDestCreateFailed": "Falha ao criar destino",
|
||||
"idpAddActionCreateNew": "Criar novo provedor de identidade",
|
||||
"idpAddActionImportFromOrg": "Importar de outra organização",
|
||||
"idpImportDialogTitle": "Importar Provedor de Identidade",
|
||||
"idpImportDialogDescription": "Escolha um provedor de identidade de uma organização onde você é administrador. Ele será vinculado a esta organização.",
|
||||
"idpImportSearchPlaceholder": "Pesquisar por nome de organização ou provedor...",
|
||||
"idpImportEmpty": "Nenhum provedor de identidade encontrado.",
|
||||
"idpImportedDescription": "Provedor de identidade importado com sucesso.",
|
||||
"idpDeleteGlobalQuestion": "Tem certeza de que deseja eliminar permanentemente este provedor de identidade?",
|
||||
"idpDeleteGlobalDescription": "Isso eliminará permanentemente o provedor de identidade de todas as organizações com as quais está associado.",
|
||||
"idpUnassociateTitle": "Desassociar Provedor de Identidade",
|
||||
"idpUnassociateQuestion": "Tem certeza de que deseja desassociar este provedor de identidade desta organização?",
|
||||
"idpUnassociateDescription": "Todos os usuários associados a este provedor de identidade serão removidos desta organização, mas o provedor de identidade continuará a existir para outras organizações associadas.",
|
||||
"idpUnassociateConfirm": "Confirmar Desassociação do Provedor de Identidade",
|
||||
"idpUnassociateWarning": "Isso não pode ser desfeito para esta organização.",
|
||||
"idpUnassociatedDescription": "Provedor de identidade desassociado desta organização com sucesso",
|
||||
"idpUnassociateMenu": "Desassociar",
|
||||
"idpDeleteAllOrgsMenu": "Excluir"
|
||||
}
|
||||
|
||||
@@ -898,6 +898,7 @@
|
||||
"idpDisplayName": "Отображаемое имя для этого поставщика удостоверений",
|
||||
"idpAutoProvisionUsers": "Автоматическое создание пользователей",
|
||||
"idpAutoProvisionUsersDescription": "При включении пользователи будут автоматически создаваться в системе при первом входе с возможностью сопоставления пользователей с ролями и организациями.",
|
||||
"idpAutoProvisionConfigureAfterCreate": "Вы можете настроить параметры автоматического обеспечения после создания поставщика удостоверений.",
|
||||
"licenseBadge": "EE",
|
||||
"idpType": "Тип поставщика",
|
||||
"idpTypeDescription": "Выберите тип поставщика удостоверений, который вы хотите настроить",
|
||||
@@ -949,7 +950,7 @@
|
||||
"defaultMappingsRole": "Сопоставление ролей по умолчанию",
|
||||
"defaultMappingsRoleDescription": "Результат этого выражения должен возвращать имя роли, как определено в организации, в виде строки.",
|
||||
"defaultMappingsOrg": "Сопоставление организаций по умолчанию",
|
||||
"defaultMappingsOrgDescription": "Это выражение должно возвращать ID организации или true для разрешения доступа пользователя к организации.",
|
||||
"defaultMappingsOrgDescription": "При установке это выражение должно возвращать ID организации или true, чтобы пользователь мог получить доступ к этой организации. При отсутствии настройка отображения роли достаточно: пользователю разрешено войти, пока для него может быть решено отображение гарантированной роли в организации.",
|
||||
"defaultMappingsSubmit": "Сохранить сопоставления по умолчанию",
|
||||
"orgPoliciesEdit": "Редактировать политику организации",
|
||||
"org": "Организация",
|
||||
@@ -2026,7 +2027,7 @@
|
||||
},
|
||||
"internationaldomaindetected": "Обнаружен международный домен",
|
||||
"willbestoredas": "Будет храниться как:",
|
||||
"roleMappingDescription": "Определите, как роли, назначаемые пользователям, когда они войдут в систему автоматического профиля.",
|
||||
"roleMappingDescription": "Определите, как роли присваиваются пользователям при входе с этим поставщиком удостоверений.",
|
||||
"selectRole": "Выберите роль",
|
||||
"roleMappingExpression": "Выражение",
|
||||
"selectRolePlaceholder": "Выберите роль",
|
||||
@@ -2899,5 +2900,22 @@
|
||||
"httpDestUpdatedSuccess": "Адрес назначения успешно обновлен",
|
||||
"httpDestCreatedSuccess": "Адрес назначения успешно создан",
|
||||
"httpDestUpdateFailed": "Не удалось обновить место назначения",
|
||||
"httpDestCreateFailed": "Не удалось создать место назначения"
|
||||
"httpDestCreateFailed": "Не удалось создать место назначения",
|
||||
"idpAddActionCreateNew": "Создать нового поставщика удостоверений",
|
||||
"idpAddActionImportFromOrg": "Импортировать из другой организации",
|
||||
"idpImportDialogTitle": "Импортировать поставщика удостоверений",
|
||||
"idpImportDialogDescription": "Выберите поставщика удостоверений из организации, где вы являетесь администратором. Он будет связан с этой организацией.",
|
||||
"idpImportSearchPlaceholder": "Поиск по организации или имени поставщика...",
|
||||
"idpImportEmpty": "Поставщики удостоверений не найдены.",
|
||||
"idpImportedDescription": "Поставщик удостоверений успешно импортирован.",
|
||||
"idpDeleteGlobalQuestion": "Вы уверены, что хотите навсегда удалить этого поставщика удостоверений?",
|
||||
"idpDeleteGlobalDescription": "Это навсегда удалит поставщика удостоверений из всех организаций, с которыми он связан.",
|
||||
"idpUnassociateTitle": "Рассоединить провайдера удостоверений",
|
||||
"idpUnassociateQuestion": "Вы уверены, что хотите рассоединить этого поставщика удостоверений с этой организацией?",
|
||||
"idpUnassociateDescription": "Все пользователи, связанные с этим поставщиком удостоверений, будут удалены из этой организации, но поставщик удостоверений будет продолжать существовать для других связанных организаций.",
|
||||
"idpUnassociateConfirm": "Подтвердите рассоединение поставщика удостоверений",
|
||||
"idpUnassociateWarning": "Это не может быть отменено для этой организации.",
|
||||
"idpUnassociatedDescription": "Поставщик удостоверений успешно рассоединен с этой организацией",
|
||||
"idpUnassociateMenu": "Рассоединить",
|
||||
"idpDeleteAllOrgsMenu": "Удалить"
|
||||
}
|
||||
|
||||
@@ -898,6 +898,7 @@
|
||||
"idpDisplayName": "Bu kimlik sağlayıcı için bir görüntü adı",
|
||||
"idpAutoProvisionUsers": "Kullanıcıları Otomatik Sağla",
|
||||
"idpAutoProvisionUsersDescription": "Etkinleştirildiğinde, kullanıcılar rol ve organizasyonlara eşleme yeteneğiyle birlikte sistemde otomatik olarak oluşturulacak.",
|
||||
"idpAutoProvisionConfigureAfterCreate": "Kimlik sağlayıcı oluşturulduktan sonra otomatik sağlama ayarlarını yapılandırabilirsiniz.",
|
||||
"licenseBadge": " ",
|
||||
"idpType": "Sağlayıcı Türü",
|
||||
"idpTypeDescription": "Yapılandırmak istediğiniz kimlik sağlayıcısı türünü seçin",
|
||||
@@ -949,7 +950,7 @@
|
||||
"defaultMappingsRole": "Varsayılan Rol Eşleme",
|
||||
"defaultMappingsRoleDescription": "JMESPath to extract role information from the ID token. The result of this expression must return the role name as defined in the organization as a string.",
|
||||
"defaultMappingsOrg": "Varsayılan Kuruluş Eşleme",
|
||||
"defaultMappingsOrgDescription": "JMESPath to extract organization information from the ID token. This expression must return the org ID or true for the user to be allowed to access the organization.",
|
||||
"defaultMappingsOrgDescription": "Ayarladığınızda, bu ifade kullanıcının o kuruluşa erişmesi için kuruluş kimliğini veya doğru değerini döndürmelidir. Ayarlamadığınızda, rol eşleme tanımlamak yeterlidir: kullanıcı, kuruluş içinde onlar için geçerli bir rol eşlemesi çözümlenebildiği sürece erişime izin verilir.",
|
||||
"defaultMappingsSubmit": "Varsayılan Eşlemeleri Kaydet",
|
||||
"orgPoliciesEdit": "Kuruluş Politikasını Düzenle",
|
||||
"org": "Kuruluş",
|
||||
@@ -2026,7 +2027,7 @@
|
||||
},
|
||||
"internationaldomaindetected": "Uluslararası Alan Adı Tespit Edildi",
|
||||
"willbestoredas": "Şu şekilde depolanacak:",
|
||||
"roleMappingDescription": "Otomatik Sağlama etkinleştirildiğinde kullanıcıların oturum açarken rollerin nasıl atandığını belirleyin.",
|
||||
"roleMappingDescription": "Bu kimlik sağlayıcı ile oturum açıldığında kullanıcılara rollerin nasıl atandığını belirleyin.",
|
||||
"selectRole": "Bir Rol Seçin",
|
||||
"roleMappingExpression": "İfade",
|
||||
"selectRolePlaceholder": "Bir rol seçin",
|
||||
@@ -2899,5 +2900,22 @@
|
||||
"httpDestUpdatedSuccess": "Hedef başarıyla güncellendi",
|
||||
"httpDestCreatedSuccess": "Hedef başarıyla oluşturuldu",
|
||||
"httpDestUpdateFailed": "Hedef güncellenemedi",
|
||||
"httpDestCreateFailed": "Hedef oluşturulamadı"
|
||||
"httpDestCreateFailed": "Hedef oluşturulamadı",
|
||||
"idpAddActionCreateNew": "Yeni kimlik sağlayıcı oluştur",
|
||||
"idpAddActionImportFromOrg": "Başka bir kuruluştan içe aktar",
|
||||
"idpImportDialogTitle": "Kimlik Sağlayıcı İçe Aktar",
|
||||
"idpImportDialogDescription": "Bir kuruluştan yönetici olduğunuz bir kimlik sağlayıcı seçin. Bu kuruluşla ilişkilendirilecektir.",
|
||||
"idpImportSearchPlaceholder": "Kuruluş veya sağlayıcı adına göre ara...",
|
||||
"idpImportEmpty": "Hiçbir kimlik sağlayıcı bulunamadı.",
|
||||
"idpImportedDescription": "Kimlik sağlayıcı başarıyla içe aktarıldı.",
|
||||
"idpDeleteGlobalQuestion": "Bu kimlik sağlayıcıyı kalıcı olarak silmek istediğinizden emin misiniz?",
|
||||
"idpDeleteGlobalDescription": "Bu, kimlik sağlayıcıyı ilişkilendirildiği tüm kuruluşlardan kalıcı olarak silecektir.",
|
||||
"idpUnassociateTitle": "Kimlik Sağlayıcının İlişkisini Kes",
|
||||
"idpUnassociateQuestion": "Bu kimlik sağlayıcının bu kuruluştan ilişiğini kesmek istediğinizden emin misiniz?",
|
||||
"idpUnassociateDescription": "Bu kimlik sağlayıcı ile ilişkilendirilen tüm kullanıcılar bu kuruluştan kaldırılacaktır, ancak kimlik sağlayıcı diğer ilişkilendirilen kuruluşlar için var olmaya devam edecektir.",
|
||||
"idpUnassociateConfirm": "Kimlik Sağlayıcının İlişkisinin Kesilmesini Onayla",
|
||||
"idpUnassociateWarning": "Bu işlem bu kuruluş için geri alınamaz.",
|
||||
"idpUnassociatedDescription": "Kimlik sağlayıcı bu kuruluştan başarıyla ayrıldı",
|
||||
"idpUnassociateMenu": "İlişkiyi Kes",
|
||||
"idpDeleteAllOrgsMenu": "Sil"
|
||||
}
|
||||
|
||||
@@ -898,6 +898,7 @@
|
||||
"idpDisplayName": "此身份提供商的显示名称",
|
||||
"idpAutoProvisionUsers": "自动提供用户",
|
||||
"idpAutoProvisionUsersDescription": "如果启用,用户将在首次登录时自动在系统中创建,并且能够映射用户到角色和组织。",
|
||||
"idpAutoProvisionConfigureAfterCreate": "您可以在创建身份提供者后配置自动配置设置。",
|
||||
"licenseBadge": "EE",
|
||||
"idpType": "提供者类型",
|
||||
"idpTypeDescription": "选择您想要配置的身份提供者类型",
|
||||
@@ -949,7 +950,7 @@
|
||||
"defaultMappingsRole": "默认角色映射",
|
||||
"defaultMappingsRoleDescription": "此表达式的结果必须返回组织中定义的角色名称作为字符串。",
|
||||
"defaultMappingsOrg": "默认组织映射",
|
||||
"defaultMappingsOrgDescription": "此表达式必须返回 组织ID 或 true 才能允许用户访问组织。",
|
||||
"defaultMappingsOrgDescription": "设置时,此表达式必须返回组织ID或true才能让用户访问该组织。如果未设置,定义角色映射就足够了:只要在组织内可以为用户找出有效角色映射,用户就被允许进入。",
|
||||
"defaultMappingsSubmit": "保存默认映射",
|
||||
"orgPoliciesEdit": "编辑组织策略",
|
||||
"org": "组织",
|
||||
@@ -2026,7 +2027,7 @@
|
||||
},
|
||||
"internationaldomaindetected": "检测到国际域",
|
||||
"willbestoredas": "储存为:",
|
||||
"roleMappingDescription": "确定当用户启用自动配送时如何分配他们的角色。",
|
||||
"roleMappingDescription": "确定当用户使用此身份提供者登陆时如何分配角色。",
|
||||
"selectRole": "选择角色",
|
||||
"roleMappingExpression": "表达式",
|
||||
"selectRolePlaceholder": "选择角色",
|
||||
@@ -2899,5 +2900,22 @@
|
||||
"httpDestUpdatedSuccess": "目标已成功更新",
|
||||
"httpDestCreatedSuccess": "目标创建成功",
|
||||
"httpDestUpdateFailed": "更新目标失败",
|
||||
"httpDestCreateFailed": "创建目标失败"
|
||||
"httpDestCreateFailed": "创建目标失败",
|
||||
"idpAddActionCreateNew": "创建新的身份提供者",
|
||||
"idpAddActionImportFromOrg": "从另一个组织导入",
|
||||
"idpImportDialogTitle": "导入身份提供者",
|
||||
"idpImportDialogDescription": "从您是管理员的组织中选择一个身份提供者。它将关联到本组织。",
|
||||
"idpImportSearchPlaceholder": "按组织或提供者名称搜索……",
|
||||
"idpImportEmpty": "未找到身份提供者。",
|
||||
"idpImportedDescription": "身份提供者已成功导入。",
|
||||
"idpDeleteGlobalQuestion": "您确定要永久删除此身份提供者吗?",
|
||||
"idpDeleteGlobalDescription": "这将永久删除与其关联的所有组织中的身份提供者。",
|
||||
"idpUnassociateTitle": "取消关联身份提供者",
|
||||
"idpUnassociateQuestion": "您确定要将此身份提供者从此组织中取消关联吗?",
|
||||
"idpUnassociateDescription": "与此身份提供者关联的所有用户将从该组织中移除,但身份提供者仍会继续存在于关联的其他组织中。",
|
||||
"idpUnassociateConfirm": "确认取消关联身份提供者",
|
||||
"idpUnassociateWarning": "此操作无法对该组织撤销。",
|
||||
"idpUnassociatedDescription": "身份提供者已成功从该组织中取消关联",
|
||||
"idpUnassociateMenu": "取消关联",
|
||||
"idpDeleteAllOrgsMenu": "删除"
|
||||
}
|
||||
|
||||
|
After Width: | Height: | Size: 8.8 KiB |
|
Before Width: | Height: | Size: 765 KiB After Width: | Height: | Size: 588 KiB |
|
Before Width: | Height: | Size: 742 KiB After Width: | Height: | Size: 569 KiB |
|
Before Width: | Height: | Size: 765 KiB After Width: | Height: | Size: 588 KiB |
|
Before Width: | Height: | Size: 2.9 MiB After Width: | Height: | Size: 2.4 MiB |
|
Before Width: | Height: | Size: 243 KiB After Width: | Height: | Size: 274 KiB |
@@ -21,6 +21,7 @@ import {
|
||||
exitNodes,
|
||||
sessions,
|
||||
clients,
|
||||
resources,
|
||||
siteResources,
|
||||
targetHealthCheck,
|
||||
sites
|
||||
@@ -475,13 +476,21 @@ export const alertRules = pgTable("alertRules", {
|
||||
.$type<
|
||||
| "site_online"
|
||||
| "site_offline"
|
||||
| "site_toggle"
|
||||
| "health_check_healthy"
|
||||
| "health_check_not_healthy"
|
||||
| "health_check_unhealthy"
|
||||
| "health_check_toggle"
|
||||
| "resource_healthy"
|
||||
| "resource_unhealthy"
|
||||
| "resource_toggle"
|
||||
>()
|
||||
.notNull(),
|
||||
// Nullable depending on eventType
|
||||
enabled: boolean("enabled").notNull().default(true),
|
||||
cooldownSeconds: integer("cooldownSeconds").notNull().default(300),
|
||||
allSites: boolean("allSites").notNull().default(false),
|
||||
allHealthChecks: boolean("allHealthChecks").notNull().default(false),
|
||||
allResources: boolean("allResources").notNull().default(false),
|
||||
lastTriggeredAt: bigint("lastTriggeredAt", { mode: "number" }), // nullable
|
||||
createdAt: bigint("createdAt", { mode: "number" }).notNull(),
|
||||
updatedAt: bigint("updatedAt", { mode: "number" }).notNull()
|
||||
@@ -507,6 +516,15 @@ export const alertHealthChecks = pgTable("alertHealthChecks", {
|
||||
})
|
||||
});
|
||||
|
||||
export const alertResources = pgTable("alertResources", {
|
||||
alertRuleId: integer("alertRuleId")
|
||||
.notNull()
|
||||
.references(() => alertRules.alertRuleId, { onDelete: "cascade" }),
|
||||
resourceId: integer("resourceId")
|
||||
.notNull()
|
||||
.references(() => resources.resourceId, { onDelete: "cascade" })
|
||||
});
|
||||
|
||||
// Separating channels by type avoids the mixed-shape problem entirely
|
||||
export const alertEmailActions = pgTable("alertEmailActions", {
|
||||
emailActionId: serial("emailActionId").primaryKey(),
|
||||
@@ -528,7 +546,7 @@ export const alertEmailRecipients = pgTable("alertEmailRecipients", {
|
||||
userId: varchar("userId").references(() => users.userId, {
|
||||
onDelete: "cascade"
|
||||
}),
|
||||
roleId: varchar("roleId").references(() => roles.roleId, {
|
||||
roleId: integer("roleId").references(() => roles.roleId, {
|
||||
onDelete: "cascade"
|
||||
}),
|
||||
email: varchar("email", { length: 255 }) // external emails not tied to a user
|
||||
@@ -582,3 +600,4 @@ export type EventStreamingDestination = InferSelectModel<
|
||||
export type EventStreamingCursor = InferSelectModel<
|
||||
typeof eventStreamingCursors
|
||||
>;
|
||||
export type AlertResources = InferSelectModel<typeof alertResources>;
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
domains,
|
||||
exitNodes,
|
||||
orgs,
|
||||
resources,
|
||||
roles,
|
||||
sessions,
|
||||
siteResources,
|
||||
@@ -467,12 +468,20 @@ export const alertRules = sqliteTable("alertRules", {
|
||||
.$type<
|
||||
| "site_online"
|
||||
| "site_offline"
|
||||
| "site_toggle"
|
||||
| "health_check_healthy"
|
||||
| "health_check_not_healthy"
|
||||
| "health_check_unhealthy"
|
||||
| "health_check_toggle"
|
||||
| "resource_healthy"
|
||||
| "resource_unhealthy"
|
||||
| "resource_toggle"
|
||||
>()
|
||||
.notNull(),
|
||||
enabled: integer("enabled", { mode: "boolean" }).notNull().default(true),
|
||||
cooldownSeconds: integer("cooldownSeconds").notNull().default(300),
|
||||
allSites: integer("allSites", { mode: "boolean" }).notNull().default(false),
|
||||
allHealthChecks: integer("allHealthChecks", { mode: "boolean" }).notNull().default(false),
|
||||
allResources: integer("allResources", { mode: "boolean" }).notNull().default(false),
|
||||
lastTriggeredAt: integer("lastTriggeredAt"),
|
||||
createdAt: integer("createdAt").notNull(),
|
||||
updatedAt: integer("updatedAt").notNull()
|
||||
@@ -498,6 +507,15 @@ export const alertHealthChecks = sqliteTable("alertHealthChecks", {
|
||||
})
|
||||
});
|
||||
|
||||
export const alertResources = sqliteTable("alertResources", {
|
||||
alertRuleId: integer("alertRuleId")
|
||||
.notNull()
|
||||
.references(() => alertRules.alertRuleId, { onDelete: "cascade" }),
|
||||
resourceId: integer("resourceId")
|
||||
.notNull()
|
||||
.references(() => resources.resourceId, { onDelete: "cascade" })
|
||||
});
|
||||
|
||||
export const alertEmailActions = sqliteTable("alertEmailActions", {
|
||||
emailActionId: integer("emailActionId").primaryKey({ autoIncrement: true }),
|
||||
alertRuleId: integer("alertRuleId")
|
||||
@@ -513,7 +531,7 @@ export const alertEmailRecipients = sqliteTable("alertEmailRecipients", {
|
||||
.notNull()
|
||||
.references(() => alertEmailActions.emailActionId, { onDelete: "cascade" }),
|
||||
userId: text("userId").references(() => users.userId, { onDelete: "cascade" }),
|
||||
roleId: text("roleId").references(() => roles.roleId, { onDelete: "cascade" }),
|
||||
roleId: integer("roleId").references(() => roles.roleId, { onDelete: "cascade" }),
|
||||
email: text("email")
|
||||
});
|
||||
|
||||
@@ -559,3 +577,4 @@ export type EventStreamingDestination = InferSelectModel<
|
||||
export type EventStreamingCursor = InferSelectModel<
|
||||
typeof eventStreamingCursors
|
||||
>;
|
||||
export type AlertResources = InferSelectModel<typeof alertResources>;
|
||||
|
||||
@@ -15,8 +15,13 @@ import {
|
||||
export type AlertEventType =
|
||||
| "site_online"
|
||||
| "site_offline"
|
||||
| "site_toggle"
|
||||
| "health_check_healthy"
|
||||
| "health_check_not_healthy";
|
||||
| "health_check_unhealthy"
|
||||
| "health_check_toggle"
|
||||
| "resource_healthy"
|
||||
| "resource_unhealthy"
|
||||
| "resource_toggle";
|
||||
|
||||
interface Props {
|
||||
eventType: AlertEventType;
|
||||
@@ -50,6 +55,15 @@ function getEventMeta(eventType: AlertEventType): {
|
||||
statusLabel: "Offline",
|
||||
statusColor: "#dc2626"
|
||||
};
|
||||
case "site_toggle":
|
||||
return {
|
||||
heading: "Site Status Changed",
|
||||
previewText: "A site in your organization has changed status.",
|
||||
summary:
|
||||
"A site in your organization has changed status. Please review the details below and take action if needed.",
|
||||
statusLabel: "Status Changed",
|
||||
statusColor: "#f59e0b"
|
||||
};
|
||||
case "health_check_healthy":
|
||||
return {
|
||||
heading: "Health Check Recovered",
|
||||
@@ -60,7 +74,7 @@ function getEventMeta(eventType: AlertEventType): {
|
||||
statusLabel: "Healthy",
|
||||
statusColor: "#16a34a"
|
||||
};
|
||||
case "health_check_not_healthy":
|
||||
case "health_check_unhealthy":
|
||||
return {
|
||||
heading: "Health Check Failing",
|
||||
previewText:
|
||||
@@ -70,6 +84,53 @@ function getEventMeta(eventType: AlertEventType): {
|
||||
statusLabel: "Not Healthy",
|
||||
statusColor: "#dc2626"
|
||||
};
|
||||
case "health_check_toggle":
|
||||
return {
|
||||
heading: "Health Check Status Changed",
|
||||
previewText:
|
||||
"A health check in your organization has changed status.",
|
||||
summary:
|
||||
"A health check in your organization has changed status. Please review the details below and take action if needed.",
|
||||
statusLabel: "Status Changed",
|
||||
statusColor: "#f59e0b"
|
||||
};
|
||||
case "resource_healthy":
|
||||
return {
|
||||
heading: "Resource Healthy",
|
||||
previewText: "A resource in your organization is now healthy.",
|
||||
summary:
|
||||
"A resource in your organization has recovered and is now reporting a healthy status.",
|
||||
statusLabel: "Healthy",
|
||||
statusColor: "#16a34a"
|
||||
};
|
||||
case "resource_unhealthy":
|
||||
return {
|
||||
heading: "Resource Unhealthy",
|
||||
previewText: "A resource in your organization is not healthy.",
|
||||
summary:
|
||||
"A resource in your organization is currently unhealthy. Please review the details below and take action if needed.",
|
||||
statusLabel: "Unhealthy",
|
||||
statusColor: "#dc2626"
|
||||
};
|
||||
case "resource_toggle":
|
||||
return {
|
||||
heading: "Resource Status Changed",
|
||||
previewText:
|
||||
"A resource in your organization has changed status.",
|
||||
summary:
|
||||
"A resource in your organization has changed status. Please review the details below and take action if needed.",
|
||||
statusLabel: "Status Changed",
|
||||
statusColor: "#f59e0b"
|
||||
};
|
||||
default:
|
||||
return {
|
||||
heading: "Alert Notification",
|
||||
previewText: "An alert event has occurred in your organization.",
|
||||
summary:
|
||||
"An alert event has occurred in your organization. Please review the details below and take action if needed.",
|
||||
statusLabel: "Alert",
|
||||
statusColor: "#f59e0b"
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -142,6 +142,7 @@ export async function updateProxyResources(
|
||||
.values({
|
||||
name: `${targetData.hostname}:${targetData.port}`,
|
||||
targetId: newTarget.targetId,
|
||||
orgId: orgId,
|
||||
hcEnabled: healthcheckData?.enabled || false,
|
||||
hcPath: healthcheckData?.path,
|
||||
hcScheme: healthcheckData?.scheme,
|
||||
|
||||
@@ -55,7 +55,7 @@ export async function fireHealthCheckHealthyAlert(
|
||||
}
|
||||
|
||||
/**
|
||||
* Fire a `health_check_not_healthy` alert for the given health check.
|
||||
* Fire a `health_check_unhealthy` alert for the given health check.
|
||||
*
|
||||
* Call this after a health check has been detected as failing so that any
|
||||
* matching `alertRules` can dispatch their email and webhook actions.
|
||||
@@ -73,7 +73,7 @@ export async function fireHealthCheckNotHealthyAlert(
|
||||
): Promise<void> {
|
||||
try {
|
||||
await processAlerts({
|
||||
eventType: "health_check_not_healthy",
|
||||
eventType: "health_check_unhealthy",
|
||||
orgId,
|
||||
healthCheckId,
|
||||
data: {
|
||||
|
||||
@@ -19,73 +19,109 @@ import { processAlerts } from "../processAlerts";
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Fire a `health_check_healthy` alert for the given health check.
|
||||
* Fire a `resource_healthy` alert for the given resource.
|
||||
*
|
||||
* Call this after a previously-failing health check has recovered so that any
|
||||
* Call this after a previously-unhealthy resource has recovered so that any
|
||||
* matching `alertRules` can dispatch their email and webhook actions.
|
||||
*
|
||||
* @param orgId - Organisation that owns the health check.
|
||||
* @param healthCheckId - Numeric primary key of the health check.
|
||||
* @param healthCheckName - Human-readable name shown in notifications (optional).
|
||||
* @param extra - Any additional key/value pairs to include in the payload.
|
||||
* @param orgId - Organisation that owns the resource.
|
||||
* @param resourceId - Numeric primary key of the resource.
|
||||
* @param resourceName - Human-readable name shown in notifications (optional).
|
||||
* @param extra - Any additional key/value pairs to include in the payload.
|
||||
*/
|
||||
export async function fireHealthCheckHealthyAlert(
|
||||
export async function fireResourceHealthyAlert(
|
||||
orgId: string,
|
||||
healthCheckId: number,
|
||||
healthCheckName?: string | null,
|
||||
resourceId: number,
|
||||
resourceName?: string | null,
|
||||
extra?: Record<string, unknown>
|
||||
): Promise<void> {
|
||||
try {
|
||||
await processAlerts({
|
||||
eventType: "health_check_healthy",
|
||||
eventType: "resource_healthy",
|
||||
orgId,
|
||||
healthCheckId,
|
||||
resourceId,
|
||||
data: {
|
||||
healthCheckId,
|
||||
...(healthCheckName != null ? { healthCheckName } : {}),
|
||||
resourceId,
|
||||
...(resourceName != null ? { resourceName } : {}),
|
||||
...extra
|
||||
}
|
||||
});
|
||||
} catch (err) {
|
||||
logger.error(
|
||||
`fireHealthCheckHealthyAlert: unexpected error for healthCheckId ${healthCheckId}`,
|
||||
`fireResourceHealthyAlert: unexpected error for resourceId ${resourceId}`,
|
||||
err
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fire a `health_check_not_healthy` alert for the given health check.
|
||||
* Fire a `resource_unhealthy` alert for the given resource.
|
||||
*
|
||||
* Call this after a health check has been detected as failing so that any
|
||||
* Call this after a resource has been detected as unhealthy so that any
|
||||
* matching `alertRules` can dispatch their email and webhook actions.
|
||||
*
|
||||
* @param orgId - Organisation that owns the health check.
|
||||
* @param healthCheckId - Numeric primary key of the health check.
|
||||
* @param healthCheckName - Human-readable name shown in notifications (optional).
|
||||
* @param extra - Any additional key/value pairs to include in the payload.
|
||||
* @param orgId - Organisation that owns the resource.
|
||||
* @param resourceId - Numeric primary key of the resource.
|
||||
* @param resourceName - Human-readable name shown in notifications (optional).
|
||||
* @param extra - Any additional key/value pairs to include in the payload.
|
||||
*/
|
||||
export async function fireHealthCheckNotHealthyAlert(
|
||||
export async function fireResourceUnhealthyAlert(
|
||||
orgId: string,
|
||||
healthCheckId: number,
|
||||
healthCheckName?: string | null,
|
||||
resourceId: number,
|
||||
resourceName?: string | null,
|
||||
extra?: Record<string, unknown>
|
||||
): Promise<void> {
|
||||
try {
|
||||
await processAlerts({
|
||||
eventType: "health_check_not_healthy",
|
||||
eventType: "resource_unhealthy",
|
||||
orgId,
|
||||
healthCheckId,
|
||||
resourceId,
|
||||
data: {
|
||||
healthCheckId,
|
||||
...(healthCheckName != null ? { healthCheckName } : {}),
|
||||
resourceId,
|
||||
...(resourceName != null ? { resourceName } : {}),
|
||||
...extra
|
||||
}
|
||||
});
|
||||
} catch (err) {
|
||||
logger.error(
|
||||
`fireHealthCheckNotHealthyAlert: unexpected error for healthCheckId ${healthCheckId}`,
|
||||
`fireResourceUnhealthyAlert: unexpected error for resourceId ${resourceId}`,
|
||||
err
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fire a `resource_toggle` alert for the given resource.
|
||||
*
|
||||
* Call this when a resource's enabled/disabled status is toggled so that any
|
||||
* matching `alertRules` can dispatch their email and webhook actions.
|
||||
*
|
||||
* @param orgId - Organisation that owns the resource.
|
||||
* @param resourceId - Numeric primary key of the resource.
|
||||
* @param resourceName - Human-readable name shown in notifications (optional).
|
||||
* @param extra - Any additional key/value pairs to include in the payload.
|
||||
*/
|
||||
export async function fireResourceToggleAlert(
|
||||
orgId: string,
|
||||
resourceId: number,
|
||||
resourceName?: string | null,
|
||||
extra?: Record<string, unknown>
|
||||
): Promise<void> {
|
||||
try {
|
||||
await processAlerts({
|
||||
eventType: "resource_toggle",
|
||||
orgId,
|
||||
resourceId,
|
||||
data: {
|
||||
resourceId,
|
||||
...(resourceName != null ? { resourceName } : {}),
|
||||
...extra
|
||||
}
|
||||
});
|
||||
} catch (err) {
|
||||
logger.error(
|
||||
`fireResourceToggleAlert: unexpected error for resourceId ${resourceId}`,
|
||||
err
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -11,12 +11,13 @@
|
||||
* This file is not licensed under the AGPLv3.
|
||||
*/
|
||||
|
||||
import { and, eq, isNull, or } from "drizzle-orm";
|
||||
import { and, eq, or } from "drizzle-orm";
|
||||
import { db } from "@server/db";
|
||||
import {
|
||||
alertRules,
|
||||
alertSites,
|
||||
alertHealthChecks,
|
||||
alertResources,
|
||||
alertEmailActions,
|
||||
alertEmailRecipients,
|
||||
alertWebhookActions,
|
||||
@@ -48,11 +49,9 @@ export async function processAlerts(context: AlertContext): Promise<void> {
|
||||
// ------------------------------------------------------------------
|
||||
// 1. Find matching alert rules
|
||||
// ------------------------------------------------------------------
|
||||
// Rules with no junction-table entries match ALL sites / health checks.
|
||||
// Rules with junction entries match only those specific IDs.
|
||||
// We implement this with a LEFT JOIN: a NULL join result means the rule
|
||||
// has no scope restrictions (match all); a non-NULL result that satisfies
|
||||
// the id equality filter means an explicit match.
|
||||
// Rules with allSites / allHealthChecks / allResources set to true match
|
||||
// ANY event of that type. Rules without these flags set match only the
|
||||
// specific IDs listed in the junction tables.
|
||||
const baseConditions = and(
|
||||
eq(alertRules.orgId, context.orgId),
|
||||
eq(alertRules.eventType, context.eventType),
|
||||
@@ -73,12 +72,20 @@ export async function processAlerts(context: AlertContext): Promise<void> {
|
||||
and(
|
||||
baseConditions,
|
||||
or(
|
||||
eq(alertSites.siteId, context.siteId),
|
||||
isNull(alertSites.alertRuleId)
|
||||
eq(alertRules.allSites, true),
|
||||
eq(alertSites.siteId, context.siteId)
|
||||
)
|
||||
)
|
||||
);
|
||||
rules = rows.map((r) => r.alertRules);
|
||||
// Deduplicate in case a rule matched on multiple junction rows
|
||||
const seen = new Set<number>();
|
||||
rules = rows
|
||||
.map((r) => r.alertRules)
|
||||
.filter((r) => {
|
||||
if (seen.has(r.alertRuleId)) return false;
|
||||
seen.add(r.alertRuleId);
|
||||
return true;
|
||||
});
|
||||
} else if (context.healthCheckId != null) {
|
||||
const rows = await db
|
||||
.select()
|
||||
@@ -91,12 +98,44 @@ export async function processAlerts(context: AlertContext): Promise<void> {
|
||||
and(
|
||||
baseConditions,
|
||||
or(
|
||||
eq(alertHealthChecks.healthCheckId, context.healthCheckId),
|
||||
isNull(alertHealthChecks.alertRuleId)
|
||||
eq(alertRules.allHealthChecks, true),
|
||||
eq(alertHealthChecks.healthCheckId, context.healthCheckId)
|
||||
)
|
||||
)
|
||||
);
|
||||
rules = rows.map((r) => r.alertRules);
|
||||
const seen = new Set<number>();
|
||||
rules = rows
|
||||
.map((r) => r.alertRules)
|
||||
.filter((r) => {
|
||||
if (seen.has(r.alertRuleId)) return false;
|
||||
seen.add(r.alertRuleId);
|
||||
return true;
|
||||
});
|
||||
} else if (context.resourceId != null) {
|
||||
const rows = await db
|
||||
.select()
|
||||
.from(alertRules)
|
||||
.leftJoin(
|
||||
alertResources,
|
||||
eq(alertResources.alertRuleId, alertRules.alertRuleId)
|
||||
)
|
||||
.where(
|
||||
and(
|
||||
baseConditions,
|
||||
or(
|
||||
eq(alertRules.allResources, true),
|
||||
eq(alertResources.resourceId, context.resourceId)
|
||||
)
|
||||
)
|
||||
);
|
||||
const seen = new Set<number>();
|
||||
rules = rows
|
||||
.map((r) => r.alertRules)
|
||||
.filter((r) => {
|
||||
if (seen.has(r.alertRuleId)) return false;
|
||||
seen.add(r.alertRuleId);
|
||||
return true;
|
||||
});
|
||||
} else {
|
||||
rules = [];
|
||||
}
|
||||
|
||||
@@ -72,10 +72,20 @@ function buildSubject(context: AlertContext): string {
|
||||
return "[Alert] Site Back Online";
|
||||
case "site_offline":
|
||||
return "[Alert] Site Offline";
|
||||
case "site_toggle":
|
||||
return "[Alert] Site Status Changed";
|
||||
case "health_check_healthy":
|
||||
return "[Alert] Health Check Recovered";
|
||||
case "health_check_not_healthy":
|
||||
case "health_check_unhealthy":
|
||||
return "[Alert] Health Check Failing";
|
||||
case "health_check_toggle":
|
||||
return "[Alert] Health Check Status Changed";
|
||||
case "resource_healthy":
|
||||
return "[Alert] Resource Healthy";
|
||||
case "resource_unhealthy":
|
||||
return "[Alert] Resource Unhealthy";
|
||||
case "resource_toggle":
|
||||
return "[Alert] Resource Status Changed";
|
||||
default: {
|
||||
// Exhaustiveness fallback – should never be reached with a
|
||||
// well-typed caller, but keeps runtime behaviour predictable.
|
||||
@@ -84,4 +94,4 @@ function buildSubject(context: AlertContext): string {
|
||||
return "[Alert] Event Notification";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,8 +18,13 @@
|
||||
export type AlertEventType =
|
||||
| "site_online"
|
||||
| "site_offline"
|
||||
| "site_toggle"
|
||||
| "health_check_healthy"
|
||||
| "health_check_not_healthy";
|
||||
| "health_check_unhealthy"
|
||||
| "health_check_toggle"
|
||||
| "resource_healthy"
|
||||
| "resource_unhealthy"
|
||||
| "resource_toggle";
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Webhook authentication config (stored as encrypted JSON in the DB)
|
||||
@@ -58,6 +63,8 @@ export interface AlertContext {
|
||||
siteId?: number;
|
||||
/** Set for health_check_* events */
|
||||
healthCheckId?: number;
|
||||
/** Set for resource_* events */
|
||||
resourceId?: number;
|
||||
/** Human-readable context data included in emails and webhook payloads */
|
||||
data: Record<string, unknown>;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,11 +13,12 @@
|
||||
|
||||
import { Request, Response, NextFunction } from "express";
|
||||
import { z } from "zod";
|
||||
import { db } from "@server/db";
|
||||
import { db, roles } from "@server/db";
|
||||
import {
|
||||
alertRules,
|
||||
alertSites,
|
||||
alertHealthChecks,
|
||||
alertResources,
|
||||
alertEmailActions,
|
||||
alertEmailRecipients,
|
||||
alertWebhookActions
|
||||
@@ -31,10 +32,16 @@ import { OpenAPITags, registry } from "@server/openApi";
|
||||
import { encrypt } from "@server/lib/crypto";
|
||||
import config from "@server/lib/config";
|
||||
|
||||
const SITE_EVENT_TYPES = ["site_online", "site_offline"] as const;
|
||||
const HC_EVENT_TYPES = [
|
||||
export const SITE_EVENT_TYPES = ["site_online", "site_offline", "site_toggle"] as const;
|
||||
export const HC_EVENT_TYPES = [
|
||||
"health_check_healthy",
|
||||
"health_check_not_healthy"
|
||||
"health_check_unhealthy",
|
||||
"health_check_toggle"
|
||||
] as const;
|
||||
export const RESOURCE_EVENT_TYPES = [
|
||||
"resource_healthy",
|
||||
"resource_unhealthy",
|
||||
"resource_toggle"
|
||||
] as const;
|
||||
|
||||
const paramsSchema = z.strictObject({
|
||||
@@ -51,22 +58,28 @@ const bodySchema = z
|
||||
.strictObject({
|
||||
name: z.string().nonempty(),
|
||||
eventType: z.enum([
|
||||
"site_online",
|
||||
"site_offline",
|
||||
"health_check_healthy",
|
||||
"health_check_not_healthy"
|
||||
...HC_EVENT_TYPES,
|
||||
...SITE_EVENT_TYPES,
|
||||
...RESOURCE_EVENT_TYPES
|
||||
]),
|
||||
enabled: z.boolean().optional().default(true),
|
||||
cooldownSeconds: z.number().int().nonnegative().optional().default(300),
|
||||
// Source join tables - which is required depends on eventType
|
||||
siteIds: z.array(z.number().int().positive()).optional().default([]),
|
||||
allSites: z.boolean().optional().default(false),
|
||||
healthCheckIds: z
|
||||
.array(z.number().int().positive())
|
||||
.optional()
|
||||
.default([]),
|
||||
allHealthChecks: z.boolean().optional().default(false),
|
||||
resourceIds: z
|
||||
.array(z.number().int().positive())
|
||||
.optional()
|
||||
.default([]),
|
||||
allResources: z.boolean().optional().default(false),
|
||||
// Email recipients (flat)
|
||||
userIds: z.array(z.string().nonempty()).optional().default([]),
|
||||
roleIds: z.array(z.string().nonempty()).optional().default([]),
|
||||
roleIds: z.array(z.number()).optional().default([]),
|
||||
emails: z.array(z.string().email()).optional().default([]),
|
||||
// Webhook actions
|
||||
webhookActions: z.array(webhookActionSchema).optional().default([])
|
||||
@@ -78,21 +91,23 @@ const bodySchema = z
|
||||
const isHcEvent = (HC_EVENT_TYPES as readonly string[]).includes(
|
||||
val.eventType
|
||||
);
|
||||
const isResourceEvent = (RESOURCE_EVENT_TYPES as readonly string[]).includes(
|
||||
val.eventType
|
||||
);
|
||||
|
||||
if (isSiteEvent && val.siteIds.length === 0) {
|
||||
if (isSiteEvent && !val.allSites && val.siteIds.length === 0) {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
message:
|
||||
"At least one siteId is required for site event types",
|
||||
message: "At least one siteId is required for site event types when allSites is false",
|
||||
path: ["siteIds"]
|
||||
});
|
||||
}
|
||||
|
||||
if (isHcEvent && val.healthCheckIds.length === 0) {
|
||||
if (isHcEvent && !val.allHealthChecks && val.healthCheckIds.length === 0) {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
message:
|
||||
"At least one healthCheckId is required for health check event types",
|
||||
"At least one healthCheckId is required for health check event types when allHealthChecks is false",
|
||||
path: ["healthCheckIds"]
|
||||
});
|
||||
}
|
||||
@@ -108,11 +123,50 @@ const bodySchema = z
|
||||
if (isHcEvent && val.siteIds.length > 0) {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
message:
|
||||
"siteIds must not be set for health check event types",
|
||||
message: "siteIds must not be set for health check event types",
|
||||
path: ["siteIds"]
|
||||
});
|
||||
}
|
||||
|
||||
if (isResourceEvent && !val.allResources && val.resourceIds.length === 0) {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
message: "At least one resourceId is required for resource event types when allResources is false",
|
||||
path: ["resourceIds"]
|
||||
});
|
||||
}
|
||||
|
||||
if (isResourceEvent && val.siteIds.length > 0) {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
message: "siteIds must not be set for resource event types",
|
||||
path: ["siteIds"]
|
||||
});
|
||||
}
|
||||
|
||||
if (isResourceEvent && val.healthCheckIds.length > 0) {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
message: "healthCheckIds must not be set for resource event types",
|
||||
path: ["healthCheckIds"]
|
||||
});
|
||||
}
|
||||
|
||||
if (isSiteEvent && val.resourceIds.length > 0) {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
message: "resourceIds must not be set for site event types",
|
||||
path: ["resourceIds"]
|
||||
});
|
||||
}
|
||||
|
||||
if (isHcEvent && val.resourceIds.length > 0) {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
message: "resourceIds must not be set for health check event types",
|
||||
path: ["resourceIds"]
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
export type CreateAlertRuleResponse = {
|
||||
@@ -171,7 +225,11 @@ export async function createAlertRule(
|
||||
enabled,
|
||||
cooldownSeconds,
|
||||
siteIds,
|
||||
allSites,
|
||||
healthCheckIds,
|
||||
allHealthChecks,
|
||||
resourceIds,
|
||||
allResources,
|
||||
userIds,
|
||||
roleIds,
|
||||
emails,
|
||||
@@ -188,13 +246,16 @@ export async function createAlertRule(
|
||||
eventType,
|
||||
enabled,
|
||||
cooldownSeconds,
|
||||
allSites,
|
||||
allHealthChecks,
|
||||
allResources,
|
||||
createdAt: now,
|
||||
updatedAt: now
|
||||
})
|
||||
.returning();
|
||||
|
||||
// Insert site associations
|
||||
if (siteIds.length > 0) {
|
||||
// Insert site associations (skipped when allSites=true — empty junction = match all)
|
||||
if (!allSites && siteIds.length > 0) {
|
||||
await db.insert(alertSites).values(
|
||||
siteIds.map((siteId) => ({
|
||||
alertRuleId: rule.alertRuleId,
|
||||
@@ -203,8 +264,8 @@ export async function createAlertRule(
|
||||
);
|
||||
}
|
||||
|
||||
// Insert health check associations
|
||||
if (healthCheckIds.length > 0) {
|
||||
// Insert health check associations (skipped when allHealthChecks=true)
|
||||
if (!allHealthChecks && healthCheckIds.length > 0) {
|
||||
await db.insert(alertHealthChecks).values(
|
||||
healthCheckIds.map((healthCheckId) => ({
|
||||
alertRuleId: rule.alertRuleId,
|
||||
@@ -213,10 +274,22 @@ export async function createAlertRule(
|
||||
);
|
||||
}
|
||||
|
||||
// Insert resource associations (skipped when allResources=true)
|
||||
if (!allResources && resourceIds.length > 0) {
|
||||
await db.insert(alertResources).values(
|
||||
resourceIds.map((resourceId) => ({
|
||||
alertRuleId: rule.alertRuleId,
|
||||
resourceId
|
||||
}))
|
||||
);
|
||||
}
|
||||
|
||||
// Create the email action pivot row and recipients if any recipients
|
||||
// were supplied (userIds, roleIds, or raw emails).
|
||||
const hasRecipients =
|
||||
userIds.length > 0 || roleIds.length > 0 || emails.length > 0;
|
||||
userIds.length > 0 ||
|
||||
roleIds.length > 0 ||
|
||||
emails.length > 0;
|
||||
|
||||
if (hasRecipients) {
|
||||
const [emailActionRow] = await db
|
||||
@@ -228,7 +301,7 @@ export async function createAlertRule(
|
||||
...userIds.map((userId) => ({
|
||||
emailActionId: emailActionRow.emailActionId,
|
||||
userId,
|
||||
roleId: null as string | null,
|
||||
roleId: null as number | null,
|
||||
email: null as string | null
|
||||
})),
|
||||
...roleIds.map((roleId) => ({
|
||||
@@ -240,7 +313,7 @@ export async function createAlertRule(
|
||||
...emails.map((email) => ({
|
||||
emailActionId: emailActionRow.emailActionId,
|
||||
userId: null as string | null,
|
||||
roleId: null as string | null,
|
||||
roleId: null as number | null,
|
||||
email
|
||||
}))
|
||||
];
|
||||
@@ -254,7 +327,10 @@ export async function createAlertRule(
|
||||
webhookActions.map((wa) => ({
|
||||
alertRuleId: rule.alertRuleId,
|
||||
webhookUrl: wa.webhookUrl,
|
||||
config: wa.config != null ? encrypt(wa.config, serverSecret) : null,
|
||||
config:
|
||||
wa.config != null
|
||||
? encrypt(wa.config, serverSecret)
|
||||
: null,
|
||||
enabled: wa.enabled
|
||||
}))
|
||||
);
|
||||
@@ -275,4 +351,4 @@ export async function createAlertRule(
|
||||
createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred")
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ import {
|
||||
alertRules,
|
||||
alertSites,
|
||||
alertHealthChecks,
|
||||
alertResources,
|
||||
alertEmailActions,
|
||||
alertEmailRecipients,
|
||||
alertWebhookActions
|
||||
@@ -31,7 +32,7 @@ import { OpenAPITags, registry } from "@server/openApi";
|
||||
import { and, eq } from "drizzle-orm";
|
||||
import { decrypt } from "@server/lib/crypto";
|
||||
import config from "@server/lib/config";
|
||||
import { WebhookAlertConfig } from "@server/lib/alerts/types";
|
||||
import { WebhookAlertConfig } from "#private/lib/alerts/types";
|
||||
|
||||
const paramsSchema = z
|
||||
.object({
|
||||
@@ -47,8 +48,13 @@ export type GetAlertRuleResponse = {
|
||||
eventType:
|
||||
| "site_online"
|
||||
| "site_offline"
|
||||
| "site_toggle"
|
||||
| "health_check_healthy"
|
||||
| "health_check_not_healthy";
|
||||
| "health_check_unhealthy"
|
||||
| "health_check_toggle"
|
||||
| "resource_healthy"
|
||||
| "resource_unhealthy"
|
||||
| "resource_toggle";
|
||||
enabled: boolean;
|
||||
cooldownSeconds: number;
|
||||
lastTriggeredAt: number | null;
|
||||
@@ -56,10 +62,11 @@ export type GetAlertRuleResponse = {
|
||||
updatedAt: number;
|
||||
siteIds: number[];
|
||||
healthCheckIds: number[];
|
||||
resourceIds: number[];
|
||||
recipients: {
|
||||
recipientId: number;
|
||||
userId: string | null;
|
||||
roleId: string | null;
|
||||
roleId: number | null;
|
||||
email: string | null;
|
||||
}[];
|
||||
webhookActions: {
|
||||
@@ -128,6 +135,12 @@ export async function getAlertRule(
|
||||
.from(alertHealthChecks)
|
||||
.where(eq(alertHealthChecks.alertRuleId, alertRuleId));
|
||||
|
||||
// Fetch resource associations
|
||||
const resourceRows = await db
|
||||
.select()
|
||||
.from(alertResources)
|
||||
.where(eq(alertResources.alertRuleId, alertRuleId));
|
||||
|
||||
// Resolve the single email action row for this rule, then collect all
|
||||
// recipients into a flat list. The emailAction pivot row is an internal
|
||||
// implementation detail and is not surfaced to callers.
|
||||
@@ -175,26 +188,30 @@ export async function getAlertRule(
|
||||
updatedAt: rule.updatedAt,
|
||||
siteIds: siteRows.map((r) => r.siteId),
|
||||
healthCheckIds: healthCheckRows.map((r) => r.healthCheckId),
|
||||
resourceIds: resourceRows.map((r) => r.resourceId),
|
||||
recipients,
|
||||
webhookActions: webhooks.map((w) => {
|
||||
let parsedConfig: WebhookAlertConfig | null = null;
|
||||
if (w.config) {
|
||||
try {
|
||||
const serverSecret = config.getRawConfig().server.secret!;
|
||||
const decrypted = decrypt(w.config, serverSecret);
|
||||
parsedConfig = JSON.parse(decrypted) as WebhookAlertConfig;
|
||||
} catch {
|
||||
// best-effort – return null if decryption fails
|
||||
}
|
||||
}
|
||||
return {
|
||||
webhookActionId: w.webhookActionId,
|
||||
webhookUrl: w.webhookUrl,
|
||||
enabled: w.enabled,
|
||||
lastSentAt: w.lastSentAt ?? null,
|
||||
config: parsedConfig
|
||||
};
|
||||
})
|
||||
let parsedConfig: WebhookAlertConfig | null = null;
|
||||
if (w.config) {
|
||||
try {
|
||||
const serverSecret =
|
||||
config.getRawConfig().server.secret!;
|
||||
const decrypted = decrypt(w.config, serverSecret);
|
||||
parsedConfig = JSON.parse(
|
||||
decrypted
|
||||
) as WebhookAlertConfig;
|
||||
} catch {
|
||||
// best-effort – return null if decryption fails
|
||||
}
|
||||
}
|
||||
return {
|
||||
webhookActionId: w.webhookActionId,
|
||||
webhookUrl: w.webhookUrl,
|
||||
enabled: w.enabled,
|
||||
lastSentAt: w.lastSentAt ?? null,
|
||||
config: parsedConfig
|
||||
};
|
||||
})
|
||||
},
|
||||
success: true,
|
||||
error: false,
|
||||
@@ -207,4 +224,4 @@ export async function getAlertRule(
|
||||
createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred")
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,14 +14,14 @@
|
||||
import { Request, Response, NextFunction } from "express";
|
||||
import { z } from "zod";
|
||||
import { db } from "@server/db";
|
||||
import { alertRules, alertSites, alertHealthChecks } from "@server/db";
|
||||
import { alertRules, alertSites, alertHealthChecks, alertResources } from "@server/db";
|
||||
import response from "@server/lib/response";
|
||||
import HttpCode from "@server/types/HttpCode";
|
||||
import createHttpError from "http-errors";
|
||||
import logger from "@server/logger";
|
||||
import { fromError } from "zod-validation-error";
|
||||
import { OpenAPITags, registry } from "@server/openApi";
|
||||
import { eq, inArray, sql } from "drizzle-orm";
|
||||
import { and, eq, inArray, like, sql } from "drizzle-orm";
|
||||
|
||||
const paramsSchema = z.strictObject({
|
||||
orgId: z.string().nonempty()
|
||||
@@ -39,7 +39,18 @@ const querySchema = z.strictObject({
|
||||
.optional()
|
||||
.default("0")
|
||||
.transform(Number)
|
||||
.pipe(z.number().int().nonnegative())
|
||||
.pipe(z.number().int().nonnegative()),
|
||||
query: z.string().optional(),
|
||||
siteId: z
|
||||
.string()
|
||||
.optional()
|
||||
.transform((v) => (v !== undefined ? Number(v) : undefined))
|
||||
.pipe(z.number().int().positive().optional()),
|
||||
resourceId: z
|
||||
.string()
|
||||
.optional()
|
||||
.transform((v) => (v !== undefined ? Number(v) : undefined))
|
||||
.pipe(z.number().int().positive().optional())
|
||||
});
|
||||
|
||||
export type ListAlertRulesResponse = {
|
||||
@@ -55,6 +66,7 @@ export type ListAlertRulesResponse = {
|
||||
updatedAt: number;
|
||||
siteIds: number[];
|
||||
healthCheckIds: number[];
|
||||
resourceIds: number[];
|
||||
}[];
|
||||
pagination: {
|
||||
total: number;
|
||||
@@ -101,12 +113,69 @@ export async function listAlertRules(
|
||||
)
|
||||
);
|
||||
}
|
||||
const { limit, offset } = parsedQuery.data;
|
||||
const { limit, offset, query, siteId, resourceId } = parsedQuery.data;
|
||||
|
||||
// Resolve siteId filter → matching alertRuleIds
|
||||
let siteFilterRuleIds: number[] | null = null;
|
||||
if (siteId !== undefined) {
|
||||
const rows = await db
|
||||
.select({ alertRuleId: alertSites.alertRuleId })
|
||||
.from(alertSites)
|
||||
.where(eq(alertSites.siteId, siteId));
|
||||
siteFilterRuleIds = rows.map((r) => r.alertRuleId);
|
||||
if (siteFilterRuleIds.length === 0) {
|
||||
return response<ListAlertRulesResponse>(res, {
|
||||
data: {
|
||||
alertRules: [],
|
||||
pagination: { total: 0, limit, offset }
|
||||
},
|
||||
success: true,
|
||||
error: false,
|
||||
message: "Alert rules retrieved successfully",
|
||||
status: HttpCode.OK
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Resolve resourceId filter → matching alertRuleIds
|
||||
let resourceFilterRuleIds: number[] | null = null;
|
||||
if (resourceId !== undefined) {
|
||||
const rows = await db
|
||||
.select({ alertRuleId: alertResources.alertRuleId })
|
||||
.from(alertResources)
|
||||
.where(eq(alertResources.resourceId, resourceId));
|
||||
resourceFilterRuleIds = rows.map((r) => r.alertRuleId);
|
||||
if (resourceFilterRuleIds.length === 0) {
|
||||
return response<ListAlertRulesResponse>(res, {
|
||||
data: {
|
||||
alertRules: [],
|
||||
pagination: { total: 0, limit, offset }
|
||||
},
|
||||
success: true,
|
||||
error: false,
|
||||
message: "Alert rules retrieved successfully",
|
||||
status: HttpCode.OK
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const whereClause = and(
|
||||
eq(alertRules.orgId, orgId),
|
||||
query
|
||||
? like(sql`LOWER(${alertRules.name})`, `%${query.toLowerCase()}%`)
|
||||
: undefined,
|
||||
siteFilterRuleIds !== null
|
||||
? inArray(alertRules.alertRuleId, siteFilterRuleIds)
|
||||
: undefined,
|
||||
resourceFilterRuleIds !== null
|
||||
? inArray(alertRules.alertRuleId, resourceFilterRuleIds)
|
||||
: undefined
|
||||
);
|
||||
|
||||
const list = await db
|
||||
.select()
|
||||
.from(alertRules)
|
||||
.where(eq(alertRules.orgId, orgId))
|
||||
.where(whereClause)
|
||||
.orderBy(sql`${alertRules.createdAt} DESC`)
|
||||
.limit(limit)
|
||||
.offset(offset);
|
||||
@@ -114,7 +183,7 @@ export async function listAlertRules(
|
||||
const [{ count }] = await db
|
||||
.select({ count: sql<number>`count(*)` })
|
||||
.from(alertRules)
|
||||
.where(eq(alertRules.orgId, orgId));
|
||||
.where(whereClause);
|
||||
|
||||
// Batch-fetch site and health-check associations for all returned rules
|
||||
// in two queries rather than N+1 individual lookups.
|
||||
@@ -138,6 +207,14 @@ export async function listAlertRules(
|
||||
)
|
||||
: [];
|
||||
|
||||
const resourceRows =
|
||||
ruleIds.length > 0
|
||||
? await db
|
||||
.select()
|
||||
.from(alertResources)
|
||||
.where(inArray(alertResources.alertRuleId, ruleIds))
|
||||
: [];
|
||||
|
||||
// Index by alertRuleId for O(1) lookup when building the response
|
||||
const sitesByRule = new Map<number, number[]>();
|
||||
for (const row of siteRows) {
|
||||
@@ -153,6 +230,13 @@ export async function listAlertRules(
|
||||
healthChecksByRule.set(row.alertRuleId, existing);
|
||||
}
|
||||
|
||||
const resourcesByRule = new Map<number, number[]>();
|
||||
for (const row of resourceRows) {
|
||||
const existing = resourcesByRule.get(row.alertRuleId) ?? [];
|
||||
existing.push(row.resourceId);
|
||||
resourcesByRule.set(row.alertRuleId, existing);
|
||||
}
|
||||
|
||||
return response<ListAlertRulesResponse>(res, {
|
||||
data: {
|
||||
alertRules: list.map((rule) => ({
|
||||
@@ -167,7 +251,8 @@ export async function listAlertRules(
|
||||
updatedAt: rule.updatedAt,
|
||||
siteIds: sitesByRule.get(rule.alertRuleId) ?? [],
|
||||
healthCheckIds:
|
||||
healthChecksByRule.get(rule.alertRuleId) ?? []
|
||||
healthChecksByRule.get(rule.alertRuleId) ?? [],
|
||||
resourceIds: resourcesByRule.get(rule.alertRuleId) ?? []
|
||||
})),
|
||||
pagination: {
|
||||
total: count,
|
||||
|
||||
@@ -18,6 +18,7 @@ import {
|
||||
alertRules,
|
||||
alertSites,
|
||||
alertHealthChecks,
|
||||
alertResources,
|
||||
alertEmailActions,
|
||||
alertEmailRecipients,
|
||||
alertWebhookActions
|
||||
@@ -31,12 +32,8 @@ import { OpenAPITags, registry } from "@server/openApi";
|
||||
import { and, eq } from "drizzle-orm";
|
||||
import { encrypt } from "@server/lib/crypto";
|
||||
import config from "@server/lib/config";
|
||||
|
||||
const SITE_EVENT_TYPES = ["site_online", "site_offline"] as const;
|
||||
const HC_EVENT_TYPES = [
|
||||
"health_check_healthy",
|
||||
"health_check_not_healthy"
|
||||
] as const;
|
||||
import { HC_EVENT_TYPES, SITE_EVENT_TYPES, RESOURCE_EVENT_TYPES } from "./createAlertRule";
|
||||
import { invalidateAllRemoteExitNodeSessions } from "@server/private/auth/sessions/remoteExitNode";
|
||||
|
||||
const paramsSchema = z
|
||||
.object({
|
||||
@@ -57,20 +54,23 @@ const bodySchema = z
|
||||
name: z.string().nonempty().optional(),
|
||||
eventType: z
|
||||
.enum([
|
||||
"site_online",
|
||||
"site_offline",
|
||||
"health_check_healthy",
|
||||
"health_check_not_healthy"
|
||||
...HC_EVENT_TYPES,
|
||||
...SITE_EVENT_TYPES,
|
||||
...RESOURCE_EVENT_TYPES
|
||||
])
|
||||
.optional(),
|
||||
enabled: z.boolean().optional(),
|
||||
cooldownSeconds: z.number().int().nonnegative().optional(),
|
||||
// Source join tables - if provided the full set is replaced
|
||||
siteIds: z.array(z.number().int().positive()).optional(),
|
||||
allSites: z.boolean().optional(),
|
||||
healthCheckIds: z.array(z.number().int().positive()).optional(),
|
||||
allHealthChecks: z.boolean().optional(),
|
||||
resourceIds: z.array(z.number().int().positive()).optional(),
|
||||
allResources: z.boolean().optional(),
|
||||
// Recipient arrays - if any are provided the full recipient set is replaced
|
||||
userIds: z.array(z.string().nonempty()).optional(),
|
||||
roleIds: z.array(z.string().nonempty()).optional(),
|
||||
roleIds: z.array(z.number()).optional(),
|
||||
emails: z.array(z.string().email()).optional(),
|
||||
// Webhook actions - if provided the full webhook set is replaced
|
||||
webhookActions: z.array(webhookActionSchema).optional()
|
||||
@@ -84,6 +84,33 @@ const bodySchema = z
|
||||
const isHcEvent = (HC_EVENT_TYPES as readonly string[]).includes(
|
||||
val.eventType
|
||||
);
|
||||
const isResourceEvent = (RESOURCE_EVENT_TYPES as readonly string[]).includes(
|
||||
val.eventType
|
||||
);
|
||||
|
||||
if (isSiteEvent && val.siteIds !== undefined && val.siteIds.length === 0 && !val.allSites) {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
message: "At least one siteId is required for site event types when allSites is false",
|
||||
path: ["siteIds"]
|
||||
});
|
||||
}
|
||||
|
||||
if (isHcEvent && val.healthCheckIds !== undefined && val.healthCheckIds.length === 0 && !val.allHealthChecks) {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
message: "At least one healthCheckId is required for health check event types when allHealthChecks is false",
|
||||
path: ["healthCheckIds"]
|
||||
});
|
||||
}
|
||||
|
||||
if (isResourceEvent && val.resourceIds !== undefined && val.resourceIds.length === 0 && !val.allResources) {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
message: "At least one resourceId is required for resource event types when allResources is false",
|
||||
path: ["resourceIds"]
|
||||
});
|
||||
}
|
||||
|
||||
if (isSiteEvent && val.healthCheckIds !== undefined && val.healthCheckIds.length > 0) {
|
||||
ctx.addIssue({
|
||||
@@ -100,6 +127,22 @@ const bodySchema = z
|
||||
path: ["siteIds"]
|
||||
});
|
||||
}
|
||||
|
||||
if (isResourceEvent && val.siteIds !== undefined && val.siteIds.length > 0) {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
message: "siteIds must not be set for resource event types",
|
||||
path: ["siteIds"]
|
||||
});
|
||||
}
|
||||
|
||||
if (isResourceEvent && val.healthCheckIds !== undefined && val.healthCheckIds.length > 0) {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
message: "healthCheckIds must not be set for resource event types",
|
||||
path: ["healthCheckIds"]
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
export type UpdateAlertRuleResponse = {
|
||||
@@ -174,7 +217,11 @@ export async function updateAlertRule(
|
||||
enabled,
|
||||
cooldownSeconds,
|
||||
siteIds,
|
||||
allSites,
|
||||
healthCheckIds,
|
||||
allHealthChecks,
|
||||
resourceIds,
|
||||
allResources,
|
||||
userIds,
|
||||
roleIds,
|
||||
emails,
|
||||
@@ -189,8 +236,10 @@ export async function updateAlertRule(
|
||||
if (name !== undefined) updateData.name = name;
|
||||
if (eventType !== undefined) updateData.eventType = eventType;
|
||||
if (enabled !== undefined) updateData.enabled = enabled;
|
||||
if (cooldownSeconds !== undefined)
|
||||
updateData.cooldownSeconds = cooldownSeconds;
|
||||
if (cooldownSeconds !== undefined) updateData.cooldownSeconds = cooldownSeconds;
|
||||
if (allSites !== undefined) updateData.allSites = allSites;
|
||||
if (allHealthChecks !== undefined) updateData.allHealthChecks = allHealthChecks;
|
||||
if (allResources !== undefined) updateData.allResources = allResources;
|
||||
|
||||
await db
|
||||
.update(alertRules)
|
||||
@@ -203,12 +252,14 @@ export async function updateAlertRule(
|
||||
);
|
||||
|
||||
// --- Full-replace site associations if siteIds was provided ---
|
||||
if (siteIds !== undefined) {
|
||||
if (siteIds !== undefined || allSites !== undefined) {
|
||||
await db
|
||||
.delete(alertSites)
|
||||
.where(eq(alertSites.alertRuleId, alertRuleId));
|
||||
|
||||
if (siteIds.length > 0) {
|
||||
// Only insert junction rows when allSites is not true
|
||||
const effectiveAllSites = allSites ?? false;
|
||||
if (!effectiveAllSites && siteIds !== undefined && siteIds.length > 0) {
|
||||
await db.insert(alertSites).values(
|
||||
siteIds.map((siteId) => ({
|
||||
alertRuleId,
|
||||
@@ -219,12 +270,13 @@ export async function updateAlertRule(
|
||||
}
|
||||
|
||||
// --- Full-replace health check associations if healthCheckIds was provided ---
|
||||
if (healthCheckIds !== undefined) {
|
||||
if (healthCheckIds !== undefined || allHealthChecks !== undefined) {
|
||||
await db
|
||||
.delete(alertHealthChecks)
|
||||
.where(eq(alertHealthChecks.alertRuleId, alertRuleId));
|
||||
|
||||
if (healthCheckIds.length > 0) {
|
||||
const effectiveAllHealthChecks = allHealthChecks ?? false;
|
||||
if (!effectiveAllHealthChecks && healthCheckIds !== undefined && healthCheckIds.length > 0) {
|
||||
await db.insert(alertHealthChecks).values(
|
||||
healthCheckIds.map((healthCheckId) => ({
|
||||
alertRuleId,
|
||||
@@ -234,6 +286,23 @@ export async function updateAlertRule(
|
||||
}
|
||||
}
|
||||
|
||||
// --- Full-replace resource associations if resourceIds was provided ---
|
||||
if (resourceIds !== undefined || allResources !== undefined) {
|
||||
await db
|
||||
.delete(alertResources)
|
||||
.where(eq(alertResources.alertRuleId, alertRuleId));
|
||||
|
||||
const effectiveAllResources = allResources ?? false;
|
||||
if (!effectiveAllResources && resourceIds !== undefined && resourceIds.length > 0) {
|
||||
await db.insert(alertResources).values(
|
||||
resourceIds.map((resourceId) => ({
|
||||
alertRuleId,
|
||||
resourceId
|
||||
}))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// --- Full-replace recipients if any recipient array was provided ---
|
||||
const recipientsProvided =
|
||||
userIds !== undefined ||
|
||||
@@ -244,7 +313,7 @@ export async function updateAlertRule(
|
||||
const newRecipients = [
|
||||
...(userIds ?? []).map((userId) => ({
|
||||
userId,
|
||||
roleId: null as string | null,
|
||||
roleId: null as number | null,
|
||||
email: null as string | null
|
||||
})),
|
||||
...(roleIds ?? []).map((roleId) => ({
|
||||
@@ -254,7 +323,7 @@ export async function updateAlertRule(
|
||||
})),
|
||||
...(emails ?? []).map((email) => ({
|
||||
userId: null as string | null,
|
||||
roleId: null as string | null,
|
||||
roleId: null as number | null,
|
||||
email
|
||||
}))
|
||||
];
|
||||
@@ -331,4 +400,4 @@ export async function updateAlertRule(
|
||||
createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred")
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,7 +42,9 @@ import {
|
||||
verifyRoleAccess,
|
||||
verifyUserAccess,
|
||||
verifyUserCanSetUserOrgRoles,
|
||||
verifySiteProvisioningKeyAccess
|
||||
verifySiteProvisioningKeyAccess,
|
||||
verifyIsLoggedInUser,
|
||||
verifyAdmin
|
||||
} from "@server/middlewares";
|
||||
import { ActionsEnum } from "@server/auth/actions";
|
||||
import {
|
||||
@@ -89,6 +91,7 @@ authenticated.put(
|
||||
"/org/:orgId/idp/oidc",
|
||||
verifyValidLicense,
|
||||
verifyValidSubscription(tierMatrix.orgOidc),
|
||||
orgIdp.requireOrgIdentityProviderMode,
|
||||
verifyOrgAccess,
|
||||
verifyLimits,
|
||||
verifyUserHasAction(ActionsEnum.createIdp),
|
||||
@@ -96,10 +99,23 @@ authenticated.put(
|
||||
orgIdp.createOrgOidcIdp
|
||||
);
|
||||
|
||||
authenticated.post(
|
||||
"/org/:orgId/idp/:idpId/import",
|
||||
verifyValidLicense,
|
||||
verifyValidSubscription(tierMatrix.orgOidc),
|
||||
orgIdp.requireOrgIdentityProviderMode,
|
||||
verifyOrgAccess,
|
||||
verifyLimits,
|
||||
verifyAdmin,
|
||||
logActionAudit(ActionsEnum.createIdp),
|
||||
orgIdp.importOrgIdp
|
||||
);
|
||||
|
||||
authenticated.post(
|
||||
"/org/:orgId/idp/:idpId/oidc",
|
||||
verifyValidLicense,
|
||||
verifyValidSubscription(tierMatrix.orgOidc),
|
||||
orgIdp.requireOrgIdentityProviderMode,
|
||||
verifyOrgAccess,
|
||||
verifyIdpAccess,
|
||||
verifyLimits,
|
||||
@@ -111,6 +127,7 @@ authenticated.post(
|
||||
authenticated.delete(
|
||||
"/org/:orgId/idp/:idpId",
|
||||
verifyValidLicense,
|
||||
orgIdp.requireOrgIdentityProviderMode,
|
||||
verifyOrgAccess,
|
||||
verifyIdpAccess,
|
||||
verifyUserHasAction(ActionsEnum.deleteIdp),
|
||||
@@ -118,6 +135,17 @@ authenticated.delete(
|
||||
orgIdp.deleteOrgIdp
|
||||
);
|
||||
|
||||
authenticated.delete(
|
||||
"/org/:orgId/idp/:idpId/association",
|
||||
verifyValidLicense,
|
||||
orgIdp.requireOrgIdentityProviderMode,
|
||||
verifyOrgAccess,
|
||||
verifyIdpAccess,
|
||||
verifyUserHasAction(ActionsEnum.deleteIdp),
|
||||
logActionAudit(ActionsEnum.deleteIdp),
|
||||
orgIdp.unassociateOrgIdp
|
||||
);
|
||||
|
||||
authenticated.get(
|
||||
"/org/:orgId/idp/:idpId",
|
||||
verifyValidLicense,
|
||||
@@ -127,16 +155,14 @@ authenticated.get(
|
||||
orgIdp.getOrgIdp
|
||||
);
|
||||
|
||||
authenticated.get(
|
||||
"/org/:orgId/idp",
|
||||
verifyValidLicense,
|
||||
verifyOrgAccess,
|
||||
verifyUserHasAction(ActionsEnum.listIdps),
|
||||
orgIdp.listOrgIdps
|
||||
);
|
||||
|
||||
authenticated.get("/org/:orgId/idp", orgIdp.listOrgIdps); // anyone can see this; it's just a list of idp names and ids
|
||||
|
||||
authenticated.get(
|
||||
"/user/:userId/admin-org-idps",
|
||||
verifyIsLoggedInUser,
|
||||
orgIdp.listUserAdminOrgIdps
|
||||
);
|
||||
|
||||
authenticated.get(
|
||||
"/org/:orgId/certificate/:domainId/:domain",
|
||||
verifyValidLicense,
|
||||
|
||||
@@ -17,7 +17,7 @@ import HttpCode from "@server/types/HttpCode";
|
||||
import createHttpError from "http-errors";
|
||||
import logger from "@server/logger";
|
||||
import { OpenAPITags, registry } from "@server/openApi";
|
||||
import { and, eq, isNull, sql } from "drizzle-orm";
|
||||
import { and, eq, like, sql } from "drizzle-orm";
|
||||
import { NextFunction, Request, Response } from "express";
|
||||
import { z } from "zod";
|
||||
import { fromError } from "zod-validation-error";
|
||||
@@ -39,7 +39,8 @@ const querySchema = z.object({
|
||||
.optional()
|
||||
.default("0")
|
||||
.transform(Number)
|
||||
.pipe(z.int().nonnegative())
|
||||
.pipe(z.int().nonnegative()),
|
||||
query: z.string().optional()
|
||||
});
|
||||
|
||||
registry.registerPath({
|
||||
@@ -80,10 +81,16 @@ export async function listHealthChecks(
|
||||
)
|
||||
);
|
||||
}
|
||||
const { limit, offset } = parsedQuery.data;
|
||||
const { limit, offset, query } = parsedQuery.data;
|
||||
|
||||
const whereClause = and(
|
||||
eq(targetHealthCheck.orgId, orgId),
|
||||
query
|
||||
? like(
|
||||
sql`LOWER(${targetHealthCheck.name})`,
|
||||
`%${query.toLowerCase()}%`
|
||||
)
|
||||
: undefined
|
||||
);
|
||||
|
||||
const list = await db
|
||||
|
||||
@@ -27,7 +27,6 @@ import config from "@server/lib/config";
|
||||
import { CreateOrgIdpResponse } from "@server/routers/orgIdp/types";
|
||||
import { isSubscribed } from "#private/lib/isSubscribed";
|
||||
import { tierMatrix } from "@server/lib/billing/tierMatrix";
|
||||
import privateConfig from "#private/lib/config";
|
||||
import { build } from "@server/build";
|
||||
|
||||
const paramsSchema = z.strictObject({ orgId: z.string().nonempty() });
|
||||
@@ -45,6 +44,7 @@ const bodySchema = z.strictObject({
|
||||
autoProvision: z.boolean().optional(),
|
||||
variant: z.enum(["oidc", "google", "azure"]).optional().default("oidc"),
|
||||
roleMapping: z.string().optional(),
|
||||
orgMapping: z.string().nullish(),
|
||||
tags: z.string().optional()
|
||||
});
|
||||
|
||||
@@ -94,18 +94,6 @@ export async function createOrgOidcIdp(
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
privateConfig.getRawPrivateConfig().app.identity_provider_mode !==
|
||||
"org"
|
||||
) {
|
||||
return next(
|
||||
createHttpError(
|
||||
HttpCode.BAD_REQUEST,
|
||||
"Organization-specific IdP creation is not allowed in the current identity provider mode. Set app.identity_provider_mode to 'org' in the private configuration to enable this feature."
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
const {
|
||||
clientId,
|
||||
clientSecret,
|
||||
@@ -118,6 +106,7 @@ export async function createOrgOidcIdp(
|
||||
name,
|
||||
variant,
|
||||
roleMapping,
|
||||
orgMapping: orgMappingBody,
|
||||
tags
|
||||
} = parsedBody.data;
|
||||
|
||||
@@ -169,7 +158,7 @@ export async function createOrgOidcIdp(
|
||||
idpId: idpRes.idpId,
|
||||
orgId: orgId,
|
||||
roleMapping: roleMapping || null,
|
||||
orgMapping: `'${orgId}'`
|
||||
orgMapping: orgMappingBody
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -22,7 +22,6 @@ import { fromError } from "zod-validation-error";
|
||||
import { idp, idpOidcConfig, idpOrg } from "@server/db";
|
||||
import { eq } from "drizzle-orm";
|
||||
import { OpenAPITags, registry } from "@server/openApi";
|
||||
import privateConfig from "#private/lib/config";
|
||||
|
||||
const paramsSchema = z
|
||||
.object({
|
||||
@@ -60,18 +59,6 @@ export async function deleteOrgIdp(
|
||||
|
||||
const { idpId } = parsedParams.data;
|
||||
|
||||
if (
|
||||
privateConfig.getRawPrivateConfig().app.identity_provider_mode !==
|
||||
"org"
|
||||
) {
|
||||
return next(
|
||||
createHttpError(
|
||||
HttpCode.BAD_REQUEST,
|
||||
"Organization-specific IdP creation is not allowed in the current identity provider mode. Set app.identity_provider_mode to 'org' in the private configuration to enable this feature."
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// Check if IDP exists
|
||||
const [existingIdp] = await db
|
||||
.select()
|
||||
|
||||
@@ -0,0 +1,211 @@
|
||||
/*
|
||||
* This file is part of a proprietary work.
|
||||
*
|
||||
* Copyright (c) 2025-2026 Fossorial, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is licensed under the Fossorial Commercial License.
|
||||
* You may not use this file except in compliance with the License.
|
||||
* Unauthorized use, copying, modification, or distribution is strictly prohibited.
|
||||
*
|
||||
* This file is not licensed under the AGPLv3.
|
||||
*/
|
||||
|
||||
import { Request, Response, NextFunction } from "express";
|
||||
import { z } from "zod";
|
||||
import { db } from "@server/db";
|
||||
import response from "@server/lib/response";
|
||||
import HttpCode from "@server/types/HttpCode";
|
||||
import createHttpError from "http-errors";
|
||||
import logger from "@server/logger";
|
||||
import { fromError } from "zod-validation-error";
|
||||
import { idp, idpOrg, orgs, roles, userOrgs } from "@server/db";
|
||||
import { and, eq, inArray } from "drizzle-orm";
|
||||
import { CreateOrgIdpResponse } from "@server/routers/orgIdp/types";
|
||||
import { generateOidcRedirectUrl } from "@server/lib/idp/generateRedirectUrl";
|
||||
import { checkOrgAccessPolicy } from "#private/lib/checkOrgAccessPolicy";
|
||||
import { getUserOrgRoleIds } from "@server/lib/userOrgRoles";
|
||||
|
||||
const paramsSchema = z.strictObject({
|
||||
orgId: z.string().nonempty(),
|
||||
idpId: z.coerce.number<number>().int().positive()
|
||||
});
|
||||
|
||||
const bodySchema = z.strictObject({
|
||||
sourceOrgId: z.string().nonempty()
|
||||
});
|
||||
|
||||
async function userIsOrgAdmin(
|
||||
userId: string,
|
||||
orgId: string,
|
||||
session: Request["session"]
|
||||
): Promise<boolean> {
|
||||
const [userOrgRow] = await db
|
||||
.select()
|
||||
.from(userOrgs)
|
||||
.where(and(eq(userOrgs.userId, userId), eq(userOrgs.orgId, orgId)))
|
||||
.limit(1);
|
||||
|
||||
if (!userOrgRow) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const policyCheck = await checkOrgAccessPolicy({
|
||||
orgId,
|
||||
userId,
|
||||
session
|
||||
});
|
||||
if (!policyCheck.allowed || policyCheck.error) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const roleIds = await getUserOrgRoleIds(userId, orgId);
|
||||
if (roleIds.length === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const [adminRole] = await db
|
||||
.select()
|
||||
.from(roles)
|
||||
.where(and(inArray(roles.roleId, roleIds), eq(roles.isAdmin, true)))
|
||||
.limit(1);
|
||||
|
||||
return !!adminRole;
|
||||
}
|
||||
|
||||
export async function importOrgIdp(
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
): Promise<any> {
|
||||
try {
|
||||
const parsedParams = paramsSchema.safeParse(req.params);
|
||||
if (!parsedParams.success) {
|
||||
return next(
|
||||
createHttpError(
|
||||
HttpCode.BAD_REQUEST,
|
||||
fromError(parsedParams.error).toString()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
const { orgId: targetOrgId, idpId } = parsedParams.data;
|
||||
|
||||
const parsedBody = bodySchema.safeParse(req.body);
|
||||
if (!parsedBody.success) {
|
||||
return next(
|
||||
createHttpError(
|
||||
HttpCode.BAD_REQUEST,
|
||||
fromError(parsedBody.error).toString()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
const { sourceOrgId } = parsedBody.data;
|
||||
|
||||
if (sourceOrgId === targetOrgId) {
|
||||
return next(
|
||||
createHttpError(
|
||||
HttpCode.BAD_REQUEST,
|
||||
"Source and target organization must be different"
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
const userId = req.user!.userId;
|
||||
|
||||
const sourceLinked = await db
|
||||
.select()
|
||||
.from(idpOrg)
|
||||
.where(and(eq(idpOrg.idpId, idpId), eq(idpOrg.orgId, sourceOrgId)))
|
||||
.limit(1);
|
||||
|
||||
if (sourceLinked.length === 0) {
|
||||
return next(
|
||||
createHttpError(
|
||||
HttpCode.NOT_FOUND,
|
||||
"IdP not found for the source organization"
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
const sourceAdmin = await userIsOrgAdmin(
|
||||
userId,
|
||||
sourceOrgId,
|
||||
req.session
|
||||
);
|
||||
if (!sourceAdmin) {
|
||||
return next(
|
||||
createHttpError(
|
||||
HttpCode.FORBIDDEN,
|
||||
"You must be an organization admin in the source organization where this IdP is linked"
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
const [targetOrg] = await db
|
||||
.select({ orgId: orgs.orgId })
|
||||
.from(orgs)
|
||||
.where(eq(orgs.orgId, targetOrgId))
|
||||
.limit(1);
|
||||
|
||||
if (!targetOrg) {
|
||||
return next(
|
||||
createHttpError(
|
||||
HttpCode.NOT_FOUND,
|
||||
"Target organization not found"
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
const [existingIdp] = await db
|
||||
.select()
|
||||
.from(idp)
|
||||
.where(eq(idp.idpId, idpId))
|
||||
.limit(1);
|
||||
|
||||
if (!existingIdp) {
|
||||
return next(createHttpError(HttpCode.NOT_FOUND, "IdP not found"));
|
||||
}
|
||||
|
||||
const alreadyTarget = await db
|
||||
.select()
|
||||
.from(idpOrg)
|
||||
.where(and(eq(idpOrg.idpId, idpId), eq(idpOrg.orgId, targetOrgId)))
|
||||
.limit(1);
|
||||
|
||||
if (alreadyTarget.length > 0) {
|
||||
return next(
|
||||
createHttpError(
|
||||
HttpCode.CONFLICT,
|
||||
"This IdP is already linked to the target organization"
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
await db.insert(idpOrg).values({
|
||||
idpId,
|
||||
orgId: targetOrgId,
|
||||
roleMapping: null,
|
||||
orgMapping: null
|
||||
});
|
||||
|
||||
const redirectUrl = await generateOidcRedirectUrl(idpId, targetOrgId);
|
||||
|
||||
return response<CreateOrgIdpResponse>(res, {
|
||||
data: {
|
||||
idpId,
|
||||
redirectUrl
|
||||
},
|
||||
success: true,
|
||||
error: false,
|
||||
message: "Org IdP imported successfully",
|
||||
status: HttpCode.CREATED
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error(error);
|
||||
return next(
|
||||
createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred")
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,11 @@
|
||||
*/
|
||||
|
||||
export * from "./createOrgOidcIdp";
|
||||
export * from "./importOrgIdp";
|
||||
export * from "./getOrgIdp";
|
||||
export * from "./listOrgIdps";
|
||||
export * from "./listUserAdminOrgIdps";
|
||||
export * from "./updateOrgOidcIdp";
|
||||
export * from "./deleteOrgIdp";
|
||||
export * from "./unassociateOrgIdp";
|
||||
export * from "./requireOrgIdentityProviderMode";
|
||||
|
||||
@@ -0,0 +1,160 @@
|
||||
/*
|
||||
* This file is part of a proprietary work.
|
||||
*
|
||||
* Copyright (c) 2025-2026 Fossorial, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is licensed under the Fossorial Commercial License.
|
||||
* You may not use this file except in compliance with the License.
|
||||
* Unauthorized use, copying, modification, or distribution is strictly prohibited.
|
||||
*
|
||||
* This file is not licensed under the AGPLv3.
|
||||
*/
|
||||
|
||||
import { Request, Response, NextFunction } from "express";
|
||||
import { z } from "zod";
|
||||
import { db, idpOidcConfig } from "@server/db";
|
||||
import { idp, idpOrg, orgs, roles, userOrgRoles } from "@server/db";
|
||||
import response from "@server/lib/response";
|
||||
import HttpCode from "@server/types/HttpCode";
|
||||
import createHttpError from "http-errors";
|
||||
import { and, eq, inArray, sql } from "drizzle-orm";
|
||||
import logger from "@server/logger";
|
||||
import { fromError } from "zod-validation-error";
|
||||
import { OpenAPITags, registry } from "@server/openApi";
|
||||
import { ListUserAdminOrgIdpsResponse } from "@server/routers/orgIdp/types";
|
||||
|
||||
const querySchema = z.strictObject({
|
||||
limit: z
|
||||
.string()
|
||||
.optional()
|
||||
.default("1000")
|
||||
.transform(Number)
|
||||
.pipe(z.int().nonnegative()),
|
||||
offset: z
|
||||
.string()
|
||||
.optional()
|
||||
.default("0")
|
||||
.transform(Number)
|
||||
.pipe(z.int().nonnegative())
|
||||
});
|
||||
|
||||
const paramsSchema = z.strictObject({
|
||||
userId: z.string().nonempty()
|
||||
});
|
||||
|
||||
async function getOrgIdsWhereUserIsAdmin(userId: string): Promise<string[]> {
|
||||
const rows = await db
|
||||
.select({ orgId: userOrgRoles.orgId })
|
||||
.from(userOrgRoles)
|
||||
.innerJoin(roles, eq(userOrgRoles.roleId, roles.roleId))
|
||||
.where(and(eq(userOrgRoles.userId, userId), eq(roles.isAdmin, true)));
|
||||
return [...new Set(rows.map((r) => r.orgId))];
|
||||
}
|
||||
|
||||
async function queryIdpsForOrgs(
|
||||
orgIds: string[],
|
||||
limit: number,
|
||||
offset: number
|
||||
) {
|
||||
return db
|
||||
.select({
|
||||
idpId: idp.idpId,
|
||||
orgId: idpOrg.orgId,
|
||||
orgName: orgs.name,
|
||||
name: idp.name,
|
||||
type: idp.type,
|
||||
variant: idpOidcConfig.variant,
|
||||
tags: idp.tags
|
||||
})
|
||||
.from(idpOrg)
|
||||
.where(inArray(idpOrg.orgId, orgIds))
|
||||
.innerJoin(orgs, eq(orgs.orgId, idpOrg.orgId))
|
||||
.innerJoin(idp, eq(idp.idpId, idpOrg.idpId))
|
||||
.innerJoin(idpOidcConfig, eq(idpOidcConfig.idpId, idpOrg.idpId))
|
||||
.orderBy(sql`idp.name DESC`)
|
||||
.limit(limit)
|
||||
.offset(offset);
|
||||
}
|
||||
|
||||
async function countIdpsForOrgs(orgIds: string[]) {
|
||||
const [{ count }] = await db
|
||||
.select({ count: sql<number>`count(*)` })
|
||||
.from(idpOrg)
|
||||
.innerJoin(idp, eq(idp.idpId, idpOrg.idpId))
|
||||
.innerJoin(idpOidcConfig, eq(idpOidcConfig.idpId, idpOrg.idpId))
|
||||
.where(inArray(idpOrg.orgId, orgIds));
|
||||
return count;
|
||||
}
|
||||
|
||||
export async function listUserAdminOrgIdps(
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
): Promise<any> {
|
||||
try {
|
||||
const parsedParams = paramsSchema.safeParse(req.params);
|
||||
if (!parsedParams.success) {
|
||||
return next(
|
||||
createHttpError(
|
||||
HttpCode.BAD_REQUEST,
|
||||
fromError(parsedParams.error).toString()
|
||||
)
|
||||
);
|
||||
}
|
||||
const { userId } = parsedParams.data;
|
||||
|
||||
const parsedQuery = querySchema.safeParse(req.query);
|
||||
if (!parsedQuery.success) {
|
||||
return next(
|
||||
createHttpError(
|
||||
HttpCode.BAD_REQUEST,
|
||||
fromError(parsedQuery.error).toString()
|
||||
)
|
||||
);
|
||||
}
|
||||
const { limit, offset } = parsedQuery.data;
|
||||
|
||||
const adminOrgIds = await getOrgIdsWhereUserIsAdmin(userId);
|
||||
|
||||
if (adminOrgIds.length === 0) {
|
||||
return response<ListUserAdminOrgIdpsResponse>(res, {
|
||||
data: {
|
||||
idps: [],
|
||||
pagination: {
|
||||
total: 0,
|
||||
limit,
|
||||
offset
|
||||
}
|
||||
},
|
||||
success: true,
|
||||
error: false,
|
||||
message: "Org Idps retrieved successfully",
|
||||
status: HttpCode.OK
|
||||
});
|
||||
}
|
||||
|
||||
const list = await queryIdpsForOrgs(adminOrgIds, limit, offset);
|
||||
const total = await countIdpsForOrgs(adminOrgIds);
|
||||
|
||||
return response<ListUserAdminOrgIdpsResponse>(res, {
|
||||
data: {
|
||||
idps: list,
|
||||
pagination: {
|
||||
total,
|
||||
limit,
|
||||
offset
|
||||
}
|
||||
},
|
||||
success: true,
|
||||
error: false,
|
||||
message: "Org Idps retrieved successfully",
|
||||
status: HttpCode.OK
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error(error);
|
||||
return next(
|
||||
createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred")
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* This file is part of a proprietary work.
|
||||
*
|
||||
* Copyright (c) 2025-2026 Fossorial, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is licensed under the Fossorial Commercial License.
|
||||
* You may not use this file except in compliance with the License.
|
||||
* Unauthorized use, copying, modification, or distribution is strictly prohibited.
|
||||
*
|
||||
* This file is not licensed under the AGPLv3.
|
||||
*/
|
||||
|
||||
import { NextFunction, Request, Response } from "express";
|
||||
import createHttpError from "http-errors";
|
||||
import privateConfig from "#private/lib/config";
|
||||
import HttpCode from "@server/types/HttpCode";
|
||||
|
||||
export function requireOrgIdentityProviderMode(
|
||||
_req: Request,
|
||||
_res: Response,
|
||||
next: NextFunction
|
||||
): void {
|
||||
if (privateConfig.getRawPrivateConfig().app.identity_provider_mode !== "org") {
|
||||
return next(
|
||||
createHttpError(
|
||||
HttpCode.BAD_REQUEST,
|
||||
"Organization-specific IdP creation is not allowed in the current identity provider mode. Set app.identity_provider_mode to 'org' in the private configuration to enable this feature."
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return next();
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
/*
|
||||
* This file is part of a proprietary work.
|
||||
*
|
||||
* Copyright (c) 2025-2026 Fossorial, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is licensed under the Fossorial Commercial License.
|
||||
* You may not use this file except in compliance with the License.
|
||||
* Unauthorized use, copying, modification, or distribution is strictly prohibited.
|
||||
*
|
||||
* This file is not licensed under the AGPLv3.
|
||||
*/
|
||||
|
||||
import { Request, Response, NextFunction } from "express";
|
||||
import { z } from "zod";
|
||||
import { db, idpOrg } from "@server/db";
|
||||
import response from "@server/lib/response";
|
||||
import HttpCode from "@server/types/HttpCode";
|
||||
import createHttpError from "http-errors";
|
||||
import logger from "@server/logger";
|
||||
import { fromError } from "zod-validation-error";
|
||||
import { and, eq, sql } from "drizzle-orm";
|
||||
import { OpenAPITags, registry } from "@server/openApi";
|
||||
|
||||
const paramsSchema = z
|
||||
.object({
|
||||
orgId: z.string().nonempty(),
|
||||
idpId: z.coerce.number<number>().int().positive()
|
||||
})
|
||||
.strict();
|
||||
|
||||
export async function unassociateOrgIdp(
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
): Promise<any> {
|
||||
try {
|
||||
const parsedParams = paramsSchema.safeParse(req.params);
|
||||
if (!parsedParams.success) {
|
||||
return next(
|
||||
createHttpError(
|
||||
HttpCode.BAD_REQUEST,
|
||||
fromError(parsedParams.error).toString()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
const { orgId, idpId } = parsedParams.data;
|
||||
|
||||
const [association] = await db
|
||||
.select()
|
||||
.from(idpOrg)
|
||||
.where(and(eq(idpOrg.idpId, idpId), eq(idpOrg.orgId, orgId)))
|
||||
.limit(1);
|
||||
|
||||
if (!association) {
|
||||
return next(
|
||||
createHttpError(
|
||||
HttpCode.NOT_FOUND,
|
||||
`IdP with ID ${idpId} is not associated with organization ${orgId}`
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
const [{ count }] = await db
|
||||
.select({ count: sql<number>`count(*)` })
|
||||
.from(idpOrg)
|
||||
.where(eq(idpOrg.idpId, idpId));
|
||||
|
||||
if (count <= 1) {
|
||||
return next(
|
||||
createHttpError(
|
||||
HttpCode.BAD_REQUEST,
|
||||
"This is the last organization associated with this identity provider. Delete it instead."
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
await db
|
||||
.delete(idpOrg)
|
||||
.where(and(eq(idpOrg.idpId, idpId), eq(idpOrg.orgId, orgId)));
|
||||
|
||||
return response<null>(res, {
|
||||
data: null,
|
||||
success: true,
|
||||
error: false,
|
||||
message: "Org IdP unassociated successfully",
|
||||
status: HttpCode.OK
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error(error);
|
||||
return next(
|
||||
createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred")
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -26,7 +26,6 @@ import { encrypt } from "@server/lib/crypto";
|
||||
import config from "@server/lib/config";
|
||||
import { isSubscribed } from "#private/lib/isSubscribed";
|
||||
import { tierMatrix } from "@server/lib/billing/tierMatrix";
|
||||
import privateConfig from "#private/lib/config";
|
||||
import { build } from "@server/build";
|
||||
|
||||
const paramsSchema = z
|
||||
@@ -48,6 +47,7 @@ const bodySchema = z.strictObject({
|
||||
scopes: z.string().optional(),
|
||||
autoProvision: z.boolean().optional(),
|
||||
roleMapping: z.string().optional(),
|
||||
orgMapping: z.string().nullish(),
|
||||
tags: z.string().optional()
|
||||
});
|
||||
|
||||
@@ -99,18 +99,6 @@ export async function updateOrgOidcIdp(
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
privateConfig.getRawPrivateConfig().app.identity_provider_mode !==
|
||||
"org"
|
||||
) {
|
||||
return next(
|
||||
createHttpError(
|
||||
HttpCode.BAD_REQUEST,
|
||||
"Organization-specific IdP creation is not allowed in the current identity provider mode. Set app.identity_provider_mode to 'org' in the private configuration to enable this feature."
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
const { idpId, orgId } = parsedParams.data;
|
||||
const {
|
||||
clientId,
|
||||
@@ -123,6 +111,7 @@ export async function updateOrgOidcIdp(
|
||||
namePath,
|
||||
name,
|
||||
roleMapping,
|
||||
orgMapping,
|
||||
tags
|
||||
} = parsedBody.data;
|
||||
|
||||
@@ -218,13 +207,20 @@ export async function updateOrgOidcIdp(
|
||||
.where(eq(idpOidcConfig.idpId, idpId));
|
||||
}
|
||||
|
||||
const idpOrgPolicyPatch: {
|
||||
roleMapping?: string;
|
||||
orgMapping?: string | null;
|
||||
} = {};
|
||||
if (roleMapping !== undefined) {
|
||||
// Update IdP-org policy
|
||||
idpOrgPolicyPatch.roleMapping = roleMapping;
|
||||
}
|
||||
if (orgMapping !== undefined) {
|
||||
idpOrgPolicyPatch.orgMapping = orgMapping;
|
||||
}
|
||||
if (Object.keys(idpOrgPolicyPatch).length > 0) {
|
||||
await trx
|
||||
.update(idpOrg)
|
||||
.set({
|
||||
roleMapping
|
||||
})
|
||||
.set(idpOrgPolicyPatch)
|
||||
.where(
|
||||
and(eq(idpOrg.idpId, idpId), eq(idpOrg.orgId, orgId))
|
||||
);
|
||||
|
||||
@@ -103,7 +103,8 @@ export async function listDomains(
|
||||
|
||||
const [{ count }] = await db
|
||||
.select({ count: sql<number>`count(*)` })
|
||||
.from(domains);
|
||||
.from(orgDomains)
|
||||
.where(eq(orgDomains.orgId, orgId));
|
||||
|
||||
return response<ListDomainsResponse>(res, {
|
||||
data: {
|
||||
|
||||
@@ -25,3 +25,22 @@ export type ListOrgIdpsResponse = {
|
||||
offset: number;
|
||||
};
|
||||
};
|
||||
|
||||
export type ListUserAdminOrgIdpsEntry = {
|
||||
idpId: number;
|
||||
orgId: string;
|
||||
orgName: string;
|
||||
name: string;
|
||||
type: string;
|
||||
variant: string;
|
||||
tags: string | null;
|
||||
};
|
||||
|
||||
export type ListUserAdminOrgIdpsResponse = {
|
||||
idps: ListUserAdminOrgIdpsEntry[];
|
||||
pagination: {
|
||||
total: number;
|
||||
limit: number;
|
||||
offset: number;
|
||||
};
|
||||
};
|
||||
|
||||
@@ -230,6 +230,7 @@ export async function createTarget(
|
||||
.values({
|
||||
orgId: resource.orgId,
|
||||
targetId: newTarget[0].targetId,
|
||||
name: `Resource ${resource.name} - ${targetData.ip}:${targetData.port}`,
|
||||
hcEnabled: targetData.hcEnabled ?? false,
|
||||
hcPath: targetData.hcPath ?? null,
|
||||
hcScheme: targetData.hcScheme ?? null,
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
import { db, apiKeys } from "@server/db";
|
||||
import { eq } from "drizzle-orm";
|
||||
import { generateRandomString, RandomReader } from "@oslojs/crypto/random";
|
||||
import moment from "moment";
|
||||
import logger from "@server/logger";
|
||||
import { hashPassword } from "@server/auth/password";
|
||||
|
||||
const random: RandomReader = {
|
||||
read(bytes: Uint8Array): void {
|
||||
crypto.getRandomValues(bytes);
|
||||
}
|
||||
};
|
||||
|
||||
function validateApiKeyId(id: string): boolean {
|
||||
return /^[a-z0-9]{15}$/.test(id);
|
||||
}
|
||||
|
||||
function validateApiKeySecret(secret: string): boolean {
|
||||
return secret.length > 0;
|
||||
}
|
||||
|
||||
function showRootApiKey(apiKeyId: string, source: string): void {
|
||||
console.log(`=== ROOT API KEY ${source} ===`);
|
||||
console.log("API Key ID:", apiKeyId);
|
||||
console.log(
|
||||
"The root API key from PANGOLIN_ROOT_API_KEY has been applied."
|
||||
);
|
||||
console.log("Use the full key value (apiKeyId.apiKeySecret) in requests.");
|
||||
console.log("================================");
|
||||
}
|
||||
|
||||
export async function ensureRootApiKey() {
|
||||
try {
|
||||
const envApiKey = process.env.PANGOLIN_ROOT_API_KEY;
|
||||
|
||||
if (!envApiKey) {
|
||||
// logger.debug(
|
||||
// "PANGOLIN_ROOT_API_KEY not set. Root API key from environment skipped."
|
||||
// );
|
||||
return;
|
||||
}
|
||||
|
||||
const parts = envApiKey.split(".");
|
||||
if (parts.length !== 2) {
|
||||
throw new Error(
|
||||
"Invalid format for PANGOLIN_ROOT_API_KEY. Expected format: {apiKeyId}.{apiKeySecret}"
|
||||
);
|
||||
}
|
||||
|
||||
const [apiKeyId, apiKeySecret] = parts;
|
||||
|
||||
if (!validateApiKeyId(apiKeyId)) {
|
||||
throw new Error(
|
||||
"Invalid apiKeyId in PANGOLIN_ROOT_API_KEY. Must be 15 lowercase alphanumeric characters."
|
||||
);
|
||||
}
|
||||
|
||||
if (!validateApiKeySecret(apiKeySecret)) {
|
||||
throw new Error(
|
||||
"Invalid apiKeySecret in PANGOLIN_ROOT_API_KEY. Secret must not be empty."
|
||||
);
|
||||
}
|
||||
|
||||
const apiKeyHash = await hashPassword(apiKeySecret);
|
||||
const lastChars = apiKeySecret.slice(-4);
|
||||
const createdAt = moment().toISOString();
|
||||
|
||||
const [existingKey] = await db
|
||||
.select()
|
||||
.from(apiKeys)
|
||||
.where(eq(apiKeys.apiKeyId, apiKeyId));
|
||||
|
||||
if (existingKey) {
|
||||
if (!existingKey.isRoot) {
|
||||
console.warn(
|
||||
`API key with ID ${apiKeyId} exists but is not a root key. Promoting to root and updating hash.`
|
||||
);
|
||||
} else {
|
||||
console.warn(
|
||||
`Overwriting existing root API key hash since PANGOLIN_ROOT_API_KEY is set (apiKeyId: ${apiKeyId})`
|
||||
);
|
||||
}
|
||||
|
||||
await db
|
||||
.update(apiKeys)
|
||||
.set({ apiKeyHash, lastChars, isRoot: true })
|
||||
.where(eq(apiKeys.apiKeyId, apiKeyId));
|
||||
|
||||
showRootApiKey(apiKeyId, "UPDATED FROM ENVIRONMENT");
|
||||
} else {
|
||||
await db.insert(apiKeys).values({
|
||||
apiKeyId,
|
||||
name: "Root API Key (Environment)",
|
||||
apiKeyHash,
|
||||
lastChars,
|
||||
createdAt,
|
||||
isRoot: true
|
||||
});
|
||||
|
||||
showRootApiKey(apiKeyId, "CREATED FROM ENVIRONMENT");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to ensure root API key:", error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
@@ -2,10 +2,12 @@ import { ensureActions } from "./ensureActions";
|
||||
import { copyInConfig } from "./copyInConfig";
|
||||
import { clearStaleData } from "./clearStaleData";
|
||||
import { ensureSetupToken } from "./ensureSetupToken";
|
||||
import { ensureRootApiKey } from "./ensureRootApiKey";
|
||||
|
||||
export async function runSetupFunctions() {
|
||||
await copyInConfig(); // copy in the config to the db as needed
|
||||
await ensureActions(); // make sure all of the actions are in the db and the roles
|
||||
await clearStaleData();
|
||||
await ensureSetupToken(); // ensure setup token exists for initial setup
|
||||
await ensureRootApiKey();
|
||||
}
|
||||
|
||||
@@ -12,6 +12,11 @@ import type { ListRolesResponse } from "@server/routers/role";
|
||||
import type { AxiosResponse } from "axios";
|
||||
import { getTranslations } from "next-intl/server";
|
||||
import { tierMatrix } from "@server/lib/billing/tierMatrix";
|
||||
import type { Metadata } from "next";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Approvals"
|
||||
};
|
||||
|
||||
export interface ApprovalFeedPageProps {
|
||||
params: Promise<{ orgId: string }>;
|
||||
|
||||
@@ -7,6 +7,11 @@ import { getTranslations } from "next-intl/server";
|
||||
import { getCachedOrgUser } from "@app/lib/api/getCachedOrgUser";
|
||||
import { getCachedOrg } from "@app/lib/api/getCachedOrg";
|
||||
import { build } from "@server/build";
|
||||
import type { Metadata } from "next";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Billing"
|
||||
};
|
||||
|
||||
type BillingSettingsProps = {
|
||||
children: React.ReactNode;
|
||||
|
||||
@@ -97,7 +97,8 @@ export default function GeneralPage() {
|
||||
emailPath: z.string().nullable().optional(),
|
||||
namePath: z.string().nullable().optional(),
|
||||
scopes: z.string().min(1, { message: t("idpScopeRequired") }),
|
||||
autoProvision: z.boolean().default(false)
|
||||
autoProvision: z.boolean().default(false),
|
||||
orgMapping: z.string().optional()
|
||||
});
|
||||
|
||||
// Google form schema (simplified)
|
||||
@@ -109,7 +110,8 @@ export default function GeneralPage() {
|
||||
.min(1, { message: t("idpClientSecretRequired") }),
|
||||
roleMapping: z.string().nullable().optional(),
|
||||
roleId: z.number().nullable().optional(),
|
||||
autoProvision: z.boolean().default(false)
|
||||
autoProvision: z.boolean().default(false),
|
||||
orgMapping: z.string().optional()
|
||||
});
|
||||
|
||||
// Azure form schema (simplified with tenant ID)
|
||||
@@ -122,7 +124,8 @@ export default function GeneralPage() {
|
||||
tenantId: z.string().min(1, { message: t("idpTenantIdRequired") }),
|
||||
roleMapping: z.string().nullable().optional(),
|
||||
roleId: z.number().nullable().optional(),
|
||||
autoProvision: z.boolean().default(false)
|
||||
autoProvision: z.boolean().default(false),
|
||||
orgMapping: z.string().optional()
|
||||
});
|
||||
|
||||
type OidcFormValues = z.infer<typeof OidcFormSchema>;
|
||||
@@ -160,7 +163,8 @@ export default function GeneralPage() {
|
||||
autoProvision: true,
|
||||
roleMapping: null,
|
||||
roleId: null,
|
||||
tenantId: ""
|
||||
tenantId: "",
|
||||
orgMapping: ""
|
||||
}
|
||||
});
|
||||
|
||||
@@ -227,7 +231,8 @@ export default function GeneralPage() {
|
||||
clientSecret: data.idpOidcConfig.clientSecret,
|
||||
autoProvision: data.idp.autoProvision,
|
||||
roleMapping: roleMapping || null,
|
||||
roleId: null
|
||||
roleId: null,
|
||||
orgMapping: data.idpOrg?.orgMapping ?? ""
|
||||
};
|
||||
|
||||
// Add variant-specific fields
|
||||
@@ -344,12 +349,14 @@ export default function GeneralPage() {
|
||||
}
|
||||
|
||||
// Build payload based on variant
|
||||
const orgMappingTrimmed = data.orgMapping?.trim() ?? "";
|
||||
let payload: any = {
|
||||
name: data.name,
|
||||
clientId: data.clientId,
|
||||
clientSecret: data.clientSecret,
|
||||
autoProvision: data.autoProvision,
|
||||
roleMapping: roleMappingExpression
|
||||
roleMapping: roleMappingExpression,
|
||||
orgMapping: orgMappingTrimmed === "" ? null : orgMappingTrimmed
|
||||
};
|
||||
|
||||
// Add variant-specific fields
|
||||
@@ -532,6 +539,10 @@ export default function GeneralPage() {
|
||||
}
|
||||
rawExpression={rawRoleExpression}
|
||||
onRawExpressionChange={setRawRoleExpression}
|
||||
orgMappingField={{
|
||||
control: form.control,
|
||||
name: "orgMapping"
|
||||
}}
|
||||
/>
|
||||
</form>
|
||||
</Form>
|
||||
|
||||
@@ -6,6 +6,11 @@ import { authCookieHeader } from "@app/lib/api/cookies";
|
||||
import { HorizontalTabs, TabItem } from "@app/components/HorizontalTabs";
|
||||
import SettingsSectionTitle from "@app/components/SettingsSectionTitle";
|
||||
import { getTranslations } from "next-intl/server";
|
||||
import type { Metadata } from "next";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Identity Provider"
|
||||
};
|
||||
|
||||
interface SettingsLayoutProps {
|
||||
children: React.ReactNode;
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
import type { Metadata } from "next";
|
||||
import { redirect } from "next/navigation";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Identity Provider"
|
||||
};
|
||||
|
||||
export default async function IdpPage(props: {
|
||||
params: Promise<{ orgId: string; idpId: string }>;
|
||||
}) {
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
import type { Metadata } from "next";
|
||||
import type { ReactNode } from "react";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Create Identity Provider"
|
||||
};
|
||||
|
||||
export default function Layout({ children }: { children: ReactNode }) {
|
||||
return children;
|
||||
}
|
||||