pgkiln 0.58.0 → 0.59.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/docs/apex-feature-parity.md +1 -1
- package/docs/guide/03-builder.md +3 -2
- package/package.json +1 -1
- package/src/builder/i18n/de.ts +328 -0
- package/src/builder/i18n/en.ts +328 -0
- package/src/builder/i18n/es.ts +328 -0
- package/src/builder/i18n/fr.ts +328 -0
- package/src/builder/i18n/nl.ts +328 -0
- package/src/builder/i18n/pt.ts +328 -0
- package/src/builder/routes.ts +31 -30
- package/src/builder/shared.ts +2 -2
- package/src/builder/users.ts +123 -118
- package/src/builder/workspaces.ts +143 -135
package/src/builder/i18n/pt.ts
CHANGED
|
@@ -1383,4 +1383,332 @@ export const pt: BuilderMessages = {
|
|
|
1383
1383
|
'st.saved': 'Salvo.',
|
|
1384
1384
|
'st.settings_saved': 'Configurações salvas.',
|
|
1385
1385
|
'st.app_deleted': 'Aplicação excluída.',
|
|
1386
|
+
// ------------------------------------------------------------ Users and identity providers (sprint 125)
|
|
1387
|
+
'us.hints_none': 'Esta aplicação ainda não verifica nenhum papel. Papéis só importam onde um esquema de autorização ou {fn} os verifica (Shared Components → Esquemas de autorização).',
|
|
1388
|
+
'us.hints_label': 'Papéis que esta aplicação verifica',
|
|
1389
|
+
'us.hints_add': 'Adicionar',
|
|
1390
|
+
'us.hints_app': '{app} verifica',
|
|
1391
|
+
'us.title': 'Usuários',
|
|
1392
|
+
'us.providers_btn': 'Provedores de identidade (login único)',
|
|
1393
|
+
'us.ldap_btn': 'Diretórios LDAP',
|
|
1394
|
+
'us.intro': 'Uma conta por pessoa. Dê às contas acesso às aplicações, com papéis por aplicação, aqui ou em {where} de uma aplicação.',
|
|
1395
|
+
'us.access_path': 'Shared Components → Controle de acesso',
|
|
1396
|
+
'us.accounts': 'Contas',
|
|
1397
|
+
'us.search_ph': 'Pesquisar nome, usuário ou e-mail…',
|
|
1398
|
+
'us.search_label': 'Pesquisar contas',
|
|
1399
|
+
'us.search': 'Pesquisar',
|
|
1400
|
+
'us.username': 'Nome de usuário',
|
|
1401
|
+
'us.name': 'Nome',
|
|
1402
|
+
'us.apps_roles': 'Aplicações (papéis)',
|
|
1403
|
+
'us.applications': 'Aplicações',
|
|
1404
|
+
'us.signin': 'Login',
|
|
1405
|
+
'us.status': 'Status',
|
|
1406
|
+
'us.none': 'nenhuma',
|
|
1407
|
+
'us.password': 'senha',
|
|
1408
|
+
'us.no_password': 'sem senha',
|
|
1409
|
+
'us.active': 'ativa',
|
|
1410
|
+
'us.inactive': 'inativa',
|
|
1411
|
+
'us.last': 'último {at}',
|
|
1412
|
+
'us.no_accounts': 'Nenhuma conta encontrada.',
|
|
1413
|
+
'us.create': 'Criar conta',
|
|
1414
|
+
'us.username_help': 'Sem espaços nem dois-pontos; não diferencia maiúsculas.',
|
|
1415
|
+
'us.email': 'E-mail',
|
|
1416
|
+
'us.password_label': 'Senha',
|
|
1417
|
+
'us.password_help': 'Pelo menos 8 caracteres. Deixe vazio para contas que só entram por login único.',
|
|
1418
|
+
'us.must_change': 'Exigir a troca da senha no primeiro uso',
|
|
1419
|
+
'us.settings': 'Configurações das contas',
|
|
1420
|
+
'us.min_length': 'Comprimento mínimo da senha',
|
|
1421
|
+
'us.lifetime': 'Validade da senha (dias)',
|
|
1422
|
+
'us.lifetime_help': 'Após esse número de dias os usuários precisam escolher uma nova senha no login. 0 = nunca.',
|
|
1423
|
+
'us.mixed': 'As senhas precisam de letras e dígitos',
|
|
1424
|
+
'us.policy_note': 'As senhas nunca podem conter o nome de usuário. O login é bloqueado por {minutes} minutos após {failures} tentativas falhas (Utilitários do workspace → Configurações da instância); Desbloquear em uma conta remove o bloqueio.',
|
|
1425
|
+
'us.save_settings': 'Salvar configurações',
|
|
1426
|
+
'us.created': 'Conta criada. Agora dê a ela acesso a aplicações.',
|
|
1427
|
+
'us.exists': 'Já existe uma conta com esse nome de usuário.',
|
|
1428
|
+
'us.settings_saved': 'Configurações das contas salvas.',
|
|
1429
|
+
'us.details': 'Detalhes',
|
|
1430
|
+
'us.active_label': 'Ativa',
|
|
1431
|
+
'us.active_help': 'Contas inativas não podem entrar em nenhuma aplicação; desativar encerra suas sessões.',
|
|
1432
|
+
'us.save': 'Salvar',
|
|
1433
|
+
'us.password_title': 'Senha',
|
|
1434
|
+
'us.set': 'Definida',
|
|
1435
|
+
'us.set_on': 'Definida em {date}',
|
|
1436
|
+
'us.sso_only': 'Sem senha (somente login único)',
|
|
1437
|
+
'us.must_next': 'Deve ser trocada no próximo login',
|
|
1438
|
+
'us.expires': 'Expira em {n} dia(s)',
|
|
1439
|
+
'us.fails': '{n} login(s) com falha na última hora',
|
|
1440
|
+
'us.unexpire': 'Cancelar a expiração',
|
|
1441
|
+
'us.expire': 'Expirar a senha',
|
|
1442
|
+
'us.unlock': 'Desbloquear o login',
|
|
1443
|
+
'us.new_password': 'Nova senha',
|
|
1444
|
+
'us.set_password_label': 'Definir uma senha',
|
|
1445
|
+
'us.ends_sessions': 'Encerra as sessões da conta.',
|
|
1446
|
+
'us.set_password': 'Definir senha',
|
|
1447
|
+
'us.remove_confirm': 'Remover a senha? A conta só poderá entrar por login único.',
|
|
1448
|
+
'us.remove_password': 'Remover senha',
|
|
1449
|
+
'us.delete_confirm': 'Excluir a conta {user} e todos os seus acessos a aplicações?',
|
|
1450
|
+
'us.delete': 'Excluir conta',
|
|
1451
|
+
'us.app_access': 'Acesso a aplicações',
|
|
1452
|
+
'us.application': 'Aplicação',
|
|
1453
|
+
'us.roles': 'Papéis',
|
|
1454
|
+
'us.roles_in': 'Papéis em {app}',
|
|
1455
|
+
'us.roles_ph': 'sem papéis (por exemplo {example})',
|
|
1456
|
+
'us.revoke_confirm': 'Revogar o acesso a {app}?',
|
|
1457
|
+
'us.revoke': 'Revogar',
|
|
1458
|
+
'us.no_access': 'Ainda sem acesso a nenhuma aplicação.',
|
|
1459
|
+
'us.grant': 'Conceder acesso',
|
|
1460
|
+
'us.roles_comma': 'separados por vírgulas, ou escolha abaixo',
|
|
1461
|
+
'us.saved': 'Conta salva.',
|
|
1462
|
+
'us.pw_removed': 'Senha removida.',
|
|
1463
|
+
'us.pw_set': 'Senha definida.',
|
|
1464
|
+
'us.pw_expired': 'Senha expirada: precisa ser trocada no próximo login.',
|
|
1465
|
+
'us.pw_unexpired': 'A senha não está mais expirada.',
|
|
1466
|
+
'us.unlocked': 'Login desbloqueado.',
|
|
1467
|
+
'us.deleted': 'Conta excluída.',
|
|
1468
|
+
'us.granted': 'Acesso concedido.',
|
|
1469
|
+
'us.roles_saved': 'Papéis salvos. O usuário os recebe no próximo login.',
|
|
1470
|
+
'us.revoked': 'Acesso revogado.',
|
|
1471
|
+
'idp.title': 'Provedores de identidade',
|
|
1472
|
+
'idp.name': 'Nome (nas URLs)',
|
|
1473
|
+
'idp.name_help': 'em minúsculas, por exemplo entra, google, keycloak',
|
|
1474
|
+
'idp.protocol': 'Protocolo',
|
|
1475
|
+
'idp.label': 'Rótulo do botão',
|
|
1476
|
+
'idp.label_help': 'Exibido como “Entrar com …”',
|
|
1477
|
+
'idp.issuer': 'Emissor (OIDC: URL; SAML: o entity ID do IdP)',
|
|
1478
|
+
'idp.issuer_help': 'por exemplo https://login.microsoftonline.com/<tenant>/v2.0, https://keycloak.example.com/realms/acme, ou para SAML o entityID dos metadados do IdP',
|
|
1479
|
+
'idp.client_id': 'ID do cliente (SAML: o entity ID do pgkiln)',
|
|
1480
|
+
'idp.client_id_help': 'OIDC: obrigatório. SAML: vazio = a URL de metadados deste provedor.',
|
|
1481
|
+
'idp.sso_url': 'SAML: URL de login do IdP',
|
|
1482
|
+
'idp.sso_url_help': 'O endereço do SingleSignOnService (binding HTTP-Redirect) nos metadados do IdP.',
|
|
1483
|
+
'idp.cert': 'SAML: certificado de assinatura do IdP (PEM)',
|
|
1484
|
+
'idp.cert_help': 'Dos metadados do IdP (X509Certificate, entre as linhas BEGIN/END CERTIFICATE). As asserções precisam ser assinadas com ele.',
|
|
1485
|
+
'idp.secret': 'Segredo do cliente',
|
|
1486
|
+
'idp.secret_kept': 'Há um segredo armazenado. Deixe vazio para mantê-lo.',
|
|
1487
|
+
'idp.secret_public': 'Deixe vazio para um cliente público (somente PKCE).',
|
|
1488
|
+
'idp.scopes': 'Escopos',
|
|
1489
|
+
'idp.username_claim': 'Claim do nome de usuário',
|
|
1490
|
+
'idp.username_claim_help': 'Use um claim que os usuários não possam alterar (por exemplo preferred_username, upn, email). SAML: um nome de atributo ou nameID.',
|
|
1491
|
+
'idp.groups_claim': 'Claim de grupos',
|
|
1492
|
+
'idp.groups_claim_help': 'Caminhos com pontos funcionam, por exemplo realm_access.roles',
|
|
1493
|
+
'idp.auto_create': 'Criar contas automaticamente no primeiro login',
|
|
1494
|
+
'idp.auto_create_help': 'Caso contrário, só pessoas com uma conta vinculada a este provedor (ou permitidas abaixo) podem entrar.',
|
|
1495
|
+
'idp.link': 'Vincular contas existentes com o mesmo nome de usuário no primeiro login',
|
|
1496
|
+
'idp.link_help': 'Somente quando os usuários não podem escolher o claim do nome neste provedor: caso contrário, alguém poderia registrar o nome de uma conta existente e tomá-la. Com o claim de e-mail, só endereços verificados são vinculados. Desligue depois que as contas estiverem vinculadas.',
|
|
1497
|
+
'idp.enabled': 'Ativado',
|
|
1498
|
+
'idp.remove_secret': 'Remover o segredo do cliente armazenado',
|
|
1499
|
+
'idp.add': 'Adicionar provedor',
|
|
1500
|
+
'idp.intro': 'Provedores OpenID Connect para login único (Microsoft Entra ID, Google, Okta, Keycloak, Auth0, …). Registre o pgkiln no provedor como aplicação web com a URI de redirecionamento mostrada e ative o provedor por aplicação em {where}.',
|
|
1501
|
+
'idp.where': 'Configurações → Métodos de login',
|
|
1502
|
+
'idp.providers': 'Provedores',
|
|
1503
|
+
'idp.provider': 'Provedor',
|
|
1504
|
+
'idp.issuer_col': 'Emissor',
|
|
1505
|
+
'idp.used_by': 'Usado por',
|
|
1506
|
+
'idp.linked': 'Contas vinculadas',
|
|
1507
|
+
'idp.no_apps': 'nenhuma aplicação',
|
|
1508
|
+
'idp.enabled_s': 'ativado',
|
|
1509
|
+
'idp.disabled_s': 'desativado',
|
|
1510
|
+
'idp.auto_s': 'criação automática',
|
|
1511
|
+
'idp.links_s': 'vincula contas existentes',
|
|
1512
|
+
'idp.none': 'Ainda não há provedores de identidade.',
|
|
1513
|
+
'idp.settings': 'Configurações',
|
|
1514
|
+
'idp.delete_confirm': 'Excluir {name}? As identidades vinculadas são removidas; as contas ficam.',
|
|
1515
|
+
'idp.delete': 'Excluir provedor',
|
|
1516
|
+
'idp.register_idp': 'Registrar no provedor de identidade',
|
|
1517
|
+
'idp.sp_metadata': 'Metadados do provedor de serviço (importe-os no IdP):',
|
|
1518
|
+
'idp.by_hand': 'Ou à mão: entity ID {entity}, assertion consumer service (HTTP-POST) {acs}.',
|
|
1519
|
+
'idp.saml_note': 'Baseado em {env}. Assine as asserções; envie os grupos no atributo {claim} para mapeá-los a papéis.',
|
|
1520
|
+
'idp.register': 'Registrar no provedor',
|
|
1521
|
+
'idp.redirect': 'URI de redirecionamento (callback):',
|
|
1522
|
+
'idp.public_url': 'Baseado em {env}; defina-o como o endereço que os usuários veem (por exemplo https://apps.example.com).',
|
|
1523
|
+
'idp.grant': 'Tipo de concessão: {type} com PKCE. Escopos: {scopes}. Inclua um claim {claim} no token de ID para mapear grupos a papéis.',
|
|
1524
|
+
'idp.auth_code': 'código de autorização',
|
|
1525
|
+
'idp.test': 'Testar a descoberta',
|
|
1526
|
+
'idp.added': 'Provedor adicionado. Registre a URI de redirecionamento no provedor e depois teste a conexão.',
|
|
1527
|
+
'idp.saved': 'Provedor salvo.',
|
|
1528
|
+
'idp.works': 'A descoberta funciona. Endpoint de autorização: {url}',
|
|
1529
|
+
'idp.failed': 'A descoberta falhou: {error}',
|
|
1530
|
+
'idp.deleted': 'Provedor excluído.',
|
|
1531
|
+
// ------------------------------------------------------------ Workspaces (sprint 125)
|
|
1532
|
+
'wsp.title': 'Workspaces',
|
|
1533
|
+
'wsp.admins_only': 'Somente administradores gerenciam workspaces.',
|
|
1534
|
+
'wsp.err.exists': 'Já existe um workspace com este nome.',
|
|
1535
|
+
'wsp.err.name_check': 'O nome tem de 1 a 60 caracteres, sem espaços no início ou no fim.',
|
|
1536
|
+
'wsp.err.name': 'O nome tem de 1 a 60 caracteres.',
|
|
1537
|
+
'wsp.iso.secret': 'defina PGKILN_SECRET_KEY (pelo menos 32 caracteres) no ambiente do servidor: os logins de banco de dados do workspace recebem senhas derivadas dela.',
|
|
1538
|
+
'wsp.iso.runtime': 'defina RUNTIME_DATABASE_URL: as aplicações são executadas na conexão do proprietário, então nenhum workspace pode ser isolado.',
|
|
1539
|
+
'wsp.err.role_taken': 'este papel já pertence a outro workspace.',
|
|
1540
|
+
'wsp.err.role_has': 'o workspace já tem este papel.',
|
|
1541
|
+
'wsp.isolated_tag': 'isolado',
|
|
1542
|
+
'wsp.iso.on': 'Suas aplicações são executadas como {run} e o SQL de seus desenvolvedores (SQL Workshop, verificações de código, o Advisor) como {dev}. Ambos alcançam somente os papéis abaixo e veem nas tabelas do pgkiln somente as aplicações, sessões e contas deste workspace. Desenvolvedores que também pertencem a um workspace compartilhado mantêm lá os direitos dele.',
|
|
1543
|
+
'wsp.db_role': 'Papel do banco de dados',
|
|
1544
|
+
'wsp.used_by': 'Usado por',
|
|
1545
|
+
'wsp.missing': 'ausente',
|
|
1546
|
+
'wsp.no_app': 'nenhuma aplicação',
|
|
1547
|
+
'wsp.remove': 'Remover',
|
|
1548
|
+
'wsp.no_roles': 'Nenhum papel de banco de dados: as aplicações daqui não podem ser executadas com um papel próprio.',
|
|
1549
|
+
'wsp.add_role': 'Adicionar um papel de banco de dados',
|
|
1550
|
+
'wsp.add_role_help': 'Um papel existente (não um superusuário, nem um que cria papéis ou ignora a segurança em nível de linha). Seus direitos passam a ser os deste workspace; ele não pode pertencer a outro.',
|
|
1551
|
+
'wsp.add_role_btn': 'Adicionar papel',
|
|
1552
|
+
'wsp.make_shared_confirm': 'Tornar {name} compartilhado de novo? Suas aplicações passam a ser executadas na conexão de runtime compartilhada, e o SQL de seus desenvolvedores como proprietário.',
|
|
1553
|
+
'wsp.make_shared': 'Tornar compartilhado',
|
|
1554
|
+
'wsp.shared_text': 'Compartilhado: as aplicações são executadas na conexão de runtime compartilhada e o SQL dos desenvolvedores como proprietário, como em todo workspace compartilhado.',
|
|
1555
|
+
'wsp.iso_help': 'Isolar dá ao workspace dois logins de banco de dados próprios e os papéis que suas aplicações usam agora. O código das aplicações então alcança somente esses papéis e as aplicações deste workspace; o SQL Workshop de seus desenvolvedores é executado no login do workspace em vez do proprietário. Novas aplicações recebem um papel e um esquema próprios.',
|
|
1556
|
+
'wsp.to_isolate': 'Para isolar um workspace, {problem}',
|
|
1557
|
+
'wsp.isolate_confirm': 'Isolar {name}?',
|
|
1558
|
+
'wsp.isolate': 'Isolar este workspace',
|
|
1559
|
+
'wsp.isolation': 'Isolamento',
|
|
1560
|
+
'wsp.service': 'Gerenciar o serviço',
|
|
1561
|
+
'wsp.message': 'Mensagem do workspace',
|
|
1562
|
+
'wsp.message_help': 'Exibida aos desenvolvedores deste workspace no início do App Builder, por exemplo uma janela de manutenção. Texto simples.',
|
|
1563
|
+
'wsp.banner': 'Faixa de ambiente',
|
|
1564
|
+
'wsp.banner_help': 'Um rótulo curto no topo de cada página do builder enquanto este workspace estiver ativo. Vazio: sem faixa.',
|
|
1565
|
+
'wsp.banner_colour': 'Cor da faixa',
|
|
1566
|
+
'wsp.save': 'Salvar',
|
|
1567
|
+
'wsp.prefs': 'Preferências do workspace',
|
|
1568
|
+
'wsp.sql_on': 'Ativar o SQL Workshop',
|
|
1569
|
+
'wsp.sql_on_help': 'Desativado: a Planilha SQL, o Browser de Objetos, os scripts SQL, a carga e descarga de dados, o Quick SQL, o construtor de consultas, DDL, Comparar e IA são recusados enquanto este workspace estiver ativo, para todos os desenvolvedores.',
|
|
1570
|
+
'wsp.rest_on': 'Ativar os serviços RESTful',
|
|
1571
|
+
'wsp.rest_on_help': 'Desativado: os módulos REST das aplicações deste workspace respondem 404 (Not found) em {path}; ainda podem ser editados.',
|
|
1572
|
+
'wsp.builder_home': 'Links do builder abrem',
|
|
1573
|
+
'wsp.home.apps': 'App Builder',
|
|
1574
|
+
'wsp.home.home': 'Início do workspace',
|
|
1575
|
+
'wsp.builder_home_help': 'Para onde um link para /builder (um favorito de antes do início do workspace) leva neste workspace. Entrar sempre abre o início do workspace; o App Builder fica em /builder/apps.',
|
|
1576
|
+
'wsp.save_prefs': 'Salvar preferências',
|
|
1577
|
+
'wsp.links': 'Links de extensões',
|
|
1578
|
+
'wsp.links_help': 'Links para outras ferramentas que os desenvolvedores deste workspace usam (monitoramento, uma wiki, um sistema de tickets), no menu {menu} do builder enquanto o workspace estiver ativo. Abrem em uma nova aba.',
|
|
1579
|
+
'wsp.extensions': 'Extensões',
|
|
1580
|
+
'wsp.label': 'Rótulo',
|
|
1581
|
+
'wsp.url': 'URL',
|
|
1582
|
+
'wsp.order': 'Ordem',
|
|
1583
|
+
'wsp.delete_link_confirm': 'Excluir o link {label}?',
|
|
1584
|
+
'wsp.delete': 'Excluir',
|
|
1585
|
+
'wsp.no_links': 'Nenhum link de extensão.',
|
|
1586
|
+
'wsp.label_ph': 'Monitoramento',
|
|
1587
|
+
'wsp.description': 'Descrição',
|
|
1588
|
+
'wsp.add_link': 'Adicionar link',
|
|
1589
|
+
'wsp.switched': 'Workspace {name}.',
|
|
1590
|
+
'wsp.intro': 'Um workspace agrupa aplicações e os desenvolvedores que as constroem. Os desenvolvedores veem e alteram as aplicações de seus próprios workspaces; administradores veem todas. Um workspace compartilhado não é uma fronteira de segurança entre desenvolvedores que escrevem SQL: seu SQL Workshop e o código das aplicações são executados com os direitos desta instalação. Um workspace {isolated} é: suas aplicações e seu SQL são executados em logins de banco de dados próprios que alcançam somente seus papéis e suas próprias aplicações.',
|
|
1591
|
+
'wsp.isolated_word': 'isolado',
|
|
1592
|
+
'wsp.workspace': 'Workspace',
|
|
1593
|
+
'wsp.applications': 'Aplicações',
|
|
1594
|
+
'wsp.developers': 'Desenvolvedores',
|
|
1595
|
+
'wsp.admins_text': 'somente administradores',
|
|
1596
|
+
'wsp.add': 'Adicionar workspace',
|
|
1597
|
+
'wsp.name': 'Nome',
|
|
1598
|
+
'wsp.name_help': 'Por exemplo a equipe ou o departamento que constrói aqui suas aplicações.',
|
|
1599
|
+
'wsp.first_dev': 'Você se torna seu primeiro desenvolvedor; adicione outros na página dele.',
|
|
1600
|
+
'wsp.added': 'Workspace {name} adicionado.',
|
|
1601
|
+
'wsp.settings': 'Configurações',
|
|
1602
|
+
'wsp.default_no_delete': 'O workspace Default não pode ser excluído: aplicações importadas e criadas pela linha de comando começam aqui.',
|
|
1603
|
+
'wsp.delete_confirm': 'Excluir o workspace {name}?',
|
|
1604
|
+
'wsp.delete_ws': 'Excluir workspace',
|
|
1605
|
+
'wsp.move_first': 'Mova primeiro suas aplicações para outro workspace.',
|
|
1606
|
+
'wsp.devs_help': 'Os desenvolvedores deste workspace veem e alteram suas aplicações. Administradores veem todos os workspaces.',
|
|
1607
|
+
'wsp.administrator': '(administrador)',
|
|
1608
|
+
'wsp.ws_admins': 'Administradores do workspace',
|
|
1609
|
+
'wsp.ws_admins_help': 'Eles gerenciam o nome e os desenvolvedores deste workspace. Isolamento, papéis de banco de dados, mover aplicações e excluir o workspace ficam com os administradores.',
|
|
1610
|
+
'wsp.save_devs': 'Salvar desenvolvedores',
|
|
1611
|
+
'wsp.mail_net': 'E-mail e acesso à rede',
|
|
1612
|
+
'wsp.mail_net_text': 'O {mail} próprio deste workspace para suas aplicações sem um próprio, sua lista de {network} (quais hosts suas aplicações podem chamar, dentro da lista da instalação) e a {queue}.',
|
|
1613
|
+
'wsp.mail_service': 'serviço de e-mail',
|
|
1614
|
+
'wsp.network': 'acesso à rede',
|
|
1615
|
+
'wsp.queue': 'fila de e-mails',
|
|
1616
|
+
'wsp.iso_short_on': 'Isolado: suas aplicações e seu SQL são executados em logins de banco de dados próprios.',
|
|
1617
|
+
'wsp.iso_short_off': 'Compartilhado.',
|
|
1618
|
+
'wsp.admins_change': 'Os administradores o alteram.',
|
|
1619
|
+
'wsp.application': 'Aplicação',
|
|
1620
|
+
'wsp.move_to': 'Mover para',
|
|
1621
|
+
'wsp.move_label': 'Mover {app} para',
|
|
1622
|
+
'wsp.move': 'Mover',
|
|
1623
|
+
'wsp.no_other': 'nenhum outro workspace',
|
|
1624
|
+
'wsp.no_apps': 'Nenhuma aplicação.',
|
|
1625
|
+
'wsp.saved': 'Workspace salvo.',
|
|
1626
|
+
'wsp.not_found': 'Não encontrado.',
|
|
1627
|
+
'wsp.devs_saved': 'Desenvolvedores salvos.',
|
|
1628
|
+
'wsp.not_moved': 'Não movida: {why}',
|
|
1629
|
+
'wsp.moved': 'Aplicação movida para {name}.',
|
|
1630
|
+
'wsp.err.default': 'O workspace Default não pode ser excluído.',
|
|
1631
|
+
'wsp.err.isolated': 'Torne o workspace compartilhado (não isolado) antes de excluí-lo.',
|
|
1632
|
+
'wsp.err.has_apps': 'Mova primeiro as aplicações deste workspace para outro.',
|
|
1633
|
+
'wsp.deleted': 'Workspace {name} excluído.',
|
|
1634
|
+
'wsp.isolated_now': 'O workspace está isolado: suas aplicações e seu SQL agora são executados em logins de banco de dados próprios.',
|
|
1635
|
+
'wsp.shared_now': 'O workspace está compartilhado de novo: suas aplicações são executadas na conexão de runtime compartilhada.',
|
|
1636
|
+
'wsp.not_isolated': 'Não isolado: {why}',
|
|
1637
|
+
'wsp.not_changed': 'Não alterado: {why}',
|
|
1638
|
+
'wsp.err.role_name': 'Um nome de papel é uma letra ou _ seguida de letras, dígitos, _ ou $ (no máximo 63).',
|
|
1639
|
+
'wsp.err.no_role': 'Não existe o papel de banco de dados {role}.',
|
|
1640
|
+
'wsp.err.role_used_one': '{apps} é executada como {role}: dê a ela outro papel primeiro.',
|
|
1641
|
+
'wsp.err.role_used': '{apps} são executadas como {role}: dê a elas outro papel primeiro.',
|
|
1642
|
+
'wsp.role_removed': 'Papel {role} removido.',
|
|
1643
|
+
'wsp.role_added': 'Papel {role} adicionado.',
|
|
1644
|
+
'wsp.not_saved': 'Não salvo: {why}',
|
|
1645
|
+
'wsp.err.message': 'A mensagem tem no máximo 2000 caracteres.',
|
|
1646
|
+
'wsp.err.banner': 'O rótulo da faixa tem no máximo 40 caracteres.',
|
|
1647
|
+
'wsp.service_saved': 'Mensagem e faixa salvas.',
|
|
1648
|
+
'wsp.prefs_saved': 'Preferências salvas.',
|
|
1649
|
+
'wsp.err.label': 'O rótulo tem de 1 a 60 caracteres.',
|
|
1650
|
+
'wsp.err.url': 'A URL começa com http:// ou https://.',
|
|
1651
|
+
'wsp.err.description': 'A descrição tem no máximo 500 caracteres.',
|
|
1652
|
+
'wsp.err.order': 'A ordem é um número inteiro.',
|
|
1653
|
+
'wsp.link_added': 'Link {label} adicionado.',
|
|
1654
|
+
'wsp.link_exists': 'Já existe um link {label}.',
|
|
1655
|
+
'wsp.link_not_saved': 'O link não foi salvo.',
|
|
1656
|
+
'wsp.link_deleted': 'Link excluído.',
|
|
1657
|
+
'wsp.util_title': 'Utilização do workspace: {name}',
|
|
1658
|
+
'wsp.pages': 'Páginas',
|
|
1659
|
+
'wsp.views30': 'Visualizações de página (30 dias)',
|
|
1660
|
+
'wsp.scripts': 'Scripts SQL',
|
|
1661
|
+
'wsp.regions': 'Regiões',
|
|
1662
|
+
'wsp.items': 'Itens',
|
|
1663
|
+
'wsp.users30': 'Usuários (30 dias)',
|
|
1664
|
+
'wsp.static': 'Arquivos estáticos',
|
|
1665
|
+
'wsp.views': 'Visualizações de página',
|
|
1666
|
+
'wsp.users': 'Usuários',
|
|
1667
|
+
'wsp.schemas': 'Esquemas',
|
|
1668
|
+
'wsp.schemas_help': 'Os esquemas que os papéis de banco de dados do workspace podem usar, com o armazenamento de suas tabelas, índices e dados TOAST.',
|
|
1669
|
+
'wsp.schema': 'Esquema',
|
|
1670
|
+
'wsp.owner': 'Proprietário',
|
|
1671
|
+
'wsp.tables': 'Tabelas',
|
|
1672
|
+
'wsp.storage': 'Armazenamento',
|
|
1673
|
+
'wsp.no_schemas': 'Nenhum esquema: as aplicações do workspace não têm papel de banco de dados próprio.',
|
|
1674
|
+
'wsp.utilization': 'Utilização',
|
|
1675
|
+
'wsp.util_crumb': 'Utilização: {name}',
|
|
1676
|
+
// ------------------------------------------------------------ Developers (sprint 125)
|
|
1677
|
+
'dev.title': 'Desenvolvedores',
|
|
1678
|
+
'dev.change_pw': 'Alterar sua senha',
|
|
1679
|
+
'dev.current': 'Senha atual',
|
|
1680
|
+
'dev.new': 'Nova senha',
|
|
1681
|
+
'dev.min8': 'Pelo menos 8 caracteres.',
|
|
1682
|
+
'dev.change_btn': 'Alterar senha',
|
|
1683
|
+
'dev.your_email': 'Seu endereço de e-mail',
|
|
1684
|
+
'dev.email': 'Endereço de e-mail',
|
|
1685
|
+
'dev.email_help': '“Encontrar meu workspace” na página de login envia seus workspaces para ele. Vazio: nenhum.',
|
|
1686
|
+
'dev.save': 'Salvar',
|
|
1687
|
+
'dev.accounts': 'Contas de desenvolvedor',
|
|
1688
|
+
'dev.accounts_help': 'Administradores adicionam e removem desenvolvedores e podem quebrar os bloqueios de páginas e aplicações de outros.',
|
|
1689
|
+
'dev.username': 'Nome de usuário',
|
|
1690
|
+
'dev.role': 'Papel',
|
|
1691
|
+
'dev.email_col': 'E-mail',
|
|
1692
|
+
'dev.you': '(você)',
|
|
1693
|
+
'dev.administrator': 'Administrador',
|
|
1694
|
+
'dev.developer': 'Desenvolvedor',
|
|
1695
|
+
'dev.email_of': 'Endereço de e-mail de {user}',
|
|
1696
|
+
'dev.none': 'nenhum',
|
|
1697
|
+
'dev.make_dev': 'Tornar desenvolvedor',
|
|
1698
|
+
'dev.make_admin': 'Tornar administrador',
|
|
1699
|
+
'dev.remove_confirm': 'Remover o desenvolvedor {user}?',
|
|
1700
|
+
'dev.remove': 'Remover',
|
|
1701
|
+
'dev.add': 'Adicionar desenvolvedor',
|
|
1702
|
+
'dev.password': 'Senha',
|
|
1703
|
+
'dev.optional_email': 'Opcional: para “Encontrar meu workspace”.',
|
|
1704
|
+
'dev.admins_only': 'Somente administradores adicionam e removem desenvolvedores.',
|
|
1705
|
+
'dev.err.email': 'O endereço de e-mail não é válido.',
|
|
1706
|
+
'dev.added_ws': 'Desenvolvedor adicionado ao workspace {name}.',
|
|
1707
|
+
'dev.added': 'Desenvolvedor adicionado.',
|
|
1708
|
+
'dev.err.current': 'A senha atual não está correta.',
|
|
1709
|
+
'dev.pw_changed': 'Senha alterada.',
|
|
1710
|
+
'dev.email_saved': 'Endereço de e-mail salvo.',
|
|
1711
|
+
'dev.not_found': 'Não encontrado.',
|
|
1712
|
+
'dev.role_changed': 'Papel alterado.',
|
|
1713
|
+
'dev.removed': 'Desenvolvedor removido.',
|
|
1386
1714
|
};
|
package/src/builder/routes.ts
CHANGED
|
@@ -689,37 +689,38 @@ export async function builderRoutes(app: FastifyInstance) {
|
|
|
689
689
|
const devs = (await owner.query('select username, is_admin, email from meta.developer order by 1')).rows;
|
|
690
690
|
const admin = devs.some((d) => d.username === s.username && d.is_admin);
|
|
691
691
|
const me = devs.find((d) => d.username === s.username);
|
|
692
|
-
const
|
|
692
|
+
const tx = tr(s);
|
|
693
|
+
const main = html`<h1 class="u-mb1">${tx('dev.title')}</h1>
|
|
693
694
|
<div class="columns">
|
|
694
|
-
${region('
|
|
695
|
+
${region(tx('dev.change_pw'), html`<form method="post" action="${BASE}/developers/password">${csrf(s)}
|
|
695
696
|
<div class="form-grid">
|
|
696
|
-
${input('current', '
|
|
697
|
-
${input('password', '
|
|
697
|
+
${input('current', tx('dev.current'), '', { type: 'password', required: true, auto: 'current-password' })}
|
|
698
|
+
${input('password', tx('dev.new'), '', { type: 'password', required: true, auto: 'new-password', help: tx('dev.min8') })}
|
|
698
699
|
</div>
|
|
699
|
-
<div class="buttons"><button class="btn btn-hot"
|
|
700
|
-
${region('
|
|
701
|
-
<div class="form-grid">${input('email', '
|
|
702
|
-
<div class="buttons"><button class="btn"
|
|
703
|
-
${region('
|
|
704
|
-
<p class="muted u-mt0"
|
|
705
|
-
<div class="table-wrap"><table class="report report-reflow"><thead><tr><th
|
|
706
|
-
${devs.map((d) => html`<tr><td data-label="
|
|
707
|
-
<td data-label="
|
|
708
|
-
? html`<form method="post" action="${BASE}/developers/email" class="search u-m0 u-mwnone">${csrf(s)}<input type="hidden" name="username" value="${d.username}"><input name="email" type="email" value="${d.email ?? ''}" aria-label="
|
|
700
|
+
<div class="buttons"><button class="btn btn-hot">${tx('dev.change_btn')}</button></div></form>`)}
|
|
701
|
+
${region(tx('dev.your_email'), html`<form method="post" action="${BASE}/developers/email">${csrf(s)}<input type="hidden" name="username" value="${s.username}">
|
|
702
|
+
<div class="form-grid">${input('email', tx('dev.email'), me?.email ?? '', { type: 'email', auto: 'email', help: tx('dev.email_help') })}</div>
|
|
703
|
+
<div class="buttons"><button class="btn">${tx('dev.save')}</button></div></form>`)}
|
|
704
|
+
${region(tx('dev.accounts'), html`
|
|
705
|
+
<p class="muted u-mt0">${tx('dev.accounts_help')}</p>
|
|
706
|
+
<div class="table-wrap"><table class="report report-reflow"><thead><tr><th>${tx('dev.username')}</th><th>${tx('dev.role')}</th><th>${tx('dev.email_col')}</th><th></th></tr></thead><tbody>
|
|
707
|
+
${devs.map((d) => html`<tr><td data-label="${tx('dev.username')}">${d.username}${d.username === s.username ? ` ${tx('dev.you')}` : ''}</td><td data-label="${tx('dev.role')}">${d.is_admin ? tx('dev.administrator') : tx('dev.developer')}</td>
|
|
708
|
+
<td data-label="${tx('dev.email_col')}">${admin && d.username !== s.username
|
|
709
|
+
? html`<form method="post" action="${BASE}/developers/email" class="search u-m0 u-mwnone">${csrf(s)}<input type="hidden" name="username" value="${d.username}"><input name="email" type="email" value="${d.email ?? ''}" aria-label="${tx('dev.email_of', { user: d.username })}" placeholder="${tx('dev.none')}"><button class="btn">${tx('dev.save')}</button></form>`
|
|
709
710
|
: html`<span class="wrap-anywhere">${d.email ?? ''}</span>`}</td><td>${d.username === s.username || !admin ? '' : html`
|
|
710
|
-
<form method="post" action="${BASE}/developers/admin" class="u-inline">${csrf(s)}<input type="hidden" name="username" value="${d.username}"><input type="hidden" name="is_admin" value="${d.is_admin ? 'false' : 'true'}"><button class="link-button">${d.is_admin ? '
|
|
711
|
-
· <form method="post" action="${BASE}/developers/delete" class="u-inline">${csrf(s)}<input type="hidden" name="username" value="${d.username}"><button class="link-button" data-confirm="
|
|
711
|
+
<form method="post" action="${BASE}/developers/admin" class="u-inline">${csrf(s)}<input type="hidden" name="username" value="${d.username}"><input type="hidden" name="is_admin" value="${d.is_admin ? 'false' : 'true'}"><button class="link-button">${d.is_admin ? tx('dev.make_dev') : tx('dev.make_admin')}</button></form>
|
|
712
|
+
· <form method="post" action="${BASE}/developers/delete" class="u-inline">${csrf(s)}<input type="hidden" name="username" value="${d.username}"><button class="link-button" data-confirm="${tx('dev.remove_confirm', { user: d.username })}">${tx('dev.remove')}</button></form>`}</td></tr>`)}
|
|
712
713
|
</tbody></table></div>
|
|
713
714
|
${admin
|
|
714
|
-
? html`<h3
|
|
715
|
+
? html`<h3>${tx('dev.add')}</h3>
|
|
715
716
|
<form method="post" action="${BASE}/developers">${csrf(s)}
|
|
716
|
-
<div class="form-grid">${input('username', '
|
|
717
|
-
<div class="field"><label class="check"><input type="checkbox" name="is_admin" value="true">
|
|
718
|
-
<div class="buttons"><button class="btn btn-hot"
|
|
717
|
+
<div class="form-grid">${input('username', tx('dev.username'), '', { required: true })}${input('password', tx('dev.password'), '', { type: 'password', required: true, auto: 'new-password' })}${input('email', tx('dev.email'), '', { type: 'email', help: tx('dev.optional_email') })}</div>
|
|
718
|
+
<div class="field"><label class="check"><input type="checkbox" name="is_admin" value="true"> ${tx('dev.administrator')}</label></div>
|
|
719
|
+
<div class="buttons"><button class="btn btn-hot">${tx('dev.add')}</button></div>
|
|
719
720
|
</form>`
|
|
720
|
-
: html`<p class="muted"
|
|
721
|
+
: html`<p class="muted">${tx('dev.admins_only')}</p>`}`)}
|
|
721
722
|
</div>`;
|
|
722
|
-
return send(reply, s, shell(s, '
|
|
723
|
+
return send(reply, s, shell(s, tx('dev.title'), [['Developers']], main, 'developers'));
|
|
723
724
|
});
|
|
724
725
|
|
|
725
726
|
app.post(`${BASE}/developers`, async (req: Req, reply) => {
|
|
@@ -732,7 +733,7 @@ export async function builderRoutes(app: FastifyInstance) {
|
|
|
732
733
|
const ws = currentWorkspace(s);
|
|
733
734
|
await owner.tx(async (c) => {
|
|
734
735
|
const email = String(req.body?.email ?? '').trim();
|
|
735
|
-
if (email && !EMAIL.test(email)) throw new Error('
|
|
736
|
+
if (email && !EMAIL.test(email)) throw new Error(tr(s)('dev.err.email'));
|
|
736
737
|
await c.query('insert into meta.developer (username, password_hash, is_admin, email) values ($1, meta.hash_password($2), $3, $4)', [req.body?.username?.trim(), req.body?.password, req.body?.is_admin === 'true', email || null]);
|
|
737
738
|
// (064) a new developer works in the current workspace; more on Workspace utilities → Workspaces
|
|
738
739
|
// (the insert trigger put them in Default)
|
|
@@ -741,7 +742,7 @@ export async function builderRoutes(app: FastifyInstance) {
|
|
|
741
742
|
await c.query('insert into meta.workspace_member (workspace_id, username) values ($1, $2)', [ws, req.body?.username?.trim()]);
|
|
742
743
|
}
|
|
743
744
|
});
|
|
744
|
-
flash(s, ws > 0 ?
|
|
745
|
+
flash(s, ws > 0 ? tr(s)('dev.added_ws', { name: s.workspace!.name }) : tr(s)('dev.added'));
|
|
745
746
|
} catch (e) {
|
|
746
747
|
flash(s, (e as Error).message, 'error');
|
|
747
748
|
}
|
|
@@ -759,11 +760,11 @@ export async function builderRoutes(app: FastifyInstance) {
|
|
|
759
760
|
where username = $1 and password_hash = crypt($2, password_hash)`,
|
|
760
761
|
[s.username, req.body?.current ?? '', req.body?.password],
|
|
761
762
|
);
|
|
762
|
-
if (r.rowCount !== 1) throw new Error('
|
|
763
|
+
if (r.rowCount !== 1) throw new Error(tr(s)('dev.err.current'));
|
|
763
764
|
// End all other builder sessions of this developer.
|
|
764
765
|
await owner.query('delete from meta.session where app_id is null and username = $1 and id <> $2', [s.username, s.id]);
|
|
765
766
|
delete s.state.__WEAK;
|
|
766
|
-
flash(s, '
|
|
767
|
+
flash(s, tr(s)('dev.pw_changed'));
|
|
767
768
|
} catch (e) {
|
|
768
769
|
flash(s, (e as Error).message, 'error');
|
|
769
770
|
}
|
|
@@ -777,10 +778,10 @@ export async function builderRoutes(app: FastifyInstance) {
|
|
|
777
778
|
const who = String(req.body?.username ?? s.username);
|
|
778
779
|
if (who !== s.username && !(await isAdmin(s.username))) return reply.code(403).send('Only administrators change other developers\' addresses.');
|
|
779
780
|
const email = String(req.body?.email ?? '').trim();
|
|
780
|
-
if (email && (!EMAIL.test(email) || email.length > 254)) flash(s, '
|
|
781
|
+
if (email && (!EMAIL.test(email) || email.length > 254)) flash(s, tr(s)('dev.err.email'), 'error');
|
|
781
782
|
else {
|
|
782
783
|
const r = await owner.query('update meta.developer set email = $2 where username = $1', [who, email || null]);
|
|
783
|
-
flash(s, r.rowCount ? '
|
|
784
|
+
flash(s, tr(s)(r.rowCount ? 'dev.email_saved' : 'dev.not_found'), r.rowCount ? 'ok' : 'error');
|
|
784
785
|
}
|
|
785
786
|
return back(reply, s, `${BASE}/developers`);
|
|
786
787
|
});
|
|
@@ -791,7 +792,7 @@ export async function builderRoutes(app: FastifyInstance) {
|
|
|
791
792
|
if (!(await isAdmin(s.username))) return reply.code(403).send('Only administrators change roles.');
|
|
792
793
|
if (req.body?.username && req.body.username !== s.username) {
|
|
793
794
|
await owner.query('update meta.developer set is_admin = $2 where username = $1', [req.body.username, req.body.is_admin === 'true']);
|
|
794
|
-
flash(s, '
|
|
795
|
+
flash(s, tr(s)('dev.role_changed'));
|
|
795
796
|
}
|
|
796
797
|
return back(reply, s, `${BASE}/developers`);
|
|
797
798
|
});
|
|
@@ -803,7 +804,7 @@ export async function builderRoutes(app: FastifyInstance) {
|
|
|
803
804
|
if (req.body?.username && req.body.username !== s.username) {
|
|
804
805
|
await owner.query('delete from meta.developer where username = $1', [req.body.username]);
|
|
805
806
|
await owner.query('delete from meta.session where app_id is null and username = $1', [req.body.username]);
|
|
806
|
-
flash(s, '
|
|
807
|
+
flash(s, tr(s)('dev.removed'));
|
|
807
808
|
}
|
|
808
809
|
return back(reply, s, `${BASE}/developers`);
|
|
809
810
|
});
|
package/src/builder/shared.ts
CHANGED
|
@@ -253,7 +253,7 @@ export async function sharedRoutes(app: FastifyInstance) {
|
|
|
253
253
|
<td data-label="${tx('sc.account')}"><a href="${BASE}/users/${u.id}">${u.username}</a>${u.display_name ? html` <span class="muted">${u.display_name}</span>` : ''}${u.active ? '' : html` <b>${tx('sc.inactive')}</b>`}</td>
|
|
254
254
|
<td data-label="${tx('sc.roles')}"><form method="post" action="${BASE}/apps/${a.id}/access/${u.id}" class="search roles-form u-m0 u-mwnone">${csrf(s)}
|
|
255
255
|
<input name="roles" value="${u.roles.join(', ')}" aria-label="${tx('sc.roles_of', { user: u.username })}" placeholder="${tx('sc.no_roles')}"><button class="btn">${tx('sc.save')}</button>
|
|
256
|
-
${roleHintsHtml(appHints, '
|
|
256
|
+
${roleHintsHtml(appHints, tx('us.hints_add'), tx)}</form></td>
|
|
257
257
|
<td data-label="${tx('sc.last_signin')}">${u.last_login_at ? String(u.last_login_at).slice(0, 16) : '—'}</td>
|
|
258
258
|
<td data-label=""><form method="post" action="${BASE}/apps/${a.id}/access/${u.id}/revoke">${csrf(s)}<button class="link-button" data-confirm="${tx('sc.revoke_confirm', { user: u.username, app: a.name })}">${tx('sc.revoke')}</button></form></td>
|
|
259
259
|
</tr>`)
|
|
@@ -269,7 +269,7 @@ export async function sharedRoutes(app: FastifyInstance) {
|
|
|
269
269
|
<small class="help">${tx('sc.existing')} <a href="${BASE}/users">${tx('sc.create_in_users')}</a></small></div>
|
|
270
270
|
<div class="field"><label class="label" for="f_roles">${tx('sc.roles')}</label>
|
|
271
271
|
<input id="f_roles" name="roles" placeholder="${tx('sc.roles_ph')}">
|
|
272
|
-
${roleHintsHtml(appHints)}</div>
|
|
272
|
+
${roleHintsHtml(appHints, undefined, tx)}</div>
|
|
273
273
|
</div>
|
|
274
274
|
<div class="buttons"><button class="btn btn-hot">${tx('sc.grant')}</button></div>
|
|
275
275
|
</form>`);
|