smoonb 0.0.21 → 0.0.24
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/README.md +25 -8
- package/bin/smoonb.js +1 -0
- package/package.json +6 -2
- package/src/commands/backup.js +30 -83
- package/src/utils/realtime-settings.js +205 -0
package/README.md
CHANGED
|
@@ -25,25 +25,42 @@ O **smoonb** resolve o problema das ferramentas existentes que fazem backup apen
|
|
|
25
25
|
|
|
26
26
|
## 🚀 Instalação
|
|
27
27
|
|
|
28
|
+
**⚠️ IMPORTANTE: Instale APENAS localmente no projeto!**
|
|
29
|
+
|
|
28
30
|
```bash
|
|
29
|
-
# Instalar localmente no projeto
|
|
31
|
+
# ✅ CORRETO - Instalar localmente no projeto
|
|
30
32
|
npm install smoonb
|
|
31
33
|
|
|
32
|
-
# Usar com npx
|
|
34
|
+
# ✅ CORRETO - Usar com npx
|
|
33
35
|
npx smoonb --help
|
|
36
|
+
|
|
37
|
+
# ❌ ERRADO - NÃO instalar globalmente
|
|
38
|
+
npm install -g smoonb # ← Isso será bloqueado!
|
|
34
39
|
```
|
|
35
40
|
|
|
41
|
+
**💡 Por que apenas local?**
|
|
42
|
+
- **🔒 Segurança**: Evita conflitos de versão
|
|
43
|
+
- **📦 Isolamento**: Cada projeto usa sua versão
|
|
44
|
+
- **🔄 Atualizações**: Controle granular por projeto
|
|
45
|
+
- **🛡️ Estabilidade**: Evita quebras em outros projetos
|
|
46
|
+
|
|
36
47
|
## 📋 Pré-requisitos
|
|
37
48
|
|
|
38
|
-
### 1.
|
|
49
|
+
### 1. Docker Desktop
|
|
39
50
|
```bash
|
|
40
|
-
|
|
51
|
+
# Instalar Docker Desktop
|
|
52
|
+
# Windows/macOS: https://docs.docker.com/desktop/install/
|
|
53
|
+
# Linux: https://docs.docker.com/engine/install/
|
|
54
|
+
|
|
55
|
+
# Verificar se está rodando
|
|
56
|
+
docker --version
|
|
57
|
+
docker ps
|
|
41
58
|
```
|
|
42
59
|
|
|
43
|
-
### 2.
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
60
|
+
### 2. Supabase CLI
|
|
61
|
+
```bash
|
|
62
|
+
npm install -g supabase
|
|
63
|
+
```
|
|
47
64
|
|
|
48
65
|
## ⚙️ Configuração
|
|
49
66
|
|
package/bin/smoonb.js
CHANGED
|
@@ -70,6 +70,7 @@ program
|
|
|
70
70
|
.command('backup')
|
|
71
71
|
.description('Fazer backup completo do projeto Supabase usando Supabase CLI')
|
|
72
72
|
.option('-o, --output <dir>', 'Diretório de saída do backup')
|
|
73
|
+
.option('--skip-realtime', 'Pular captura interativa de Realtime Settings')
|
|
73
74
|
.action(commands.backup);
|
|
74
75
|
|
|
75
76
|
program
|
package/package.json
CHANGED
|
@@ -1,14 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "smoonb",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.24",
|
|
4
4
|
"description": "Complete Supabase backup and migration tool - EXPERIMENTAL VERSION - USE AT YOUR OWN RISK",
|
|
5
|
+
"preferGlobal": false,
|
|
6
|
+
"preventGlobalInstall": true,
|
|
5
7
|
"main": "index.js",
|
|
6
8
|
"bin": {
|
|
7
9
|
"smoonb": "bin/smoonb.js"
|
|
8
10
|
},
|
|
9
11
|
"scripts": {
|
|
10
12
|
"test": "echo \"Error: no test specified\" && exit 1",
|
|
11
|
-
"start": "node bin/smoonb.js"
|
|
13
|
+
"start": "node bin/smoonb.js",
|
|
14
|
+
"preinstall": "node -e \"if(process.env.npm_config_global) { console.error('\\n❌ SMOONB NÃO DEVE SER INSTALADO GLOBALMENTE!\\n\\n📋 Para usar o smoonb, instale localmente no seu projeto:\\n npm install smoonb\\n\\n💡 Depois execute com:\\n npx smoonb backup\\n\\n🚫 Instalação global cancelada!\\n'); process.exit(1); }\"",
|
|
15
|
+
"postinstall": "echo '\\n✅ smoonb instalado com sucesso!\\n💡 Execute: npx smoonb backup\\n📖 Documentação: https://github.com/almmello/smoonb\\n'"
|
|
12
16
|
},
|
|
13
17
|
"keywords": [
|
|
14
18
|
"supabase",
|
package/src/commands/backup.js
CHANGED
|
@@ -8,6 +8,7 @@ const { sha256 } = require('../utils/hash');
|
|
|
8
8
|
const { readConfig, validateFor } = require('../utils/config');
|
|
9
9
|
const { showBetaBanner } = require('../utils/banner');
|
|
10
10
|
const { canPerformCompleteBackup, getDockerVersion } = require('../utils/docker');
|
|
11
|
+
const { captureRealtimeSettings } = require('../utils/realtime-settings');
|
|
11
12
|
|
|
12
13
|
const execAsync = promisify(exec);
|
|
13
14
|
|
|
@@ -135,9 +136,9 @@ async function performFullBackup(config, options) {
|
|
|
135
136
|
const rolesResult = await backupCustomRoles(config.supabase.databaseUrl, backupDir);
|
|
136
137
|
manifest.components.custom_roles = rolesResult;
|
|
137
138
|
|
|
138
|
-
// 6. Backup Realtime Settings via
|
|
139
|
-
console.log(chalk.blue('\n🔄 6/6 - Backup das Realtime Settings via
|
|
140
|
-
const realtimeResult = await backupRealtimeSettings(config.supabase.
|
|
139
|
+
// 6. Backup Realtime Settings via Captura Interativa
|
|
140
|
+
console.log(chalk.blue('\n🔄 6/6 - Backup das Realtime Settings via Captura Interativa...'));
|
|
141
|
+
const realtimeResult = await backupRealtimeSettings(config.supabase.projectId, backupDir, options.skipRealtime);
|
|
141
142
|
manifest.components.realtime = realtimeResult;
|
|
142
143
|
|
|
143
144
|
// Salvar manifest
|
|
@@ -230,7 +231,7 @@ async function backupDatabaseWithDocker(databaseUrl, backupDir) {
|
|
|
230
231
|
const schemaFile = path.join(backupDir, 'schema.sql');
|
|
231
232
|
|
|
232
233
|
try {
|
|
233
|
-
await execAsync(`supabase db dump --db-url "${databaseUrl}"
|
|
234
|
+
await execAsync(`supabase db dump --db-url "${databaseUrl}" -f "${schemaFile}"`);
|
|
234
235
|
|
|
235
236
|
const schemaValidation = await validateSqlFile(schemaFile);
|
|
236
237
|
if (schemaValidation.valid) {
|
|
@@ -524,102 +525,48 @@ async function backupStorage(projectId, accessToken, backupDir) {
|
|
|
524
525
|
}
|
|
525
526
|
}
|
|
526
527
|
|
|
527
|
-
// Backup dos Custom Roles via
|
|
528
|
+
// Backup dos Custom Roles via Docker
|
|
528
529
|
async function backupCustomRoles(databaseUrl, backupDir) {
|
|
529
530
|
try {
|
|
530
|
-
console.log(chalk.gray(' - Exportando Custom Roles...'));
|
|
531
|
+
console.log(chalk.gray(' - Exportando Custom Roles via Docker...'));
|
|
531
532
|
|
|
532
533
|
const customRolesFile = path.join(backupDir, 'custom-roles.sql');
|
|
533
534
|
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
--
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
);
|
|
549
|
-
|
|
550
|
-
const rolesContent = `-- Custom Roles Backup
|
|
551
|
-
-- Generated at: ${new Date().toISOString()}
|
|
552
|
-
|
|
553
|
-
${customRolesQuery}
|
|
554
|
-
|
|
555
|
-
-- Results:
|
|
556
|
-
${stdout}
|
|
557
|
-
`;
|
|
558
|
-
|
|
559
|
-
await fs.promises.writeFile(customRolesFile, rolesContent);
|
|
560
|
-
|
|
561
|
-
const stats = fs.statSync(customRolesFile);
|
|
562
|
-
const sizeKB = (stats.size / 1024).toFixed(1);
|
|
563
|
-
|
|
564
|
-
console.log(chalk.green(` ✅ Custom Roles exportados: ${sizeKB} KB`));
|
|
565
|
-
|
|
566
|
-
return { success: true, roles: [{ filename: 'custom-roles.sql', sizeKB }] };
|
|
535
|
+
try {
|
|
536
|
+
// ✅ Usar Supabase CLI via Docker para roles
|
|
537
|
+
await execAsync(`supabase db dump --db-url "${databaseUrl}" --role-only -f "${customRolesFile}"`);
|
|
538
|
+
|
|
539
|
+
const stats = fs.statSync(customRolesFile);
|
|
540
|
+
const sizeKB = (stats.size / 1024).toFixed(1);
|
|
541
|
+
|
|
542
|
+
console.log(chalk.green(` ✅ Custom Roles exportados via Docker: ${sizeKB} KB`));
|
|
543
|
+
|
|
544
|
+
return { success: true, roles: [{ filename: 'custom-roles.sql', sizeKB }] };
|
|
545
|
+
} catch (error) {
|
|
546
|
+
console.log(chalk.yellow(` ⚠️ Erro ao exportar Custom Roles via Docker: ${error.message}`));
|
|
547
|
+
return { success: false, roles: [] };
|
|
548
|
+
}
|
|
567
549
|
} catch (error) {
|
|
568
|
-
console.log(chalk.yellow(` ⚠️ Erro
|
|
550
|
+
console.log(chalk.yellow(` ⚠️ Erro no backup dos Custom Roles: ${error.message}`));
|
|
569
551
|
return { success: false, roles: [] };
|
|
570
552
|
}
|
|
571
553
|
}
|
|
572
554
|
|
|
573
|
-
// Backup das Realtime Settings via
|
|
574
|
-
async function backupRealtimeSettings(
|
|
555
|
+
// Backup das Realtime Settings via Captura Interativa
|
|
556
|
+
async function backupRealtimeSettings(projectId, backupDir, skipInteractive = false) {
|
|
575
557
|
try {
|
|
576
|
-
console.log(chalk.gray(' -
|
|
558
|
+
console.log(chalk.gray(' - Capturando Realtime Settings interativamente...'));
|
|
577
559
|
|
|
578
|
-
const
|
|
560
|
+
const result = await captureRealtimeSettings(projectId, backupDir, skipInteractive);
|
|
579
561
|
|
|
580
|
-
|
|
581
|
-
const realtimeQuery = `
|
|
582
|
-
-- Realtime Settings Backup
|
|
583
|
-
-- Publicações e configurações de Realtime
|
|
584
|
-
|
|
585
|
-
-- Publicações
|
|
586
|
-
SELECT pubname, puballtables, pubinsert, pubupdate, pubdelete, pubtruncate
|
|
587
|
-
FROM pg_publication
|
|
588
|
-
ORDER BY pubname;
|
|
589
|
-
|
|
590
|
-
-- Tabelas publicadas
|
|
591
|
-
SELECT p.pubname, c.relname as table_name, n.nspname as schema_name
|
|
592
|
-
FROM pg_publication_tables pt
|
|
593
|
-
JOIN pg_publication p ON p.oid = pt.ptpubid
|
|
594
|
-
JOIN pg_class c ON c.oid = pt.ptrelid
|
|
595
|
-
JOIN pg_namespace n ON n.oid = c.relnamespace
|
|
596
|
-
ORDER BY p.pubname, n.nspname, c.relname;
|
|
597
|
-
`;
|
|
598
|
-
|
|
599
|
-
// Executar query e salvar resultado
|
|
600
|
-
const { stdout } = await execAsync(
|
|
601
|
-
`psql "${databaseUrl}" -t -c "${realtimeQuery}"`
|
|
602
|
-
);
|
|
603
|
-
|
|
604
|
-
const realtimeContent = `-- Realtime Settings Backup
|
|
605
|
-
-- Generated at: ${new Date().toISOString()}
|
|
606
|
-
|
|
607
|
-
${realtimeQuery}
|
|
608
|
-
|
|
609
|
-
-- Results:
|
|
610
|
-
${stdout}
|
|
611
|
-
`;
|
|
612
|
-
|
|
613
|
-
await fs.promises.writeFile(realtimeFile, realtimeContent);
|
|
614
|
-
|
|
615
|
-
const stats = fs.statSync(realtimeFile);
|
|
562
|
+
const stats = fs.statSync(path.join(backupDir, 'realtime-settings.json'));
|
|
616
563
|
const sizeKB = (stats.size / 1024).toFixed(1);
|
|
617
564
|
|
|
618
|
-
console.log(chalk.green(` ✅ Realtime Settings
|
|
565
|
+
console.log(chalk.green(` ✅ Realtime Settings capturadas: ${sizeKB} KB`));
|
|
619
566
|
|
|
620
|
-
return { success: true };
|
|
567
|
+
return { success: true, settings: result };
|
|
621
568
|
} catch (error) {
|
|
622
|
-
console.log(chalk.yellow(` ⚠️ Erro ao
|
|
569
|
+
console.log(chalk.yellow(` ⚠️ Erro ao capturar Realtime Settings: ${error.message}`));
|
|
623
570
|
return { success: false };
|
|
624
571
|
}
|
|
625
572
|
}
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
const readline = require('readline');
|
|
2
|
+
const fs = require('fs').promises;
|
|
3
|
+
const path = require('path');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Captura configurações de Realtime Settings interativamente
|
|
7
|
+
* @param {string} projectId - ID do projeto Supabase
|
|
8
|
+
* @param {string} backupDir - Diretório do backup atual
|
|
9
|
+
* @param {boolean} skipInteractive - Se deve pular a etapa interativa
|
|
10
|
+
* @returns {Promise<Object>} Configurações capturadas
|
|
11
|
+
*/
|
|
12
|
+
async function captureRealtimeSettings(projectId, backupDir, skipInteractive = false) {
|
|
13
|
+
const settingsFile = path.join(backupDir, 'realtime-settings.json');
|
|
14
|
+
const dashboardUrl = `https://supabase.com/dashboard/project/${projectId}/realtime/settings`;
|
|
15
|
+
|
|
16
|
+
// Tentar ler configurações de backup anterior
|
|
17
|
+
const previousSettings = await getPreviousRealtimeSettings(backupDir);
|
|
18
|
+
|
|
19
|
+
if (skipInteractive && previousSettings) {
|
|
20
|
+
console.log('📋 Usando configurações de Realtime Settings do backup anterior...');
|
|
21
|
+
await fs.writeFile(settingsFile, JSON.stringify(previousSettings, null, 2));
|
|
22
|
+
return previousSettings;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (previousSettings && !skipInteractive) {
|
|
26
|
+
const shouldReuse = await askToReusePreviousSettings();
|
|
27
|
+
if (shouldReuse) {
|
|
28
|
+
console.log('📋 Reutilizando configurações de Realtime Settings do backup anterior...');
|
|
29
|
+
await fs.writeFile(settingsFile, JSON.stringify(previousSettings, null, 2));
|
|
30
|
+
return previousSettings;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Capturar configurações interativamente
|
|
35
|
+
console.log('\n🔧 Configurações de Realtime Settings');
|
|
36
|
+
console.log('═'.repeat(50));
|
|
37
|
+
console.log(`📱 Acesse: ${dashboardUrl}`);
|
|
38
|
+
console.log('📝 Anote os valores dos 4 parâmetros abaixo:\n');
|
|
39
|
+
|
|
40
|
+
const settings = await captureSettingsInteractively(projectId, previousSettings);
|
|
41
|
+
|
|
42
|
+
// Salvar configurações
|
|
43
|
+
await fs.writeFile(settingsFile, JSON.stringify(settings, null, 2));
|
|
44
|
+
console.log('\n✅ Configurações de Realtime Settings salvas!');
|
|
45
|
+
|
|
46
|
+
return settings;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Busca configurações de backup anterior
|
|
51
|
+
* @param {string} backupDir - Diretório do backup atual
|
|
52
|
+
* @returns {Promise<Object|null>} Configurações anteriores ou null
|
|
53
|
+
*/
|
|
54
|
+
async function getPreviousRealtimeSettings(backupDir) {
|
|
55
|
+
try {
|
|
56
|
+
// Buscar em backups anteriores
|
|
57
|
+
const backupsDir = path.dirname(backupDir);
|
|
58
|
+
const entries = await fs.readdir(backupsDir, { withFileTypes: true });
|
|
59
|
+
const backupDirs = entries
|
|
60
|
+
.filter(entry => entry.isDirectory() && entry.name.startsWith('backup-'))
|
|
61
|
+
.map(entry => entry.name)
|
|
62
|
+
.sort()
|
|
63
|
+
.reverse(); // Mais recente primeiro
|
|
64
|
+
|
|
65
|
+
for (const backupName of backupDirs) {
|
|
66
|
+
const settingsPath = path.join(backupsDir, backupName, 'realtime-settings.json');
|
|
67
|
+
try {
|
|
68
|
+
const content = await fs.readFile(settingsPath, 'utf8');
|
|
69
|
+
const settings = JSON.parse(content);
|
|
70
|
+
if (settings.realtime_settings && settings.realtime_settings.settings) {
|
|
71
|
+
return settings;
|
|
72
|
+
}
|
|
73
|
+
} catch (error) {
|
|
74
|
+
// Continuar para próximo backup
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return null;
|
|
80
|
+
} catch (error) {
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Pergunta se deve reutilizar configurações anteriores
|
|
87
|
+
* @returns {Promise<boolean>} true se deve reutilizar
|
|
88
|
+
*/
|
|
89
|
+
async function askToReusePreviousSettings() {
|
|
90
|
+
const rl = readline.createInterface({
|
|
91
|
+
input: process.stdin,
|
|
92
|
+
output: process.stdout
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
return new Promise((resolve) => {
|
|
96
|
+
rl.question('🔄 Foi identificada uma gravação anterior de Realtime Settings.\n Deseja reutilizar as configurações anteriores? (S/n): ', (answer) => {
|
|
97
|
+
rl.close();
|
|
98
|
+
const shouldReuse = !answer.toLowerCase().startsWith('n');
|
|
99
|
+
resolve(shouldReuse);
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Captura configurações interativamente via perguntas
|
|
106
|
+
* @param {string} projectId - ID do projeto Supabase
|
|
107
|
+
* @param {Object} previousSettings - Configurações anteriores para usar como padrão
|
|
108
|
+
* @returns {Promise<Object>} Configurações capturadas
|
|
109
|
+
*/
|
|
110
|
+
async function captureSettingsInteractively(projectId, previousSettings) {
|
|
111
|
+
const rl = readline.createInterface({
|
|
112
|
+
input: process.stdin,
|
|
113
|
+
output: process.stdout
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
const askQuestion = (question, defaultValue) => {
|
|
117
|
+
return new Promise((resolve) => {
|
|
118
|
+
rl.question(`${question} [${defaultValue}]: `, (answer) => {
|
|
119
|
+
resolve(answer.trim() || defaultValue);
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
try {
|
|
125
|
+
console.log('📋 Responda as perguntas abaixo (pressione Enter para usar o valor padrão):\n');
|
|
126
|
+
|
|
127
|
+
// Valores padrão baseados na imagem ou configurações anteriores
|
|
128
|
+
const defaults = previousSettings?.realtime_settings?.settings || {
|
|
129
|
+
allow_public_access: { value: true },
|
|
130
|
+
database_connection_pool_size: { value: 2 },
|
|
131
|
+
max_concurrent_clients: { value: 200 },
|
|
132
|
+
max_events_per_second: { value: 100 }
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
const allowPublicAccess = await askQuestion(
|
|
136
|
+
'1. Allow public access (true/false):',
|
|
137
|
+
defaults.allow_public_access.value
|
|
138
|
+
);
|
|
139
|
+
|
|
140
|
+
const poolSize = await askQuestion(
|
|
141
|
+
'2. Database connection pool size:',
|
|
142
|
+
defaults.database_connection_pool_size.value
|
|
143
|
+
);
|
|
144
|
+
|
|
145
|
+
const maxClients = await askQuestion(
|
|
146
|
+
'3. Max concurrent clients:',
|
|
147
|
+
defaults.max_concurrent_clients.value
|
|
148
|
+
);
|
|
149
|
+
|
|
150
|
+
const maxEvents = await askQuestion(
|
|
151
|
+
'4. Max events per second:',
|
|
152
|
+
defaults.max_events_per_second.value
|
|
153
|
+
);
|
|
154
|
+
|
|
155
|
+
const settings = {
|
|
156
|
+
realtime_settings: {
|
|
157
|
+
note: "Configurações de Realtime Settings capturadas interativamente",
|
|
158
|
+
dashboard_url: `https://supabase.com/dashboard/project/${projectId}/realtime/settings`,
|
|
159
|
+
captured_at: new Date().toISOString(),
|
|
160
|
+
settings: {
|
|
161
|
+
allow_public_access: {
|
|
162
|
+
label: "Allow public access",
|
|
163
|
+
description: "If disabled, only private channels will be allowed",
|
|
164
|
+
value: allowPublicAccess === 'true'
|
|
165
|
+
},
|
|
166
|
+
database_connection_pool_size: {
|
|
167
|
+
label: "Database connection pool size",
|
|
168
|
+
description: "Realtime Authorization uses this database pool to check client access",
|
|
169
|
+
value: parseInt(poolSize)
|
|
170
|
+
},
|
|
171
|
+
max_concurrent_clients: {
|
|
172
|
+
label: "Max concurrent clients",
|
|
173
|
+
description: "Sets maximum number of concurrent clients that can connect to your Realtime service",
|
|
174
|
+
value: parseInt(maxClients)
|
|
175
|
+
},
|
|
176
|
+
max_events_per_second: {
|
|
177
|
+
label: "Max events per second",
|
|
178
|
+
description: "Sets maximum number of events per second that can be sent to your Realtime service",
|
|
179
|
+
value: parseInt(maxEvents)
|
|
180
|
+
}
|
|
181
|
+
},
|
|
182
|
+
restore_instructions: {
|
|
183
|
+
url: `https://supabase.com/dashboard/project/${projectId}/realtime/settings`,
|
|
184
|
+
steps: [
|
|
185
|
+
"1. Acesse a URL acima",
|
|
186
|
+
"2. Configure 'Allow public access' conforme o valor em settings.allow_public_access.value",
|
|
187
|
+
"3. Configure 'Database connection pool size' conforme o valor em settings.database_connection_pool_size.value",
|
|
188
|
+
"4. Configure 'Max concurrent clients' conforme o valor em settings.max_concurrent_clients.value",
|
|
189
|
+
"5. Configure 'Max events per second' conforme o valor em settings.max_events_per_second.value",
|
|
190
|
+
"6. Clique em 'Save changes'"
|
|
191
|
+
]
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
return settings;
|
|
197
|
+
|
|
198
|
+
} finally {
|
|
199
|
+
rl.close();
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
module.exports = {
|
|
204
|
+
captureRealtimeSettings
|
|
205
|
+
};
|