@omnizap-system/omnizap 2.6.0 → 2.6.2
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/.env.example +58 -13
- package/.github/workflows/ci.yml +5 -5
- package/.github/workflows/codeql.yml +1 -1
- package/.github/workflows/db-migration-check.yml +2 -2
- package/.github/workflows/dependency-review.yml +1 -1
- package/.github/workflows/deploy.yml +2 -2
- package/.github/workflows/release.yml +2 -2
- package/.github/workflows/security-attest-provenance.yml +2 -2
- package/.github/workflows/security-gitleaks.yml +13 -4
- package/.github/workflows/security-runner-hardening.yml +2 -2
- package/.github/workflows/security-scorecard.yml +1 -1
- package/.github/workflows/security-zap-baseline.yml +1 -1
- package/.github/workflows/security-zap-full-scan.yml +2 -1
- package/.github/workflows/security-zizmor.yml +1 -1
- package/.github/workflows/wiki-sync.yml +1 -1
- package/.gitleaksignore +9 -0
- package/CODE_OF_CONDUCT.md +2 -2
- package/GEMINI.md +64 -0
- package/README.md +52 -82
- package/SECURITY.md +1 -1
- package/app/config/index.js +2 -0
- package/app/configParts/adminIdentity.js +5 -5
- package/app/configParts/baileysConfig.js +230 -58
- package/app/configParts/groupUtils.js +5 -0
- package/app/configParts/messagePersistenceService.js +145 -4
- package/app/configParts/sessionConfig.js +157 -0
- package/app/connection/baileysCompatibility.test.js +1 -1
- package/app/connection/groupOwnerWriteStateResolver.js +109 -0
- package/app/connection/socketController.js +660 -158
- package/app/connection/socketController.multiSession.test.js +108 -0
- package/app/controllers/messageController.js +1 -1
- package/app/controllers/messagePipeline/commandMiddleware.js +12 -10
- package/app/controllers/messagePipeline/conversationMiddleware.js +2 -1
- package/app/controllers/messagePipeline/messagePipelineMiddlewares.test.js +104 -0
- package/app/controllers/messagePipeline/preProcessingMiddlewares.js +80 -2
- package/app/controllers/messageProcessingPipeline.js +93 -13
- package/app/controllers/messageProcessingPipeline.test.js +200 -0
- package/app/modules/adminModule/AGENT.md +1 -1
- package/app/modules/adminModule/commandConfig.json +3318 -1347
- package/app/modules/adminModule/groupCommandHandlers.js +858 -15
- package/app/modules/adminModule/groupCommandHandlers.test.js +378 -11
- package/app/modules/adminModule/groupWarningRepository.js +152 -0
- package/app/modules/aiModule/AGENT.md +47 -30
- package/app/modules/aiModule/aiConfigRuntime.js +1 -0
- package/app/modules/aiModule/catCommand.js +135 -27
- package/app/modules/aiModule/commandConfig.json +114 -28
- package/app/modules/analyticsModule/messageAnalysisEventRepository.js +54 -6
- package/app/modules/gameModule/AGENT.md +1 -1
- package/app/modules/gameModule/commandConfig.json +29 -0
- package/app/modules/menuModule/AGENT.md +1 -1
- package/app/modules/menuModule/commandConfig.json +45 -10
- package/app/modules/menuModule/menuCatalogService.js +190 -0
- package/app/modules/menuModule/menuCommandUsageRepository.js +109 -0
- package/app/modules/menuModule/menuDynamicService.js +511 -0
- package/app/modules/menuModule/menuDynamicService.test.js +141 -0
- package/app/modules/menuModule/menus.js +36 -5
- package/app/modules/playModule/AGENT.md +10 -5
- package/app/modules/playModule/commandConfig.json +140 -12
- package/app/modules/playModule/playCommand.js +1 -1417
- package/app/modules/playModule/playCommandConstants.js +80 -0
- package/app/modules/playModule/playCommandCore.js +361 -0
- package/app/modules/playModule/playCommandHandlers.js +41 -0
- package/app/modules/playModule/playCommandMediaClient.js +1872 -0
- package/app/modules/playModule/playConfigRuntime.js +245 -4
- package/app/modules/playModule/playModuleCriticalFlows.test.js +152 -0
- package/app/modules/quoteModule/AGENT.md +1 -1
- package/app/modules/quoteModule/commandConfig.json +29 -0
- package/app/modules/quoteModule/quoteCommand.js +3 -2
- package/app/modules/rpgPokemonModule/AGENT.md +1 -1
- package/app/modules/rpgPokemonModule/commandConfig.json +29 -0
- package/app/modules/rpgPokemonModule/rpgBattleCanvasRenderer.js +5 -4
- package/app/modules/rpgPokemonModule/rpgBattleService.test.js +2 -1
- package/app/modules/rpgPokemonModule/rpgPokemonDomain.js +2 -1
- package/app/modules/rpgPokemonModule/rpgPokemonService.js +38 -37
- package/app/modules/rpgPokemonModule/rpgProfileCanvasRenderer.js +4 -3
- package/app/modules/statsModule/AGENT.md +1 -1
- package/app/modules/statsModule/commandConfig.json +58 -0
- package/app/modules/statsModule/rankingCommon.js +5 -4
- package/app/modules/stickerModule/AGENT.md +1 -1
- package/app/modules/stickerModule/addStickerMetadata.js +4 -3
- package/app/modules/stickerModule/commandConfig.json +145 -0
- package/app/modules/stickerModule/stickerCommand.js +1 -1
- package/app/modules/stickerPackModule/AGENT.md +1 -1
- package/app/modules/stickerPackModule/autoPackCollectorService.js +5 -1
- package/app/modules/stickerPackModule/commandConfig.json +29 -0
- package/app/modules/stickerPackModule/semanticThemeClusterService.js +7 -6
- package/app/modules/stickerPackModule/stickerAutoPackByTagsRuntime.js +10 -9
- package/app/modules/stickerPackModule/stickerClassificationBackgroundRuntime.js +9 -8
- package/app/modules/stickerPackModule/stickerDomainEventConsumerRuntime.js +3 -2
- package/app/modules/stickerPackModule/stickerMarketplaceDriftService.js +2 -1
- package/app/modules/stickerPackModule/stickerPackCommandHandlers.js +80 -58
- package/app/modules/stickerPackModule/stickerPackMarketplaceService.js +2 -1
- package/app/modules/stickerPackModule/stickerPackRepository.js +2 -1
- package/app/modules/stickerPackModule/stickerPackScoreSnapshotRuntime.js +5 -4
- package/app/modules/stickerPackModule/stickerPackService.js +13 -6
- package/app/modules/stickerPackModule/stickerStorageService.js +3 -2
- package/app/modules/stickerPackModule/stickerWorkerPipelineRuntime.js +2 -1
- package/app/modules/systemMetricsModule/AGENT.md +1 -1
- package/app/modules/systemMetricsModule/commandConfig.json +29 -0
- package/app/modules/systemMetricsModule/pingCommand.js +6 -5
- package/app/modules/tiktokModule/AGENT.md +1 -1
- package/app/modules/tiktokModule/commandConfig.json +29 -0
- package/app/modules/tiktokModule/tiktokCommand.js +2 -1
- package/app/modules/userModule/AGENT.md +1 -1
- package/app/modules/userModule/commandConfig.json +29 -0
- package/app/modules/userModule/userCommand.js +72 -23
- package/app/modules/waifuPicsModule/AGENT.md +57 -27
- package/app/modules/waifuPicsModule/commandConfig.json +87 -0
- package/app/modules/waifuPicsModule/waifuPicsCommand.js +3 -2
- package/app/observability/metrics.js +136 -0
- package/app/services/ai/commandConfigEnrichmentService.js +229 -47
- package/app/services/ai/conversationRouterService.js +4 -3
- package/app/services/ai/geminiService.js +132 -7
- package/app/services/ai/geminiService.test.js +59 -2
- package/app/services/ai/globalModuleAiHelpService.js +3 -2
- package/app/services/ai/messageCommandExecutionService.js +2 -1
- package/app/services/ai/moduleAiHelpCoreService.js +45 -14
- package/app/services/ai/moduleToolExecutorService.js +3 -2
- package/app/services/ai/moduleToolRegistryService.js +2 -1
- package/app/services/ai/toolCandidateSelectorService.js +6 -5
- package/app/services/auth/googleWebLinkService.js +3 -2
- package/app/services/auth/whatsappLoginLinkService.js +3 -2
- package/app/services/external/pokeApiService.js +4 -3
- package/app/services/group/groupMetadataService.js +24 -1
- package/app/services/infra/dbWriteQueue.js +57 -26
- package/app/services/infra/featureFlagService.js +2 -1
- package/app/services/messaging/captchaService.js +3 -2
- package/app/services/messaging/newsBroadcastService.js +846 -29
- package/app/services/multiSession/assignmentBalancerService.js +457 -0
- package/app/services/multiSession/groupOwnershipRepository.js +381 -0
- package/app/services/multiSession/groupOwnershipService.js +890 -0
- package/app/services/multiSession/groupOwnershipService.test.js +309 -0
- package/app/services/multiSession/sessionRegistryService.js +293 -0
- package/app/services/sticker/stickerFocusService.js +11 -10
- package/app/store/aiPromptStore.js +36 -19
- package/app/store/conversationSessionStore.js +7 -6
- package/app/store/groupConfigStore.js +41 -5
- package/app/store/premiumUserStore.js +21 -7
- package/app/utils/antiLink/antiLinkModule.js +352 -16
- package/app/workers/aiHelperContinuousLearningWorker.js +512 -0
- package/app/workers/aiLearningWorker.js +6 -5
- package/app/workers/commandConfigEnrichmentWorker.js +4 -3
- package/database/index.js +14 -8
- package/database/migrations/20260307_d0_hardening_down.sql +1 -1
- package/database/migrations/20260314_d7_canonical_sender_down.sql +1 -1
- package/database/migrations/20260406_d30_security_analytics_down.sql +1 -1
- package/database/migrations/20260411_d35_group_community_metadata_down.sql +59 -0
- package/database/migrations/20260411_d35_group_community_metadata_up.sql +62 -0
- package/database/migrations/20260412_d36_system_config_tables_down.sql +32 -0
- package/database/migrations/20260412_d36_system_config_tables_up.sql +66 -0
- package/database/migrations/20260413_d37_group_user_warnings_down.sql +11 -0
- package/database/migrations/20260413_d37_group_user_warnings_up.sql +24 -0
- package/database/migrations/20260414_d38_multi_session_foundation_down.sql +72 -0
- package/database/migrations/20260414_d38_multi_session_foundation_up.sql +125 -0
- package/database/migrations/20260414_d39_multi_session_cutover_down.sql +103 -0
- package/database/migrations/20260414_d39_multi_session_cutover_up.sql +83 -0
- package/database/schema.sql +102 -1
- package/docker-compose.yml +4 -1
- package/docs/compliance/acceptable-use-policy-2026-03-07.md +1 -1
- package/docs/compliance/dpa-b2b-standard-2026-03-07.md +1 -1
- package/docs/compliance/privacy-policy-2026-03-07.md +4 -4
- package/docs/security/dsar-lgpd-runbook-2026-03-07.md +1 -1
- package/docs/security/incident-response-lgpd-anpd-runbook-2026-03-07.md +1 -1
- package/docs/security/network-hardening-runbook-2026-03-07.md +53 -0
- package/docs/security/omnizap-static-security-headers.conf +25 -0
- package/docs/wiki/Home.md +1 -1
- package/ecosystem.prod.config.cjs +32 -12
- package/index.js +57 -23
- package/observability/alert-rules.yml +20 -0
- package/observability/grafana/dashboards/omnizap-system-admin.json +229 -0
- package/observability/mysql-setup.sql +4 -4
- package/observability/system-admin-observability.md +26 -0
- package/package.json +20 -6
- package/public/apple-touch-icon.png +0 -0
- package/public/comandos/commands-catalog.json +2853 -3326
- package/public/favicon-16x16.png +0 -0
- package/public/favicon-32x32.png +0 -0
- package/public/favicon.ico +0 -0
- package/public/js/apps/apiDocsApp.js +3 -2
- package/public/js/apps/commandsReactApp.js +280 -99
- package/public/js/apps/createPackApp.js +11 -10
- package/public/js/apps/homeReactApp.js +181 -130
- package/public/js/apps/loginReactApp.js +1 -1
- package/public/js/apps/stickersApp.js +263 -110
- package/public/js/apps/termsReactApp.js +73 -24
- package/public/js/apps/userApp.js +4 -3
- package/public/js/apps/userPasswordResetReactApp.js +406 -0
- package/public/js/apps/userReactApp.js +355 -280
- package/public/js/apps/userSystemAdmReactApp.js +1506 -0
- package/public/pages/api-docs.html +1 -1
- package/public/pages/aup.html +2 -2
- package/public/pages/dpa.html +3 -3
- package/public/pages/licenca.html +4 -4
- package/public/pages/login.html +1 -1
- package/public/pages/notice-and-takedown.html +2 -2
- package/public/pages/politica-de-privacidade.html +6 -6
- package/public/pages/seo-bot-whatsapp-para-grupo.html +3 -3
- package/public/pages/seo-bot-whatsapp-sem-programar.html +3 -3
- package/public/pages/seo-como-automatizar-avisos-no-whatsapp.html +3 -3
- package/public/pages/seo-como-criar-comandos-whatsapp.html +3 -3
- package/public/pages/seo-como-evitar-spam-no-whatsapp.html +3 -3
- package/public/pages/seo-como-moderar-grupo-whatsapp.html +3 -3
- package/public/pages/seo-como-organizar-comunidade-whatsapp.html +3 -3
- package/public/pages/seo-melhor-bot-whatsapp-para-grupos.html +3 -3
- package/public/pages/stickers-admin.html +1 -1
- package/public/pages/stickers-create.html +1 -1
- package/public/pages/stickers.html +6 -6
- package/public/pages/suboperadores.html +2 -2
- package/public/pages/termos-de-uso-texto-integral.html +6 -6
- package/public/pages/termos-de-uso.html +3 -3
- package/public/pages/user-password-reset.html +4 -5
- package/public/pages/user-systemadm.html +9 -463
- package/public/pages/user.html +2 -2
- package/scripts/clear-whatsapp-session.sh +123 -0
- package/scripts/core-ai-mode.mjs +163 -0
- package/scripts/deploy.sh +11 -1
- package/scripts/email-broadcast-terms-update.mjs +2 -1
- package/scripts/enrich-command-config-ux-openai.mjs +492 -0
- package/scripts/generate-commands-catalog.mjs +166 -2
- package/scripts/generate-module-agents.mjs +2 -1
- package/scripts/generate-seo-satellite-pages.mjs +5 -4
- package/scripts/github-deploy-notify.mjs +2 -1
- package/scripts/github-release-notify.mjs +25 -10
- package/scripts/new-whatsapp-session.sh +317 -0
- package/scripts/release.sh +2 -19
- package/scripts/security-smoketest.mjs +6 -5
- package/scripts/security-web-surface-check.mjs +218 -0
- package/scripts/sticker-catalog-loadtest.mjs +5 -4
- package/server/auth/googleWebAuth/googleWebAuthService.js +8 -7
- package/server/auth/jwt/webJwtService.js +1 -1
- package/server/auth/stickerCatalogAuthContext.js +2 -1
- package/server/auth/termsAcceptance/termsAcceptanceHandler.js +2 -1
- package/server/auth/userPassword/userPasswordAuthService.js +2 -1
- package/server/auth/userPassword/userPasswordRecoveryService.js +4 -3
- package/server/auth/webAccount/webAccountHandlers.js +9 -10
- package/server/controllers/admin/adminPanelHandlers.js +267 -16
- package/server/controllers/admin/systemAdminController.js +267 -0
- package/server/controllers/seo/stickerCatalogSeoContext.js +10 -9
- package/server/controllers/sticker/nonCatalogHandlers.js +2 -1
- package/server/controllers/sticker/stickerCatalogController.js +23 -36
- package/server/controllers/system/contactController.js +9 -17
- package/server/controllers/system/githubController.js +3 -2
- package/server/controllers/system/stickerCatalogSystemContext.js +41 -19
- package/server/controllers/system/systemController.js +254 -1
- package/server/controllers/system/systemMetricsController.js +2 -1
- package/server/controllers/userController.js +6 -0
- package/server/email/emailTemplateService.js +5 -3
- package/server/http/httpServer.js +11 -6
- package/server/middleware/rateLimit.js +2 -1
- package/server/middleware/securityHeaders.js +20 -1
- package/server/routes/admin/systemAdminRouter.js +6 -0
- package/server/routes/indexRouter.js +30 -6
- package/server/routes/observability/grafanaProxyRouter.js +254 -0
- package/server/routes/static/staticPageRouter.js +27 -1
- package/server/utils/publicContact.js +31 -0
- package/utils/time/timeModule.js +135 -0
- package/utils/time/timeModule.test.js +65 -0
- package/utils/whatsapp/contactEnv.js +39 -0
- package/vite.config.mjs +7 -1
- package/public/assets/images/brand-icon-192.png +0 -0
- package/scripts/sync-readme-snapshot.mjs +0 -133
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
import { now as __timeNow, nowIso as __timeNowIso, toUnixMs as __timeNowMs } from '#time';
|
|
1
2
|
import { randomUUID, randomBytes, scryptSync, timingSafeEqual } from 'node:crypto';
|
|
2
|
-
import { URLSearchParams } from 'node:url';
|
|
3
|
+
import { URL, URLSearchParams } from 'node:url';
|
|
3
4
|
|
|
5
|
+
import { setAdminOverviewSnapshot } from '../../../app/observability/metrics.js';
|
|
4
6
|
import { parseAdminModeratorUpsertPayload, parseAdminSessionPasswordPayload } from '../../auth/validation/authSchemas.js';
|
|
5
7
|
|
|
6
8
|
export const createStickerCatalogAdminHandlers = ({ executeQuery, tables, logger, sendJson, readJsonBody, parseCookies, getCookieValuesFromRequest, appendSetCookie, buildCookieString, sanitizeText, normalizeGoogleSubject, normalizeEmail, normalizeJid, toIsoOrNull, toWhatsAppPhoneDigits, mapGoogleSessionResponseData, resolveGoogleWebSessionFromRequest, revokeGoogleWebSessionsByIdentity, getMarketplaceGlobalStatsCached, getSystemSummaryCached, getFeatureFlagsSnapshot, refreshFeatureFlags, listAdminBans, createAdminBanRecord, revokeAdminBanRecord, normalizeVisitPath, stickerWebPath, findStickerPackByPackKey, stickerPackService, buildManagedPackResponseData, sendManagedMutationStatus, sendManagedPackMutationStatus, deleteManagedPackWithCleanup, mapStickerPackWebManageError, cleanupOrphanStickerAssets, invalidateStickerCatalogDerivedCaches }) => {
|
|
@@ -15,6 +17,229 @@ export const createStickerCatalogAdminHandlers = ({ executeQuery, tables, logger
|
|
|
15
17
|
const ADMIN_PANEL_SESSION_TTL_MS = Math.max(10 * 60 * 1000, Number(process.env.ADM_PANEL_SESSION_TTL_MS) || 12 * 60 * 60 * 1000);
|
|
16
18
|
const ADMIN_MODERATOR_PASSWORD_MIN_LENGTH = Math.max(6, Number(process.env.ADM_MODERATOR_PASSWORD_MIN_LENGTH) || 8);
|
|
17
19
|
const ADMIN_PANEL_SESSION_COOKIE_NAME = 'omnizap_admin_panel_session';
|
|
20
|
+
const SYSTEM_ADMIN_GRAFANA_TIME_FROM = sanitizeText(process.env.SYSTEM_ADMIN_GRAFANA_TIME_FROM || 'now-6h', 40, { allowEmpty: true }) || 'now-6h';
|
|
21
|
+
const SYSTEM_ADMIN_GRAFANA_TIME_TO = sanitizeText(process.env.SYSTEM_ADMIN_GRAFANA_TIME_TO || 'now', 40, { allowEmpty: true }) || 'now';
|
|
22
|
+
const SYSTEM_ADMIN_GRAFANA_REFRESH = sanitizeText(process.env.SYSTEM_ADMIN_GRAFANA_REFRESH || '10s', 20, { allowEmpty: true }) || '10s';
|
|
23
|
+
const SYSTEM_ADMIN_GRAFANA_STATUS_TIMEOUT_MS = Math.max(800, Number(process.env.SYSTEM_ADMIN_GRAFANA_STATUS_TIMEOUT_MS) || 2500);
|
|
24
|
+
const SYSTEM_ADMIN_GRAFANA_STATUS_CACHE_TTL_MS = Math.max(5000, Number(process.env.SYSTEM_ADMIN_GRAFANA_STATUS_CACHE_TTL_MS) || 15000);
|
|
25
|
+
|
|
26
|
+
const normalizeGrafanaBaseUrl = (value) => {
|
|
27
|
+
const raw = String(value || '').trim();
|
|
28
|
+
if (!raw) return '';
|
|
29
|
+
try {
|
|
30
|
+
const parsed = new URL(raw);
|
|
31
|
+
if (!['http:', 'https:'].includes(parsed.protocol)) return '';
|
|
32
|
+
const normalizedPathname = String(parsed.pathname || '/').replace(/\/+$/, '');
|
|
33
|
+
parsed.search = '';
|
|
34
|
+
parsed.hash = '';
|
|
35
|
+
return `${parsed.origin}${normalizedPathname === '/' ? '' : normalizedPathname}`;
|
|
36
|
+
} catch {
|
|
37
|
+
return '';
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const normalizeGrafanaUid = (value) =>
|
|
42
|
+
String(value || '')
|
|
43
|
+
.trim()
|
|
44
|
+
.replace(/[^a-zA-Z0-9_-]/g, '')
|
|
45
|
+
.slice(0, 120);
|
|
46
|
+
|
|
47
|
+
const slugifyGrafanaDashboard = (value) => {
|
|
48
|
+
const raw = String(value || '')
|
|
49
|
+
.trim()
|
|
50
|
+
.toLowerCase();
|
|
51
|
+
const slug = raw
|
|
52
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
53
|
+
.replace(/^-+|-+$/g, '')
|
|
54
|
+
.slice(0, 90);
|
|
55
|
+
return slug || 'dashboard';
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
const parseGrafanaDashboardDefinitions = () => {
|
|
59
|
+
const raw = String(process.env.SYSTEM_ADMIN_GRAFANA_DASHBOARDS || '')
|
|
60
|
+
.split(',')
|
|
61
|
+
.map((item) => String(item || '').trim())
|
|
62
|
+
.filter(Boolean);
|
|
63
|
+
|
|
64
|
+
const parsed = raw
|
|
65
|
+
.map((entry) => {
|
|
66
|
+
const [uidPart, ...titleParts] = entry.split('|');
|
|
67
|
+
const uid = normalizeGrafanaUid(uidPart);
|
|
68
|
+
const title = sanitizeText(titleParts.join('|') || '', 90, { allowEmpty: true }) || '';
|
|
69
|
+
return uid ? { uid, title } : null;
|
|
70
|
+
})
|
|
71
|
+
.filter(Boolean);
|
|
72
|
+
|
|
73
|
+
if (parsed.length) return parsed;
|
|
74
|
+
|
|
75
|
+
return [
|
|
76
|
+
{ uid: 'omnizap-system-admin', title: 'System Admin' },
|
|
77
|
+
{ uid: 'omnizap-overview', title: 'Overview' },
|
|
78
|
+
{ uid: 'omnizap-mysql', title: 'MySQL' },
|
|
79
|
+
];
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
const buildAdminGrafanaObservabilityLinks = () => {
|
|
83
|
+
const baseUrl = normalizeGrafanaBaseUrl(process.env.SYSTEM_ADMIN_GRAFANA_URL || process.env.GRAFANA_PUBLIC_URL || '');
|
|
84
|
+
if (!baseUrl) {
|
|
85
|
+
return {
|
|
86
|
+
enabled: false,
|
|
87
|
+
base_url: null,
|
|
88
|
+
dashboards: [],
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const base = new URL(`${baseUrl}/`);
|
|
93
|
+
const definitions = parseGrafanaDashboardDefinitions();
|
|
94
|
+
|
|
95
|
+
const dashboards = definitions
|
|
96
|
+
.map((entry, index) => {
|
|
97
|
+
const uid = normalizeGrafanaUid(entry?.uid || '');
|
|
98
|
+
if (!uid) return null;
|
|
99
|
+
const title = sanitizeText(entry?.title || '', 90, { allowEmpty: true }) || `Dashboard ${index + 1}`;
|
|
100
|
+
const slug = slugifyGrafanaDashboard(title || uid);
|
|
101
|
+
const viewUrl = new URL(`d/${encodeURIComponent(uid)}/${encodeURIComponent(slug)}`, base);
|
|
102
|
+
viewUrl.searchParams.set('orgId', '1');
|
|
103
|
+
viewUrl.searchParams.set('from', SYSTEM_ADMIN_GRAFANA_TIME_FROM);
|
|
104
|
+
viewUrl.searchParams.set('to', SYSTEM_ADMIN_GRAFANA_TIME_TO);
|
|
105
|
+
viewUrl.searchParams.set('refresh', SYSTEM_ADMIN_GRAFANA_REFRESH);
|
|
106
|
+
|
|
107
|
+
const embedUrl = new URL(String(viewUrl));
|
|
108
|
+
embedUrl.searchParams.set('kiosk', 'tv');
|
|
109
|
+
|
|
110
|
+
return {
|
|
111
|
+
uid,
|
|
112
|
+
title,
|
|
113
|
+
view_url: viewUrl.toString(),
|
|
114
|
+
embed_url: embedUrl.toString(),
|
|
115
|
+
};
|
|
116
|
+
})
|
|
117
|
+
.filter(Boolean);
|
|
118
|
+
|
|
119
|
+
return {
|
|
120
|
+
enabled: dashboards.length > 0,
|
|
121
|
+
base_url: baseUrl,
|
|
122
|
+
dashboards,
|
|
123
|
+
};
|
|
124
|
+
};
|
|
125
|
+
const grafanaObservabilityLinks = buildAdminGrafanaObservabilityLinks();
|
|
126
|
+
let grafanaRuntimeSnapshotCache = {
|
|
127
|
+
expires_at_ms: 0,
|
|
128
|
+
payload: null,
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
const resolveGrafanaHealthEndpointUrl = () => {
|
|
132
|
+
const candidates = [process.env.GRAFANA_PROXY_TARGET_URL, process.env.GRAFANA_INTERNAL_URL, process.env.SYSTEM_ADMIN_GRAFANA_URL, process.env.GRAFANA_PUBLIC_URL];
|
|
133
|
+
for (const candidate of candidates) {
|
|
134
|
+
const baseUrl = normalizeGrafanaBaseUrl(candidate);
|
|
135
|
+
if (!baseUrl) continue;
|
|
136
|
+
try {
|
|
137
|
+
return new URL('api/health', `${baseUrl}/`).toString();
|
|
138
|
+
} catch {
|
|
139
|
+
// noop
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
return '';
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
const getGrafanaRuntimeSnapshot = async () => {
|
|
146
|
+
const nowMs = __timeNowMs();
|
|
147
|
+
if (grafanaRuntimeSnapshotCache.payload && nowMs < grafanaRuntimeSnapshotCache.expires_at_ms) {
|
|
148
|
+
return grafanaRuntimeSnapshotCache.payload;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const dashboardsTotal = Array.isArray(grafanaObservabilityLinks?.dashboards) ? grafanaObservabilityLinks.dashboards.length : 0;
|
|
152
|
+
const baseSnapshot = {
|
|
153
|
+
enabled: Boolean(grafanaObservabilityLinks?.enabled),
|
|
154
|
+
available: false,
|
|
155
|
+
status: 'unavailable',
|
|
156
|
+
response_ms: null,
|
|
157
|
+
checked_at: __timeNowIso(),
|
|
158
|
+
http_status: null,
|
|
159
|
+
version: null,
|
|
160
|
+
database: null,
|
|
161
|
+
commit: null,
|
|
162
|
+
dashboards_total: dashboardsTotal,
|
|
163
|
+
error: null,
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
const healthEndpointUrl = resolveGrafanaHealthEndpointUrl();
|
|
167
|
+
if (!healthEndpointUrl) {
|
|
168
|
+
grafanaRuntimeSnapshotCache = {
|
|
169
|
+
expires_at_ms: nowMs + SYSTEM_ADMIN_GRAFANA_STATUS_CACHE_TTL_MS,
|
|
170
|
+
payload: baseSnapshot,
|
|
171
|
+
};
|
|
172
|
+
return baseSnapshot;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const controller = typeof AbortController === 'function' ? new AbortController() : null;
|
|
176
|
+
const timeoutId =
|
|
177
|
+
controller && Number.isFinite(SYSTEM_ADMIN_GRAFANA_STATUS_TIMEOUT_MS)
|
|
178
|
+
? setTimeout(() => {
|
|
179
|
+
controller.abort();
|
|
180
|
+
}, SYSTEM_ADMIN_GRAFANA_STATUS_TIMEOUT_MS)
|
|
181
|
+
: null;
|
|
182
|
+
const startedAtMs = __timeNowMs();
|
|
183
|
+
|
|
184
|
+
try {
|
|
185
|
+
const response = await globalThis.fetch(healthEndpointUrl, {
|
|
186
|
+
method: 'GET',
|
|
187
|
+
headers: {
|
|
188
|
+
Accept: 'application/json',
|
|
189
|
+
},
|
|
190
|
+
signal: controller?.signal,
|
|
191
|
+
});
|
|
192
|
+
const responseMs = Math.max(0, __timeNowMs() - startedAtMs);
|
|
193
|
+
let payload = null;
|
|
194
|
+
try {
|
|
195
|
+
payload = await response.json();
|
|
196
|
+
} catch {
|
|
197
|
+
payload = null;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const version = sanitizeText(payload?.version || '', 40, { allowEmpty: true }) || null;
|
|
201
|
+
const commit = sanitizeText(payload?.commit || '', 80, { allowEmpty: true }) || null;
|
|
202
|
+
const database = sanitizeText(payload?.database || '', 30, { allowEmpty: true }) || null;
|
|
203
|
+
const normalizedDatabase = String(database || '')
|
|
204
|
+
.trim()
|
|
205
|
+
.toLowerCase();
|
|
206
|
+
const status = !response.ok ? 'offline' : normalizedDatabase === 'ok' ? 'online' : normalizedDatabase ? 'degraded' : 'online';
|
|
207
|
+
|
|
208
|
+
const snapshot = {
|
|
209
|
+
...baseSnapshot,
|
|
210
|
+
available: response.ok,
|
|
211
|
+
status,
|
|
212
|
+
response_ms: responseMs,
|
|
213
|
+
checked_at: __timeNowIso(),
|
|
214
|
+
http_status: Number(response.status || 0) || null,
|
|
215
|
+
version,
|
|
216
|
+
database,
|
|
217
|
+
commit,
|
|
218
|
+
};
|
|
219
|
+
grafanaRuntimeSnapshotCache = {
|
|
220
|
+
expires_at_ms: __timeNowMs() + SYSTEM_ADMIN_GRAFANA_STATUS_CACHE_TTL_MS,
|
|
221
|
+
payload: snapshot,
|
|
222
|
+
};
|
|
223
|
+
return snapshot;
|
|
224
|
+
} catch (error) {
|
|
225
|
+
const responseMs = Math.max(0, __timeNowMs() - startedAtMs);
|
|
226
|
+
const isAbort = error?.name === 'AbortError';
|
|
227
|
+
const snapshot = {
|
|
228
|
+
...baseSnapshot,
|
|
229
|
+
status: 'offline',
|
|
230
|
+
response_ms: responseMs,
|
|
231
|
+
checked_at: __timeNowIso(),
|
|
232
|
+
error: sanitizeText(isAbort ? 'timeout' : error?.message || 'fetch_failed', 80, { allowEmpty: true }) || 'fetch_failed',
|
|
233
|
+
};
|
|
234
|
+
grafanaRuntimeSnapshotCache = {
|
|
235
|
+
expires_at_ms: __timeNowMs() + SYSTEM_ADMIN_GRAFANA_STATUS_CACHE_TTL_MS,
|
|
236
|
+
payload: snapshot,
|
|
237
|
+
};
|
|
238
|
+
return snapshot;
|
|
239
|
+
} finally {
|
|
240
|
+
if (timeoutId) clearTimeout(timeoutId);
|
|
241
|
+
}
|
|
242
|
+
};
|
|
18
243
|
|
|
19
244
|
const adminPanelSessionMap = new Map();
|
|
20
245
|
let adminPanelSessionPruneAt = 0;
|
|
@@ -298,7 +523,7 @@ export const createStickerCatalogAdminHandlers = ({ executeQuery, tables, logger
|
|
|
298
523
|
};
|
|
299
524
|
|
|
300
525
|
const pruneExpiredAdminPanelSessions = () => {
|
|
301
|
-
const now =
|
|
526
|
+
const now = __timeNowMs();
|
|
302
527
|
if (now - adminPanelSessionPruneAt < 30_000) return;
|
|
303
528
|
adminPanelSessionPruneAt = now;
|
|
304
529
|
for (const [token, session] of adminPanelSessionMap.entries()) {
|
|
@@ -334,7 +559,7 @@ export const createStickerCatalogAdminHandlers = ({ executeQuery, tables, logger
|
|
|
334
559
|
|
|
335
560
|
const createAdminPanelSession = (googleSession, { role = 'owner' } = {}) => {
|
|
336
561
|
pruneExpiredAdminPanelSessions();
|
|
337
|
-
const now =
|
|
562
|
+
const now = __timeNowMs();
|
|
338
563
|
const normalizedRole = normalizeAdminPanelRole(role, 'owner');
|
|
339
564
|
const token = randomUUID();
|
|
340
565
|
const session = {
|
|
@@ -359,7 +584,7 @@ export const createStickerCatalogAdminHandlers = ({ executeQuery, tables, logger
|
|
|
359
584
|
if (!token) return null;
|
|
360
585
|
const session = adminPanelSessionMap.get(token);
|
|
361
586
|
if (!session) return null;
|
|
362
|
-
if (Number(session.expiresAt || 0) <=
|
|
587
|
+
if (Number(session.expiresAt || 0) <= __timeNowMs()) {
|
|
363
588
|
adminPanelSessionMap.delete(token);
|
|
364
589
|
return null;
|
|
365
590
|
}
|
|
@@ -616,7 +841,7 @@ export const createStickerCatalogAdminHandlers = ({ executeQuery, tables, logger
|
|
|
616
841
|
rollout_percent: Math.max(0, Math.min(100, Number(row?.rollout_percent ?? normalizedRollout))),
|
|
617
842
|
description: sanitizeText(row?.description || '', 255, { allowEmpty: true }) || null,
|
|
618
843
|
updated_by: sanitizeText(row?.updated_by || '', 120, { allowEmpty: true }) || null,
|
|
619
|
-
updated_at: toIsoOrNull(row?.updated_at) ||
|
|
844
|
+
updated_at: toIsoOrNull(row?.updated_at) || __timeNowIso(),
|
|
620
845
|
};
|
|
621
846
|
};
|
|
622
847
|
|
|
@@ -759,7 +984,7 @@ export const createStickerCatalogAdminHandlers = ({ executeQuery, tables, logger
|
|
|
759
984
|
|
|
760
985
|
const buildAdminAlertSnapshot = ({ dashboardQuick = null, systemHealth = null, systemSummary = null, systemMeta = null } = {}) => {
|
|
761
986
|
const alerts = [];
|
|
762
|
-
const updatedAt = toIsoOrNull(systemSummary?.updated_at) ||
|
|
987
|
+
const updatedAt = toIsoOrNull(systemSummary?.updated_at) || __timeNowIso();
|
|
763
988
|
const pushAlert = (severity, code, title, message) => {
|
|
764
989
|
alerts.push({
|
|
765
990
|
id: `${code}:${severity}`,
|
|
@@ -956,7 +1181,7 @@ export const createStickerCatalogAdminHandlers = ({ executeQuery, tables, logger
|
|
|
956
1181
|
};
|
|
957
1182
|
|
|
958
1183
|
const buildAdminOverviewPayload = async ({ adminSession = null } = {}) => {
|
|
959
|
-
const [marketplaceStats, activeSessions, knownUsers, bans, packsCountRows, stickersCountRows, recentPacks, visitSummary, systemSummaryPayload, messageFlowDaily, moderationQueue, auditLog, featureFlags] = await Promise.all([
|
|
1184
|
+
const [marketplaceStats, activeSessions, knownUsers, bans, packsCountRows, stickersCountRows, recentPacks, visitSummary, systemSummaryPayload, messageFlowDaily, moderationQueue, auditLog, featureFlags, grafanaRuntimeSnapshot] = await Promise.all([
|
|
960
1185
|
getMarketplaceGlobalStatsCached().catch(() => null),
|
|
961
1186
|
listAdminActiveGoogleWebSessions({ limit: 80 }),
|
|
962
1187
|
listAdminKnownGoogleUsers({ limit: 120 }),
|
|
@@ -975,6 +1200,19 @@ export const createStickerCatalogAdminHandlers = ({ executeQuery, tables, logger
|
|
|
975
1200
|
buildModerationQueueSnapshot({ limit: 80 }).catch(() => []),
|
|
976
1201
|
listAdminAuditLog({ limit: 120 }).catch(() => []),
|
|
977
1202
|
listAdminFeatureFlagsDetailed({ limit: 300 }).catch(() => []),
|
|
1203
|
+
getGrafanaRuntimeSnapshot().catch(() => ({
|
|
1204
|
+
enabled: Boolean(grafanaObservabilityLinks?.enabled),
|
|
1205
|
+
available: false,
|
|
1206
|
+
status: 'offline',
|
|
1207
|
+
response_ms: null,
|
|
1208
|
+
checked_at: __timeNowIso(),
|
|
1209
|
+
http_status: null,
|
|
1210
|
+
version: null,
|
|
1211
|
+
database: null,
|
|
1212
|
+
commit: null,
|
|
1213
|
+
dashboards_total: Array.isArray(grafanaObservabilityLinks?.dashboards) ? grafanaObservabilityLinks.dashboards.length : 0,
|
|
1214
|
+
error: 'runtime_snapshot_failed',
|
|
1215
|
+
})),
|
|
978
1216
|
]);
|
|
979
1217
|
|
|
980
1218
|
const systemSummary = systemSummaryPayload?.data || null;
|
|
@@ -996,7 +1234,7 @@ export const createStickerCatalogAdminHandlers = ({ executeQuery, tables, logger
|
|
|
996
1234
|
systemMeta,
|
|
997
1235
|
});
|
|
998
1236
|
|
|
999
|
-
|
|
1237
|
+
const overviewPayload = {
|
|
1000
1238
|
admin_session: mapAdminPanelSessionResponseData(adminSession),
|
|
1001
1239
|
marketplace_stats: marketplaceStats,
|
|
1002
1240
|
counters: {
|
|
@@ -1044,9 +1282,22 @@ export const createStickerCatalogAdminHandlers = ({ executeQuery, tables, logger
|
|
|
1044
1282
|
visit_metrics: visitSummary,
|
|
1045
1283
|
system_summary: systemSummary,
|
|
1046
1284
|
system_meta: systemMeta,
|
|
1285
|
+
observability_links: {
|
|
1286
|
+
grafana: grafanaObservabilityLinks,
|
|
1287
|
+
},
|
|
1288
|
+
observability_runtime: {
|
|
1289
|
+
grafana: grafanaRuntimeSnapshot,
|
|
1290
|
+
},
|
|
1047
1291
|
message_flow_daily: messageFlowDaily,
|
|
1048
|
-
updated_at:
|
|
1292
|
+
updated_at: __timeNowIso(),
|
|
1049
1293
|
};
|
|
1294
|
+
|
|
1295
|
+
setAdminOverviewSnapshot({
|
|
1296
|
+
overview: overviewPayload,
|
|
1297
|
+
source: 'admin_overview_payload',
|
|
1298
|
+
});
|
|
1299
|
+
|
|
1300
|
+
return overviewPayload;
|
|
1050
1301
|
};
|
|
1051
1302
|
|
|
1052
1303
|
const findAdminPackContextByKey = async (rawPackKey) => {
|
|
@@ -1364,7 +1615,7 @@ export const createStickerCatalogAdminHandlers = ({ executeQuery, tables, logger
|
|
|
1364
1615
|
action,
|
|
1365
1616
|
success: true,
|
|
1366
1617
|
message: 'Caches internos invalidados com sucesso.',
|
|
1367
|
-
updated_at:
|
|
1618
|
+
updated_at: __timeNowIso(),
|
|
1368
1619
|
},
|
|
1369
1620
|
});
|
|
1370
1621
|
return;
|
|
@@ -1408,7 +1659,7 @@ export const createStickerCatalogAdminHandlers = ({ executeQuery, tables, logger
|
|
|
1408
1659
|
success: true,
|
|
1409
1660
|
released_processing_items: released,
|
|
1410
1661
|
message: released > 0 ? 'Itens em processamento foram recolocados em pending.' : 'Nenhum item travado encontrado nas filas.',
|
|
1411
|
-
updated_at:
|
|
1662
|
+
updated_at: __timeNowIso(),
|
|
1412
1663
|
},
|
|
1413
1664
|
});
|
|
1414
1665
|
return;
|
|
@@ -1418,7 +1669,7 @@ export const createStickerCatalogAdminHandlers = ({ executeQuery, tables, logger
|
|
|
1418
1669
|
const payloadJson = JSON.stringify({
|
|
1419
1670
|
source: 'admin_panel',
|
|
1420
1671
|
requested_by: normalizeEmail(adminSession?.email) || normalizeGoogleSubject(adminSession?.googleSub) || 'admin',
|
|
1421
|
-
requested_at:
|
|
1672
|
+
requested_at: __timeNowIso(),
|
|
1422
1673
|
});
|
|
1423
1674
|
await executeQuery(
|
|
1424
1675
|
`INSERT INTO ${TABLES.STICKER_WORKER_TASK_QUEUE}
|
|
@@ -1440,7 +1691,7 @@ export const createStickerCatalogAdminHandlers = ({ executeQuery, tables, logger
|
|
|
1440
1691
|
success: true,
|
|
1441
1692
|
enqueued_tasks: 2,
|
|
1442
1693
|
message: 'Ciclos de classificação e curadoria foram agendados.',
|
|
1443
|
-
updated_at:
|
|
1694
|
+
updated_at: __timeNowIso(),
|
|
1444
1695
|
},
|
|
1445
1696
|
});
|
|
1446
1697
|
return;
|
|
@@ -1632,7 +1883,7 @@ export const createStickerCatalogAdminHandlers = ({ executeQuery, tables, logger
|
|
|
1632
1883
|
data: {
|
|
1633
1884
|
type,
|
|
1634
1885
|
format: 'json',
|
|
1635
|
-
exported_at:
|
|
1886
|
+
exported_at: __timeNowIso(),
|
|
1636
1887
|
payload: exportData,
|
|
1637
1888
|
},
|
|
1638
1889
|
});
|
|
@@ -1705,7 +1956,7 @@ export const createStickerCatalogAdminHandlers = ({ executeQuery, tables, logger
|
|
|
1705
1956
|
}
|
|
1706
1957
|
|
|
1707
1958
|
const csv = buildCsv(rows, headers);
|
|
1708
|
-
const stamp =
|
|
1959
|
+
const stamp = __timeNowIso().slice(0, 19).replace(/[:T]/g, '-');
|
|
1709
1960
|
res.statusCode = 200;
|
|
1710
1961
|
res.setHeader('Content-Type', 'text/csv; charset=utf-8');
|
|
1711
1962
|
res.setHeader('Content-Disposition', `attachment; filename="admin-${type}-${stamp}.csv"`);
|
|
@@ -1862,7 +2113,7 @@ export const createStickerCatalogAdminHandlers = ({ executeQuery, tables, logger
|
|
|
1862
2113
|
deleted: !result?.missing,
|
|
1863
2114
|
pack_key: result?.deletedPack?.pack_key || context.packKey,
|
|
1864
2115
|
id: result?.deletedPack?.id || context.fullPack?.id || null,
|
|
1865
|
-
deleted_at: toIsoOrNull(result?.deletedPack?.deleted_at ||
|
|
2116
|
+
deleted_at: toIsoOrNull(result?.deletedPack?.deleted_at || __timeNow()),
|
|
1866
2117
|
removed_sticker_count: Number(result?.removedCount || 0),
|
|
1867
2118
|
});
|
|
1868
2119
|
};
|