Compare commits

..

30 Commits

Author SHA1 Message Date
Owen Schwartz 5a77f3a723 New translations en-us.json (French)
[ci skip]
2026-07-10 16:44:53 -04:00
Owen Schwartz 8f5ebf8378 New translations en-us.json (Norwegian Bokmal)
[ci skip]
2026-07-10 16:44:52 -04:00
Owen Schwartz d0282c5e82 New translations en-us.json (Chinese Simplified)
[ci skip]
2026-07-10 16:44:50 -04:00
Owen Schwartz 8f188b9315 New translations en-us.json (Turkish)
[ci skip]
2026-07-10 16:44:48 -04:00
Owen Schwartz 899c4061ce New translations en-us.json (Russian)
[ci skip]
2026-07-10 16:44:46 -04:00
Owen Schwartz 09c1c27f00 New translations en-us.json (Portuguese)
[ci skip]
2026-07-10 16:44:43 -04:00
Owen Schwartz d846172bcc New translations en-us.json (Polish)
[ci skip]
2026-07-10 16:44:42 -04:00
Owen Schwartz 26cd0175a1 New translations en-us.json (Dutch)
[ci skip]
2026-07-10 16:44:39 -04:00
Owen Schwartz baac7530cb New translations en-us.json (Korean)
[ci skip]
2026-07-10 16:44:37 -04:00
Owen Schwartz 02572afd36 New translations en-us.json (Italian)
[ci skip]
2026-07-10 16:44:35 -04:00
Owen Schwartz 9446f43266 New translations en-us.json (German)
[ci skip]
2026-07-10 16:44:33 -04:00
Owen Schwartz 70e396c31a New translations en-us.json (Danish)
[ci skip]
2026-07-10 16:44:31 -04:00
Owen Schwartz bc94f700dc New translations en-us.json (Czech)
[ci skip]
2026-07-10 16:44:29 -04:00
Owen Schwartz 7a02695541 New translations en-us.json (Bulgarian)
[ci skip]
2026-07-10 16:44:27 -04:00
Owen Schwartz 15f5fa3b33 New translations en-us.json (Spanish)
[ci skip]
2026-07-10 16:44:25 -04:00
Owen Schwartz fbca05f10f New translations en-us.json (French)
[ci skip]
2026-07-09 12:12:59 -04:00
Owen Schwartz c31608f970 New translations en-us.json (Norwegian Bokmal)
[ci skip]
2026-07-09 12:12:57 -04:00
Owen Schwartz d52d8e5f24 New translations en-us.json (Chinese Simplified)
[ci skip]
2026-07-09 12:12:55 -04:00
Owen Schwartz 9176ccc151 New translations en-us.json (Turkish)
[ci skip]
2026-07-09 12:12:53 -04:00
Owen Schwartz 36da66afb9 New translations en-us.json (Russian)
[ci skip]
2026-07-09 12:12:51 -04:00
Owen Schwartz b19751112e New translations en-us.json (Portuguese)
[ci skip]
2026-07-09 12:12:48 -04:00
Owen Schwartz 9f074c1341 New translations en-us.json (Polish)
[ci skip]
2026-07-09 12:12:46 -04:00
Owen Schwartz cd76a09df4 New translations en-us.json (Dutch)
[ci skip]
2026-07-09 12:12:44 -04:00
Owen Schwartz 68f24f8afb New translations en-us.json (Korean)
[ci skip]
2026-07-09 12:12:42 -04:00
Owen Schwartz 6427ca5523 New translations en-us.json (Italian)
[ci skip]
2026-07-09 12:12:40 -04:00
Owen Schwartz 71e817fd8c New translations en-us.json (German)
[ci skip]
2026-07-09 12:12:38 -04:00
Owen Schwartz 73b30bf9fb New translations en-us.json (Danish)
[ci skip]
2026-07-09 12:12:36 -04:00
Owen Schwartz 757e39b55b New translations en-us.json (Czech)
[ci skip]
2026-07-09 12:12:34 -04:00
Owen Schwartz f1d06c6780 New translations en-us.json (Bulgarian)
[ci skip]
2026-07-09 12:12:32 -04:00
Owen Schwartz 932f1089ba New translations en-us.json (Spanish)
[ci skip]
2026-07-09 12:12:29 -04:00
27 changed files with 76 additions and 238 deletions
+2
View File
@@ -1397,6 +1397,7 @@
"createOrgUser": "Създаване на Организационна Потребител",
"actionUpdateOrg": "Актуализиране на организацията",
"actionRemoveInvitation": "Премахване на поканата.",
"actionRemoveUserRole": "Remove User Role",
"actionUpdateUser": "Актуализиране на потребител",
"actionGetUser": "Получаване на потребител",
"actionGetOrgUser": "Вземете потребител на организация",
@@ -1516,6 +1517,7 @@
"otpAuthDescription": "Въведете кода от приложението за удостоверяване или един от вашите резервни кодове за еднократна употреба.",
"otpAuthSubmit": "Изпрати код",
"idpContinue": "Или продължете със",
"idpLastUsed": "Last Used",
"otpAuthBack": "Назад към парола",
"navbar": "Навигационно меню",
"navbarDescription": "Главно навигационно меню за приложението",
+2
View File
@@ -1397,6 +1397,7 @@
"createOrgUser": "Vytvořit Org uživatele",
"actionUpdateOrg": "Aktualizovat organizaci",
"actionRemoveInvitation": "Odstranit pozvání",
"actionRemoveUserRole": "Remove User Role",
"actionUpdateUser": "Aktualizovat uživatele",
"actionGetUser": "Získat uživatele",
"actionGetOrgUser": "Získat uživatele organizace",
@@ -1516,6 +1517,7 @@
"otpAuthDescription": "Zadejte kód z vaší autentizační aplikace nebo jeden z vlastních záložních kódů.",
"otpAuthSubmit": "Odeslat kód",
"idpContinue": "Nebo pokračovat s",
"idpLastUsed": "Last Used",
"otpAuthBack": "Zpět na heslo",
"navbar": "Navigation Menu",
"navbarDescription": "Hlavní navigační menu aplikace",
+2
View File
@@ -1397,6 +1397,7 @@
"createOrgUser": "Opret organisationsbruger",
"actionUpdateOrg": "Opdater organisation",
"actionRemoveInvitation": "Fjern invitation",
"actionRemoveUserRole": "Remove User Role",
"actionUpdateUser": "Opdater bruger",
"actionGetUser": "Hent bruger",
"actionGetOrgUser": "Hent organisationsbruger",
@@ -1516,6 +1517,7 @@
"otpAuthDescription": "Indtast koden fra autentiseringsappen din eller en af dine engangs reservekoder.",
"otpAuthSubmit": "Send kode",
"idpContinue": "Eller fortsæt med",
"idpLastUsed": "Last Used",
"otpAuthBack": "Tilbage til adgangskode",
"navbar": "Navigationsmenu",
"navbarDescription": "Hovednavigasjonsmeny for applikationen",
+2
View File
@@ -1397,6 +1397,7 @@
"createOrgUser": "Org Benutzer erstellen",
"actionUpdateOrg": "Organisation aktualisieren",
"actionRemoveInvitation": "Einladung entfernen",
"actionRemoveUserRole": "Remove User Role",
"actionUpdateUser": "Benutzer aktualisieren",
"actionGetUser": "Benutzer abrufen",
"actionGetOrgUser": "Organisationsbenutzer abrufen",
@@ -1516,6 +1517,7 @@
"otpAuthDescription": "Geben Sie den Code aus Ihrer Authenticator-App oder einen Ihrer einmaligen Backup-Codes ein.",
"otpAuthSubmit": "Code absenden",
"idpContinue": "Oder weiter mit",
"idpLastUsed": "Last Used",
"otpAuthBack": "Zurück zum Passwort",
"navbar": "Navigationsmenü",
"navbarDescription": "Hauptnavigationsmenü für die Anwendung",
+2
View File
@@ -1397,6 +1397,7 @@
"createOrgUser": "Crear usuario Org",
"actionUpdateOrg": "Actualizar organización",
"actionRemoveInvitation": "Eliminar invitación",
"actionRemoveUserRole": "Remove User Role",
"actionUpdateUser": "Actualizar usuario",
"actionGetUser": "Obtener usuario",
"actionGetOrgUser": "Obtener usuario de la organización",
@@ -1516,6 +1517,7 @@
"otpAuthDescription": "Introduzca el código de su aplicación de autenticación o uno de sus códigos de copia de seguridad de un solo uso.",
"otpAuthSubmit": "Enviar código",
"idpContinue": "O continuar con",
"idpLastUsed": "Last Used",
"otpAuthBack": "Volver a la contraseña",
"navbar": "Menú de navegación",
"navbarDescription": "Menú de navegación principal para la aplicación",
+2
View File
@@ -1397,6 +1397,7 @@
"createOrgUser": "Créer un utilisateur Org",
"actionUpdateOrg": "Mettre à jour l'organisation",
"actionRemoveInvitation": "Supprimer l'invitation",
"actionRemoveUserRole": "Remove User Role",
"actionUpdateUser": "Mettre à jour l'utilisateur",
"actionGetUser": "Obtenir l'utilisateur",
"actionGetOrgUser": "Obtenir l'utilisateur de l'organisation",
@@ -1516,6 +1517,7 @@
"otpAuthDescription": "Entrez le code de votre application d'authentification ou l'un de vos codes de secours à usage unique.",
"otpAuthSubmit": "Soumettre le code",
"idpContinue": "Ou continuer avec",
"idpLastUsed": "Last Used",
"otpAuthBack": "Retour au mot de passe",
"navbar": "Menu de navigation",
"navbarDescription": "Menu de navigation principal de l'application",
+2
View File
@@ -1397,6 +1397,7 @@
"createOrgUser": "Crea Utente Org",
"actionUpdateOrg": "Aggiorna Organizzazione",
"actionRemoveInvitation": "Rimuovi Invito",
"actionRemoveUserRole": "Remove User Role",
"actionUpdateUser": "Aggiorna Utente",
"actionGetUser": "Ottieni Utente",
"actionGetOrgUser": "Ottieni Utente Organizzazione",
@@ -1516,6 +1517,7 @@
"otpAuthDescription": "Inserisci il codice dalla tua app di autenticazione o uno dei tuoi codici di backup monouso.",
"otpAuthSubmit": "Invia Codice",
"idpContinue": "O continua con",
"idpLastUsed": "Last Used",
"otpAuthBack": "Torna alla Password",
"navbar": "Menu di Navigazione",
"navbarDescription": "Menu di navigazione principale dell'applicazione",
+2
View File
@@ -1397,6 +1397,7 @@
"createOrgUser": "조직 사용자 생성",
"actionUpdateOrg": "조직 업데이트",
"actionRemoveInvitation": "초대 제거",
"actionRemoveUserRole": "Remove User Role",
"actionUpdateUser": "사용자 업데이트",
"actionGetUser": "사용자 조회",
"actionGetOrgUser": "조직 사용자 가져오기",
@@ -1516,6 +1517,7 @@
"otpAuthDescription": "인증 앱에서 코드를 입력하거나 단일 사용 백업 코드 중 하나를 입력하세요.",
"otpAuthSubmit": "코드 제출",
"idpContinue": "또는 계속 진행하십시오.",
"idpLastUsed": "Last Used",
"otpAuthBack": "비밀번호로 돌아가기",
"navbar": "탐색 메뉴",
"navbarDescription": "애플리케이션의 주요 탐색 메뉴",
+2
View File
@@ -1397,6 +1397,7 @@
"createOrgUser": "Opprett Org bruker",
"actionUpdateOrg": "Oppdater organisasjon",
"actionRemoveInvitation": "Fjern invitasjon",
"actionRemoveUserRole": "Remove User Role",
"actionUpdateUser": "Oppdater bruker",
"actionGetUser": "Hent bruker",
"actionGetOrgUser": "Hent organisasjonsbruker",
@@ -1516,6 +1517,7 @@
"otpAuthDescription": "Skriv inn koden fra autentiseringsappen din eller en av dine engangs reservekoder.",
"otpAuthSubmit": "Send inn kode",
"idpContinue": "Eller fortsett med",
"idpLastUsed": "Last Used",
"otpAuthBack": "Tilbake til passord",
"navbar": "Navigasjonsmeny",
"navbarDescription": "Hovednavigasjonsmeny for applikasjonen",
+2
View File
@@ -1397,6 +1397,7 @@
"createOrgUser": "Org gebruiker aanmaken",
"actionUpdateOrg": "Organisatie bijwerken",
"actionRemoveInvitation": "Verwijder uitnodiging",
"actionRemoveUserRole": "Remove User Role",
"actionUpdateUser": "Gebruiker bijwerken",
"actionGetUser": "Gebruiker ophalen",
"actionGetOrgUser": "Krijg organisatie-gebruiker",
@@ -1516,6 +1517,7 @@
"otpAuthDescription": "Voer de code van je authenticator-app of een van je reservekopiecodes voor het eenmalig gebruik in.",
"otpAuthSubmit": "Code indienen",
"idpContinue": "Of ga verder met",
"idpLastUsed": "Last Used",
"otpAuthBack": "Terug naar wachtwoord",
"navbar": "Navigatiemenu",
"navbarDescription": "Hoofd navigatie menu voor de applicatie",
+2
View File
@@ -1397,6 +1397,7 @@
"createOrgUser": "Utwórz użytkownika Org",
"actionUpdateOrg": "Aktualizuj organizację",
"actionRemoveInvitation": "Usuń zaproszenie",
"actionRemoveUserRole": "Remove User Role",
"actionUpdateUser": "Zaktualizuj użytkownika",
"actionGetUser": "Pobierz użytkownika",
"actionGetOrgUser": "Pobierz użytkownika organizacji",
@@ -1516,6 +1517,7 @@
"otpAuthDescription": "Wprowadź kod z aplikacji uwierzytelniającej lub jeden z jednorazowych kodów zapasowych.",
"otpAuthSubmit": "Wyślij kod",
"idpContinue": "Lub kontynuuj z",
"idpLastUsed": "Last Used",
"otpAuthBack": "Powrót do hasła",
"navbar": "Menu nawigacyjne",
"navbarDescription": "Główne menu nawigacyjne aplikacji",
+2
View File
@@ -1397,6 +1397,7 @@
"createOrgUser": "Criar utilizador Org",
"actionUpdateOrg": "Atualizar Organização",
"actionRemoveInvitation": "Remover Convite",
"actionRemoveUserRole": "Remove User Role",
"actionUpdateUser": "Atualizar Usuário",
"actionGetUser": "Obter Usuário",
"actionGetOrgUser": "Obter Utilizador da Organização",
@@ -1516,6 +1517,7 @@
"otpAuthDescription": "Insira o código da sua aplicação de autenticação ou um dos seus códigos de backup de uso único.",
"otpAuthSubmit": "Submeter Código",
"idpContinue": "Ou continuar com",
"idpLastUsed": "Last Used",
"otpAuthBack": "Voltar à Palavra-passe",
"navbar": "Menu de Navegação",
"navbarDescription": "Menu de navegação principal da aplicação",
+2
View File
@@ -1397,6 +1397,7 @@
"createOrgUser": "Создать пользователя Org",
"actionUpdateOrg": "Обновить организацию",
"actionRemoveInvitation": "Удалить приглашение",
"actionRemoveUserRole": "Remove User Role",
"actionUpdateUser": "Обновить пользователя",
"actionGetUser": "Получить пользователя",
"actionGetOrgUser": "Получить пользователя организации",
@@ -1516,6 +1517,7 @@
"otpAuthDescription": "Введите код из вашего приложения-аутентификатора или один из ваших одноразовых резервных кодов.",
"otpAuthSubmit": "Отправить код",
"idpContinue": "Или продолжить с",
"idpLastUsed": "Last Used",
"otpAuthBack": "Назад к паролю",
"navbar": "Навигационное меню",
"navbarDescription": "Главное навигационное меню приложения",
+2
View File
@@ -1397,6 +1397,7 @@
"createOrgUser": "Organizasyon Kullanıcısı Oluştur",
"actionUpdateOrg": "Kuruluşu Güncelle",
"actionRemoveInvitation": "Daveti Kaldır",
"actionRemoveUserRole": "Remove User Role",
"actionUpdateUser": "Kullanıcıyı Güncelle",
"actionGetUser": "Kullanıcıyı Getir",
"actionGetOrgUser": "Kuruluş Kullanıcısını Al",
@@ -1516,6 +1517,7 @@
"otpAuthDescription": "Authenticator uygulamanızdan veya tek kullanımlık yedek kodlarınızdan birini girin.",
"otpAuthSubmit": "Kodu Gönder",
"idpContinue": "Veya devam et:",
"idpLastUsed": "Last Used",
"otpAuthBack": "Şifreye Geri Dön",
"navbar": "Navigasyon Menüsü",
"navbarDescription": "Uygulamanın ana navigasyon menüsü",
+2
View File
@@ -1397,6 +1397,7 @@
"createOrgUser": "创建组织用户",
"actionUpdateOrg": "更新组织",
"actionRemoveInvitation": "移除邀请",
"actionRemoveUserRole": "Remove User Role",
"actionUpdateUser": "更新用户",
"actionGetUser": "获取用户",
"actionGetOrgUser": "获取组织用户",
@@ -1516,6 +1517,7 @@
"otpAuthDescription": "从您的身份验证程序中输入代码或您的单次备份代码。",
"otpAuthSubmit": "提交代码",
"idpContinue": "或者继续",
"idpLastUsed": "Last Used",
"otpAuthBack": "回到密码",
"navbar": "导航菜单",
"navbarDescription": "应用程序的主导航菜单",
+6
View File
@@ -34,6 +34,12 @@ import {
rebuildClientAssociationsFromSiteResource,
waitForSiteResourceRebuildIdle
} from "../rebuildClientAssociations";
import { build } from "@server/build";
import HttpCode from "@server/types/HttpCode";
import createHttpError from "http-errors";
import next from "next";
import { LimitId } from "../billing";
import { usageService } from "../billing/usageService";
type ApplyBlueprintArgs = {
orgId: string;
+7 -2
View File
@@ -26,6 +26,9 @@ import { createCertificate } from "#dynamic/routers/certificates/createCertifica
import { isLicensedOrSubscribed } from "#dynamic/lib/isLicencedOrSubscribed";
import { tierMatrix } from "../billing/tierMatrix";
import { build } from "@server/build";
import HttpCode from "@server/types/HttpCode";
import createHttpError from "http-errors";
import next from "next";
import { LimitId } from "../billing";
import { usageService } from "../billing/usageService";
@@ -240,7 +243,8 @@ export async function updatePrivateResources(
scheme: resourceData.scheme,
destination: resourceData.destination,
destinationPort: resourceData["destination-port"],
enabled: resourceData.enabled ?? true,
enabled: true, // hardcoded for now
// enabled: resourceData.enabled ?? true,
alias: resourceData.alias || null,
disableIcmp:
resourceData["disable-icmp"] ||
@@ -492,7 +496,8 @@ export async function updatePrivateResources(
scheme: resourceData.scheme,
destination: resourceData.destination,
destinationPort: resourceData["destination-port"],
enabled: resourceData.enabled ?? true,
enabled: true, // hardcoded for now
// enabled: resourceData.enabled ?? true,
alias: resourceData.alias || null,
aliasAddress: aliasAddress,
disableIcmp:
+1 -1
View File
@@ -470,7 +470,7 @@ export const PrivateResourceSchema = z
// proxyPort: z.int().positive().optional(),
"destination-port": z.int().positive().optional(),
destination: z.string().min(1).optional(),
enabled: z.boolean().default(true),
// enabled: z.boolean().default(true),
"tcp-ports": portRangeStringSchema.optional().default("*"),
"udp-ports": portRangeStringSchema.optional().default("*"),
"disable-icmp": z.boolean().optional().default(false),
-9
View File
@@ -496,7 +496,6 @@ export function generateRemoteSubnets(
): string[] {
const remoteSubnets = allSiteResources
.filter((sr) => {
if (!sr.enabled) return false;
if (!sr.destination) return false;
if (sr.mode === "cidr") {
@@ -531,7 +530,6 @@ export function generateAliasConfig(allSiteResources: SiteResource[]): Alias[] {
return allSiteResources
.filter(
(sr) =>
sr.enabled &&
sr.aliasAddress &&
((sr.alias && (sr.mode == "host" || sr.mode == "ssh")) ||
(sr.fullDomain && sr.mode == "http"))
@@ -664,13 +662,6 @@ export async function generateSubnetProxyTargetV2(
subnet: string | null;
}[]
): Promise<SubnetProxyTargetV2[] | undefined> {
if (!siteResource.enabled) {
logger.debug(
`Site resource ${siteResource.siteResourceId} is disabled, skipping target generation.`
);
return;
}
if (clients.length === 0) {
logger.debug(
`No clients have access to site resource ${siteResource.siteResourceId}, skipping target generation.`
+16 -30
View File
@@ -1561,19 +1561,9 @@ export async function handleMessagingForUpdatedSiteResource(
updatedSiteResource.udpPortRangeString ||
existingSiteResource.disableIcmp !==
updatedSiteResource.disableIcmp);
// Toggling enabled on/off doesn't change any of the fields above, but it
// does change whether targets/peer data should exist at all, so it needs
// to drive the same old->new diff machinery: going enabled->disabled
// diffs "real data" against "nothing" (a remove), and disabled->enabled
// diffs "nothing" against "real data" (an add). generateSubnetProxyTargetV2/
// generateRemoteSubnets/generateAliasConfig already return nothing for a
// disabled resource, so no other changes are needed here.
const enabledChanged =
existingSiteResource &&
existingSiteResource.enabled !== updatedSiteResource.enabled;
logger.debug(
`handleMessagingForUpdatedSiteResource: change flags destinationChanged=${Boolean(destinationChanged)} destinationPortChanged=${Boolean(destinationPortChanged)} aliasChanged=${Boolean(aliasChanged)} fullDomainChanged=${Boolean(fullDomainChanged)} sslChanged=${Boolean(sslChanged)} portRangesChanged=${Boolean(portRangesChanged)} enabledChanged=${Boolean(enabledChanged)}`
`handleMessagingForUpdatedSiteResource: change flags destinationChanged=${Boolean(destinationChanged)} destinationPortChanged=${Boolean(destinationPortChanged)} aliasChanged=${Boolean(aliasChanged)} fullDomainChanged=${Boolean(fullDomainChanged)} sslChanged=${Boolean(sslChanged)} portRangesChanged=${Boolean(portRangesChanged)}`
);
// if the existingSiteResource is undefined (new resource) we don't need to do anything here, the rebuild above handled it all
@@ -1584,16 +1574,14 @@ export async function handleMessagingForUpdatedSiteResource(
fullDomainChanged ||
sslChanged ||
portRangesChanged ||
destinationPortChanged ||
enabledChanged
destinationPortChanged
) {
const shouldUpdateTargets =
destinationChanged ||
sslChanged ||
portRangesChanged ||
fullDomainChanged ||
destinationPortChanged ||
enabledChanged;
destinationPortChanged;
logger.debug(
`handleMessagingForUpdatedSiteResource: entering unchanged-site update path shouldUpdateTargets=${shouldUpdateTargets}`
@@ -1669,22 +1657,20 @@ export async function handleMessagingForUpdatedSiteResource(
peerDataUpdateBatch.push({
clientId: client.clientId,
siteId,
remoteSubnets:
destinationChanged || enabledChanged
? {
oldRemoteSubnets:
!oldDestinationStillInUseBySite
? generateRemoteSubnets([
existingSiteResource
])
: [],
newRemoteSubnets: generateRemoteSubnets([
updatedSiteResource
])
}
: undefined,
remoteSubnets: destinationChanged
? {
oldRemoteSubnets: !oldDestinationStillInUseBySite
? generateRemoteSubnets([
existingSiteResource
])
: [],
newRemoteSubnets: generateRemoteSubnets([
updatedSiteResource
])
}
: undefined,
aliases:
aliasChanged || fullDomainChanged || enabledChanged // the full domain is sent down as an alias
aliasChanged || fullDomainChanged // the full domain is sent down as an alias
? {
oldAliases: generateAliasConfig([
existingSiteResource
+1 -6
View File
@@ -148,12 +148,7 @@ export async function buildClientConfigurationForNewtClient(
.from(siteResources)
.innerJoin(networks, eq(siteResources.networkId, networks.networkId))
.innerJoin(siteNetworks, eq(networks.networkId, siteNetworks.networkId))
.where(
and(
eq(siteNetworks.siteId, siteId),
eq(siteResources.enabled, true)
)
)
.where(eq(siteNetworks.siteId, siteId))
.then((rows) => rows.map((r) => r.siteResources));
const targetsToSend: SubnetProxyTargetV2[] = [];
+2 -8
View File
@@ -16,7 +16,7 @@ import {
generateRemoteSubnets
} from "@server/lib/ip";
import logger from "@server/logger";
import { and, eq, inArray } from "drizzle-orm";
import { eq, inArray } from "drizzle-orm";
import { addPeer, deletePeer } from "../newt/peers";
import config from "@server/lib/config";
@@ -70,13 +70,7 @@ export async function buildSiteConfigurationForOlmClient(
.innerJoin(networks, eq(siteResources.networkId, networks.networkId))
.innerJoin(siteNetworks, eq(networks.networkId, siteNetworks.networkId))
.where(
and(
eq(
clientSiteResourcesAssociationsCache.clientId,
client.clientId
),
eq(siteResources.enabled, true)
)
eq(clientSiteResourcesAssociationsCache.clientId, client.clientId)
);
const siteResourcesBySiteId = new Map<number, SiteResource[]>();
@@ -56,6 +56,7 @@ const createSiteResourceSchema = z
siteId: z.number().int().positive().optional(), // DEPRECATED: for backward compatibility, we will convert this to siteIds array if provided
destinationPort: z.int().positive().optional(),
destination: z.string().min(1).nullish(),
enabled: z.boolean().default(true),
alias: z
.string()
.regex(
@@ -274,6 +275,7 @@ export async function createSiteResource(
scheme,
destinationPort,
destination,
enabled,
ssl,
alias,
userIds,
@@ -537,6 +539,7 @@ export async function createSiteResource(
destination: destination, // the ssh can be null
scheme,
destinationPort,
enabled,
alias: alias ? alias.trim() : null,
aliasAddress,
tcpPortRangeString: tcpPortRangeStringAdjusted,
@@ -152,11 +152,6 @@ const updateSiteResourceSchema = z
)
.refine(
(data) => {
// this is a partial update; only enforce destination when the
// caller is actually changing mode or destination
if (data.mode === undefined && data.destination === undefined) {
return true;
}
// destination is only optional for ssh mode with native authDaemonMode
if (data.mode === "ssh" && data.authDaemonMode === "native") {
return true;
@@ -416,10 +411,8 @@ export async function updateSiteResource(
: [];
const existingSiteIds = existingSiteNetworks.map((sn) => sn.siteId);
// undefined means "leave unchanged" (partial update); only nulled out
// when the mode is explicitly being changed away from http
let fullDomain: string | null | undefined = undefined;
let finalSubdomain: string | null | undefined = undefined;
let fullDomain: string | null = null;
let finalSubdomain: string | null = null;
if (domainId) {
// Validate domain and construct full domain
const domainResult = await validateAndConstructDomain(
@@ -455,11 +448,6 @@ export async function updateSiteResource(
)
);
}
} else if (mode !== undefined && mode !== "http") {
// mode is explicitly changing away from http, so the resource
// can no longer have a domain associated with it
fullDomain = null;
finalSubdomain = null;
}
// make sure the alias is unique within the org if provided
@@ -528,28 +516,15 @@ export async function updateSiteResource(
destination: destination,
destinationPort: destinationPort,
enabled: enabled,
alias:
alias !== undefined
? alias
? alias.trim()
: null
: mode !== undefined &&
mode !== "host" &&
mode !== "ssh"
? null
: undefined,
alias: alias ? alias.trim() : null,
tcpPortRangeString: tcpPortRangeStringAdjusted,
udpPortRangeString:
mode == "http" || mode == "ssh"
? ""
: udpPortRangeString,
disableIcmp:
mode !== undefined
? disableIcmp ||
(mode == "http" || mode == "ssh"
? true
: false)
: disableIcmp,
disableIcmp ||
(mode == "http" || mode == "ssh" ? true : false),
domainId,
subdomain: finalSubdomain,
fullDomain,
@@ -16,14 +16,12 @@ import { Button } from "@app/components/ui/button";
import {
Form,
FormControl,
FormDescription,
FormField,
FormItem,
FormLabel,
FormMessage
} from "@app/components/ui/form";
import { Input } from "@app/components/ui/input";
import { SwitchInput } from "@app/components/SwitchInput";
import { createGeneralFormSchema } from "@app/lib/privateResourceForm";
import { zodResolver } from "@hookform/resolvers/zod";
import { useTranslations } from "next-intl";
@@ -43,8 +41,7 @@ export default function PrivateResourceGeneralPage() {
resolver: zodResolver(formSchema),
defaultValues: {
name: siteResource.name,
niceId: siteResource.niceId,
enabled: siteResource.enabled
niceId: siteResource.niceId
}
});
@@ -55,8 +52,7 @@ export default function PrivateResourceGeneralPage() {
const data = form.getValues();
await save({
name: data.name,
niceId: data.niceId,
enabled: data.enabled
niceId: data.niceId
});
}, null);
@@ -80,42 +76,6 @@ export default function PrivateResourceGeneralPage() {
id="private-resource-general-form"
>
<SettingsFormGrid>
<SettingsFormCell span="full">
<FormField
control={form.control}
name="enabled"
render={() => (
<FormItem>
<FormControl>
<SwitchInput
id="enable-resource"
defaultChecked={
siteResource.enabled
}
label={t(
"resourceEnable"
)}
onCheckedChange={(
val
) =>
form.setValue(
"enabled",
val
)
}
/>
</FormControl>
<FormDescription>
{t(
"disabledResourceDescription"
)}
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
</SettingsFormCell>
<SettingsFormCell span="half">
<FormField
control={form.control}
+1 -108
View File
@@ -23,7 +23,6 @@ import {
PopoverContent,
PopoverTrigger
} from "@app/components/ui/popover";
import { Switch } from "@app/components/ui/switch";
import { useEnvContext } from "@app/hooks/useEnvContext";
import { useNavigationContext } from "@app/hooks/useNavigationContext";
import { useOptimisticLabels } from "@app/hooks/useOptimisticLabels";
@@ -37,9 +36,7 @@ import { getPrivateResourceSettingsHref } from "@app/lib/launcherResourceAdminHr
import { getNextSortOrder, getSortDirection } from "@app/lib/sortColumn";
import { build } from "@server/build";
import { tierMatrix } from "@server/lib/billing/tierMatrix";
import { UpdateSiteResourceResponse } from "@server/routers/siteResource";
import type { PaginationState } from "@tanstack/react-table";
import { AxiosResponse } from "axios";
import {
ArrowDown01Icon,
ArrowRight,
@@ -52,17 +49,8 @@ import {
import { useTranslations } from "next-intl";
import Link from "next/link";
import { useRouter } from "next/navigation";
import {
startTransition,
useMemo,
useOptimistic,
useRef,
useState,
useTransition,
type ComponentRef
} from "react";
import { startTransition, useMemo, useState, useTransition } from "react";
import { useDebouncedCallback } from "use-debounce";
import z from "zod";
import { ColumnFilterButton } from "./ColumnFilterButton";
import { LabelColumnFilterButton } from "./LabelColumnFilterButton";
import { LabelsTableCell } from "./LabelsTableCell";
@@ -126,11 +114,6 @@ function isSafeUrlForLink(href: string): boolean {
}
}
const booleanSearchFilterSchema = z
.enum(["true", "false"])
.optional()
.catch(undefined);
type ClientResourcesTableProps = {
internalResources: InternalResourceRow[];
orgId: string;
@@ -191,30 +174,6 @@ export default function PrivateResourcesTable({
});
};
async function toggleInternalResourceEnabled(
val: boolean,
resourceId: number
) {
try {
await api.post<AxiosResponse<UpdateSiteResourceResponse>>(
`site-resource/${resourceId}`,
{
enabled: val
}
);
router.refresh();
} catch (e) {
toast({
variant: "destructive",
title: t("resourcesErrorUpdate"),
description: formatAxiosError(
e,
t("resourcesErrorUpdateDescription")
)
});
}
}
const deleteInternalResource = async (
resourceId: number,
siteId: number
@@ -470,36 +429,6 @@ export default function PrivateResourcesTable({
);
}
},
{
accessorKey: "enabled",
friendlyName: t("enabled"),
header: () => (
<ColumnFilterButton
options={[
{ value: "true", label: t("enabled") },
{ value: "false", label: t("disabled") }
]}
selectedValue={booleanSearchFilterSchema.parse(
searchParams.get("enabled")
)}
onValueChange={(value) =>
handleFilterChange("enabled", value)
}
searchPlaceholder={t("searchPlaceholder")}
emptyMessage={t("emptySearchOptions")}
label={t("enabled")}
className="p-3"
/>
),
cell: ({ row }) => (
<InternalResourceEnabledForm
resource={row.original}
onToggleInternalResourceEnabled={
toggleInternalResourceEnabled
}
/>
)
},
{
id: "labels",
accessorKey: "labels",
@@ -714,39 +643,3 @@ function ClientResourceLabelCell({
/>
);
}
type InternalResourceEnabledFormProps = {
resource: InternalResourceRow;
onToggleInternalResourceEnabled: (
val: boolean,
resourceId: number
) => Promise<void>;
};
function InternalResourceEnabledForm({
resource,
onToggleInternalResourceEnabled
}: InternalResourceEnabledFormProps) {
const [optimisticEnabled, setOptimisticEnabled] = useOptimistic(
resource.enabled
);
const formRef = useRef<ComponentRef<"form">>(null);
async function submitAction(formData: FormData) {
const newEnabled = !(formData.get("enabled") === "on");
setOptimisticEnabled(newEnabled);
await onToggleInternalResourceEnabled(newEnabled, resource.id);
}
return (
<form action={submitAction} ref={formRef}>
<Switch
checked={optimisticEnabled}
disabled={optimisticEnabled !== resource.enabled}
name="enabled"
onCheckedChange={() => formRef.current?.requestSubmit()}
/>
</form>
);
}
+2 -2
View File
@@ -165,6 +165,7 @@ export function buildCreateSiteResourcePayload(
siteIds: data.siteIds,
mode: data.mode,
destination: isNativeSsh ? undefined : (data.destination ?? undefined),
enabled: true,
...(data.mode === "http" && {
scheme: data.scheme,
ssl: data.ssl ?? false,
@@ -341,8 +342,7 @@ export function createGeneralFormSchema(t: TranslateFn) {
.string()
.min(1)
.max(255)
.regex(/^[a-zA-Z0-9-]+$/),
enabled: z.boolean()
.regex(/^[a-zA-Z0-9-]+$/)
});
}