Ready for testing

This commit is contained in:
Julian van der Horst
2026-05-27 20:37:56 +02:00
parent e2441ce284
commit 9804c0db28
30 changed files with 308 additions and 145 deletions
+4 -2
View File
@@ -1964,8 +1964,10 @@
"retryAttempts": "Опити за повторно",
"expectedResponseCodes": "Очаквани кодове за отговор",
"expectedResponseCodesDescription": "HTTP статус код, указващ здравословно състояние. Ако бъде оставено празно, между 200-300 се счита за здравословно.",
"customHeaders": "Персонализирани заглавия",
"customHeadersDescription": "Add custom headers to be sent when proxying requests. One per line in the format Header-Name: value",
"customRequestHeaders": "Персонализирани заглавия",
"customRequestHeadersDescription": "Add custom headers to be sent when proxying requests. One per line in the format Header-Name: value",
"customResponseHeaders": "Custom Response Headers",
"customResponseHeadersDescription": "Response headers sent back to the client. One per line: Header-Name: value",
"headersValidationError": "Заглавията трябва да бъдат във формат: Име на заглавието: стойност.",
"saveHealthCheck": "Запазване на проверка на здравето",
"healthCheckSaved": "Проверка на здравето е запазена",
+4 -2
View File
@@ -1964,8 +1964,10 @@
"retryAttempts": "Opakovat pokusy",
"expectedResponseCodes": "Očekávané kódy odezvy",
"expectedResponseCodesDescription": "HTTP kód stavu, který označuje zdravý stav. Ponecháte-li prázdné, 200-300 je považováno za zdravé.",
"customHeaders": "Vlastní záhlaví",
"customHeadersDescription": "Záhlaví oddělená nová řádka: hodnota",
"customRequestHeaders": "Vlastní záhlaví",
"customRequestHeadersDescription": "Záhlaví oddělená nová řádka: hodnota",
"customResponseHeaders": "Custom Response Headers",
"customResponseHeadersDescription": "Response headers sent back to the client. One per line: Header-Name: value",
"headersValidationError": "Headers must be in the format: Header-Name: value.",
"saveHealthCheck": "Uložit kontrolu stavu",
"healthCheckSaved": "Kontrola stavu uložena",
+4 -2
View File
@@ -1964,8 +1964,10 @@
"retryAttempts": "Wiederholungsversuche",
"expectedResponseCodes": "Erwartete Antwortcodes",
"expectedResponseCodesDescription": "HTTP-Statuscode, der einen gesunden Zustand anzeigt. Wenn leer gelassen, wird 200-300 als gesund angesehen.",
"customHeaders": "Eigene Kopfzeilen",
"customHeadersDescription": "Header neue Zeile getrennt: Header-Name: Wert",
"customRequestHeaders": "Eigene Kopfzeilen",
"customRequestHeadersDescription": "Header neue Zeile getrennt: Header-Name: Wert",
"customResponseHeaders": "Custom Response Headers",
"customResponseHeadersDescription": "Response headers sent back to the client. One per line: Header-Name: value",
"headersValidationError": "Header müssen im Format Header-Name: Wert sein.",
"saveHealthCheck": "Gesundheits-Check speichern",
"healthCheckSaved": "Gesundheits-Check gespeichert",
+4 -2
View File
@@ -1994,8 +1994,10 @@
"retryAttempts": "Retry Attempts",
"expectedResponseCodes": "Expected Response Codes",
"expectedResponseCodesDescription": "HTTP status code that indicates healthy status. If left blank, 200-300 is considered healthy.",
"customHeaders": "Custom Request Headers",
"customHeadersDescription": "Request headers sent to the downstream targets. Headers new line separated: Header-Name: value",
"customRequestHeaders": "Custom Request Headers",
"customRequestHeadersDescription": "Request headers sent to the downstream targets. One per line: Header-Name: value",
"customResponseHeaders": "Custom Response Headers",
"customResponseHeadersDescription": "Response headers sent back to the client. One per line: Header-Name: value",
"headersValidationError": "Headers must be in the format: Header-Name: value",
"saveHealthCheck": "Save Health Check",
"healthCheckSaved": "Health Check Saved",
+4 -2
View File
@@ -1964,8 +1964,10 @@
"retryAttempts": "Intentos de Reintento",
"expectedResponseCodes": "Códigos de respuesta esperados",
"expectedResponseCodesDescription": "Código de estado HTTP que indica un estado saludable. Si se deja en blanco, se considera saludable de 200 a 300.",
"customHeaders": "Cabeceras personalizadas",
"customHeadersDescription": "Nueva línea de cabeceras separada: Nombre de cabecera: valor",
"customRequestHeaders": "Cabeceras personalizadas",
"customRequestHeadersDescription": "Nueva línea de cabeceras separada: Nombre de cabecera: valor",
"customResponseHeaders": "Custom Response Headers",
"customResponseHeadersDescription": "Response headers sent back to the client. One per line: Header-Name: value",
"headersValidationError": "Los encabezados deben estar en el formato: Nombre de cabecera: valor.",
"saveHealthCheck": "Guardar Chequeo de Salud",
"healthCheckSaved": "Chequeo de Salud Guardado",
+4 -2
View File
@@ -1964,8 +1964,10 @@
"retryAttempts": "Tentatives de réessai",
"expectedResponseCodes": "Codes de réponse attendus",
"expectedResponseCodesDescription": "Code de statut HTTP indiquant un état de santé satisfaisant. Si non renseigné, 200-300 est considéré comme satisfaisant.",
"customHeaders": "En-têtes personnalisés",
"customHeadersDescription": "En-têtes séparés par une nouvelle ligne: En-nom: valeur",
"customRequestHeaders": "En-têtes personnalisés",
"customRequestHeadersDescription": "En-têtes séparés par une nouvelle ligne: En-nom: valeur",
"customResponseHeaders": "Custom Response Headers",
"customResponseHeadersDescription": "Response headers sent back to the client. One per line: Header-Name: value",
"headersValidationError": "Les entêtes doivent être au format : Header-Name: valeur.",
"saveHealthCheck": "Sauvegarder la vérification de l'état de santé",
"healthCheckSaved": "Vérification de l'état de santé enregistrée",
+4 -2
View File
@@ -1964,8 +1964,10 @@
"retryAttempts": "Tentativi di Riprova",
"expectedResponseCodes": "Codici di Risposta Attesi",
"expectedResponseCodesDescription": "Codice di stato HTTP che indica lo stato di salute. Se lasciato vuoto, considerato sano è compreso tra 200-300.",
"customHeaders": "Intestazioni Personalizzate",
"customHeadersDescription": "Intestazioni nuova riga separate: Intestazione-Nome: valore",
"customRequestHeaders": "Intestazioni Personalizzate",
"customRequestHeadersDescription": "Intestazioni nuova riga separate: Intestazione-Nome: valore",
"customResponseHeaders": "Custom Response Headers",
"customResponseHeadersDescription": "Response headers sent back to the client. One per line: Header-Name: value",
"headersValidationError": "Le intestazioni devono essere nel formato: Intestazione-Nome: valore.",
"saveHealthCheck": "Salva Controllo Salute",
"healthCheckSaved": "Controllo Salute Salvato",
+4 -2
View File
@@ -1964,8 +1964,10 @@
"retryAttempts": "재시도 횟수",
"expectedResponseCodes": "예상 응답 코드",
"expectedResponseCodesDescription": "정상 상태를 나타내는 HTTP 상태 코드입니다. 비워 두면 200-300이 정상으로 간주됩니다.",
"customHeaders": "사용자 정의 헤더",
"customHeadersDescription": "헤더는 새 줄로 구분됨: Header-Name: value",
"customRequestHeaders": "사용자 정의 헤더",
"customRequestHeadersDescription": "헤더는 새 줄로 구분됨: Header-Name: value",
"customResponseHeaders": "Custom Response Headers",
"customResponseHeadersDescription": "Response headers sent back to the client. One per line: Header-Name: value",
"headersValidationError": "헤더는 형식이어야 합니다: 헤더명: 값.",
"saveHealthCheck": "상태 확인 저장",
"healthCheckSaved": "상태 확인이 저장되었습니다.",
+4 -2
View File
@@ -1964,8 +1964,10 @@
"retryAttempts": "Forsøk på nytt",
"expectedResponseCodes": "Forventede svarkoder",
"expectedResponseCodesDescription": "HTTP-statuskode som indikerer sunn status. Hvis den blir stående tom, regnes 200-300 som sunn.",
"customHeaders": "Egendefinerte topptekster",
"customHeadersDescription": "Overskrifter som er adskilt med linje: Overskriftsnavn: verdi",
"customRequestHeaders": "Egendefinerte topptekster",
"customRequestHeadersDescription": "Overskrifter som er adskilt med linje: Overskriftsnavn: verdi",
"customResponseHeaders": "Custom Response Headers",
"customResponseHeadersDescription": "Response headers sent back to the client. One per line: Header-Name: value",
"headersValidationError": "Topptekst må være i formatet: header-navn: verdi.",
"saveHealthCheck": "Lagre Helsekontroll",
"healthCheckSaved": "Helsekontroll Lagret",
+4 -2
View File
@@ -1964,8 +1964,10 @@
"retryAttempts": "Herhaal Pogingen",
"expectedResponseCodes": "Verwachte Reactiecodes",
"expectedResponseCodesDescription": "HTTP-statuscode die gezonde status aangeeft. Indien leeg wordt 200-300 als gezond beschouwd.",
"customHeaders": "Aangepaste headers",
"customHeadersDescription": "Kopregeleinde: Header-Naam: waarde",
"customRequestHeaders": "Aangepaste headers",
"customRequestHeadersDescription": "Kopregeleinde: Header-Naam: waarde",
"customResponseHeaders": "Custom Response Headers",
"customResponseHeadersDescription": "Response headers sent back to the client. One per line: Header-Name: value",
"headersValidationError": "Headers moeten in het formaat zijn: Header-Naam: waarde.",
"saveHealthCheck": "Opslaan Gezondheidscontrole",
"healthCheckSaved": "Gezondheidscontrole Opgeslagen",
+4 -2
View File
@@ -1964,8 +1964,10 @@
"retryAttempts": "Próby Ponowienia",
"expectedResponseCodes": "Oczekiwane Kody Odpowiedzi",
"expectedResponseCodesDescription": "Kod statusu HTTP, który wskazuje zdrowy status. Jeśli pozostanie pusty, uznaje się 200-300 za zdrowy.",
"customHeaders": "Niestandardowe nagłówki",
"customHeadersDescription": "Nagłówki oddzielone: Nazwa nagłówka: wartość",
"customRequestHeaders": "Niestandardowe nagłówki",
"customRequestHeadersDescription": "Nagłówki oddzielone: Nazwa nagłówka: wartość",
"customResponseHeaders": "Custom Response Headers",
"customResponseHeadersDescription": "Response headers sent back to the client. One per line: Header-Name: value",
"headersValidationError": "Nagłówki muszą być w formacie: Nazwa nagłówka: wartość.",
"saveHealthCheck": "Zapisz Kontrolę Zdrowia",
"healthCheckSaved": "Kontrola Zdrowia Zapisana",
+4 -2
View File
@@ -1964,8 +1964,10 @@
"retryAttempts": "Tentativas de Repetição",
"expectedResponseCodes": "Códigos de Resposta Esperados",
"expectedResponseCodesDescription": "Código de status HTTP que indica estado saudável. Se deixado em branco, 200-300 é considerado saudável.",
"customHeaders": "Cabeçalhos Personalizados",
"customHeadersDescription": "Separados por cabeçalhos da nova linha: Nome do Cabeçalho: valor",
"customRequestHeaders": "Cabeçalhos Personalizados",
"customRequestHeadersDescription": "Separados por cabeçalhos da nova linha: Nome do Cabeçalho: valor",
"customResponseHeaders": "Custom Response Headers",
"customResponseHeadersDescription": "Response headers sent back to the client. One per line: Header-Name: value",
"headersValidationError": "Cabeçalhos devem estar no formato: Nome do Cabeçalho: valor.",
"saveHealthCheck": "Salvar Verificação de Saúde",
"healthCheckSaved": "Verificação de Saúde Salva",
+4 -2
View File
@@ -1964,8 +1964,10 @@
"retryAttempts": "Количество попыток повторного запроса",
"expectedResponseCodes": "Ожидаемые коды ответов",
"expectedResponseCodesDescription": "HTTP-код состояния, указывающий на здоровое состояние. Если оставить пустым, 200-300 считается здоровым.",
"customHeaders": "Пользовательские заголовки",
"customHeadersDescription": "Заголовки новой строки, разделённые: название заголовка: значение",
"customRequestHeaders": "Пользовательские заголовки",
"customRequestHeadersDescription": "Заголовки новой строки, разделённые: название заголовка: значение",
"customResponseHeaders": "Custom Response Headers",
"customResponseHeadersDescription": "Response headers sent back to the client. One per line: Header-Name: value",
"headersValidationError": "Заголовки должны быть в формате: Название заголовка: значение.",
"saveHealthCheck": "Сохранить проверку здоровья",
"healthCheckSaved": "Проверка здоровья сохранена",
+4 -2
View File
@@ -1964,8 +1964,10 @@
"retryAttempts": "Tekrar Deneme Girişimleri",
"expectedResponseCodes": "Beklenen Yanıt Kodları",
"expectedResponseCodesDescription": "Sağlıklı durumu gösteren HTTP durum kodu. Boş bırakılırsa, 200-300 arası sağlıklı kabul edilir.",
"customHeaders": "Özel Başlıklar",
"customHeadersDescription": "Başlıklar yeni satırla ayrılmış: Başlık-Adı: değer",
"customRequestHeaders": "Özel Başlıklar",
"customRequestHeadersDescription": "Başlıklar yeni satırla ayrılmış: Başlık-Adı: değer",
"customResponseHeaders": "Custom Response Headers",
"customResponseHeadersDescription": "Response headers sent back to the client. One per line: Header-Name: value",
"headersValidationError": "Başlıklar şu formatta olmalıdır: Başlık-Adı: değer.",
"saveHealthCheck": "Sağlık Kontrolünü Kaydet",
"healthCheckSaved": "Sağlık Kontrolü Kaydedildi",
+4 -2
View File
@@ -1964,8 +1964,10 @@
"retryAttempts": "重试次数",
"expectedResponseCodes": "期望响应代码",
"expectedResponseCodesDescription": "HTTP 状态码表示健康状态。如留空,200-300 被视为健康。",
"customHeaders": "自定义标题",
"customHeadersDescription": "头部新行分隔:头部名称:值",
"customRequestHeaders": "自定义标题",
"customRequestHeadersDescription": "头部新行分隔:头部名称:值",
"customResponseHeaders": "Custom Response Headers",
"customResponseHeadersDescription": "Response headers sent back to the client. One per line: Header-Name: value",
"headersValidationError": "头部必须是格式:头部名称:值。",
"saveHealthCheck": "保存健康检查",
"healthCheckSaved": "健康检查已保存",
+4 -2
View File
@@ -1555,8 +1555,10 @@
"retryAttempts": "重試次數",
"expectedResponseCodes": "期望響應代碼",
"expectedResponseCodesDescription": "HTTP 狀態碼表示健康狀態。如留空,200-300 被視為健康。",
"customHeaders": "自訂 Headers",
"customHeadersDescription": "Header 斷行分隔:Header 名稱:值",
"customRequestHeaders": "自訂 Headers",
"customRequestHeadersDescription": "Header 斷行分隔:Header 名稱:值",
"customResponseHeaders": "Custom Response Headers",
"customResponseHeadersDescription": "Response headers sent back to the client. One per line: Header-Name: value",
"headersValidationError": "Header 必須是格式:Header 名稱:值。",
"saveHealthCheck": "保存健康檢查",
"healthCheckSaved": "健康檢查已保存",
+6 -31
View File
@@ -1058,7 +1058,6 @@
"integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@babel/code-frame": "^7.29.0",
"@babel/generator": "^7.29.0",
@@ -3034,7 +3033,6 @@
"integrity": "sha512-2I0gnIVPtfnMw9ee9h1dJG7tp81+8Ob3OJb3Mv37rx5L40/b0i7djjCVvGOVqc9AEIQyvyu1i6ypKdFw8R8gQw==",
"dev": true,
"license": "MIT",
"peer": true,
"engines": {
"node": "^14.21.3 || >=16"
},
@@ -6981,7 +6979,6 @@
"resolved": "https://registry.npmjs.org/@react-email/text/-/text-0.1.6.tgz",
"integrity": "sha512-TYqkioRS45wTR5il3dYk/SbUjjEdhSwh9BtRNB99qNH1pXAwA45H7rAuxehiu8iJQJH0IyIr+6n62gBz9ezmsw==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">=20.0.0"
},
@@ -8442,7 +8439,6 @@
"version": "5.90.21",
"resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.90.21.tgz",
"integrity": "sha512-0Lu6y5t+tvlTJMTO7oh5NSpJfpg/5D41LlThfepTixPYkJ0sE2Jj0m0f6yYqujBwIXlId87e234+MxG3D3g7kg==",
"peer": true,
"dependencies": {
"@tanstack/query-core": "5.90.20"
},
@@ -8558,7 +8554,6 @@
"integrity": "sha512-NMv9ASNARoKksWtsq/SHakpYAYnhBrQgGD8zkLYk/jaK8jUGn08CfEdTRgYhMypUQAfzSP8W6gNLe0q19/t4VA==",
"devOptional": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@types/node": "*"
}
@@ -8906,7 +8901,6 @@
"integrity": "sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@types/body-parser": "*",
"@types/express-serve-static-core": "^5.0.0",
@@ -9002,7 +8996,6 @@
"integrity": "sha512-oX8xrhvpiyRCQkG1MFchB09f+cXftgIXb3a7UUa4Y3wpmZPw5tyZGTLWhlESOLq1Rq6oDlc8npVU2/9xiCuXMA==",
"devOptional": true,
"license": "MIT",
"peer": true,
"dependencies": {
"undici-types": "~7.18.0"
}
@@ -9030,7 +9023,6 @@
"integrity": "sha512-gT+oueVQkqnj6ajGJXblFR4iavIXWsGAFCk3dP4Kki5+a9R4NMt0JARdk6s8cUKcfUoqP5dAtDSLU8xYUTFV+Q==",
"devOptional": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@types/node": "*",
"pg-protocol": "*",
@@ -9056,7 +9048,6 @@
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz",
"integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==",
"devOptional": true,
"peer": true,
"dependencies": {
"csstype": "^3.2.2"
}
@@ -9067,7 +9058,6 @@
"integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==",
"devOptional": true,
"license": "MIT",
"peer": true,
"peerDependencies": {
"@types/react": "^19.2.0"
}
@@ -9154,7 +9144,8 @@
"resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz",
"integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==",
"license": "MIT",
"optional": true
"optional": true,
"peer": true
},
"node_modules/@types/ws": {
"version": "8.18.1",
@@ -9228,7 +9219,6 @@
"integrity": "sha512-klQbnPAAiGYFyI02+znpBRLyjL4/BrBd0nyWkdC0s/6xFLkXYQ8OoRrSkqacS1ddVxf/LDyODIKbQ5TgKAf/Fg==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@typescript-eslint/scope-manager": "8.56.1",
"@typescript-eslint/types": "8.56.1",
@@ -9702,7 +9692,6 @@
"integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==",
"dev": true,
"license": "MIT",
"peer": true,
"bin": {
"acorn": "bin/acorn"
},
@@ -10152,7 +10141,6 @@
"integrity": "sha512-Ixm8tFfoKKIPYdCCKYTsqv+Fd4IJ0DQqMyEimo+pxUOMUR9cVPlwTrFt9Avu+3cb6Zp3mAzl+t1MrG2fxxKsxw==",
"devOptional": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@babel/types": "^7.26.0"
}
@@ -10224,7 +10212,6 @@
"integrity": "sha512-Ba0KR+Fzxh2jDRhdg6TSH0SJGzb8C0aBY4hR8w8madIdIzzC6Y1+kx5qR6eS1Z+Gy20h6ZU28aeyg0z1VIrShQ==",
"hasInstallScript": true,
"license": "MIT",
"peer": true,
"dependencies": {
"bindings": "^1.5.0",
"prebuild-install": "^7.1.1"
@@ -10353,7 +10340,6 @@
}
],
"license": "MIT",
"peer": true,
"dependencies": {
"baseline-browser-mapping": "^2.9.0",
"caniuse-lite": "^1.0.30001759",
@@ -11260,7 +11246,6 @@
"resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz",
"integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==",
"license": "ISC",
"peer": true,
"engines": {
"node": ">=12"
}
@@ -11701,6 +11686,7 @@
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.3.2.tgz",
"integrity": "sha512-6obghkliLdmKa56xdbLOpUZ43pAR6xFy1uOrxBaIDjT+yaRuuybLjGS9eVBoSR/UPU5fq3OXClEHLJNGvbxKpQ==",
"license": "(MPL-2.0 OR Apache-2.0)",
"peer": true,
"engines": {
"node": ">=20"
},
@@ -12335,7 +12321,6 @@
"dev": true,
"hasInstallScript": true,
"license": "MIT",
"peer": true,
"bin": {
"esbuild": "bin/esbuild"
},
@@ -12421,7 +12406,6 @@
"integrity": "sha512-COV33RzXZkqhG9P2rZCFl9ZmJ7WL+gQSCRzE7RhkbclbQPtLAWReL7ysA0Sh4c8Im2U9ynybdR56PV0XcKvqaQ==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.8.0",
"@eslint-community/regexpp": "^4.12.2",
@@ -12558,7 +12542,6 @@
"integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@rtsao/scc": "^1.1.0",
"array-includes": "^3.1.9",
@@ -12952,7 +12935,6 @@
"resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz",
"integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==",
"license": "MIT",
"peer": true,
"dependencies": {
"accepts": "^2.0.0",
"body-parser": "^2.2.1",
@@ -15370,6 +15352,7 @@
"resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.55.1.tgz",
"integrity": "sha512-jz4x+TJNFHwHtwuV9vA9rMujcZRb0CEilTEwG2rRSpe/A7Jdkuj8xPKttCgOh+v/lkHy7HsZ64oj+q3xoAFl9A==",
"license": "MIT",
"peer": true,
"dependencies": {
"dompurify": "3.2.7",
"marked": "14.0.0"
@@ -15380,6 +15363,7 @@
"resolved": "https://registry.npmjs.org/marked/-/marked-14.0.0.tgz",
"integrity": "sha512-uIj4+faQ+MgHgwUW1l2PsPglZLOLOT1uErt06dAPtx2kjteLAkbsd/0FiYg/MGS+i7ZKLb7w2WClxHkzOOuryQ==",
"license": "MIT",
"peer": true,
"bin": {
"marked": "bin/marked.js"
},
@@ -15468,7 +15452,6 @@
"resolved": "https://registry.npmjs.org/next/-/next-15.5.15.tgz",
"integrity": "sha512-VSqCrJwtLVGwAVE0Sb/yikrQfkwkZW9p+lL/J4+xe+G3ZA+QnWPqgcfH1tDUEuk9y+pthzzVFp4L/U8JerMfMQ==",
"license": "MIT",
"peer": true,
"dependencies": {
"@next/env": "15.5.15",
"@swc/helpers": "0.5.15",
@@ -16428,7 +16411,6 @@
"resolved": "https://registry.npmjs.org/pg/-/pg-8.20.0.tgz",
"integrity": "sha512-ldhMxz2r8fl/6QkXnBD3CR9/xg694oT6DZQ2s6c/RI28OjtSOpxnPrUCGOBJ46RCUxcWdx3p6kw/xnDHjKvaRA==",
"license": "MIT",
"peer": true,
"dependencies": {
"pg-connection-string": "^2.12.0",
"pg-pool": "^3.13.0",
@@ -16936,7 +16918,6 @@
"resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz",
"integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">=0.10.0"
}
@@ -16968,7 +16949,6 @@
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz",
"integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==",
"license": "MIT",
"peer": true,
"dependencies": {
"scheduler": "^0.27.0"
},
@@ -17261,7 +17241,6 @@
"resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.71.2.tgz",
"integrity": "sha512-1CHvcDYzuRUNOflt4MOq3ZM46AronNJtQ1S7tnX6YN4y72qhgiUItpacZUAQ0TyWYci3yz1X+rXaSxiuEm86PA==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">=18.0.0"
},
@@ -18723,8 +18702,7 @@
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.2.2.tgz",
"integrity": "sha512-KWBIxs1Xb6NoLdMVqhbhgwZf2PGBpPEiwOqgI4pFIYbNTfBXiKYyWoTsXgBQ9WFg/OlhnvHaY+AEpW7wSmFo2Q==",
"license": "MIT",
"peer": true
"license": "MIT"
},
"node_modules/tapable": {
"version": "2.3.2",
@@ -19199,7 +19177,6 @@
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
"devOptional": true,
"license": "Apache-2.0",
"peer": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
@@ -19627,7 +19604,6 @@
"resolved": "https://registry.npmjs.org/winston/-/winston-3.19.0.tgz",
"integrity": "sha512-LZNJgPzfKR+/J3cHkxcpHKpKKvGfDZVPS4hfJCc4cCG0CgYzvlD6yE/S3CIL/Yt91ak327YCpiF/0MyeZHEHKA==",
"license": "MIT",
"peer": true,
"dependencies": {
"@colors/colors": "^1.6.0",
"@dabh/diagnostics": "^2.0.8",
@@ -19834,7 +19810,6 @@
"resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz",
"integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==",
"license": "MIT",
"peer": true,
"funding": {
"url": "https://github.com/sponsors/colinhacks"
}
+2 -1
View File
@@ -144,7 +144,8 @@ export const resources = pgTable("resources", {
skipToIdpId: integer("skipToIdpId").references(() => idp.idpId, {
onDelete: "set null"
}),
headers: text("headers"), // comma-separated list of headers to add to the request
requestHeaders: text("requestHeaders"),
responseHeaders: text("responseHeaders"),
proxyProtocol: boolean("proxyProtocol").notNull().default(false),
proxyProtocolVersion: integer("proxyProtocolVersion").default(1),
+2 -1
View File
@@ -161,7 +161,8 @@ export const resources = sqliteTable("resources", {
skipToIdpId: integer("skipToIdpId").references(() => idp.idpId, {
onDelete: "set null"
}),
headers: text("headers"), // comma-separated list of headers to add to the request
requestHeaders: text("requestHeaders"),
responseHeaders: text("responseHeaders"),
proxyProtocol: integer("proxyProtocol", { mode: "boolean" })
.notNull()
.default(false),
+37 -14
View File
@@ -65,7 +65,8 @@ export async function getTraefikConfig(
tlsServerName: resources.tlsServerName,
setHostHeader: resources.setHostHeader,
enableProxy: resources.enableProxy,
headers: resources.headers,
requestHeaders: resources.requestHeaders,
responseHeaders: resources.responseHeaders,
proxyProtocol: resources.proxyProtocol,
proxyProtocolVersion: resources.proxyProtocolVersion,
@@ -177,7 +178,8 @@ export async function getTraefikConfig(
setHostHeader: row.setHostHeader,
enableProxy: row.enableProxy,
targets: [],
headers: row.headers,
requestHeaders: row.requestHeaders,
responseHeaders: row.responseHeaders,
proxyProtocol: row.proxyProtocol,
proxyProtocolVersion: row.proxyProtocolVersion ?? 1,
path: row.path, // the targets will all have the same path
@@ -364,38 +366,59 @@ export async function getTraefikConfig(
}
// Handle custom headers middleware
if (resource.headers || resource.setHostHeader) {
const headersObj: { [key: string]: string } = {};
if (resource.requestHeaders || resource.responseHeaders || resource.setHostHeader) {
const requestHeadersObj: { [key: string]: string } = {};
const responseHeadersObj: { [key: string]: string } = {};
if (resource.headers) {
let headersArr: { name: string; value: string }[] = [];
if (resource.requestHeaders) {
let requestHeadersArr: { name: string; value: string }[] = [];
try {
headersArr = JSON.parse(resource.headers) as {
requestHeadersArr = JSON.parse(resource.requestHeaders) as {
name: string;
value: string;
}[];
} catch (e) {
logger.warn(
`Failed to parse headers for resource ${resource.resourceId}: ${e}`
`Failed to parse requestHeaders for resource ${resource.resourceId}: ${e}`
);
}
headersArr.forEach((header) => {
headersObj[header.name] = header.value;
requestHeadersArr.forEach((header) => {
requestHeadersObj[header.name] = header.value;
});
}
if (resource.setHostHeader) {
headersObj["Host"] = resource.setHostHeader;
requestHeadersObj["Host"] = resource.setHostHeader;
}
if (Object.keys(headersObj).length > 0) {
if (resource.responseHeaders) {
let responseHeadersArr: { name: string; value: string }[] = [];
try {
responseHeadersArr = JSON.parse(resource.responseHeaders) as {
name: string;
value: string;
}[];
} catch (e) {
logger.warn(
`Failed to parse responseHeaders for resource ${resource.resourceId}: ${e}`
);
}
responseHeadersArr.forEach((header) => {
responseHeadersObj[header.name] = header.value;
});
}
const hasRequestHeaders = Object.keys(requestHeadersObj).length > 0;
const hasResponseHeaders = Object.keys(responseHeadersObj).length > 0;
if (hasRequestHeaders || hasResponseHeaders) {
if (!config_output.http.middlewares) {
config_output.http.middlewares = {};
}
config_output.http.middlewares[headersMiddlewareName] = {
headers: {
customRequestHeaders: headersObj
...(hasRequestHeaders && { customRequestHeaders: requestHeadersObj }),
...(hasResponseHeaders && { customResponseHeaders: responseHeadersObj })
}
};
+38 -17
View File
@@ -105,7 +105,8 @@ export async function getTraefikConfig(
tlsServerName: resources.tlsServerName,
setHostHeader: resources.setHostHeader,
enableProxy: resources.enableProxy,
headers: resources.headers,
requestHeaders: resources.requestHeaders,
responseHeaders: resources.responseHeaders,
proxyProtocol: resources.proxyProtocol,
proxyProtocolVersion: resources.proxyProtocolVersion,
wildcard: resources.wildcard,
@@ -237,7 +238,8 @@ export async function getTraefikConfig(
setHostHeader: row.setHostHeader,
enableProxy: row.enableProxy,
targets: [],
headers: row.headers,
requestHeaders: row.requestHeaders,
responseHeaders: row.responseHeaders,
proxyProtocol: row.proxyProtocol,
proxyProtocolVersion: row.proxyProtocolVersion ?? 1,
path: row.path, // the targets will all have the same path
@@ -648,40 +650,59 @@ export async function getTraefikConfig(
}
}
if (resource.headers || resource.setHostHeader) {
// if there are headers, parse them into an object
const headersObj: { [key: string]: string } = {};
if (resource.headers) {
let headersArr: { name: string; value: string }[] = [];
if (resource.requestHeaders || resource.responseHeaders || resource.setHostHeader) {
const requestHeadersObj: { [key: string]: string } = {};
const responseHeadersObj: { [key: string]: string } = {};
if (resource.requestHeaders) {
let requestHeadersArr: { name: string; value: string }[] = [];
try {
headersArr = JSON.parse(resource.headers) as {
requestHeadersArr = JSON.parse(resource.requestHeaders) as {
name: string;
value: string;
}[];
} catch (e) {
logger.warn(
`Failed to parse headers for resource ${resource.resourceId}: ${e}`
`Failed to parse requestHeaders for resource ${resource.resourceId}: ${e}`
);
}
headersArr.forEach((header) => {
headersObj[header.name] = header.value;
requestHeadersArr.forEach((header) => {
requestHeadersObj[header.name] = header.value;
});
}
if (resource.setHostHeader) {
headersObj["Host"] = resource.setHostHeader;
requestHeadersObj["Host"] = resource.setHostHeader;
}
// check if the object is not empty
if (Object.keys(headersObj).length > 0) {
// Add the headers middleware
if (resource.responseHeaders) {
let responseHeadersArr: { name: string; value: string }[] = [];
try {
responseHeadersArr = JSON.parse(resource.responseHeaders) as {
name: string;
value: string;
}[];
} catch (e) {
logger.warn(
`Failed to parse responseHeaders for resource ${resource.resourceId}: ${e}`
);
}
responseHeadersArr.forEach((header) => {
responseHeadersObj[header.name] = header.value;
});
}
const hasRequestHeaders = Object.keys(requestHeadersObj).length > 0;
const hasResponseHeaders = Object.keys(responseHeadersObj).length > 0;
if (hasRequestHeaders || hasResponseHeaders) {
if (!config_output.http.middlewares) {
config_output.http.middlewares = {};
}
config_output.http.middlewares[headersMiddlewareName] = {
headers: {
customRequestHeaders: headersObj
...(hasRequestHeaders && { customRequestHeaders: requestHeadersObj }),
...(hasResponseHeaders && { customResponseHeaders: responseHeadersObj })
}
};
+6 -3
View File
@@ -105,9 +105,12 @@ export async function getResource(
return response<GetResourceResponse>(res, {
data: {
...resource,
headers: resource.headers
? JSON.parse(resource.headers)
: resource.headers
requestHeaders: resource.requestHeaders
? JSON.parse(resource.requestHeaders)
: resource.requestHeaders,
responseHeaders: resource.responseHeaders
? JSON.parse(resource.responseHeaders)
: resource.responseHeaders
},
success: true,
error: false,
+40 -31
View File
@@ -58,7 +58,11 @@ const updateHttpResourceBodySchema = z
tlsServerName: z.string().nullable().optional(),
setHostHeader: z.string().nullable().optional(),
skipToIdpId: z.int().positive().nullable().optional(),
headers: z
requestHeaders: z
.array(z.strictObject({ name: z.string(), value: z.string() }))
.nullable()
.optional(),
responseHeaders: z
.array(z.strictObject({ name: z.string(), value: z.string() }))
.nullable()
.optional(),
@@ -111,12 +115,12 @@ const updateHttpResourceBodySchema = z
)
.refine(
(data) => {
if (data.headers) {
// HTTP header names must be valid token characters (RFC 7230)
const validHeaderName = /^[a-zA-Z0-9!#$%&'*+\-.^_`|~]+$/;
return data.headers.every((h) => validHeaderName.test(h.name));
}
return true;
const validHeaderName = /^[a-zA-Z0-9!#$%&'*+\-.^_`|~]+$/;
const allHeaders = [
...(data.requestHeaders ?? []),
...(data.responseHeaders ?? [])
];
return allHeaders.every((h) => validHeaderName.test(h.name));
},
{
error: "Header names may only contain valid HTTP token characters (letters, digits, and !#$%&'*+-.^_`|~)."
@@ -124,14 +128,12 @@ const updateHttpResourceBodySchema = z
)
.refine(
(data) => {
if (data.headers) {
// HTTP header values must be visible ASCII or horizontal whitespace, no control chars (RFC 7230)
const validHeaderValue = /^[\t\x20-\x7E]*$/;
return data.headers.every((h) =>
validHeaderValue.test(h.value)
);
}
return true;
const validHeaderValue = /^[\t\x20-\x7E]*$/;
const allHeaders = [
...(data.requestHeaders ?? []),
...(data.responseHeaders ?? [])
];
return allHeaders.every((h) => validHeaderValue.test(h.value));
},
{
error: "Header values may only contain printable ASCII characters and horizontal whitespace."
@@ -139,16 +141,16 @@ const updateHttpResourceBodySchema = z
)
.refine(
(data) => {
if (data.headers) {
// Reject Traefik template syntax {{word}} in names or values
const templatePattern = /\{\{[^}]+\}\}/;
return data.headers.every(
(h) =>
!templatePattern.test(h.name) &&
!templatePattern.test(h.value)
);
}
return true;
const templatePattern = /\{\{[^}]+\}\}/;
const allHeaders = [
...(data.requestHeaders ?? []),
...(data.responseHeaders ?? [])
];
return allHeaders.every(
(h) =>
!templatePattern.test(h.name) &&
!templatePattern.test(h.value)
);
},
{
error: "Header names and values must not contain template expressions such as {{value}}."
@@ -467,11 +469,18 @@ async function updateHttpResource(
}
}
let headers = undefined;
if (updateData.headers) {
headers = JSON.stringify(updateData.headers);
} else if (updateData.headers === null) {
headers = null;
let requestHeaders = undefined;
if (updateData.requestHeaders) {
requestHeaders = JSON.stringify(updateData.requestHeaders);
} else if (updateData.requestHeaders === null) {
requestHeaders = null;
}
let responseHeaders = undefined;
if (updateData.responseHeaders) {
responseHeaders = JSON.stringify(updateData.responseHeaders);
} else if (updateData.responseHeaders === null) {
responseHeaders = null;
}
const isLicensed = await isLicensedOrSubscribed(
@@ -488,7 +497,7 @@ async function updateHttpResource(
const updatedResource = await db
.update(resources)
.set({ ...updateData, headers })
.set({ ...updateData, requestHeaders, responseHeaders })
.where(eq(resources.resourceId, resource.resourceId))
.returning();
+3 -1
View File
@@ -25,6 +25,7 @@ import m16 from "./scriptsPg/1.17.0";
import m17 from "./scriptsPg/1.18.0";
import m18 from "./scriptsPg/1.18.3";
import m19 from "./scriptsPg/1.18.4";
import m20 from "./scriptsPg/1.18.5";
// THIS CANNOT IMPORT ANYTHING FROM THE SERVER
// EXCEPT FOR THE DATABASE AND THE SCHEMA
@@ -49,7 +50,8 @@ const migrations = [
{ version: "1.17.0", run: m16 },
{ version: "1.18.0", run: m17 },
{ version: "1.18.3", run: m18 },
{ version: "1.18.4", run: m19 }
{ version: "1.18.4", run: m19 },
{ version: "1.18.5", run: m20 }
// Add new migrations here as they are created
] as {
version: string;
+3 -1
View File
@@ -43,6 +43,7 @@ import m37 from "./scriptsSqlite/1.17.0";
import m38 from "./scriptsSqlite/1.18.0";
import m39 from "./scriptsSqlite/1.18.3";
import m40 from "./scriptsSqlite/1.18.4";
import m41 from "./scriptsSqlite/1.18.5";
// THIS CANNOT IMPORT ANYTHING FROM THE SERVER
// EXCEPT FOR THE DATABASE AND THE SCHEMA
@@ -83,7 +84,8 @@ const migrations = [
{ version: "1.17.0", run: m37 },
{ version: "1.18.0", run: m38 },
{ version: "1.18.3", run: m39 },
{ version: "1.18.4", run: m40 }
{ version: "1.18.4", run: m40 },
{ version: "1.18.5", run: m41 }
// Add new migrations here as they are created
] as const;
+30
View File
@@ -0,0 +1,30 @@
import { db } from "@server/db/pg/driver";
import { sql } from "drizzle-orm";
const version = "1.18.5";
export default async function migration() {
console.log(`Running setup script ${version}...`);
try {
await db.execute(sql`BEGIN`);
await db.execute(sql`
ALTER TABLE "resources" RENAME COLUMN "headers" TO "requestHeaders";
`);
await db.execute(sql`
ALTER TABLE "resources" ADD COLUMN "responseHeaders" text;
`);
await db.execute(sql`COMMIT`);
console.log("Migrated database");
} catch (e) {
await db.execute(sql`ROLLBACK`);
console.log("Unable to migrate database");
console.log(e);
throw e;
}
console.log(`${version} migration complete`);
}
+34
View File
@@ -0,0 +1,34 @@
import { APP_PATH } from "@server/lib/consts";
import Database from "better-sqlite3";
import path from "path";
const version = "1.18.5";
export default async function migration() {
console.log(`Running setup script ${version}...`);
const location = path.join(APP_PATH, "db", "db.sqlite");
const db = new Database(location);
try {
db.pragma("foreign_keys = OFF");
db.transaction(() => {
db.prepare(
`ALTER TABLE 'resources' RENAME COLUMN 'headers' TO 'requestHeaders';`
).run();
db.prepare(
`ALTER TABLE 'resources' ADD 'responseHeaders' text;`
).run();
})();
db.pragma("foreign_keys = ON");
console.log("Migrated database");
} catch (e) {
console.log("Failed to migrate db:", e);
throw e;
}
console.log(`${version} migration complete`);
}
@@ -1097,7 +1097,10 @@ function ProxyResourceHttpForm({
message: t("proxyErrorInvalidHeader")
}
),
headers: z
requestHeaders: z
.array(z.object({ name: z.string(), value: z.string() }))
.nullable(),
responseHeaders: z
.array(z.object({ name: z.string(), value: z.string() }))
.nullable(),
proxyProtocol: z.boolean().optional(),
@@ -1108,7 +1111,8 @@ function ProxyResourceHttpForm({
resolver: zodResolver(proxySettingsSchema),
defaultValues: {
setHostHeader: resource.setHostHeader || "",
headers: resource.headers,
requestHeaders: resource.requestHeaders,
responseHeaders: resource.responseHeaders,
proxyProtocol: resource.proxyProtocol || false,
proxyProtocolVersion: resource.proxyProtocolVersion || 1
}
@@ -1148,7 +1152,8 @@ function ProxyResourceHttpForm({
ssl: tlsData.ssl,
tlsServerName: tlsData.tlsServerName || null,
setHostHeader: proxyData.setHostHeader || null,
headers: proxyData.headers || null
requestHeaders: proxyData.requestHeaders || null,
responseHeaders: proxyData.responseHeaders || null
};
// Single API call to update all settings
@@ -1161,7 +1166,8 @@ function ProxyResourceHttpForm({
ssl: tlsData.ssl,
tlsServerName: tlsData.tlsServerName || null,
setHostHeader: proxyData.setHostHeader || null,
headers: proxyData.headers || null
requestHeaders: proxyData.requestHeaders || null,
responseHeaders: proxyData.responseHeaders || null
});
toast({
@@ -1307,11 +1313,11 @@ function ProxyResourceHttpForm({
/>
<FormField
control={proxySettingsForm.control}
name="headers"
name="requestHeaders"
render={({ field }) => (
<FormItem>
<FormLabel>
{t("customHeaders")}
{t("customRequestHeaders")}
</FormLabel>
<FormControl>
<HeadersInput
@@ -1323,7 +1329,31 @@ function ProxyResourceHttpForm({
/>
</FormControl>
<FormDescription>
{t("customHeadersDescription")}
{t("customRequestHeadersDescription")}
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={proxySettingsForm.control}
name="responseHeaders"
render={({ field }) => (
<FormItem>
<FormLabel>
{t("customResponseHeaders")}
</FormLabel>
<FormControl>
<HeadersInput
value={field.value}
onChange={(value) => {
field.onChange(value);
}}
rows={4}
/>
</FormControl>
<FormDescription>
{t("customResponseHeadersDescription")}
</FormDescription>
<FormMessage />
</FormItem>
@@ -1369,7 +1399,7 @@ function ProxyResourceProtocolForm({
message: t("proxyErrorInvalidHeader")
}
),
headers: z
requestHeaders: z
.array(z.object({ name: z.string(), value: z.string() }))
.nullable(),
proxyProtocol: z.boolean().optional(),
@@ -1380,7 +1410,7 @@ function ProxyResourceProtocolForm({
resolver: zodResolver(proxySettingsSchema),
defaultValues: {
setHostHeader: resource.setHostHeader || "",
headers: resource.headers,
requestHeaders: resource.requestHeaders,
proxyProtocol: resource.proxyProtocol || false,
proxyProtocolVersion: resource.proxyProtocolVersion || 1
}
+2 -2
View File
@@ -1317,7 +1317,7 @@ export function HealthCheckCredenza(props: HealthCheckCredenzaProps) {
<FormItem>
<FormLabel>
{t(
"customHeaders"
"customRequestHeaders"
)}
</FormLabel>
<FormControl>
@@ -1341,7 +1341,7 @@ export function HealthCheckCredenza(props: HealthCheckCredenzaProps) {
</FormControl>
<FormDescription>
{t(
"customHeadersDescription"
"customRequestHeadersDescription"
)}
</FormDescription>
<FormMessage />
+2 -2
View File
@@ -733,7 +733,7 @@ export function HealthCheckFormFields({
render={({ field }) => (
<FormItem>
<FormLabel>
{t("customHeaders")}
{t("customRequestHeaders")}
</FormLabel>
<FormControl>
<HeadersInput
@@ -750,7 +750,7 @@ export function HealthCheckFormFields({
</FormControl>
<FormDescription>
{t(
"customHeadersDescription"
"customRequestHeadersDescription"
)}
</FormDescription>
<FormMessage />