chore: sync workspace state
This commit is contained in:
parent
ed2052c8ec
commit
3a6a571361
38 changed files with 3978 additions and 620 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -76,3 +76,4 @@ errors_utf8.txt
|
||||||
|
|
||||||
vault-unseal-keysdefault-vault.tar
|
vault-unseal-keysdefault-vault.tar
|
||||||
prompts/sec-bots.md
|
prompts/sec-bots.md
|
||||||
|
AGENTS-PROD.md
|
||||||
|
|
|
||||||
37
.playwright-mcp/cristo-snapshot.md
Normal file
37
.playwright-mcp/cristo-snapshot.md
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
- generic [ref=e5]:
|
||||||
|
- generic [ref=e6]:
|
||||||
|
- generic [ref=e7]: Chat
|
||||||
|
- generic [ref=e8]:
|
||||||
|
- button [ref=e9] [cursor=pointer]:
|
||||||
|
- img [ref=e10]
|
||||||
|
- button [ref=e11] [cursor=pointer]:
|
||||||
|
- img [ref=e12]
|
||||||
|
- button [ref=e14] [cursor=pointer]:
|
||||||
|
- img [ref=e15]
|
||||||
|
- generic [ref=e18]:
|
||||||
|
- generic [ref=e19]:
|
||||||
|
- main [ref=e20]:
|
||||||
|
- paragraph [ref=e31]: Olá! Sou o assistente virtual do Santuário Cristo Redentor. Como posso ajudá-lo hoje com informações sobre celebrações, eventos, visitação ou orações?
|
||||||
|
- contentinfo [ref=e21]:
|
||||||
|
- generic [ref=e22]:
|
||||||
|
- button "Agendar Batizado" [ref=e32] [cursor=pointer]
|
||||||
|
- button "Agendar Casamento" [ref=e33] [cursor=pointer]
|
||||||
|
- button "Agendar Missa" [ref=e34] [cursor=pointer]
|
||||||
|
- button "Agendar Peregrinação" [ref=e35] [cursor=pointer]
|
||||||
|
- button "Pedido de Oração" [ref=e36] [cursor=pointer]
|
||||||
|
- button "Uso de Imagem" [ref=e37] [cursor=pointer]
|
||||||
|
- button "Licenciamento" [ref=e38] [cursor=pointer]
|
||||||
|
- button "Evento/Iluminação" [ref=e39] [cursor=pointer]
|
||||||
|
- button "Cadastrar Guia" [ref=e40] [cursor=pointer]
|
||||||
|
- button "Fazer Doação" [ref=e41] [cursor=pointer]
|
||||||
|
- generic [ref=e23]:
|
||||||
|
- generic [ref=e24]:
|
||||||
|
- button "Agent" [ref=e25] [cursor=pointer]
|
||||||
|
- button "Chat" [ref=e26] [cursor=pointer]
|
||||||
|
- textbox "Message... (type @ to mention)" [active] [ref=e27]
|
||||||
|
- button "↑" [ref=e28] [cursor=pointer]
|
||||||
|
- button "Scroll to bottom":
|
||||||
|
- img
|
||||||
|
- generic:
|
||||||
|
- generic:
|
||||||
|
- button "View"
|
||||||
45
.playwright-mcp/page-2026-03-31T19-16-51-748Z.yml
Normal file
45
.playwright-mcp/page-2026-03-31T19-16-51-748Z.yml
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
- generic [ref=e2]:
|
||||||
|
- generic [ref=e3]:
|
||||||
|
- img "General Bots" [ref=e6]
|
||||||
|
- heading "Welcome Back" [level=1] [ref=e7]
|
||||||
|
- paragraph [ref=e8]: Sign in to your General Bots account
|
||||||
|
- generic [ref=e10]:
|
||||||
|
- generic [ref=e11]:
|
||||||
|
- generic [ref=e12]:
|
||||||
|
- generic [ref=e13]: Email Address
|
||||||
|
- generic [ref=e14]:
|
||||||
|
- img
|
||||||
|
- textbox "Email Address" [ref=e15]:
|
||||||
|
- /placeholder: you@example.com
|
||||||
|
- generic [ref=e16]:
|
||||||
|
- generic [ref=e17]: Password
|
||||||
|
- generic [ref=e18]:
|
||||||
|
- img
|
||||||
|
- textbox "Password" [ref=e19]:
|
||||||
|
- /placeholder: ••••••••
|
||||||
|
- button [ref=e20] [cursor=pointer]:
|
||||||
|
- img [ref=e21]
|
||||||
|
- generic [ref=e24]:
|
||||||
|
- generic [ref=e27] [cursor=pointer]: Remember me
|
||||||
|
- link "Forgot password?" [ref=e28] [cursor=pointer]:
|
||||||
|
- /url: /auth/forgot-password
|
||||||
|
- button "Sign In" [ref=e29] [cursor=pointer]:
|
||||||
|
- generic [ref=e30]: Sign In
|
||||||
|
- generic [ref=e32]: or continue with
|
||||||
|
- generic [ref=e33]:
|
||||||
|
- button "Google" [ref=e34] [cursor=pointer]:
|
||||||
|
- img [ref=e35]
|
||||||
|
- text: Google
|
||||||
|
- button "Microsoft" [ref=e40] [cursor=pointer]:
|
||||||
|
- img [ref=e41]
|
||||||
|
- text: Microsoft
|
||||||
|
- button "GitHub" [ref=e46] [cursor=pointer]:
|
||||||
|
- img [ref=e47]
|
||||||
|
- text: GitHub
|
||||||
|
- button "Apple" [ref=e49] [cursor=pointer]:
|
||||||
|
- img [ref=e50]
|
||||||
|
- text: Apple
|
||||||
|
- paragraph [ref=e53]:
|
||||||
|
- text: Don't have an account?
|
||||||
|
- link "Create account" [ref=e54] [cursor=pointer]:
|
||||||
|
- /url: /auth/register
|
||||||
25
.playwright-mcp/page-2026-03-31T19-17-49-094Z.yml
Normal file
25
.playwright-mcp/page-2026-03-31T19-17-49-094Z.yml
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
- generic [ref=e5]:
|
||||||
|
- generic [ref=e6]:
|
||||||
|
- generic [ref=e7]: Chat
|
||||||
|
- generic [ref=e8]:
|
||||||
|
- button [ref=e9] [cursor=pointer]:
|
||||||
|
- img [ref=e10]
|
||||||
|
- button [ref=e11] [cursor=pointer]:
|
||||||
|
- img [ref=e12]
|
||||||
|
- button [ref=e14] [cursor=pointer]:
|
||||||
|
- img [ref=e15]
|
||||||
|
- generic [ref=e18]:
|
||||||
|
- generic [ref=e19]:
|
||||||
|
- main [ref=e20]
|
||||||
|
- contentinfo [ref=e21]:
|
||||||
|
- generic [ref=e23]:
|
||||||
|
- generic [ref=e24]:
|
||||||
|
- button "Agent" [ref=e25] [cursor=pointer]
|
||||||
|
- button "Chat" [ref=e26] [cursor=pointer]
|
||||||
|
- textbox "Message... (type @ to mention)" [active] [ref=e27]
|
||||||
|
- button "↑" [ref=e28] [cursor=pointer]
|
||||||
|
- button "Scroll to bottom":
|
||||||
|
- img
|
||||||
|
- generic:
|
||||||
|
- generic:
|
||||||
|
- button "View"
|
||||||
111
.playwright-mcp/page-2026-03-31T19-18-09-643Z.yml
Normal file
111
.playwright-mcp/page-2026-03-31T19-18-09-643Z.yml
Normal file
|
|
@ -0,0 +1,111 @@
|
||||||
|
- generic [ref=e2]:
|
||||||
|
- complementary [ref=e3]:
|
||||||
|
- generic "Home" [ref=e4] [cursor=pointer]:
|
||||||
|
- img [ref=e5]
|
||||||
|
- generic "Search" [ref=e8] [cursor=pointer]:
|
||||||
|
- img [ref=e9]
|
||||||
|
- generic "Terminal" [ref=e12] [cursor=pointer]:
|
||||||
|
- img [ref=e13]
|
||||||
|
- generic "User" [ref=e15] [cursor=pointer]:
|
||||||
|
- img [ref=e16]
|
||||||
|
- generic "Apps" [ref=e19] [cursor=pointer]:
|
||||||
|
- img [ref=e20]
|
||||||
|
- generic "Settings" [ref=e25] [cursor=pointer]:
|
||||||
|
- img [ref=e26]
|
||||||
|
- generic [ref=e29]:
|
||||||
|
- generic [ref=e30]:
|
||||||
|
- generic [ref=e31]:
|
||||||
|
- generic [ref=e32] [cursor=pointer]:
|
||||||
|
- img [ref=e34]
|
||||||
|
- generic [ref=e36]: Vibe
|
||||||
|
- generic [ref=e37] [cursor=pointer]:
|
||||||
|
- img [ref=e39]
|
||||||
|
- generic [ref=e44]: CRM
|
||||||
|
- generic [ref=e45] [cursor=pointer]:
|
||||||
|
- img [ref=e47]
|
||||||
|
- generic [ref=e49]: Campaigns
|
||||||
|
- generic [ref=e50] [cursor=pointer]:
|
||||||
|
- img [ref=e52]
|
||||||
|
- generic [ref=e53]: Lists
|
||||||
|
- generic [ref=e54] [cursor=pointer]:
|
||||||
|
- img [ref=e56]
|
||||||
|
- generic [ref=e58]: Templates
|
||||||
|
- generic [ref=e59] [cursor=pointer]:
|
||||||
|
- img [ref=e61]
|
||||||
|
- generic [ref=e64]: Tasks
|
||||||
|
- generic [ref=e65] [cursor=pointer]:
|
||||||
|
- img [ref=e67]
|
||||||
|
- generic [ref=e69]: Chat
|
||||||
|
- generic [ref=e70] [cursor=pointer]:
|
||||||
|
- img [ref=e72]
|
||||||
|
- generic [ref=e74]: Terminal
|
||||||
|
- generic [ref=e75] [cursor=pointer]:
|
||||||
|
- img [ref=e77]
|
||||||
|
- generic [ref=e79]: Explorer
|
||||||
|
- generic [ref=e80] [cursor=pointer]:
|
||||||
|
- img [ref=e82]
|
||||||
|
- generic [ref=e85]: Editor
|
||||||
|
- generic [ref=e86] [cursor=pointer]:
|
||||||
|
- img [ref=e88]
|
||||||
|
- generic [ref=e93]: Designer
|
||||||
|
- generic [ref=e94] [cursor=pointer]:
|
||||||
|
- img [ref=e96]
|
||||||
|
- generic [ref=e99]: BASIC
|
||||||
|
- generic [ref=e100] [cursor=pointer]:
|
||||||
|
- img [ref=e102]
|
||||||
|
- generic [ref=e105]: Browser
|
||||||
|
- generic [ref=e106]:
|
||||||
|
- generic [ref=e107]:
|
||||||
|
- generic [ref=e108]: Chat
|
||||||
|
- generic [ref=e109]:
|
||||||
|
- button [ref=e110] [cursor=pointer]:
|
||||||
|
- img [ref=e111]
|
||||||
|
- button [ref=e112] [cursor=pointer]:
|
||||||
|
- img [ref=e113]
|
||||||
|
- button [ref=e115] [cursor=pointer]:
|
||||||
|
- img [ref=e116]
|
||||||
|
- generic [ref=e119]:
|
||||||
|
- generic [ref=e120]:
|
||||||
|
- generic:
|
||||||
|
- generic: Connecting...
|
||||||
|
- main [ref=e121]
|
||||||
|
- contentinfo [ref=e122]:
|
||||||
|
- generic [ref=e124]:
|
||||||
|
- generic [ref=e125]:
|
||||||
|
- button "Agent" [ref=e126] [cursor=pointer]
|
||||||
|
- button "Chat" [ref=e127] [cursor=pointer]
|
||||||
|
- textbox "Message... (type @ to mention)" [active] [ref=e128]
|
||||||
|
- button "↑" [ref=e129] [cursor=pointer]
|
||||||
|
- button "Scroll to bottom":
|
||||||
|
- img
|
||||||
|
- generic:
|
||||||
|
- generic:
|
||||||
|
- button "View"
|
||||||
|
- contentinfo [ref=e130]:
|
||||||
|
- img [ref=e134] [cursor=pointer]
|
||||||
|
- generic [ref=e136]:
|
||||||
|
- combobox [ref=e138] [cursor=pointer]:
|
||||||
|
- option "🎨 Default"
|
||||||
|
- option "☀️ Light" [selected]
|
||||||
|
- option "🍊 Orange"
|
||||||
|
- option "🌃 Cyberpunk"
|
||||||
|
- option "🌴 Retrowave"
|
||||||
|
- option "💭 Vapor Dream"
|
||||||
|
- option "✨ Y2K"
|
||||||
|
- option "🔲 3D Bevel"
|
||||||
|
- option "🕹️ Arcade"
|
||||||
|
- option "🪩 Disco"
|
||||||
|
- option "🎸 Grunge"
|
||||||
|
- option "🎺 Jazz"
|
||||||
|
- option "🌻 Mellow"
|
||||||
|
- option "🏠 Mid Century"
|
||||||
|
- option "📷 Polaroid"
|
||||||
|
- option "📺 Cartoons"
|
||||||
|
- option "🏖️ Seaside"
|
||||||
|
- option "⌨️ Typewriter"
|
||||||
|
- option "📠 Xerox"
|
||||||
|
- option "📁 XTree"
|
||||||
|
- button "Sign In" [ref=e139] [cursor=pointer]
|
||||||
|
- generic [ref=e140]:
|
||||||
|
- generic [ref=e141]: 00:00
|
||||||
|
- generic [ref=e142]: 01/01/2026
|
||||||
39
.playwright-mcp/page-2026-03-31T19-18-17-256Z.yml
Normal file
39
.playwright-mcp/page-2026-03-31T19-18-17-256Z.yml
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
- generic [ref=e106]:
|
||||||
|
- generic [ref=e107]:
|
||||||
|
- generic [ref=e108]: Chat
|
||||||
|
- generic [ref=e109]:
|
||||||
|
- button [ref=e110] [cursor=pointer]:
|
||||||
|
- img [ref=e111]
|
||||||
|
- button [ref=e112] [cursor=pointer]:
|
||||||
|
- img [ref=e113]
|
||||||
|
- button [ref=e115] [cursor=pointer]:
|
||||||
|
- img [ref=e116]
|
||||||
|
- generic [ref=e119]:
|
||||||
|
- generic [ref=e120]:
|
||||||
|
- main [ref=e121]:
|
||||||
|
- paragraph [ref=e145]: Olá! Sou o Assistente Virtual da SEAD — Secretaria de Estado da Administração de Sergipe. Estou aqui para ajudá-lo com contracheques, agendamentos no CEAC, perícias médicas, documentos funcionais, cursos da Escola de Governo e muito mais. Como posso te ajudar hoje?
|
||||||
|
- generic [ref=e158]: 🔍 Detectar Desvios na Folha
|
||||||
|
- contentinfo [ref=e122]:
|
||||||
|
- generic [ref=e123]:
|
||||||
|
- button "📄 Segunda Via de Contracheque" [ref=e146] [cursor=pointer]
|
||||||
|
- button "📅 Agendar Atendimento no CEAC" [ref=e147] [cursor=pointer]
|
||||||
|
- button "🩺 Agendar Perícia Médica" [ref=e148] [cursor=pointer]
|
||||||
|
- button "📋 Solicitar Documentos Funcionais" [ref=e149] [cursor=pointer]
|
||||||
|
- button "✏️ Atualizar Dados Cadastrais" [ref=e150] [cursor=pointer]
|
||||||
|
- button "🎓 Cursos da Escola de Governo" [ref=e151] [cursor=pointer]
|
||||||
|
- button "💰 Informações sobre Benefícios" [ref=e152] [cursor=pointer]
|
||||||
|
- button "🔍 Consultar Protocolo" [ref=e153] [cursor=pointer]
|
||||||
|
- button "📞 Falar com a SEAD" [ref=e154] [cursor=pointer]
|
||||||
|
- button "🔍 Detectar Desvios na Folha" [active] [ref=e155] [cursor=pointer]
|
||||||
|
- button "❓ Outras Solicitações" [ref=e156] [cursor=pointer]
|
||||||
|
- generic [ref=e124]:
|
||||||
|
- generic [ref=e125]:
|
||||||
|
- button "Agent" [ref=e126] [cursor=pointer]
|
||||||
|
- button "Chat" [ref=e127] [cursor=pointer]
|
||||||
|
- textbox "Message... (type @ to mention)" [ref=e128]
|
||||||
|
- button "↑" [ref=e129] [cursor=pointer]
|
||||||
|
- button "Scroll to bottom":
|
||||||
|
- img
|
||||||
|
- generic:
|
||||||
|
- generic:
|
||||||
|
- button "View"
|
||||||
109
.playwright-mcp/page-2026-03-31T19-18-59-132Z.yml
Normal file
109
.playwright-mcp/page-2026-03-31T19-18-59-132Z.yml
Normal file
|
|
@ -0,0 +1,109 @@
|
||||||
|
- generic [ref=e2]:
|
||||||
|
- complementary [ref=e3]:
|
||||||
|
- generic "Home" [ref=e4] [cursor=pointer]:
|
||||||
|
- img [ref=e5]
|
||||||
|
- generic "Search" [ref=e8] [cursor=pointer]:
|
||||||
|
- img [ref=e9]
|
||||||
|
- generic "Terminal" [ref=e12] [cursor=pointer]:
|
||||||
|
- img [ref=e13]
|
||||||
|
- generic "User" [ref=e15] [cursor=pointer]:
|
||||||
|
- img [ref=e16]
|
||||||
|
- generic "Apps" [ref=e19] [cursor=pointer]:
|
||||||
|
- img [ref=e20]
|
||||||
|
- generic "Settings" [ref=e25] [cursor=pointer]:
|
||||||
|
- img [ref=e26]
|
||||||
|
- generic [ref=e29]:
|
||||||
|
- generic [ref=e30]:
|
||||||
|
- generic [ref=e31]:
|
||||||
|
- generic [ref=e32] [cursor=pointer]:
|
||||||
|
- img [ref=e34]
|
||||||
|
- generic [ref=e36]: Vibe
|
||||||
|
- generic [ref=e37] [cursor=pointer]:
|
||||||
|
- img [ref=e39]
|
||||||
|
- generic [ref=e44]: CRM
|
||||||
|
- generic [ref=e45] [cursor=pointer]:
|
||||||
|
- img [ref=e47]
|
||||||
|
- generic [ref=e49]: Campaigns
|
||||||
|
- generic [ref=e50] [cursor=pointer]:
|
||||||
|
- img [ref=e52]
|
||||||
|
- generic [ref=e53]: Lists
|
||||||
|
- generic [ref=e54] [cursor=pointer]:
|
||||||
|
- img [ref=e56]
|
||||||
|
- generic [ref=e58]: Templates
|
||||||
|
- generic [ref=e59] [cursor=pointer]:
|
||||||
|
- img [ref=e61]
|
||||||
|
- generic [ref=e64]: Tasks
|
||||||
|
- generic [ref=e65] [cursor=pointer]:
|
||||||
|
- img [ref=e67]
|
||||||
|
- generic [ref=e69]: Chat
|
||||||
|
- generic [ref=e70] [cursor=pointer]:
|
||||||
|
- img [ref=e72]
|
||||||
|
- generic [ref=e74]: Terminal
|
||||||
|
- generic [ref=e75] [cursor=pointer]:
|
||||||
|
- img [ref=e77]
|
||||||
|
- generic [ref=e79]: Explorer
|
||||||
|
- generic [ref=e80] [cursor=pointer]:
|
||||||
|
- img [ref=e82]
|
||||||
|
- generic [ref=e85]: Editor
|
||||||
|
- generic [ref=e86] [cursor=pointer]:
|
||||||
|
- img [ref=e88]
|
||||||
|
- generic [ref=e93]: Designer
|
||||||
|
- generic [ref=e94] [cursor=pointer]:
|
||||||
|
- img [ref=e96]
|
||||||
|
- generic [ref=e99]: BASIC
|
||||||
|
- generic [ref=e100] [cursor=pointer]:
|
||||||
|
- img [ref=e102]
|
||||||
|
- generic [ref=e105]: Browser
|
||||||
|
- generic [ref=e106]:
|
||||||
|
- generic [ref=e107]:
|
||||||
|
- generic [ref=e108]: Chat
|
||||||
|
- generic [ref=e109]:
|
||||||
|
- button [ref=e110] [cursor=pointer]:
|
||||||
|
- img [ref=e111]
|
||||||
|
- button [ref=e112] [cursor=pointer]:
|
||||||
|
- img [ref=e113]
|
||||||
|
- button [ref=e115] [cursor=pointer]:
|
||||||
|
- img [ref=e116]
|
||||||
|
- generic [ref=e119]:
|
||||||
|
- generic [ref=e120]:
|
||||||
|
- main [ref=e121]
|
||||||
|
- contentinfo [ref=e122]:
|
||||||
|
- generic [ref=e124]:
|
||||||
|
- generic [ref=e125]:
|
||||||
|
- button "Agent" [ref=e126] [cursor=pointer]
|
||||||
|
- button "Chat" [ref=e127] [cursor=pointer]
|
||||||
|
- textbox "Message... (type @ to mention)" [active] [ref=e128]
|
||||||
|
- button "↑" [ref=e129] [cursor=pointer]
|
||||||
|
- button "Scroll to bottom":
|
||||||
|
- img
|
||||||
|
- generic:
|
||||||
|
- generic:
|
||||||
|
- button "View"
|
||||||
|
- contentinfo [ref=e130]:
|
||||||
|
- img [ref=e134] [cursor=pointer]
|
||||||
|
- generic [ref=e136]:
|
||||||
|
- combobox [ref=e138] [cursor=pointer]:
|
||||||
|
- option "🎨 Default"
|
||||||
|
- option "☀️ Light" [selected]
|
||||||
|
- option "🍊 Orange"
|
||||||
|
- option "🌃 Cyberpunk"
|
||||||
|
- option "🌴 Retrowave"
|
||||||
|
- option "💭 Vapor Dream"
|
||||||
|
- option "✨ Y2K"
|
||||||
|
- option "🔲 3D Bevel"
|
||||||
|
- option "🕹️ Arcade"
|
||||||
|
- option "🪩 Disco"
|
||||||
|
- option "🎸 Grunge"
|
||||||
|
- option "🎺 Jazz"
|
||||||
|
- option "🌻 Mellow"
|
||||||
|
- option "🏠 Mid Century"
|
||||||
|
- option "📷 Polaroid"
|
||||||
|
- option "📺 Cartoons"
|
||||||
|
- option "🏖️ Seaside"
|
||||||
|
- option "⌨️ Typewriter"
|
||||||
|
- option "📠 Xerox"
|
||||||
|
- option "📁 XTree"
|
||||||
|
- button "Sign In" [ref=e139] [cursor=pointer]
|
||||||
|
- generic [ref=e140]:
|
||||||
|
- generic [ref=e141]: 00:00
|
||||||
|
- generic [ref=e142]: 01/01/2026
|
||||||
50
.playwright-mcp/page-2026-03-31T19-19-05-563Z.yml
Normal file
50
.playwright-mcp/page-2026-03-31T19-19-05-563Z.yml
Normal file
|
|
@ -0,0 +1,50 @@
|
||||||
|
- generic [ref=e106]:
|
||||||
|
- generic [ref=e107]:
|
||||||
|
- generic [ref=e108]: Chat
|
||||||
|
- generic [ref=e109]:
|
||||||
|
- button [ref=e110] [cursor=pointer]:
|
||||||
|
- img [ref=e111]
|
||||||
|
- button [ref=e112] [cursor=pointer]:
|
||||||
|
- img [ref=e113]
|
||||||
|
- button [ref=e115] [cursor=pointer]:
|
||||||
|
- img [ref=e116]
|
||||||
|
- generic [ref=e119]:
|
||||||
|
- generic [ref=e120]:
|
||||||
|
- main [ref=e121]:
|
||||||
|
- paragraph [ref=e145]: Olá! Sou o Assistente Virtual da SEAD — Secretaria de Estado da Administração de Sergipe. Estou aqui para ajudá-lo com contracheques, agendamentos no CEAC, perícias médicas, documentos funcionais, cursos da Escola de Governo e muito mais. Como posso te ajudar hoje?
|
||||||
|
- generic [ref=e169]: 🔍 Detectar Desvios na Folha
|
||||||
|
- contentinfo [ref=e122]:
|
||||||
|
- generic [ref=e123]:
|
||||||
|
- button "📄 Segunda Via de Contracheque" [ref=e146] [cursor=pointer]
|
||||||
|
- button "📅 Agendar Atendimento no CEAC" [ref=e147] [cursor=pointer]
|
||||||
|
- button "🩺 Agendar Perícia Médica" [ref=e148] [cursor=pointer]
|
||||||
|
- button "📋 Solicitar Documentos Funcionais" [ref=e149] [cursor=pointer]
|
||||||
|
- button "✏️ Atualizar Dados Cadastrais" [ref=e150] [cursor=pointer]
|
||||||
|
- button "🎓 Cursos da Escola de Governo" [ref=e151] [cursor=pointer]
|
||||||
|
- button "💰 Informações sobre Benefícios" [ref=e152] [cursor=pointer]
|
||||||
|
- button "🔍 Consultar Protocolo" [ref=e153] [cursor=pointer]
|
||||||
|
- button "📞 Falar com a SEAD" [ref=e154] [cursor=pointer]
|
||||||
|
- button "🔍 Detectar Desvios na Folha" [active] [ref=e155] [cursor=pointer]
|
||||||
|
- button "❓ Outras Solicitações" [ref=e156] [cursor=pointer]
|
||||||
|
- button "📄 Segunda Via de Contracheque" [ref=e157] [cursor=pointer]
|
||||||
|
- button "📅 Agendar Atendimento no CEAC" [ref=e158] [cursor=pointer]
|
||||||
|
- button "🩺 Agendar Perícia Médica" [ref=e159] [cursor=pointer]
|
||||||
|
- button "📋 Solicitar Documentos Funcionais" [ref=e160] [cursor=pointer]
|
||||||
|
- button "✏️ Atualizar Dados Cadastrais" [ref=e161] [cursor=pointer]
|
||||||
|
- button "🎓 Cursos da Escola de Governo" [ref=e162] [cursor=pointer]
|
||||||
|
- button "💰 Informações sobre Benefícios" [ref=e163] [cursor=pointer]
|
||||||
|
- button "🔍 Consultar Protocolo" [ref=e164] [cursor=pointer]
|
||||||
|
- button "📞 Falar com a SEAD" [ref=e165] [cursor=pointer]
|
||||||
|
- button "🔍 Detectar Desvios na Folha" [ref=e166] [cursor=pointer]
|
||||||
|
- button "❓ Outras Solicitações" [ref=e167] [cursor=pointer]
|
||||||
|
- generic [ref=e124]:
|
||||||
|
- generic [ref=e125]:
|
||||||
|
- button "Agent" [ref=e126] [cursor=pointer]
|
||||||
|
- button "Chat" [ref=e127] [cursor=pointer]
|
||||||
|
- textbox "Message... (type @ to mention)" [ref=e128]
|
||||||
|
- button "↑" [ref=e129] [cursor=pointer]
|
||||||
|
- button "Scroll to bottom":
|
||||||
|
- img
|
||||||
|
- generic:
|
||||||
|
- generic:
|
||||||
|
- button "View"
|
||||||
123
.playwright-mcp/page-2026-03-31T19-19-18-155Z.yml
Normal file
123
.playwright-mcp/page-2026-03-31T19-19-18-155Z.yml
Normal file
|
|
@ -0,0 +1,123 @@
|
||||||
|
- generic [ref=e106]:
|
||||||
|
- generic [ref=e107]:
|
||||||
|
- generic [ref=e108]: Chat
|
||||||
|
- generic [ref=e109]:
|
||||||
|
- button [ref=e110] [cursor=pointer]:
|
||||||
|
- img [ref=e111]
|
||||||
|
- button [ref=e112] [cursor=pointer]:
|
||||||
|
- img [ref=e113]
|
||||||
|
- button [ref=e115] [cursor=pointer]:
|
||||||
|
- img [ref=e116]
|
||||||
|
- generic [ref=e119]:
|
||||||
|
- generic [ref=e120]:
|
||||||
|
- main [ref=e121]:
|
||||||
|
- paragraph [ref=e145]: Olá! Sou o Assistente Virtual da SEAD — Secretaria de Estado da Administração de Sergipe. Estou aqui para ajudá-lo com contracheques, agendamentos no CEAC, perícias médicas, documentos funcionais, cursos da Escola de Governo e muito mais. Como posso te ajudar hoje?
|
||||||
|
- generic [ref=e169]: 🔍 Detectar Desvios na Folha
|
||||||
|
- generic [ref=e171]:
|
||||||
|
- paragraph [ref=e172]:
|
||||||
|
- text: 🕵️♀️
|
||||||
|
- strong [ref=e173]: Passo a passo para detectar desvios na sua folha de pagamento
|
||||||
|
- list [ref=e174]:
|
||||||
|
- listitem [ref=e175]:
|
||||||
|
- paragraph [ref=e176]:
|
||||||
|
- strong [ref=e177]: Acesse o Contracheque Online
|
||||||
|
- list [ref=e178]:
|
||||||
|
- listitem [ref=e179]: Entre no portal SIPES/SEAD com seu login.
|
||||||
|
- listitem [ref=e180]: Visualize o contracheque do mês corrente e dos últimos 6 meses.
|
||||||
|
- listitem [ref=e181]:
|
||||||
|
- paragraph [ref=e182]:
|
||||||
|
- strong [ref=e183]: Compare os valores
|
||||||
|
- list [ref=e184]:
|
||||||
|
- listitem [ref=e185]:
|
||||||
|
- text: 📈
|
||||||
|
- strong [ref=e186]: Remuneração Base
|
||||||
|
- text: ": deve permanecer constante (exceto progressões ou promoções)."
|
||||||
|
- listitem [ref=e187]:
|
||||||
|
- text: ➕
|
||||||
|
- strong [ref=e188]: Verbas Variáveis
|
||||||
|
- text: ": identifique adicionais, horas extras ou gratificações que não reconhece."
|
||||||
|
- listitem [ref=e189]:
|
||||||
|
- text: ➖
|
||||||
|
- strong [ref=e190]: Descontos
|
||||||
|
- text: ": cheque descontos de INSS, IRRF, pensão alimentícia e demais deduções."
|
||||||
|
- listitem [ref=e191]:
|
||||||
|
- paragraph [ref=e192]:
|
||||||
|
- strong [ref=e193]: Solicite documentos complementares (se necessário)
|
||||||
|
- list [ref=e194]:
|
||||||
|
- listitem [ref=e195]:
|
||||||
|
- strong [ref=e196]: Histórico de contracheques
|
||||||
|
- text: – tipo
|
||||||
|
- code [ref=e197]: CONTRACHEQUE_HISTORICO
|
||||||
|
- text: .
|
||||||
|
- listitem [ref=e198]:
|
||||||
|
- strong [ref=e199]: Informe de rendimentos
|
||||||
|
- text: ou
|
||||||
|
- strong [ref=e200]: Ficha financeira
|
||||||
|
- text: – para cruzar com a Receita Federal.
|
||||||
|
- listitem [ref=e201]:
|
||||||
|
- paragraph [ref=e202]:
|
||||||
|
- strong [ref=e203]: Cruzamento de informações
|
||||||
|
- list [ref=e204]:
|
||||||
|
- listitem [ref=e205]:
|
||||||
|
- text: Use o
|
||||||
|
- strong [ref=e206]: Informe de Rendimentos
|
||||||
|
- text: para verificar se os valores declarados coincidem com o que consta na folha.
|
||||||
|
- listitem [ref=e207]:
|
||||||
|
- text: Compare a
|
||||||
|
- strong [ref=e208]: Ficha Financeira
|
||||||
|
- text: com o extrato bancário (se houver depósito direto).
|
||||||
|
- listitem [ref=e209]:
|
||||||
|
- paragraph [ref=e210]:
|
||||||
|
- strong [ref=e211]: Registre um protocolo na SEAD/SEPLAG‑SE
|
||||||
|
- list [ref=e212]:
|
||||||
|
- listitem [ref=e213]:
|
||||||
|
- text: "Caso encontre alguma divergência, abra um protocolo (ex.:"
|
||||||
|
- code [ref=e214]: CCH123456
|
||||||
|
- text: ).
|
||||||
|
- listitem [ref=e215]: Descreva claramente a diferença encontrada e anexe prints ou documentos.
|
||||||
|
- listitem [ref=e216]:
|
||||||
|
- paragraph [ref=e217]:
|
||||||
|
- strong [ref=e218]: Acompanhamento
|
||||||
|
- list [ref=e219]:
|
||||||
|
- listitem [ref=e220]: Acompanhe o status do protocolo pelo portal ou pelo telefone da Ouvidoria.
|
||||||
|
- listitem [ref=e221]: Se a resposta for insatisfatória, solicite revisão ou escalonamento.
|
||||||
|
- separator [ref=e222]
|
||||||
|
- paragraph [ref=e223]:
|
||||||
|
- text: ⚙️
|
||||||
|
- strong [ref=e224]: Posso ajudar a solicitar algum desses documentos
|
||||||
|
- text: (histórico de contracheques, informe de rendimentos, ficha financeira) ou a abrir um protocolo de auditoria. Basta informar os dados solicitados (CPF, matrícula, e‑mail, etc.).
|
||||||
|
- contentinfo [ref=e122]:
|
||||||
|
- generic [ref=e123]:
|
||||||
|
- button "📄 Segunda Via de Contracheque" [ref=e225] [cursor=pointer]
|
||||||
|
- button "📅 Agendar Atendimento no CEAC" [ref=e226] [cursor=pointer]
|
||||||
|
- button "🩺 Agendar Perícia Médica" [ref=e227] [cursor=pointer]
|
||||||
|
- button "📋 Solicitar Documentos Funcionais" [ref=e228] [cursor=pointer]
|
||||||
|
- button "✏️ Atualizar Dados Cadastrais" [ref=e229] [cursor=pointer]
|
||||||
|
- button "🎓 Cursos da Escola de Governo" [ref=e230] [cursor=pointer]
|
||||||
|
- button "💰 Informações sobre Benefícios" [ref=e231] [cursor=pointer]
|
||||||
|
- button "🔍 Consultar Protocolo" [ref=e232] [cursor=pointer]
|
||||||
|
- button "📞 Falar com a SEAD" [ref=e233] [cursor=pointer]
|
||||||
|
- button "🔍 Detectar Desvios na Folha" [ref=e234] [cursor=pointer]
|
||||||
|
- button "❓ Outras Solicitações" [ref=e235] [cursor=pointer]
|
||||||
|
- button "📄 Segunda Via de Contracheque" [ref=e236] [cursor=pointer]
|
||||||
|
- button "📅 Agendar Atendimento no CEAC" [ref=e237] [cursor=pointer]
|
||||||
|
- button "🩺 Agendar Perícia Médica" [ref=e238] [cursor=pointer]
|
||||||
|
- button "📋 Solicitar Documentos Funcionais" [ref=e239] [cursor=pointer]
|
||||||
|
- button "✏️ Atualizar Dados Cadastrais" [ref=e240] [cursor=pointer]
|
||||||
|
- button "🎓 Cursos da Escola de Governo" [ref=e241] [cursor=pointer]
|
||||||
|
- button "💰 Informações sobre Benefícios" [ref=e242] [cursor=pointer]
|
||||||
|
- button "🔍 Consultar Protocolo" [ref=e243] [cursor=pointer]
|
||||||
|
- button "📞 Falar com a SEAD" [ref=e244] [cursor=pointer]
|
||||||
|
- button "🔍 Detectar Desvios na Folha" [ref=e245] [cursor=pointer]
|
||||||
|
- button "❓ Outras Solicitações" [ref=e246] [cursor=pointer]
|
||||||
|
- generic [ref=e124]:
|
||||||
|
- generic [ref=e125]:
|
||||||
|
- button "Agent" [ref=e126] [cursor=pointer]
|
||||||
|
- button "Chat" [ref=e127] [cursor=pointer]
|
||||||
|
- textbox "Message... (type @ to mention)" [ref=e128]
|
||||||
|
- button "↑" [ref=e129] [cursor=pointer]
|
||||||
|
- button "Scroll to bottom":
|
||||||
|
- img
|
||||||
|
- generic:
|
||||||
|
- generic:
|
||||||
|
- button "View"
|
||||||
111
.playwright-mcp/page-2026-03-31T19-21-55-814Z.yml
Normal file
111
.playwright-mcp/page-2026-03-31T19-21-55-814Z.yml
Normal file
|
|
@ -0,0 +1,111 @@
|
||||||
|
- generic [ref=e2]:
|
||||||
|
- complementary [ref=e3]:
|
||||||
|
- generic "Home" [ref=e4] [cursor=pointer]:
|
||||||
|
- img [ref=e5]
|
||||||
|
- generic "Search" [ref=e8] [cursor=pointer]:
|
||||||
|
- img [ref=e9]
|
||||||
|
- generic "Terminal" [ref=e12] [cursor=pointer]:
|
||||||
|
- img [ref=e13]
|
||||||
|
- generic "User" [ref=e15] [cursor=pointer]:
|
||||||
|
- img [ref=e16]
|
||||||
|
- generic "Apps" [ref=e19] [cursor=pointer]:
|
||||||
|
- img [ref=e20]
|
||||||
|
- generic "Settings" [ref=e25] [cursor=pointer]:
|
||||||
|
- img [ref=e26]
|
||||||
|
- generic [ref=e29]:
|
||||||
|
- generic [ref=e30]:
|
||||||
|
- generic [ref=e31]:
|
||||||
|
- generic [ref=e32] [cursor=pointer]:
|
||||||
|
- img [ref=e34]
|
||||||
|
- generic [ref=e36]: Vibe
|
||||||
|
- generic [ref=e37] [cursor=pointer]:
|
||||||
|
- img [ref=e39]
|
||||||
|
- generic [ref=e44]: CRM
|
||||||
|
- generic [ref=e45] [cursor=pointer]:
|
||||||
|
- img [ref=e47]
|
||||||
|
- generic [ref=e49]: Campaigns
|
||||||
|
- generic [ref=e50] [cursor=pointer]:
|
||||||
|
- img [ref=e52]
|
||||||
|
- generic [ref=e53]: Lists
|
||||||
|
- generic [ref=e54] [cursor=pointer]:
|
||||||
|
- img [ref=e56]
|
||||||
|
- generic [ref=e58]: Templates
|
||||||
|
- generic [ref=e59] [cursor=pointer]:
|
||||||
|
- img [ref=e61]
|
||||||
|
- generic [ref=e64]: Tasks
|
||||||
|
- generic [ref=e65] [cursor=pointer]:
|
||||||
|
- img [ref=e67]
|
||||||
|
- generic [ref=e69]: Chat
|
||||||
|
- generic [ref=e70] [cursor=pointer]:
|
||||||
|
- img [ref=e72]
|
||||||
|
- generic [ref=e74]: Terminal
|
||||||
|
- generic [ref=e75] [cursor=pointer]:
|
||||||
|
- img [ref=e77]
|
||||||
|
- generic [ref=e79]: Explorer
|
||||||
|
- generic [ref=e80] [cursor=pointer]:
|
||||||
|
- img [ref=e82]
|
||||||
|
- generic [ref=e85]: Editor
|
||||||
|
- generic [ref=e86] [cursor=pointer]:
|
||||||
|
- img [ref=e88]
|
||||||
|
- generic [ref=e93]: Designer
|
||||||
|
- generic [ref=e94] [cursor=pointer]:
|
||||||
|
- img [ref=e96]
|
||||||
|
- generic [ref=e99]: BASIC
|
||||||
|
- generic [ref=e100] [cursor=pointer]:
|
||||||
|
- img [ref=e102]
|
||||||
|
- generic [ref=e105]: Browser
|
||||||
|
- generic [ref=e106]:
|
||||||
|
- generic [ref=e107]:
|
||||||
|
- generic [ref=e108]: Chat
|
||||||
|
- generic [ref=e109]:
|
||||||
|
- button [ref=e110] [cursor=pointer]:
|
||||||
|
- img [ref=e111]
|
||||||
|
- button [ref=e112] [cursor=pointer]:
|
||||||
|
- img [ref=e113]
|
||||||
|
- button [ref=e115] [cursor=pointer]:
|
||||||
|
- img [ref=e116]
|
||||||
|
- generic [ref=e119]:
|
||||||
|
- generic [ref=e120]:
|
||||||
|
- generic:
|
||||||
|
- generic: Connecting...
|
||||||
|
- main [ref=e121]
|
||||||
|
- contentinfo [ref=e122]:
|
||||||
|
- generic [ref=e124]:
|
||||||
|
- generic [ref=e125]:
|
||||||
|
- button "Agent" [ref=e126] [cursor=pointer]
|
||||||
|
- button "Chat" [ref=e127] [cursor=pointer]
|
||||||
|
- textbox "Message... (type @ to mention)" [active] [ref=e128]
|
||||||
|
- button "↑" [ref=e129] [cursor=pointer]
|
||||||
|
- button "Scroll to bottom":
|
||||||
|
- img
|
||||||
|
- generic:
|
||||||
|
- generic:
|
||||||
|
- button "View"
|
||||||
|
- contentinfo [ref=e130]:
|
||||||
|
- img [ref=e134] [cursor=pointer]
|
||||||
|
- generic [ref=e136]:
|
||||||
|
- combobox [ref=e138] [cursor=pointer]:
|
||||||
|
- option "🎨 Default"
|
||||||
|
- option "☀️ Light" [selected]
|
||||||
|
- option "🍊 Orange"
|
||||||
|
- option "🌃 Cyberpunk"
|
||||||
|
- option "🌴 Retrowave"
|
||||||
|
- option "💭 Vapor Dream"
|
||||||
|
- option "✨ Y2K"
|
||||||
|
- option "🔲 3D Bevel"
|
||||||
|
- option "🕹️ Arcade"
|
||||||
|
- option "🪩 Disco"
|
||||||
|
- option "🎸 Grunge"
|
||||||
|
- option "🎺 Jazz"
|
||||||
|
- option "🌻 Mellow"
|
||||||
|
- option "🏠 Mid Century"
|
||||||
|
- option "📷 Polaroid"
|
||||||
|
- option "📺 Cartoons"
|
||||||
|
- option "🏖️ Seaside"
|
||||||
|
- option "⌨️ Typewriter"
|
||||||
|
- option "📠 Xerox"
|
||||||
|
- option "📁 XTree"
|
||||||
|
- button "Sign In" [ref=e139] [cursor=pointer]
|
||||||
|
- generic [ref=e140]:
|
||||||
|
- generic [ref=e141]: 00:00
|
||||||
|
- generic [ref=e142]: 01/01/2026
|
||||||
61
.playwright-mcp/page-2026-03-31T19-22-10-960Z.yml
Normal file
61
.playwright-mcp/page-2026-03-31T19-22-10-960Z.yml
Normal file
|
|
@ -0,0 +1,61 @@
|
||||||
|
- generic [ref=e106]:
|
||||||
|
- generic [ref=e107]:
|
||||||
|
- generic [ref=e108]: Chat
|
||||||
|
- generic [ref=e109]:
|
||||||
|
- button [ref=e110] [cursor=pointer]:
|
||||||
|
- img [ref=e111]
|
||||||
|
- button [ref=e112] [cursor=pointer]:
|
||||||
|
- img [ref=e113]
|
||||||
|
- button [ref=e115] [cursor=pointer]:
|
||||||
|
- img [ref=e116]
|
||||||
|
- generic [ref=e119]:
|
||||||
|
- generic [ref=e120]:
|
||||||
|
- main [ref=e121]:
|
||||||
|
- paragraph [ref=e145]: Olá! Sou o Assistente Virtual da SEAD — Secretaria de Estado da Administração de Sergipe. Estou aqui para ajudá-lo com contracheques, agendamentos no CEAC, perícias médicas, documentos funcionais, cursos da Escola de Governo e muito mais. Como posso te ajudar hoje?
|
||||||
|
- generic [ref=e180]: 🔍 Detectar Desvios na Folha
|
||||||
|
- contentinfo [ref=e122]:
|
||||||
|
- generic [ref=e123]:
|
||||||
|
- button "📄 Segunda Via de Contracheque" [ref=e146] [cursor=pointer]
|
||||||
|
- button "📅 Agendar Atendimento no CEAC" [ref=e147] [cursor=pointer]
|
||||||
|
- button "🩺 Agendar Perícia Médica" [ref=e148] [cursor=pointer]
|
||||||
|
- button "📋 Solicitar Documentos Funcionais" [ref=e149] [cursor=pointer]
|
||||||
|
- button "✏️ Atualizar Dados Cadastrais" [ref=e150] [cursor=pointer]
|
||||||
|
- button "🎓 Cursos da Escola de Governo" [ref=e151] [cursor=pointer]
|
||||||
|
- button "💰 Informações sobre Benefícios" [ref=e152] [cursor=pointer]
|
||||||
|
- button "🔍 Consultar Protocolo" [ref=e153] [cursor=pointer]
|
||||||
|
- button "📞 Falar com a SEAD" [ref=e154] [cursor=pointer]
|
||||||
|
- button "🔍 Detectar Desvios na Folha" [active] [ref=e155] [cursor=pointer]
|
||||||
|
- button "❓ Outras Solicitações" [ref=e156] [cursor=pointer]
|
||||||
|
- button "📄 Segunda Via de Contracheque" [ref=e157] [cursor=pointer]
|
||||||
|
- button "📅 Agendar Atendimento no CEAC" [ref=e158] [cursor=pointer]
|
||||||
|
- button "🩺 Agendar Perícia Médica" [ref=e159] [cursor=pointer]
|
||||||
|
- button "📋 Solicitar Documentos Funcionais" [ref=e160] [cursor=pointer]
|
||||||
|
- button "✏️ Atualizar Dados Cadastrais" [ref=e161] [cursor=pointer]
|
||||||
|
- button "🎓 Cursos da Escola de Governo" [ref=e162] [cursor=pointer]
|
||||||
|
- button "💰 Informações sobre Benefícios" [ref=e163] [cursor=pointer]
|
||||||
|
- button "🔍 Consultar Protocolo" [ref=e164] [cursor=pointer]
|
||||||
|
- button "📞 Falar com a SEAD" [ref=e165] [cursor=pointer]
|
||||||
|
- button "🔍 Detectar Desvios na Folha" [ref=e166] [cursor=pointer]
|
||||||
|
- button "❓ Outras Solicitações" [ref=e167] [cursor=pointer]
|
||||||
|
- button "📄 Segunda Via de Contracheque" [ref=e168] [cursor=pointer]
|
||||||
|
- button "📅 Agendar Atendimento no CEAC" [ref=e169] [cursor=pointer]
|
||||||
|
- button "🩺 Agendar Perícia Médica" [ref=e170] [cursor=pointer]
|
||||||
|
- button "📋 Solicitar Documentos Funcionais" [ref=e171] [cursor=pointer]
|
||||||
|
- button "✏️ Atualizar Dados Cadastrais" [ref=e172] [cursor=pointer]
|
||||||
|
- button "🎓 Cursos da Escola de Governo" [ref=e173] [cursor=pointer]
|
||||||
|
- button "💰 Informações sobre Benefícios" [ref=e174] [cursor=pointer]
|
||||||
|
- button "🔍 Consultar Protocolo" [ref=e175] [cursor=pointer]
|
||||||
|
- button "📞 Falar com a SEAD" [ref=e176] [cursor=pointer]
|
||||||
|
- button "🔍 Detectar Desvios na Folha" [ref=e177] [cursor=pointer]
|
||||||
|
- button "❓ Outras Solicitações" [ref=e178] [cursor=pointer]
|
||||||
|
- generic [ref=e124]:
|
||||||
|
- generic [ref=e125]:
|
||||||
|
- button "Agent" [ref=e126] [cursor=pointer]
|
||||||
|
- button "Chat" [ref=e127] [cursor=pointer]
|
||||||
|
- textbox "Message... (type @ to mention)" [ref=e128]
|
||||||
|
- button "↑" [ref=e129] [cursor=pointer]
|
||||||
|
- button "Scroll to bottom":
|
||||||
|
- img
|
||||||
|
- generic:
|
||||||
|
- generic:
|
||||||
|
- button "View"
|
||||||
82
.playwright-mcp/page-2026-03-31T19-22-29-106Z.yml
Normal file
82
.playwright-mcp/page-2026-03-31T19-22-29-106Z.yml
Normal file
|
|
@ -0,0 +1,82 @@
|
||||||
|
- generic [ref=e106]:
|
||||||
|
- generic [ref=e107]:
|
||||||
|
- generic [ref=e108]: Chat
|
||||||
|
- generic [ref=e109]:
|
||||||
|
- button [ref=e110] [cursor=pointer]:
|
||||||
|
- img [ref=e111]
|
||||||
|
- button [ref=e112] [cursor=pointer]:
|
||||||
|
- img [ref=e113]
|
||||||
|
- button [ref=e115] [cursor=pointer]:
|
||||||
|
- img [ref=e116]
|
||||||
|
- generic [ref=e119]:
|
||||||
|
- generic [ref=e120]:
|
||||||
|
- main [ref=e121]:
|
||||||
|
- paragraph [ref=e145]: Olá! Sou o Assistente Virtual da SEAD — Secretaria de Estado da Administração de Sergipe. Estou aqui para ajudá-lo com contracheques, agendamentos no CEAC, perícias médicas, documentos funcionais, cursos da Escola de Governo e muito mais. Como posso te ajudar hoje?
|
||||||
|
- generic [ref=e180]: 🔍 Detectar Desvios na Folha
|
||||||
|
- generic [ref=e182]:
|
||||||
|
- paragraph [ref=e183]:
|
||||||
|
- text: 🔎
|
||||||
|
- strong [ref=e184]: Vamos analisar a sua folha de pagamento para identificar possíveis desvios.
|
||||||
|
- text: Para isso preciso que você me informe os dados cadastrais do servidor (mesmo que já os tenha usado antes). Assim consigo solicitar o histórico de contracheques e compará‑los.
|
||||||
|
- blockquote [ref=e185]:
|
||||||
|
- paragraph [ref=e186]:
|
||||||
|
- strong [ref=e187]: Dados necessários
|
||||||
|
- list [ref=e188]:
|
||||||
|
- listitem [ref=e189]: CPF (com ou sem formatação)
|
||||||
|
- listitem [ref=e190]: Matrícula funcional
|
||||||
|
- listitem [ref=e191]: Nome completo do servidor
|
||||||
|
- listitem [ref=e192]: "Órgão de lotação (ex.: SEAD, SEED, SES, SSP)"
|
||||||
|
- listitem [ref=e193]: E‑mail (para envio do documento)
|
||||||
|
- listitem [ref=e194]: Telefone com DDD
|
||||||
|
- paragraph [ref=e195]:
|
||||||
|
- text: ⚠️
|
||||||
|
- strong [ref=e196]: Confirme
|
||||||
|
- text: que está de acordo em fornecer essas informações e eu solicito o documento
|
||||||
|
- code [ref=e197]: CONTRACHEQUE_HISTORICO
|
||||||
|
- text: . Depois analisaremos juntos as variações encontradas.
|
||||||
|
- contentinfo [ref=e122]:
|
||||||
|
- generic [ref=e123]:
|
||||||
|
- button "📄 Segunda Via de Contracheque" [ref=e198] [cursor=pointer]
|
||||||
|
- button "📅 Agendar Atendimento no CEAC" [ref=e199] [cursor=pointer]
|
||||||
|
- button "🩺 Agendar Perícia Médica" [ref=e200] [cursor=pointer]
|
||||||
|
- button "📋 Solicitar Documentos Funcionais" [ref=e201] [cursor=pointer]
|
||||||
|
- button "✏️ Atualizar Dados Cadastrais" [ref=e202] [cursor=pointer]
|
||||||
|
- button "🎓 Cursos da Escola de Governo" [ref=e203] [cursor=pointer]
|
||||||
|
- button "💰 Informações sobre Benefícios" [ref=e204] [cursor=pointer]
|
||||||
|
- button "🔍 Consultar Protocolo" [ref=e205] [cursor=pointer]
|
||||||
|
- button "📞 Falar com a SEAD" [ref=e206] [cursor=pointer]
|
||||||
|
- button "🔍 Detectar Desvios na Folha" [ref=e207] [cursor=pointer]
|
||||||
|
- button "❓ Outras Solicitações" [ref=e208] [cursor=pointer]
|
||||||
|
- button "📄 Segunda Via de Contracheque" [ref=e209] [cursor=pointer]
|
||||||
|
- button "📅 Agendar Atendimento no CEAC" [ref=e210] [cursor=pointer]
|
||||||
|
- button "🩺 Agendar Perícia Médica" [ref=e211] [cursor=pointer]
|
||||||
|
- button "📋 Solicitar Documentos Funcionais" [ref=e212] [cursor=pointer]
|
||||||
|
- button "✏️ Atualizar Dados Cadastrais" [ref=e213] [cursor=pointer]
|
||||||
|
- button "🎓 Cursos da Escola de Governo" [ref=e214] [cursor=pointer]
|
||||||
|
- button "💰 Informações sobre Benefícios" [ref=e215] [cursor=pointer]
|
||||||
|
- button "🔍 Consultar Protocolo" [ref=e216] [cursor=pointer]
|
||||||
|
- button "📞 Falar com a SEAD" [ref=e217] [cursor=pointer]
|
||||||
|
- button "🔍 Detectar Desvios na Folha" [ref=e218] [cursor=pointer]
|
||||||
|
- button "❓ Outras Solicitações" [ref=e219] [cursor=pointer]
|
||||||
|
- button "📄 Segunda Via de Contracheque" [ref=e220] [cursor=pointer]
|
||||||
|
- button "📅 Agendar Atendimento no CEAC" [ref=e221] [cursor=pointer]
|
||||||
|
- button "🩺 Agendar Perícia Médica" [ref=e222] [cursor=pointer]
|
||||||
|
- button "📋 Solicitar Documentos Funcionais" [ref=e223] [cursor=pointer]
|
||||||
|
- button "✏️ Atualizar Dados Cadastrais" [ref=e224] [cursor=pointer]
|
||||||
|
- button "🎓 Cursos da Escola de Governo" [ref=e225] [cursor=pointer]
|
||||||
|
- button "💰 Informações sobre Benefícios" [ref=e226] [cursor=pointer]
|
||||||
|
- button "🔍 Consultar Protocolo" [ref=e227] [cursor=pointer]
|
||||||
|
- button "📞 Falar com a SEAD" [ref=e228] [cursor=pointer]
|
||||||
|
- button "🔍 Detectar Desvios na Folha" [ref=e229] [cursor=pointer]
|
||||||
|
- button "❓ Outras Solicitações" [ref=e230] [cursor=pointer]
|
||||||
|
- generic [ref=e124]:
|
||||||
|
- generic [ref=e125]:
|
||||||
|
- button "Agent" [ref=e126] [cursor=pointer]
|
||||||
|
- button "Chat" [ref=e127] [cursor=pointer]
|
||||||
|
- textbox "Message... (type @ to mention)" [ref=e128]
|
||||||
|
- button "↑" [ref=e129] [cursor=pointer]
|
||||||
|
- button "Scroll to bottom":
|
||||||
|
- img
|
||||||
|
- generic:
|
||||||
|
- generic:
|
||||||
|
- button "View"
|
||||||
27
.playwright-mcp/page-2026-04-02T11-04-19-343Z.yml
Normal file
27
.playwright-mcp/page-2026-04-02T11-04-19-343Z.yml
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
- generic [ref=e5]:
|
||||||
|
- generic [ref=e6]:
|
||||||
|
- generic [ref=e7]: Chat
|
||||||
|
- generic [ref=e8]:
|
||||||
|
- button [ref=e9] [cursor=pointer]:
|
||||||
|
- img [ref=e10]
|
||||||
|
- button [ref=e11] [cursor=pointer]:
|
||||||
|
- img [ref=e12]
|
||||||
|
- button [ref=e14] [cursor=pointer]:
|
||||||
|
- img [ref=e15]
|
||||||
|
- generic [ref=e18]:
|
||||||
|
- generic [ref=e19]:
|
||||||
|
- generic:
|
||||||
|
- generic: Connecting...
|
||||||
|
- main [ref=e20]
|
||||||
|
- contentinfo [ref=e21]:
|
||||||
|
- generic [ref=e23]:
|
||||||
|
- generic [ref=e24]:
|
||||||
|
- button "Agent" [ref=e25] [cursor=pointer]
|
||||||
|
- button "Chat" [ref=e26] [cursor=pointer]
|
||||||
|
- textbox "Message... (type @ to mention)" [active] [ref=e27]
|
||||||
|
- button "↑" [ref=e28] [cursor=pointer]
|
||||||
|
- button "Scroll to bottom":
|
||||||
|
- img
|
||||||
|
- generic:
|
||||||
|
- generic:
|
||||||
|
- button "View"
|
||||||
147
.playwright-mcp/page-2026-04-02T11-05-27-254Z.yml
Normal file
147
.playwright-mcp/page-2026-04-02T11-05-27-254Z.yml
Normal file
|
|
@ -0,0 +1,147 @@
|
||||||
|
- generic [ref=e2]:
|
||||||
|
- complementary [ref=e3]:
|
||||||
|
- generic "Home" [ref=e4] [cursor=pointer]:
|
||||||
|
- img [ref=e5]
|
||||||
|
- generic "Search" [ref=e8] [cursor=pointer]:
|
||||||
|
- img [ref=e9]
|
||||||
|
- generic "Terminal" [ref=e12] [cursor=pointer]:
|
||||||
|
- img [ref=e13]
|
||||||
|
- generic "User" [ref=e15] [cursor=pointer]:
|
||||||
|
- img [ref=e16]
|
||||||
|
- generic "Apps" [ref=e19] [cursor=pointer]:
|
||||||
|
- img [ref=e20]
|
||||||
|
- generic "Settings" [ref=e25] [cursor=pointer]:
|
||||||
|
- img [ref=e26]
|
||||||
|
- generic [ref=e29]:
|
||||||
|
- generic [ref=e30]:
|
||||||
|
- generic [ref=e31]:
|
||||||
|
- generic [ref=e32] [cursor=pointer]:
|
||||||
|
- img [ref=e34]
|
||||||
|
- generic [ref=e36]: Vibe
|
||||||
|
- generic [ref=e37] [cursor=pointer]:
|
||||||
|
- img [ref=e39]
|
||||||
|
- generic [ref=e44]: CRM
|
||||||
|
- generic [ref=e45] [cursor=pointer]:
|
||||||
|
- img [ref=e47]
|
||||||
|
- generic [ref=e49]: Campaigns
|
||||||
|
- generic [ref=e50] [cursor=pointer]:
|
||||||
|
- img [ref=e52]
|
||||||
|
- generic [ref=e53]: Lists
|
||||||
|
- generic [ref=e54] [cursor=pointer]:
|
||||||
|
- img [ref=e56]
|
||||||
|
- generic [ref=e58]: Templates
|
||||||
|
- generic [ref=e59] [cursor=pointer]:
|
||||||
|
- img [ref=e61]
|
||||||
|
- generic [ref=e64]: Tasks
|
||||||
|
- generic [ref=e65] [cursor=pointer]:
|
||||||
|
- img [ref=e67]
|
||||||
|
- generic [ref=e69]: Chat
|
||||||
|
- generic [ref=e70] [cursor=pointer]:
|
||||||
|
- img [ref=e72]
|
||||||
|
- generic [ref=e74]: Terminal
|
||||||
|
- generic [ref=e75] [cursor=pointer]:
|
||||||
|
- img [ref=e77]
|
||||||
|
- generic [ref=e79]: Explorer
|
||||||
|
- generic [ref=e80] [cursor=pointer]:
|
||||||
|
- img [ref=e82]
|
||||||
|
- generic [ref=e85]: Editor
|
||||||
|
- generic [ref=e86] [cursor=pointer]:
|
||||||
|
- img [ref=e88]
|
||||||
|
- generic [ref=e93]: Designer
|
||||||
|
- generic [ref=e94] [cursor=pointer]:
|
||||||
|
- img [ref=e96]
|
||||||
|
- generic [ref=e99]: BASIC
|
||||||
|
- generic [ref=e100] [cursor=pointer]:
|
||||||
|
- img [ref=e102]
|
||||||
|
- generic [ref=e105]: Browser
|
||||||
|
- generic [ref=e106]:
|
||||||
|
- generic [ref=e107]:
|
||||||
|
- generic [ref=e108]: Chat
|
||||||
|
- generic [ref=e109]:
|
||||||
|
- button [ref=e110] [cursor=pointer]:
|
||||||
|
- img [ref=e111]
|
||||||
|
- button [ref=e112] [cursor=pointer]:
|
||||||
|
- img [ref=e113]
|
||||||
|
- button [ref=e115] [cursor=pointer]:
|
||||||
|
- img [ref=e116]
|
||||||
|
- generic [ref=e119]:
|
||||||
|
- generic [ref=e120]:
|
||||||
|
- complementary [ref=e121]:
|
||||||
|
- button "Chat" [ref=e122]:
|
||||||
|
- img [ref=e123]
|
||||||
|
- button "Tasks" [ref=e125]:
|
||||||
|
- img [ref=e126]
|
||||||
|
- button "Terminal" [ref=e129]:
|
||||||
|
- img [ref=e130]
|
||||||
|
- button "Explorer" [ref=e132]:
|
||||||
|
- img [ref=e133]
|
||||||
|
- button "Editor" [ref=e135]:
|
||||||
|
- img [ref=e136]
|
||||||
|
- button "Browser" [ref=e139]:
|
||||||
|
- img [ref=e140]
|
||||||
|
- main
|
||||||
|
- generic [ref=e143]:
|
||||||
|
- generic [ref=e144]:
|
||||||
|
- text: // BROWSER
|
||||||
|
- textbox "No preview active" [ref=e145]
|
||||||
|
- generic [ref=e147]: Waiting for app preview...
|
||||||
|
- generic [ref=e149]: // TERMINAL
|
||||||
|
- generic [ref=e150]:
|
||||||
|
- generic [ref=e151]: "Agent #1"
|
||||||
|
- text: EVOLVED
|
||||||
|
- generic [ref=e152]: Claude Opus 4.5 — 99%
|
||||||
|
- generic [ref=e153]:
|
||||||
|
- generic [ref=e154]:
|
||||||
|
- text: Plan
|
||||||
|
- button [ref=e155]
|
||||||
|
- generic [ref=e156]:
|
||||||
|
- text: YOLO
|
||||||
|
- button [ref=e157]
|
||||||
|
- generic [ref=e158]:
|
||||||
|
- generic [ref=e159]:
|
||||||
|
- button "◄" [ref=e160]
|
||||||
|
- text: 0 / 0
|
||||||
|
- button "►" [ref=e161]
|
||||||
|
- generic [ref=e162]:
|
||||||
|
- button "💬" [ref=e163]
|
||||||
|
- button "✏️" [ref=e164]
|
||||||
|
- button "</>" [ref=e165]
|
||||||
|
- contentinfo [ref=e166]:
|
||||||
|
- generic [ref=e168]: Reference Entity
|
||||||
|
- generic [ref=e169]:
|
||||||
|
- generic [ref=e170]:
|
||||||
|
- button "Agent" [ref=e171]
|
||||||
|
- button "Chat" [ref=e172]
|
||||||
|
- textbox "Message... (type @ to mention)" [active] [ref=e173]
|
||||||
|
- button "↑" [ref=e174]
|
||||||
|
- button "Scroll to bottom" [ref=e175]:
|
||||||
|
- img [ref=e176]
|
||||||
|
- button "View" [ref=e180]
|
||||||
|
- contentinfo [ref=e181]:
|
||||||
|
- img [ref=e185] [cursor=pointer]
|
||||||
|
- generic [ref=e187]:
|
||||||
|
- combobox [ref=e189] [cursor=pointer]:
|
||||||
|
- option "🎨 Default"
|
||||||
|
- option "☀️ Light"
|
||||||
|
- option "🍊 Orange"
|
||||||
|
- option "🌃 Cyberpunk"
|
||||||
|
- option "🌴 Retrowave"
|
||||||
|
- option "💭 Vapor Dream"
|
||||||
|
- option "✨ Y2K"
|
||||||
|
- option "🔲 3D Bevel"
|
||||||
|
- option "🕹️ Arcade"
|
||||||
|
- option "🪩 Disco"
|
||||||
|
- option "🎸 Grunge"
|
||||||
|
- option "🎺 Jazz"
|
||||||
|
- option "🌻 Mellow"
|
||||||
|
- option "🏠 Mid Century"
|
||||||
|
- option "📷 Polaroid"
|
||||||
|
- option "📺 Cartoons"
|
||||||
|
- option "🏖️ Seaside"
|
||||||
|
- option "⌨️ Typewriter" [selected]
|
||||||
|
- option "📠 Xerox"
|
||||||
|
- option "📁 XTree"
|
||||||
|
- button "Sign In" [ref=e190] [cursor=pointer]
|
||||||
|
- generic [ref=e191]:
|
||||||
|
- generic [ref=e192]: 00:00
|
||||||
|
- generic [ref=e193]: 01/01/2026
|
||||||
16
.playwright-mcp/suggestions-container-check.json
Normal file
16
.playwright-mcp/suggestions-container-check.json
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
{
|
||||||
|
"found": true,
|
||||||
|
"tag": "DIV",
|
||||||
|
"className": "suggestions-container",
|
||||||
|
"innerHTML": "",
|
||||||
|
"childCount": 0,
|
||||||
|
"display": "flex",
|
||||||
|
"visibility": "visible",
|
||||||
|
"opacity": "1",
|
||||||
|
"height": "4px",
|
||||||
|
"maxHeight": "100px",
|
||||||
|
"overflow": "auto",
|
||||||
|
"hasClass": false,
|
||||||
|
"parent": "FOOTER ",
|
||||||
|
"styleAttr": null
|
||||||
|
}
|
||||||
64
.playwright-mcp/suggestions-css-rules.json
Normal file
64
.playwright-mcp/suggestions-css-rules.json
Normal file
|
|
@ -0,0 +1,64 @@
|
||||||
|
{
|
||||||
|
"rules": [
|
||||||
|
{
|
||||||
|
"sheet": "https://chat.pragmatismo.com.br/suite/css/app.css",
|
||||||
|
"selector": "*",
|
||||||
|
"display": "",
|
||||||
|
"maxHeight": "",
|
||||||
|
"overflow": "",
|
||||||
|
"opacity": "",
|
||||||
|
"visibility": "",
|
||||||
|
"height": "",
|
||||||
|
"padding": "0px",
|
||||||
|
"margin": "0px"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"sheet": "https://chat.pragmatismo.com.br/suite/css/app.css",
|
||||||
|
"selector": "*",
|
||||||
|
"display": "",
|
||||||
|
"maxHeight": "",
|
||||||
|
"overflow": "",
|
||||||
|
"opacity": "",
|
||||||
|
"visibility": "",
|
||||||
|
"height": "",
|
||||||
|
"padding": "",
|
||||||
|
"margin": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"sheet": "https://chat.pragmatismo.com.br/suite/css/base.css",
|
||||||
|
"selector": "*",
|
||||||
|
"display": "",
|
||||||
|
"maxHeight": "",
|
||||||
|
"overflow": "",
|
||||||
|
"opacity": "",
|
||||||
|
"visibility": "",
|
||||||
|
"height": "",
|
||||||
|
"padding": "0px",
|
||||||
|
"margin": "0px"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"sheet": "inline",
|
||||||
|
"selector": "*",
|
||||||
|
"display": "",
|
||||||
|
"maxHeight": "",
|
||||||
|
"overflow": "",
|
||||||
|
"opacity": "",
|
||||||
|
"visibility": "",
|
||||||
|
"height": "",
|
||||||
|
"padding": "0px",
|
||||||
|
"margin": "0px"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"sheet": "https://chat.pragmatismo.com.br/suite/chat/chat.css?v=3",
|
||||||
|
"selector": ".suggestions-container",
|
||||||
|
"display": "flex",
|
||||||
|
"maxHeight": "100px",
|
||||||
|
"overflow": "",
|
||||||
|
"opacity": "",
|
||||||
|
"visibility": "",
|
||||||
|
"height": "",
|
||||||
|
"padding": "",
|
||||||
|
"margin": ""
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
112
.playwright-mcp/suggestions-dom-check.json
Normal file
112
.playwright-mcp/suggestions-dom-check.json
Normal file
|
|
@ -0,0 +1,112 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"tag": "DIV",
|
||||||
|
"className": "suggestions-container has-bot-suggestions",
|
||||||
|
"id": "suggestions",
|
||||||
|
"text": "Agendar BatizadoAgendar CasamentoAgendar MissaAgendar PeregrinaçãoPedido de OraçãoUso de ImagemLicen",
|
||||||
|
"display": "flex",
|
||||||
|
"visibility": "visible",
|
||||||
|
"hidden": false,
|
||||||
|
"parent": "FOOTER."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tag": "BUTTON",
|
||||||
|
"className": "suggestion-chip",
|
||||||
|
"id": "",
|
||||||
|
"text": "Agendar Batizado",
|
||||||
|
"display": "flex",
|
||||||
|
"visibility": "visible",
|
||||||
|
"hidden": false,
|
||||||
|
"parent": "DIV.suggestions-container has-bot-suggestions"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tag": "BUTTON",
|
||||||
|
"className": "suggestion-chip",
|
||||||
|
"id": "",
|
||||||
|
"text": "Agendar Casamento",
|
||||||
|
"display": "flex",
|
||||||
|
"visibility": "visible",
|
||||||
|
"hidden": false,
|
||||||
|
"parent": "DIV.suggestions-container has-bot-suggestions"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tag": "BUTTON",
|
||||||
|
"className": "suggestion-chip",
|
||||||
|
"id": "",
|
||||||
|
"text": "Agendar Missa",
|
||||||
|
"display": "flex",
|
||||||
|
"visibility": "visible",
|
||||||
|
"hidden": false,
|
||||||
|
"parent": "DIV.suggestions-container has-bot-suggestions"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tag": "BUTTON",
|
||||||
|
"className": "suggestion-chip",
|
||||||
|
"id": "",
|
||||||
|
"text": "Agendar Peregrinação",
|
||||||
|
"display": "flex",
|
||||||
|
"visibility": "visible",
|
||||||
|
"hidden": false,
|
||||||
|
"parent": "DIV.suggestions-container has-bot-suggestions"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tag": "BUTTON",
|
||||||
|
"className": "suggestion-chip",
|
||||||
|
"id": "",
|
||||||
|
"text": "Pedido de Oração",
|
||||||
|
"display": "flex",
|
||||||
|
"visibility": "visible",
|
||||||
|
"hidden": false,
|
||||||
|
"parent": "DIV.suggestions-container has-bot-suggestions"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tag": "BUTTON",
|
||||||
|
"className": "suggestion-chip",
|
||||||
|
"id": "",
|
||||||
|
"text": "Uso de Imagem",
|
||||||
|
"display": "flex",
|
||||||
|
"visibility": "visible",
|
||||||
|
"hidden": false,
|
||||||
|
"parent": "DIV.suggestions-container has-bot-suggestions"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tag": "BUTTON",
|
||||||
|
"className": "suggestion-chip",
|
||||||
|
"id": "",
|
||||||
|
"text": "Licenciamento",
|
||||||
|
"display": "flex",
|
||||||
|
"visibility": "visible",
|
||||||
|
"hidden": false,
|
||||||
|
"parent": "DIV.suggestions-container has-bot-suggestions"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tag": "BUTTON",
|
||||||
|
"className": "suggestion-chip",
|
||||||
|
"id": "",
|
||||||
|
"text": "Evento/Iluminação",
|
||||||
|
"display": "flex",
|
||||||
|
"visibility": "visible",
|
||||||
|
"hidden": false,
|
||||||
|
"parent": "DIV.suggestions-container has-bot-suggestions"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tag": "BUTTON",
|
||||||
|
"className": "suggestion-chip",
|
||||||
|
"id": "",
|
||||||
|
"text": "Cadastrar Guia",
|
||||||
|
"display": "flex",
|
||||||
|
"visibility": "visible",
|
||||||
|
"hidden": false,
|
||||||
|
"parent": "DIV.suggestions-container has-bot-suggestions"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tag": "BUTTON",
|
||||||
|
"className": "suggestion-chip",
|
||||||
|
"id": "",
|
||||||
|
"text": "Fazer Doação",
|
||||||
|
"display": "flex",
|
||||||
|
"visibility": "visible",
|
||||||
|
"hidden": false,
|
||||||
|
"parent": "DIV.suggestions-container has-bot-suggestions"
|
||||||
|
}
|
||||||
|
]
|
||||||
68
AGENTS.md
68
AGENTS.md
|
|
@ -246,6 +246,7 @@ match x {
|
||||||
|
|
||||||
## ❌ Absolute Prohibitions
|
## ❌ Absolute Prohibitions
|
||||||
- NEVER search /target folder! It is binary compiled.
|
- NEVER search /target folder! It is binary compiled.
|
||||||
|
- ❌ **NEVER** hardcode passwords, tokens, API keys, or any credentials in source code — ALWAYS use `generate_random_string()` or environment variables
|
||||||
- ❌ **NEVER** build in release mode - ONLY debug builds allowed
|
- ❌ **NEVER** build in release mode - ONLY debug builds allowed
|
||||||
- ❌ **NEVER** use `--release` flag on ANY cargo command
|
- ❌ **NEVER** use `--release` flag on ANY cargo command
|
||||||
- ❌ **NEVER** run `cargo build` - use `cargo check` for syntax verification
|
- ❌ **NEVER** run `cargo build` - use `cargo check` for syntax verification
|
||||||
|
|
@ -860,3 +861,70 @@ Continue on gb/ workspace. Follow AGENTS.md strictly:
|
||||||
- **VERIFY LAST** - Only compile/diagnostics after ALL fixes
|
- **VERIFY LAST** - Only compile/diagnostics after ALL fixes
|
||||||
- **DELETE DEAD CODE** - Don't keep unused code around
|
- **DELETE DEAD CODE** - Don't keep unused code around
|
||||||
- **GIT WORKFLOW** - ALWAYS push to ALL repositories (github, pragmatismo)
|
- **GIT WORKFLOW** - ALWAYS push to ALL repositories (github, pragmatismo)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Deploy in Prod Workflow
|
||||||
|
|
||||||
|
### CI/CD Pipeline (Primary Method)
|
||||||
|
|
||||||
|
1. **Push to ALM** — triggers CI/CD automatically:
|
||||||
|
```bash
|
||||||
|
cd botserver
|
||||||
|
git push alm main
|
||||||
|
git push origin main
|
||||||
|
cd ..
|
||||||
|
git add botserver
|
||||||
|
git commit -m "Update botserver: <description>"
|
||||||
|
git push alm main
|
||||||
|
git push origin main
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Wait for CI** — build takes ~3-4 minutes. Check status:
|
||||||
|
```bash
|
||||||
|
# Via web: https://alm.pragmatismo.com.br/GeneralBots/botserver/actions
|
||||||
|
# Or check binary timestamp after ~4 min sleep:
|
||||||
|
sleep 240
|
||||||
|
ssh -o StrictHostKeyChecking=no -o ConnectTimeout=10 administrator@prod-host \
|
||||||
|
"sudo incus exec system -- stat -c '%y' /opt/gbo/bin/botserver"
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Restart in prod** — after binary updates:
|
||||||
|
```bash
|
||||||
|
ssh -o StrictHostKeyChecking=no -o ConnectTimeout=10 administrator@prod-host \
|
||||||
|
"sudo incus exec system -- pkill -f botserver || true"
|
||||||
|
sleep 2
|
||||||
|
ssh -o StrictHostKeyChecking=no -o ConnectTimeout=10 administrator@prod-host \
|
||||||
|
"sudo incus exec system -- bash -c 'cd /opt/gbo/bin && sudo -u gbuser RUST_LOG=info ./botserver --noconsole > /opt/gbo/logs/botserver-output.log 2>&1 &'"
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **Verify deployment**:
|
||||||
|
```bash
|
||||||
|
# Wait for bootstrap (~2 min)
|
||||||
|
sleep 120
|
||||||
|
# Check health
|
||||||
|
ssh -o StrictHostKeyChecking=no -o ConnectTimeout=10 administrator@prod-host \
|
||||||
|
"sudo incus exec system -- curl -s -o /dev/null -w '%{http_code}' http://localhost:8080/health"
|
||||||
|
# Check logs
|
||||||
|
ssh -o StrictHostKeyChecking=no -o ConnectTimeout=10 administrator@prod-host \
|
||||||
|
"sudo incus exec system -- tail -30 /opt/gbo/logs/botserver-output.log"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Troubleshooting
|
||||||
|
|
||||||
|
| Symptom | Cause | Fix |
|
||||||
|
|---------|-------|-----|
|
||||||
|
| `valkey-cli ping` hangs indefinitely | Valkey requires password auth | Install `nc` or `ss` in container for health checks |
|
||||||
|
| `nc: command not found` | Prod container lacks netcat | `sudo incus exec system -- apt-get install -y netcat-openbsd` |
|
||||||
|
| Cache connection timeout | iptables DROP rule on port 6379 | `sudo incus exec system -- iptables -I INPUT -i lo -j ACCEPT` |
|
||||||
|
| `AUTH failed` on Valkey | Valkey runs without password but Vault has one | Code tries no-password URL first, then with password |
|
||||||
|
| `Cannot start a runtime from within a runtime` | `block_on()` called from async context | Use `.await` directly, never `runtime.block_on()` in async functions |
|
||||||
|
| Secret not found in Vault | Path mismatch between seeding and reading | Seeding: `secret/gbo/cache`, Reading: `gbo/cache` (kv2 prepends `secret/`) |
|
||||||
|
| CI completed but binary not updated | Deploy step fails silently (SSH/transfer issue) | Build locally and transfer, or re-push to trigger CI again |
|
||||||
|
|
||||||
|
### Critical Paths in Vault
|
||||||
|
|
||||||
|
- **Seeding writes to**: `secret/gbo/{service}` (e.g., `secret/gbo/cache`)
|
||||||
|
- **Code reads via**: `SecretPaths::{SERVICE}` which maps to `gbo/{service}`
|
||||||
|
- **kv2::read** prepends `secret/` automatically and looks up `secret/data/gbo/{service}`
|
||||||
|
- **All paths must match**: `gbo/cache`, `gbo/drive`, `gbo/tables`, `gbo/directory`, `gbo/llm`, `gbo/meet`, `gbo/alm`, `gbo/vectordb`, `gbo/encryption`, `gbo/email`
|
||||||
|
|
|
||||||
2
Cargo.lock
generated
2
Cargo.lock
generated
|
|
@ -1275,7 +1275,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "botapp"
|
name = "botapp"
|
||||||
version = "6.3.0"
|
version = "6.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"botlib",
|
"botlib",
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ members = [
|
||||||
"bottest",
|
"bottest",
|
||||||
"botui",
|
"botui",
|
||||||
]
|
]
|
||||||
|
exclude = ["backup-to-s3"]
|
||||||
|
|
||||||
[workspace.lints.rust]
|
[workspace.lints.rust]
|
||||||
|
|
||||||
|
|
|
||||||
7
backup-to-s3/.env.example
Normal file
7
backup-to-s3/.env.example
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
AWS_ACCESS_KEY_ID=your_access_key
|
||||||
|
AWS_SECRET_ACCESS_KEY=your_secret_key
|
||||||
|
AWS_REGION=us-east-1
|
||||||
|
S3_BUCKET=prod-gbo-backup
|
||||||
|
INCUS_PROJECT=PROD-GBO1
|
||||||
|
CONTAINER_NAME=
|
||||||
|
KEEP_COUNT=10
|
||||||
1818
backup-to-s3/Cargo.lock
generated
Normal file
1818
backup-to-s3/Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
15
backup-to-s3/Cargo.toml
Normal file
15
backup-to-s3/Cargo.toml
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
[package]
|
||||||
|
name = "backup-to-s3"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
tokio = { version = "1", features = ["full"] }
|
||||||
|
rusoto_s3 = "0.48"
|
||||||
|
rusoto_core = "0.48"
|
||||||
|
rusoto_credential = "0.48"
|
||||||
|
dotenv = "0.15"
|
||||||
|
chrono = "0.4"
|
||||||
|
tracing = "0.1"
|
||||||
|
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
||||||
|
tracing-appender = "0.2"
|
||||||
213
backup-to-s3/src/main.rs
Normal file
213
backup-to-s3/src/main.rs
Normal file
|
|
@ -0,0 +1,213 @@
|
||||||
|
use chrono::Utc;
|
||||||
|
use rusoto_core::Region;
|
||||||
|
use rusoto_credential::StaticProvider;
|
||||||
|
use rusoto_s3::{S3, S3Client};
|
||||||
|
use std::env;
|
||||||
|
use tracing::{error, info, warn};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct Config {
|
||||||
|
access_key: String,
|
||||||
|
secret_key: String,
|
||||||
|
bucket: String,
|
||||||
|
project: String,
|
||||||
|
keep_count: usize,
|
||||||
|
region: Region,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Config {
|
||||||
|
fn from_env() -> Result<Self, String> {
|
||||||
|
dotenv::from_path(".env").ok();
|
||||||
|
|
||||||
|
let access_key = env::var("AWS_ACCESS_KEY_ID").map_err(|_| "AWS_ACCESS_KEY_ID not set")?;
|
||||||
|
let secret_key = env::var("AWS_SECRET_ACCESS_KEY").map_err(|_| "AWS_SECRET_ACCESS_KEY not set")?;
|
||||||
|
let bucket = env::var("S3_BUCKET").map_err(|_| "S3_BUCKET not set")?;
|
||||||
|
let project = env::var("INCUS_PROJECT").unwrap_or_else(|_| "PROD-GBO1".to_string());
|
||||||
|
let keep_count: usize = env::var("KEEP_COUNT").unwrap_or_else(|_| "10".to_string()).parse().unwrap_or(10);
|
||||||
|
let region_name = env::var("AWS_REGION").unwrap_or_else(|_| "us-east-1".to_string());
|
||||||
|
let region = region_name.parse().unwrap_or(Region::UsEast1);
|
||||||
|
|
||||||
|
Ok(Config {
|
||||||
|
access_key,
|
||||||
|
secret_key,
|
||||||
|
bucket,
|
||||||
|
project,
|
||||||
|
keep_count,
|
||||||
|
region,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
tracing_subscriber::fmt()
|
||||||
|
.with_env_filter("info")
|
||||||
|
.init();
|
||||||
|
|
||||||
|
let config = Config::from_env()?;
|
||||||
|
info!("Starting backup to S3 bucket: {}", config.bucket);
|
||||||
|
|
||||||
|
let credential_provider = StaticProvider::new_minimal(
|
||||||
|
config.access_key.clone(),
|
||||||
|
config.secret_key.clone(),
|
||||||
|
);
|
||||||
|
let s3_client = S3Client::new_with(
|
||||||
|
rusoto_core::HttpClient::new().map_err(|e| format!("HTTP client: {}", e))?,
|
||||||
|
credential_provider,
|
||||||
|
config.region.clone(),
|
||||||
|
);
|
||||||
|
|
||||||
|
let timestamp = Utc::now().format("%Y-%m-%d").to_string();
|
||||||
|
let container_name = env::var("CONTAINER_NAME").unwrap_or_default();
|
||||||
|
|
||||||
|
let containers = if container_name.is_empty() {
|
||||||
|
list_inc_containers(&config.project).await?
|
||||||
|
} else {
|
||||||
|
vec![container_name]
|
||||||
|
};
|
||||||
|
|
||||||
|
for container in containers {
|
||||||
|
info!("Backing up container: {}", container);
|
||||||
|
|
||||||
|
match backup_container(&container, &config, &s3_client, ×tamp).await {
|
||||||
|
Ok(_) => info!("Successfully backed up {}", container),
|
||||||
|
Err(e) => {
|
||||||
|
error!("Failed to backup {}: {}", container, e);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanup_old_backups(&config, &s3_client).await?;
|
||||||
|
|
||||||
|
info!("Backup completed!");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn list_inc_containers(project: &str) -> Result<Vec<String>, String> {
|
||||||
|
let output = tokio::process::Command::new("incus")
|
||||||
|
.args(["list", "--project", project, "--format", "csv", "-c", "n"])
|
||||||
|
.output()
|
||||||
|
.await
|
||||||
|
.map_err(|e| format!("Failed to list containers: {}", e))?;
|
||||||
|
|
||||||
|
if !output.status.success() {
|
||||||
|
return Err("incus list failed".to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
let stdout = String::from_utf8_lossy(&output.stdout);
|
||||||
|
let containers: Vec<String> = stdout.lines()
|
||||||
|
.map(|s| s.trim().to_string())
|
||||||
|
.filter(|s| !s.is_empty())
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
Ok(containers)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn backup_container(
|
||||||
|
container: &str,
|
||||||
|
config: &Config,
|
||||||
|
s3_client: &S3Client,
|
||||||
|
timestamp: &str,
|
||||||
|
) -> Result<(), String> {
|
||||||
|
let temp_dir = std::env::temp_dir();
|
||||||
|
let backup_file = temp_dir.join(format!("{}.tar.gz", container));
|
||||||
|
|
||||||
|
info!("Exporting {} to {:?}", container, backup_file);
|
||||||
|
|
||||||
|
let export_output = tokio::process::Command::new("incus")
|
||||||
|
.args([
|
||||||
|
"export",
|
||||||
|
container,
|
||||||
|
backup_file.to_str().unwrap(),
|
||||||
|
"--instance-only",
|
||||||
|
"--project",
|
||||||
|
&config.project,
|
||||||
|
])
|
||||||
|
.output()
|
||||||
|
.await
|
||||||
|
.map_err(|e| format!("Failed to export container: {}", e))?;
|
||||||
|
|
||||||
|
if !export_output.status.success() {
|
||||||
|
let stderr = String::from_utf8_lossy(&export_output.stderr);
|
||||||
|
return Err(format!("incus export failed: {}", stderr));
|
||||||
|
}
|
||||||
|
|
||||||
|
let s3_key = format!("{}/{}.tar.gz", timestamp, container);
|
||||||
|
info!("Uploading to s3://{}/{}", config.bucket, s3_key);
|
||||||
|
|
||||||
|
let file_data = tokio::fs::read(&backup_file)
|
||||||
|
.await
|
||||||
|
.map_err(|e| format!("Failed to read backup file: {}", e))?;
|
||||||
|
|
||||||
|
let put_request = rusoto_s3::PutObjectRequest {
|
||||||
|
body: Some(file_data.into()),
|
||||||
|
bucket: config.bucket.clone(),
|
||||||
|
key: s3_key,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
s3_client.put_object(put_request)
|
||||||
|
.await
|
||||||
|
.map_err(|e| format!("Failed to upload to S3: {}", e))?;
|
||||||
|
|
||||||
|
tokio::fs::remove_file(&backup_file)
|
||||||
|
.await
|
||||||
|
.map_err(|e| warn!("Failed to delete temp file: {}", e)).ok();
|
||||||
|
|
||||||
|
info!("Uploaded {}", container);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn cleanup_old_backups(config: &Config, s3_client: &S3Client) -> Result<(), String> {
|
||||||
|
info!("Cleaning up old backups, keeping {} most recent", config.keep_count);
|
||||||
|
|
||||||
|
let list_output = s3_client
|
||||||
|
.list_objects_v2(rusoto_s3::ListObjectsV2Request {
|
||||||
|
bucket: config.bucket.clone(),
|
||||||
|
delimiter: Some("/".to_string()),
|
||||||
|
..Default::default()
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.map_err(|e| format!("Failed to list S3: {}", e))?;
|
||||||
|
|
||||||
|
let mut prefixes: Vec<String> = list_output
|
||||||
|
.common_prefixes
|
||||||
|
.unwrap_or_default()
|
||||||
|
.iter()
|
||||||
|
.map(|p| p.prefix.clone().unwrap_or_default().trim_end_matches('/').to_string())
|
||||||
|
.filter(|p| !p.is_empty())
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
prefixes.sort_by(|a, b| b.cmp(a));
|
||||||
|
|
||||||
|
if prefixes.len() > config.keep_count {
|
||||||
|
for prefix in prefixes.iter().skip(config.keep_count) {
|
||||||
|
info!("Deleting old backup: {}", prefix);
|
||||||
|
|
||||||
|
let objects = s3_client
|
||||||
|
.list_objects_v2(rusoto_s3::ListObjectsV2Request {
|
||||||
|
bucket: config.bucket.clone(),
|
||||||
|
prefix: Some(format!("{}/", prefix)),
|
||||||
|
..Default::default()
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.map_err(|e| format!("Failed to list objects: {}", e))?;
|
||||||
|
|
||||||
|
for obj in objects.contents.unwrap_or_default() {
|
||||||
|
if let Some(key) = obj.key {
|
||||||
|
s3_client
|
||||||
|
.delete_object(rusoto_s3::DeleteObjectRequest {
|
||||||
|
bucket: config.bucket.clone(),
|
||||||
|
key,
|
||||||
|
..Default::default()
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.ok();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
@ -1 +1 @@
|
||||||
Subproject commit dae0feb6a567dd1b7e66a382786deef4e7f1fb8a
|
Subproject commit 7b4753af0d1c8b2ce763e5171676e8116ddd5fb4
|
||||||
Binary file not shown.
5
ping_google.cmd
Normal file
5
ping_google.cmd
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
@echo off
|
||||||
|
:loop
|
||||||
|
ping google.com
|
||||||
|
timeout /t 18000 /nobreak >nul
|
||||||
|
goto loop
|
||||||
110
prompts/c1.md
110
prompts/c1.md
|
|
@ -1,85 +1,29 @@
|
||||||
# Plan: Migrate LXC Containers to Incus (COMPLETED)
|
Migrate 10 LXC containers from source (root@82.29.59.188 - LXD/ZFS) to destination (administrator@63.141.255.9 - Incus/Btrfs) using streaming method.
|
||||||
|
Instructions
|
||||||
|
- Use Btrfs storage on destination (loop1 mounted at /mnt/btrfs)
|
||||||
|
- Use streaming method: tar | zstd | ssh (no local storage)
|
||||||
|
- Migrate one by one: stop → copy → create → start → delete
|
||||||
|
- DO NOT delete containers before copying data
|
||||||
|
Discoveries
|
||||||
|
- Source: LXD with ZFS backend (default/containers/*), containers at /var/snap/lxd/common/lxd/storage-pools/default/containers/
|
||||||
|
- Destination: Incus 6.23, Btrfs pool PROD-GBO on loop1, project PROD-GBO1
|
||||||
|
- SSH: Works from source root → destination administrator
|
||||||
|
- MISTAKE MADE: Deleted all Incus containers with cleanup command before data was properly inside them
|
||||||
|
Accomplished
|
||||||
|
- ✅ Created Btrfs storage pool PROD-GBO on destination
|
||||||
|
- ✅ SSH access configured from source to destination
|
||||||
|
- ❌ Containers were deleted during cleanup - they need to be recreated
|
||||||
|
- ❌ Data is outside containers, needs to be copied INTO containers properly
|
||||||
|
|
||||||
## Summary
|
Next steps
|
||||||
✅ All containers migrated from LXD (pragmatismo.com.br) to Incus (63.141.255.9)
|
1. Create empty Incus containers: incus create --empty --project PROD-GBO1 -s PROD-GBO
|
||||||
✅ All data synced from host /opt/gbo/tenants/ to containers
|
2. Migrate from source: tar -C rootfs -cf - . | zstd | ssh dest 'sudo tar -I zstd -xf - -C /mnt/btrfs/containers/PROD-GBO1_<name>/rootfs'
|
||||||
✅ All binaries copied from source to containers
|
3. Start containers: incus start <name>
|
||||||
✅ All services configured and running
|
4. Delete from source: lxc delete --force
|
||||||
|
|
||||||
## Container & Service Status
|
Accomplished
|
||||||
| Container | Service | Status |
|
- Recreated Btrfs storage pool PROD-GBO on /mnt/btrfs
|
||||||
|-----------|---------|--------|
|
- Created 10 empty Incus containers
|
||||||
| dns | coredns | ✅ RUNNING |
|
- Migrated all containers using tar | zstd | ssh
|
||||||
| email | stalwart-mail | ✅ RUNNING |
|
- All 10 containers running on Btrfs
|
||||||
| webmail | php built-in server (:5252) | ✅ RUNNING |
|
- Used ~24GB of Btrfs storage
|
||||||
| alm | forgejo | ✅ RUNNING |
|
|
||||||
| drive | minio | ✅ RUNNING |
|
|
||||||
| tables | postgresql | ✅ RUNNING |
|
|
||||||
| system | botserver | ✅ RUNNING |
|
|
||||||
|
|
||||||
|
|
||||||
## Service Files Location
|
|
||||||
All service files in `/etc/systemd/system/` inside containers:
|
|
||||||
- `dns.service` - coredns (User=root)
|
|
||||||
- `email.service` - stalwart-mail (User=root)
|
|
||||||
- `alm.service` - forgejo (User=alm, Group=alm)
|
|
||||||
- `minio.service` - minio (User=root)
|
|
||||||
|
|
||||||
## Binary Locations
|
|
||||||
| Service | Binary Path |
|
|
||||||
|---------|-------------|
|
|
||||||
| coredns | /opt/gbo/bin/coredns |
|
|
||||||
| stalwart | /opt/gbo/bin/stalwart |
|
|
||||||
| forgejo | /opt/gbo/bin/forgejo |
|
|
||||||
| minio | /usr/local/bin/minio |
|
|
||||||
|
|
||||||
## Key Paths Inside Containers
|
|
||||||
- **Binaries**: /opt/gbo/bin/
|
|
||||||
- **Data**: /opt/gbo/data/
|
|
||||||
- **Config**: /opt/gbo/conf/
|
|
||||||
- **Logs**: /opt/gbo/logs/
|
|
||||||
|
|
||||||
## IPS (Destination)
|
|
||||||
- dns: 10.107.115.155
|
|
||||||
- email: 10.107.115.200
|
|
||||||
- webmail: 10.107.115.208
|
|
||||||
- alm: 10.107.115.4
|
|
||||||
- drive: 10.107.115.114
|
|
||||||
- tables: 10.107.115.33
|
|
||||||
- system: 10.107.115.229
|
|
||||||
|
|
||||||
- alm-ci: 10.107.115.190
|
|
||||||
- table-editor: 10.107.115.73
|
|
||||||
|
|
||||||
## Port Forwarding (iptables NAT)
|
|
||||||
```
|
|
||||||
# DNS
|
|
||||||
sudo iptables -t nat -A PREROUTING -p tcp --dport 53 -j DNAT --to-destination 10.107.115.155:53
|
|
||||||
sudo iptables -t nat -A PREROUTING -p udp --dport 53 -j DNAT --to-destination 10.107.115.155:53
|
|
||||||
|
|
||||||
# Email
|
|
||||||
sudo iptables -t nat -A PREROUTING -p tcp --dport 25 -j DNAT --to-destination 10.107.115.200:25
|
|
||||||
sudo iptables -t nat -A PREROUTING -p tcp --dport 587 -j DNAT --to-destination 10.107.115.200:587
|
|
||||||
sudo iptables -t nat -A PREROUTING -p tcp --dport 465 -j DNAT --to-destination 10.107.115.200:465
|
|
||||||
sudo iptables -t nat -A PREROUTING -p tcp --dport 143 -j DNAT --to-destination 10.107.115.200:143
|
|
||||||
sudo iptables -t nat -A PREROUTING -p tcp --dport 993 -j DNAT --to-destination 10.107.115.200:993
|
|
||||||
sudo iptables -t nat -A PREROUTING -p tcp --dport 110 -j DNAT --to-destination 10.107.115.200:110
|
|
||||||
sudo iptables -t nat -A PREROUTING -p tcp --dport 995 -j DNAT --to-destination 10.107.115.200:995
|
|
||||||
sudo iptables -t nat -A PREROUTING -p tcp --dport 4190 -j DNAT --to-destination 10.107.115.200:4190
|
|
||||||
|
|
||||||
# Webmail
|
|
||||||
sudo iptables -t nat -A PREROUTING -p tcp --dport 5252 -j DNAT --to-destination 10.107.115.208:5252
|
|
||||||
|
|
||||||
# ALM (forgejo)
|
|
||||||
sudo iptables -t nat -A PREROUTING -p tcp --dport 4747 -j DNAT --to-destination 10.107.115.4:4747
|
|
||||||
|
|
||||||
# Caddy (80, 443) - already exists for proxy container
|
|
||||||
```
|
|
||||||
|
|
||||||
## Workflow (PRODUCTION TESTED)
|
|
||||||
1. Copy container: `incus copy --instance-only lxd-source:<source> <dest>`
|
|
||||||
2. Add eth0 network: `incus config device add <c> eth0 nic name=eth0 network=PROD-GBO`
|
|
||||||
3. Sync data: `incus file push --recursive /opt/gbo/tenants/pragmatismo/<container>/ <container>/opt/gbo/`
|
|
||||||
4. Copy binaries: from source via `lxc file pull` → scp to dest → `incus file push`
|
|
||||||
5. Create service file: `cat > /tmp/<svc>.service && incus file push <svc>.service <c>/etc/systemd/system/`
|
|
||||||
6. Enable/start: `incus exec <c> -- systemctl enable <svc> && systemctl start <svc>`
|
|
||||||
|
|
|
||||||
461
prompts/c3.md
Normal file
461
prompts/c3.md
Normal file
|
|
@ -0,0 +1,461 @@
|
||||||
|
# Fast Migration Plan - LXC/LXD to Incus (ONE BY ONE)
|
||||||
|
|
||||||
|
## Strategy: STREAMING DATA TRANSFER (NO DISK I/O ON SOURCE)
|
||||||
|
|
||||||
|
Transfer container data directly via compressed stream, NO intermediate tarballs on source.
|
||||||
|
Use `pv` (pipe viewer) for colorful progress bars and rate monitoring.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Why This is FASTEST
|
||||||
|
|
||||||
|
✅ **NO disk writes on source** (99% full) - streaming only
|
||||||
|
✅ **zstd compression** - 3-5x faster than gzip
|
||||||
|
✅ **pv monitoring** - colorful progress, ETA, rate
|
||||||
|
✅ **Parallel transfers** - migrate multiple containers simultaneously
|
||||||
|
✅ **Immediate deletion** - frees space for next transfer
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
### On Source (82.29.59.188)
|
||||||
|
```bash
|
||||||
|
# Install required tools
|
||||||
|
sudo apt update && sudo apt install -y zstd pv
|
||||||
|
|
||||||
|
# List all containers
|
||||||
|
lxc list
|
||||||
|
```
|
||||||
|
|
||||||
|
### On Destination (63.141.255.9)
|
||||||
|
```bash
|
||||||
|
# Incus is already installed and clean (LXC/LXD removed)
|
||||||
|
|
||||||
|
# Create PROD-GBO network bridge
|
||||||
|
sudo incus network create PROD-GBO --type bridge ipv4.address=10.107.115.1/24
|
||||||
|
|
||||||
|
# Create PROD-GBO1 project
|
||||||
|
sudo incus project create PROD-GBO1
|
||||||
|
sudo incus project switch PROD-GBO1
|
||||||
|
|
||||||
|
# Configure project
|
||||||
|
sudo incus project set PROD-GBO1 features.storage.volumes=true
|
||||||
|
sudo incus project set PROD-GBO1 features.profiles=true
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Migration Workflow (Per Container)
|
||||||
|
|
||||||
|
### Step 1: Stop Container on Source
|
||||||
|
```bash
|
||||||
|
# On source server
|
||||||
|
lxc stop pragmatismo-alm
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 2: Stream Data to Destination (FASTEST - NO DISK I/O)
|
||||||
|
```bash
|
||||||
|
# On source server - stream compressed data directly
|
||||||
|
lxc exec pragmatismo-alm -- tar -cf - /opt/gbo/ | \
|
||||||
|
zstd -3 -q | \
|
||||||
|
pv -s $(du -sb /opt/gbo | awk '{print $1}') | \
|
||||||
|
ssh administrator@63.141.255.9 "zstd -d | tar -xf - -C ~/gbo/pragmatismo-alm/"
|
||||||
|
```
|
||||||
|
|
||||||
|
**What happens:**
|
||||||
|
1. `tar` reads /opt/gbo from container (streamed, no disk write)
|
||||||
|
2. `zstd -3` compresses on-the-fly (fast compression)
|
||||||
|
3. `pv` shows colorful progress with ETA and rate
|
||||||
|
4. `ssh` streams to destination
|
||||||
|
5. Destination decompresses and extracts directly to ~/gbo/pragmatismo-alm/
|
||||||
|
6. **NO tarball on source disk** - solves 99% full problem!
|
||||||
|
|
||||||
|
**pv Options:**
|
||||||
|
- `-s` : Total size (from `du -sb` for accurate progress)
|
||||||
|
- Colorful output with ETA, rate, elapsed time
|
||||||
|
|
||||||
|
### Step 3: Delete Container from Source
|
||||||
|
```bash
|
||||||
|
# Free space immediately for next transfer
|
||||||
|
lxc delete pragmatismo-alm
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 4: Create Fresh Incus Container
|
||||||
|
```bash
|
||||||
|
# On destination server
|
||||||
|
sudo incus launch images:debian/12/cloud pragmatismo-alm
|
||||||
|
|
||||||
|
# Add network
|
||||||
|
sudo incus config device add pragmatismo-alm eth0 nic name=eth0 network=PROD-GBO
|
||||||
|
|
||||||
|
# Set static IP
|
||||||
|
sudo incus config set pragmatismo-alm ipv4.address=10.107.115.4
|
||||||
|
|
||||||
|
# Create gbuser inside container
|
||||||
|
sudo incus exec pragmatismo-alm -- useradd -m -s /bin/bash gbuser
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 5: Push Data to Container
|
||||||
|
```bash
|
||||||
|
# Create directory structure
|
||||||
|
sudo incus exec pragmatismo-alm -- mkdir -p /opt/gbo
|
||||||
|
|
||||||
|
# Push data (recursive)
|
||||||
|
sudo incus file push -r ~/gbo/pragmatismo-alm/* pragmatismo-alm/opt/gbo/
|
||||||
|
|
||||||
|
# Fix ownership
|
||||||
|
sudo incus exec pragmatismo-alm -- chown -R gbuser:gbuser /opt/gbo
|
||||||
|
|
||||||
|
# Make binaries executable
|
||||||
|
sudo incus exec pragmatismo-alm -- chmod +x /opt/gbo/bin/*
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 6: Start Container and Verify Service
|
||||||
|
```bash
|
||||||
|
sudo incus start pragmatismo-alm
|
||||||
|
|
||||||
|
# Check service status
|
||||||
|
sudo incus exec pragmatismo-alm -- systemctl status alm
|
||||||
|
|
||||||
|
# Check logs
|
||||||
|
sudo incus exec pragmatismo-alm -- journalctl -u alm -f
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 7: Configure NAT Rules (ON HOST, NOT IN CONTAINER)
|
||||||
|
```bash
|
||||||
|
# On destination host (63.141.255.9)
|
||||||
|
|
||||||
|
# For ALM (example):
|
||||||
|
sudo iptables -t nat -A PREROUTING -p tcp --dport 4747 -j DNAT --to-destination 10.107.115.4:4747
|
||||||
|
sudo iptables -t nat -A OUTPUT -p tcp --dport 4747 -j DNAT --to-destination 10.107.115.4:4747
|
||||||
|
sudo iptables -A FORWARD -p tcp -d 10.107.115.4 --dport 4747 -j ACCEPT
|
||||||
|
sudo iptables -t nat -A POSTROUTING -p tcp -d 10.107.115.4 -j MASQUERADE
|
||||||
|
|
||||||
|
# Save rules
|
||||||
|
sudo sh -c 'iptables-save > /etc/iptables/rules.v4'
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Full Migration Script (ALL CONTAINERS)
|
||||||
|
|
||||||
|
### Run Multiple Containers in Parallel (for speed)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
#!/bin/bash
|
||||||
|
# MIGRATE-ALL.sh - Fast parallel migration
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# Container list with IPs
|
||||||
|
declare -A CONTAINERS=(
|
||||||
|
["dns"]="10.107.115.155"
|
||||||
|
["email"]="10.107.115.200"
|
||||||
|
["webmail"]="10.107.115.208"
|
||||||
|
["alm"]="10.107.115.4"
|
||||||
|
["drive"]="10.107.115.114"
|
||||||
|
["tables"]="10.107.115.33"
|
||||||
|
["system"]="10.107.115.229"
|
||||||
|
["alm-ci"]="10.107.115.190"
|
||||||
|
["table-editor"]="10.107.115.73"
|
||||||
|
)
|
||||||
|
|
||||||
|
for container in "${!CONTAINERS[@]}"; do
|
||||||
|
ip="${CONTAINERS[$container]}"
|
||||||
|
|
||||||
|
echo -e "\e[1;32m=== Migrating $container ($ip) ===\e[0m"
|
||||||
|
|
||||||
|
# Step 1: Stop
|
||||||
|
echo -e "\e[1;33mStopping $container...\e[0m"
|
||||||
|
ssh root@82.29.59.188 "lxc stop $container" || true
|
||||||
|
|
||||||
|
# Step 2: Get size for pv
|
||||||
|
echo -e "\e[1;33mGetting size of /opt/gbo...\e[0m"
|
||||||
|
size=$(ssh root@82.29.59.188 "lxc exec $container -- du -sb /opt/gbo | awk '{print \$1}'")
|
||||||
|
|
||||||
|
# Step 3: Stream data (FAST!)
|
||||||
|
echo -e "\e[1;33mStreaming data to destination...\e[0m"
|
||||||
|
ssh root@82.29.59.188 "lxc exec $container -- tar -cf - /opt/gbo/" | \
|
||||||
|
zstd -3 -q | \
|
||||||
|
pv -s $size | \
|
||||||
|
ssh administrator@63.141.255.9 "mkdir -p ~/gbo/$container && zstd -d | tar -xf - -C ~/gbo/$container/"
|
||||||
|
|
||||||
|
# Step 4: Delete from source
|
||||||
|
echo -e "\e[1;33mDeleting $container from source...\e[0m"
|
||||||
|
ssh root@82.29.59.188 "lxc delete $container"
|
||||||
|
|
||||||
|
# Step 5: Create fresh container
|
||||||
|
echo -e "\e[1;33mCreating Incus container...\e[0m"
|
||||||
|
ssh administrator@63.141.255.9 "sudo incus launch images:debian/12/cloud $container && \
|
||||||
|
sudo incus config device add $container eth0 nic name=eth0 network=PROD-GBO && \
|
||||||
|
sudo incus config set $container ipv4.address=$ip && \
|
||||||
|
sudo incus exec $container -- useradd -m -s /bin/bash gbuser"
|
||||||
|
|
||||||
|
# Step 6: Push data
|
||||||
|
echo -e "\e[1;33mPushing data to container...\e[0m"
|
||||||
|
ssh administrator@63.141.255.9 "sudo incus exec $container -- mkdir -p /opt/gbo && \
|
||||||
|
sudo incus file push -r ~/gbo/$container/* $container:/opt/gbo/ && \
|
||||||
|
sudo incus exec $container -- chown -R gbuser:gbuser /opt/gbo && \
|
||||||
|
sudo incus exec $container -- chmod +x /opt/gbo/bin/*"
|
||||||
|
|
||||||
|
# Step 7: Start
|
||||||
|
echo -e "\e[1;33mStarting $container...\e[0m"
|
||||||
|
ssh administrator@63.141.255.9 "sudo incus start $container"
|
||||||
|
|
||||||
|
echo -e "\e[1;32m✓ $container migrated successfully!\e[0m"
|
||||||
|
echo ""
|
||||||
|
done
|
||||||
|
|
||||||
|
echo -e "\e[1;32m=== ALL MIGRATIONS COMPLETE ===\e[0m"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Single Container Migration (Quick Test)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
#!/bin/bash
|
||||||
|
# migrate-one.sh - Migrate single container
|
||||||
|
|
||||||
|
CONTAINER=$1
|
||||||
|
|
||||||
|
if [ -z "$CONTAINER" ]; then
|
||||||
|
echo "Usage: $0 <container-name>"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo -e "\e[1;32m=== Migrating $CONTAINER ===\e[0m"
|
||||||
|
|
||||||
|
# Stop on source
|
||||||
|
echo -e "\e[1;33m[1/7] Stopping container...\e[0m"
|
||||||
|
ssh root@82.29.59.188 "lxc stop $CONTAINER"
|
||||||
|
|
||||||
|
# Get size
|
||||||
|
echo -e "\e[1;33m[2/7] Getting data size...\e[0m"
|
||||||
|
SIZE=$(ssh root@82.29.59.188 "lxc exec $CONTAINER -- du -sb /opt/gbo | awk '{print \$1}'")
|
||||||
|
|
||||||
|
# Stream data (NO DISK WRITE!)
|
||||||
|
echo -e "\e[1;33m[3/7] Streaming data (pv will show progress)...\e[0m"
|
||||||
|
ssh root@82.29.59.188 "lxc exec $CONTAINER -- tar -cf - /opt/gbo/" | \
|
||||||
|
zstd -3 -q | \
|
||||||
|
pv -s $SIZE | \
|
||||||
|
ssh administrator@63.141.255.9 "mkdir -p ~/gbo/$CONTAINER && zstd -d | tar -xf - -C ~/gbo/$CONTAINER/"
|
||||||
|
|
||||||
|
# Delete from source
|
||||||
|
echo -e "\e[1;33m[4/7] Deleting from source...\e[0m"
|
||||||
|
ssh root@82.29.59.188 "lxc delete $CONTAINER"
|
||||||
|
|
||||||
|
# Create container
|
||||||
|
echo -e "\e[1;33m[5/7] Creating Incus container...\e[0m"
|
||||||
|
ssh administrator@63.141.255.9 "sudo incus launch images:debian/12/cloud $CONTAINER && \
|
||||||
|
sudo incus config device add $CONTAINER eth0 nic name=eth0 network=PROD-GBO && \
|
||||||
|
sudo incus exec $CONTAINER -- useradd -m -s /bin/bash gbuser"
|
||||||
|
|
||||||
|
# Push data
|
||||||
|
echo -e "\e[1;33m[6/7] Pushing data...\e[0m"
|
||||||
|
ssh administrator@63.141.255.9 "sudo incus exec $CONTAINER -- mkdir -p /opt/gbo && \
|
||||||
|
sudo incus file push -r ~/gbo/$CONTAINER/* $CONTAINER:/opt/gbo/ && \
|
||||||
|
sudo incus exec $CONTAINER -- chown -R gbuser:gbuser /opt/gbo && \
|
||||||
|
sudo incus exec $CONTAINER -- chmod +x /opt/gbo/bin/*"
|
||||||
|
|
||||||
|
# Start
|
||||||
|
echo -e "\e[1;33m[7/7] Starting container...\e[0m"
|
||||||
|
ssh administrator@63.141.255.9 "sudo incus start $CONTAINER"
|
||||||
|
|
||||||
|
echo -e "\e[1;32m✓ Migration complete! Check with: incus list\e[0m"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Port Forwarding (iptables NAT) - Complete Rules
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Enable IP forwarding (persistent)
|
||||||
|
echo "net.ipv4.ip_forward = 1" | sudo tee /etc/sysctl.d/99-ipforward.conf
|
||||||
|
sudo sysctl -w net.ipv4.ip_forward=1
|
||||||
|
|
||||||
|
# Enable route_localnet
|
||||||
|
echo "net.ipv4.conf.all.route_localnet = 1" | sudo tee /etc/sysctl.d/99-localnet.conf
|
||||||
|
sudo sysctl -w net.ipv4.conf.all.route_localnet=1
|
||||||
|
|
||||||
|
# DNS (53)
|
||||||
|
sudo iptables -t nat -A PREROUTING -p udp --dport 53 -j DNAT --to-destination 10.107.115.155:53
|
||||||
|
sudo iptables -t nat -A PREROUTING -p tcp --dport 53 -j DNAT --to-destination 10.107.115.155:53
|
||||||
|
sudo iptables -t nat -A OUTPUT -p udp --dport 53 -j DNAT --to-destination 10.107.115.155:53
|
||||||
|
sudo iptables -t nat -A OUTPUT -p tcp --dport 53 -j DNAT --to-destination 10.107.115.155:53
|
||||||
|
|
||||||
|
# Email (25,465,587,993,995,143,110,4190)
|
||||||
|
sudo iptables -t nat -A PREROUTING -p tcp --dport 25 -j DNAT --to-destination 10.107.115.200:25
|
||||||
|
sudo iptables -t nat -A PREROUTING -p tcp --dport 465 -j DNAT --to-destination 10.107.115.200:465
|
||||||
|
sudo iptables -t nat -A PREROUTING -p tcp --dport 587 -j DNAT --to-destination 10.107.115.200:587
|
||||||
|
sudo iptables -t nat -A PREROUTING -p tcp --dport 993 -j DNAT --to-destination 10.107.115.200:993
|
||||||
|
sudo iptables -t nat -A PREROUTING -p tcp --dport 995 -j DNAT --to-destination 10.107.115.200:995
|
||||||
|
sudo iptables -t nat -A PREROUTING -p tcp --dport 143 -j DNAT --to-destination 10.107.115.200:143
|
||||||
|
sudo iptables -t nat -A PREROUTING -p tcp --dport 110 -j DNAT --to-destination 10.107.115.200:110
|
||||||
|
sudo iptables -t nat -A PREROUTING -p tcp --dport 4190 -j DNAT --to-destination 10.107.115.200:4190
|
||||||
|
sudo iptables -t nat -A OUTPUT -p tcp --dport 25 -j DNAT --to-destination 10.107.115.200:25
|
||||||
|
sudo iptables -t nat -A OUTPUT -p tcp --dport 465 -j DNAT --to-destination 10.107.115.200:465
|
||||||
|
sudo iptables -t nat -A OUTPUT -p tcp --dport 587 -j DNAT --to-destination 10.107.115.200:587
|
||||||
|
sudo iptables -t nat -A OUTPUT -p tcp --dport 993 -j DNAT --to-destination 10.107.115.200:993
|
||||||
|
sudo iptables -t nat -A OUTPUT -p tcp --dport 995 -j DNAT --to-destination 10.107.115.200:995
|
||||||
|
sudo iptables -t nat -A OUTPUT -p tcp --dport 143 -j DNAT --to-destination 10.107.115.200:143
|
||||||
|
sudo iptables -t nat -A OUTPUT -p tcp --dport 110 -j DNAT --to-destination 10.107.115.200:110
|
||||||
|
sudo iptables -t nat -A OUTPUT -p tcp --dport 4190 -j DNAT --to-destination 10.107.115.200:4190
|
||||||
|
|
||||||
|
# Webmail (5252)
|
||||||
|
sudo iptables -t nat -A PREROUTING -p tcp --dport 5252 -j DNAT --to-destination 10.107.115.208:5252
|
||||||
|
sudo iptables -t nat -A OUTPUT -p tcp --dport 5252 -j DNAT --to-destination 10.107.115.208:5252
|
||||||
|
|
||||||
|
# ALM (4747)
|
||||||
|
sudo iptables -t nat -A PREROUTING -p tcp --dport 4747 -j DNAT --to-destination 10.107.115.4:4747
|
||||||
|
sudo iptables -t nat -A OUTPUT -p tcp --dport 4747 -j DNAT --to-destination 10.107.115.4:4747
|
||||||
|
|
||||||
|
# Tables/PostgreSQL (5432)
|
||||||
|
sudo iptables -t nat -A PREROUTING -p tcp --dport 5432 -j DNAT --to-destination 10.107.115.33:5432
|
||||||
|
sudo iptables -t nat -A OUTPUT -p tcp --dport 5432 -j DNAT --to-destination 10.107.115.33:5432
|
||||||
|
|
||||||
|
# Proxy/Caddy (80, 443)
|
||||||
|
sudo iptables -t nat -A PREROUTING -p tcp --dport 80 -j DNAT --to-destination 10.107.115.236:80
|
||||||
|
sudo iptables -t nat -A PREROUTING -p tcp --dport 443 -j DNAT --to-destination 10.107.115.236:443
|
||||||
|
sudo iptables -t nat -A OUTPUT -p tcp --dport 80 -j DNAT --to-destination 10.107.115.236:80
|
||||||
|
sudo iptables -t nat -A OUTPUT -p tcp --dport 443 -j DNAT --to-destination 10.107.115.236:443
|
||||||
|
|
||||||
|
# FORWARD rules (allow traffic to containers)
|
||||||
|
sudo iptables -A FORWARD -p tcp -d 10.107.115.155 -j ACCEPT
|
||||||
|
sudo iptables -A FORWARD -p udp -d 10.107.115.155 -j ACCEPT
|
||||||
|
sudo iptables -A FORWARD -p tcp -d 10.107.115.200 -j ACCEPT
|
||||||
|
sudo iptables -A FORWARD -p tcp -s 10.107.115.200 -j ACCEPT
|
||||||
|
sudo iptables -A FORWARD -p tcp -d 10.107.115.208 -j ACCEPT
|
||||||
|
sudo iptables -A FORWARD -p tcp -d 10.107.115.4 -j ACCEPT
|
||||||
|
sudo iptables -A FORWARD -p tcp -d 10.107.115.33 -j ACCEPT
|
||||||
|
sudo iptables -A FORWARD -p tcp -d 10.107.115.236 -j ACCEPT
|
||||||
|
sudo iptables -A FORWARD -p tcp -s 10.107.115.236 -j ACCEPT
|
||||||
|
|
||||||
|
# POSTROUTING MASQUERADE (return traffic)
|
||||||
|
sudo iptables -t nat -A POSTROUTING -p tcp -d 10.107.115.155 -j MASQUERADE
|
||||||
|
sudo iptables -t nat -A POSTROUTING -p udp -d 10.107.115.155 -j MASQUERADE
|
||||||
|
sudo iptables -t nat -A POSTROUTING -p tcp -d 10.107.115.200 -j MASQUERADE
|
||||||
|
sudo iptables -t nat -A POSTROUTING -p tcp -d 10.107.115.208 -j MASQUERADE
|
||||||
|
sudo iptables -t nat -A POSTROUTING -p tcp -d 10.107.115.4 -j MASQUERADE
|
||||||
|
sudo iptables -t nat -A POSTROUTING -p tcp -d 10.107.115.33 -j MASQUERADE
|
||||||
|
sudo iptables -t nat -A POSTROUTING -p tcp -d 10.107.115.236 -j MASQUERADE
|
||||||
|
|
||||||
|
# Save rules
|
||||||
|
sudo sh -c 'iptables-save > /etc/iptables/rules.v4'
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Benefits of This Approach
|
||||||
|
|
||||||
|
✅ **FASTEST** - No intermediate tarballs, pure streaming
|
||||||
|
✅ **COLORFUL** - pv shows beautiful progress bars
|
||||||
|
✅ **EFFICIENT** - zstd compression (3-5x gzip)
|
||||||
|
✅ **SAFE** - One container at a time, verify before next
|
||||||
|
✅ **CLEAN** - Immediate deletion frees disk space
|
||||||
|
✅ **MONITORED** - Real-time transfer rate and ETA
|
||||||
|
✅ **PARALLEL** - Can migrate multiple containers at once
|
||||||
|
|
||||||
|
**Estimated time:**
|
||||||
|
- Small containers (<1GB): ~5-10 minutes each
|
||||||
|
- Medium containers (1-5GB): ~10-20 minutes each
|
||||||
|
- Large containers (>5GB): ~20-40 minutes each
|
||||||
|
- Total (parallel): ~1-2 hours for all containers
|
||||||
|
|
||||||
|
vs lxc export method: ~4-8 hours total
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. Install pv on source
|
||||||
|
ssh root@82.29.59.188 "apt install -y pv zstd"
|
||||||
|
|
||||||
|
# 2. Save migration scripts
|
||||||
|
# Copy migrate-one.sh and MIGRATE-ALL.sh
|
||||||
|
|
||||||
|
# 3. Run single test
|
||||||
|
./migrate-one.sh pragmatismo-alm
|
||||||
|
|
||||||
|
# 4. If successful, run all
|
||||||
|
./MIGRATE-ALL.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### pv not showing progress?
|
||||||
|
```bash
|
||||||
|
# Check pv is installed
|
||||||
|
pv --version
|
||||||
|
|
||||||
|
# Check size detection
|
||||||
|
ssh root@82.29.59.188 "lxc exec pragmatismo-alm -- du -sb /opt/gbo"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Container not starting?
|
||||||
|
```bash
|
||||||
|
# Check logs
|
||||||
|
sudo incus list
|
||||||
|
sudo incus info pragmatismo-alm
|
||||||
|
sudo incus logs pragmatismo-alm --show-log
|
||||||
|
|
||||||
|
# Check network
|
||||||
|
sudo incus exec pragmatismo-alm -- ip addr
|
||||||
|
```
|
||||||
|
|
||||||
|
### Service not running?
|
||||||
|
```bash
|
||||||
|
# Inside container
|
||||||
|
sudo incus exec pragmatismo-alm -- bash
|
||||||
|
systemctl status alm
|
||||||
|
journalctl -u alm -n 50
|
||||||
|
```
|
||||||
|
|
||||||
|
### NAT not working?
|
||||||
|
```bash
|
||||||
|
# Check rules
|
||||||
|
sudo iptables -t nat -L -n -v
|
||||||
|
|
||||||
|
# Check forwarding
|
||||||
|
sudo iptables -L FORWARD -n -v
|
||||||
|
|
||||||
|
# Test from host
|
||||||
|
nc -zv 127.0.0.1 4747
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
This plan achieves:
|
||||||
|
✅ **FASTEST possible migration** - streaming, no disk I/O
|
||||||
|
✅ **Colorful progress** - pv with ETA, rate, elapsed
|
||||||
|
✅ **Immediate space cleanup** - delete as we go
|
||||||
|
✅ **Parallel migration** - do multiple containers at once
|
||||||
|
✅ **Clean Incus** - fresh start, no LXC/LXD trash
|
||||||
|
✅ **Proper NAT** - iptables only, no socat/proxy devices
|
||||||
|
|
||||||
|
**No mbuffer needed** - pv does the job with colorful output!
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Verification Checklist
|
||||||
|
|
||||||
|
After each migration:
|
||||||
|
- [ ] Container running: `sudo incus list`
|
||||||
|
- [ ] Service active: `sudo incus exec $c -- systemctl status <service>`
|
||||||
|
- [ ] Data intact: `sudo incus exec $c -- ls -la /opt/gbo`
|
||||||
|
- [ ] Port accessible: `nc -zv 127.0.0.1 <port>`
|
||||||
|
- [ ] Source deleted: `ssh root@82.29.59.188 lxc list` (should not show container)
|
||||||
|
- [ ] NAT rules added: `sudo iptables -t nat -L -n | grep <ip>`
|
||||||
|
|
||||||
|
After all migrations:
|
||||||
|
- [ ] All containers running on Incus
|
||||||
|
- [ ] All services active
|
||||||
|
- [ ] All NAT rules configured
|
||||||
|
- [ ] External access works
|
||||||
|
- [ ] Source server: no containers left
|
||||||
|
|
@ -6,6 +6,33 @@ This document describes how to improve `installer.rs` to automate the deployment
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
# rust incus install base
|
||||||
|
sudo apt install -y curl gnupg
|
||||||
|
|
||||||
|
|
||||||
|
sudo mkdir -p /etc/apt/keyrings
|
||||||
|
|
||||||
|
|
||||||
|
sudo curl -fsSL https://pkgs.zabbly.com/key.asc -o /etc/apt/keyrings/zabbly.asc
|
||||||
|
|
||||||
|
|
||||||
|
sudo sh -c 'cat > /etc/apt/sources.list.d/zabbly-incus-stable.sources << EOF
|
||||||
|
Enabled: yes
|
||||||
|
Types: deb
|
||||||
|
URIs: https://pkgs.zabbly.com/incus/stable
|
||||||
|
Suites: bookworm
|
||||||
|
Components: main
|
||||||
|
Architectures: $(dpkg --print-architecture)
|
||||||
|
Signed-By: /etc/apt/keyrings/zabbly.asc
|
||||||
|
EOF'
|
||||||
|
|
||||||
|
|
||||||
|
sudo apt update
|
||||||
|
|
||||||
|
|
||||||
|
sudo apt install incus
|
||||||
|
|
||||||
|
|
||||||
## What Was Done Manually (Reference Implementation)
|
## What Was Done Manually (Reference Implementation)
|
||||||
|
|
||||||
### Migration Summary (pragmatismo tenant)
|
### Migration Summary (pragmatismo tenant)
|
||||||
|
|
@ -449,8 +476,6 @@ mail IN A <HOST_IP>
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Container Cleanup (BEFORE Setting Up NAT)
|
|
||||||
|
|
||||||
**ALWAYS remove socat and Incus proxy devices before configuring iptables NAT:**
|
**ALWAYS remove socat and Incus proxy devices before configuring iptables NAT:**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|
|
||||||
|
|
@ -1,11 +0,0 @@
|
||||||
#!/bin/bash
|
|
||||||
# fail2ban startup script for Incus containers
|
|
||||||
# Usage: Place in /opt/gbo/bin/ and run as root in container
|
|
||||||
|
|
||||||
LOGFILE=/opt/gbo/logs/fail2ban.log
|
|
||||||
|
|
||||||
mkdir -p /opt/gbo/logs
|
|
||||||
nohup /usr/bin/fail2ban-server -x -f > $LOGFILE 2>&1 &
|
|
||||||
sleep 2
|
|
||||||
fail2ban-client reload
|
|
||||||
echo "Fail2ban started - check status with: fail2ban-client status"
|
|
||||||
|
|
@ -1,15 +0,0 @@
|
||||||
# Production Smoke Test Plan
|
|
||||||
|
|
||||||
## Status: Caddy running ✅
|
|
||||||
|
|
||||||
## URLs to Test (YOLO)
|
|
||||||
1. https://webmail.pragmatismo.com.br/
|
|
||||||
2. https://chat.pragmatismo.com.br/cristo
|
|
||||||
3. https://alm.pragmatismo.com.br/
|
|
||||||
4. https://tables.pragmatismo.com.br/
|
|
||||||
|
|
||||||
## Steps
|
|
||||||
1. Navigate to each URL
|
|
||||||
2. Take screenshot
|
|
||||||
3. Check for errors in console/network
|
|
||||||
4. Report pass/fail per URL
|
|
||||||
|
|
@ -1,18 +0,0 @@
|
||||||
# LXC to Incus Migration Script
|
|
||||||
|
|
||||||
```bash
|
|
||||||
#!/bin/bash
|
|
||||||
# Export all LXC containers and transfer to destination
|
|
||||||
|
|
||||||
containers="pragmatismo-alm pragmatismo-alm-ci pragmatismo-dns pragmatismo-drive pragmatismo-email pragmatismo-proxy pragmatismo-system pragmatismo-table-editor pragmatismo-tables pragmatismo-webmail"
|
|
||||||
|
|
||||||
for name in $containers; do
|
|
||||||
echo "=== Migrating $name ==="
|
|
||||||
ssh root@pragmatismo.com.br "lxc export $name /tmp/$name.tar.gz"
|
|
||||||
scp root@pragmatismo.com.br:/tmp/$name.tar.gz administrator@63.141.255.9:~/
|
|
||||||
ssh root@pragmatismo.com.br "rm /tmp/$name.tar.gz"
|
|
||||||
echo "✓ $name transferred"
|
|
||||||
done
|
|
||||||
|
|
||||||
echo "Migration complete. All .tar.gz files in ~/ on destination."
|
|
||||||
```
|
|
||||||
244
prompts/win.md
244
prompts/win.md
|
|
@ -1,244 +0,0 @@
|
||||||
# General Bots — Plano de Execução no Windows
|
|
||||||
|
|
||||||
## Pré-requisitos
|
|
||||||
|
|
||||||
- Windows 10/11 (x64)
|
|
||||||
- Visual Studio Build Tools (com C++ workload) ou Visual Studio
|
|
||||||
- Rust toolchain (`rustup` com `stable-x86_64-pc-windows-msvc`)
|
|
||||||
- Git para Windows
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 1. Dependências de Build
|
|
||||||
|
|
||||||
### 1.1 PostgreSQL (libpq.lib para Diesel ORM)
|
|
||||||
|
|
||||||
O BotServer usa Diesel com backend Postgres. No Windows, o linker precisa de `libpq.lib`.
|
|
||||||
|
|
||||||
```powershell
|
|
||||||
# Baixar binários do PostgreSQL (não precisa instalar o serviço)
|
|
||||||
Invoke-WebRequest -Uri "https://get.enterprisedb.com/postgresql/postgresql-17.4-1-windows-x64-binaries.zip" -OutFile "$env:TEMP\pgsql.zip" -UseBasicParsing
|
|
||||||
|
|
||||||
# Extrair para C:\pgsql
|
|
||||||
Expand-Archive -Path "$env:TEMP\pgsql.zip" -DestinationPath "C:\pgsql" -Force
|
|
||||||
|
|
||||||
# Configurar variável de ambiente permanente
|
|
||||||
[System.Environment]::SetEnvironmentVariable("PQ_LIB_DIR", "C:\pgsql\pgsql\lib", "User")
|
|
||||||
$env:PQ_LIB_DIR = "C:\pgsql\pgsql\lib"
|
|
||||||
$env:PATH = "C:\pgsql\pgsql\bin;$env:PATH"
|
|
||||||
```
|
|
||||||
|
|
||||||
Ou simplesmente execute:
|
|
||||||
```powershell
|
|
||||||
.\DEPENDENCIES.ps1
|
|
||||||
```
|
|
||||||
|
|
||||||
### 1.2 sccache (opcional)
|
|
||||||
|
|
||||||
O `.cargo/config.toml` referencia `sccache`. Se não estiver instalado, comente a linha:
|
|
||||||
```toml
|
|
||||||
# [build]
|
|
||||||
# rustc-wrapper = "sccache"
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 2. Compilação
|
|
||||||
|
|
||||||
```powershell
|
|
||||||
# Garantir que PQ_LIB_DIR está configurado
|
|
||||||
$env:PQ_LIB_DIR = "C:\pgsql\pgsql\lib"
|
|
||||||
|
|
||||||
# Build do botserver
|
|
||||||
cargo build -p botserver
|
|
||||||
|
|
||||||
# Build do botui (interface desktop Tauri)
|
|
||||||
cargo build -p botui
|
|
||||||
```
|
|
||||||
|
|
||||||
### Correções de compilação aplicadas
|
|
||||||
|
|
||||||
| Arquivo | Problema | Correção |
|
|
||||||
|---------|----------|----------|
|
|
||||||
| `bootstrap.rs` | `use std::os::unix::fs::PermissionsExt` | Envolvido com `#[cfg(unix)]` |
|
|
||||||
| `antivirus.rs` | `error!` macro não importada | Adicionado `use tracing::error` |
|
|
||||||
| `installer.rs` | `check_root()` só existe no Unix | Adicionado `check_admin()` para Windows com `#[cfg(windows)]` |
|
|
||||||
| `installer.rs` | `SUDOERS_CONTENT` não usado no Windows | Envolvido com `#[cfg(not(windows))]` |
|
|
||||||
| `facade.rs` | `make_executable` param `path` não usado | Renomeado para `_path` no Windows |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 3. Compatibilidade de Runtime (3rdparty.toml)
|
|
||||||
|
|
||||||
O `3rdparty.toml` agora tem entradas `_windows` para cada componente:
|
|
||||||
|
|
||||||
| Componente | Linux | Windows |
|
|
||||||
|------------|-------|---------|
|
|
||||||
| `drive` | `minio` (linux-amd64) | `minio.exe` (windows-amd64) |
|
|
||||||
| `tables` | `postgresql-*-linux-gnu.tar.gz` | `postgresql-*-windows-x64-binaries.zip` |
|
|
||||||
| `cache` | `valkey-*-jammy-x86_64.tar.gz` | `memurai-developer.msi` |
|
|
||||||
| `llm` | `llama-*-ubuntu-x64.zip` | `llama-*-win-cpu-x64.zip` (ou CUDA/Vulkan) |
|
|
||||||
| `email` | `stalwart-mail-*-linux.tar.gz` | `stalwart-mail-*-windows.zip` |
|
|
||||||
| `proxy` | `caddy_*_linux_amd64.tar.gz` | `caddy_*_windows_amd64.zip` |
|
|
||||||
| `directory` | `zitadel-linux-amd64.tar.gz` | `zitadel-windows-amd64.zip` |
|
|
||||||
| `alm` | `forgejo-*-linux-amd64` | `forgejo-*-windows-amd64.exe` |
|
|
||||||
| `alm_ci` | `forgejo-runner-*-linux-amd64` | `forgejo-runner-*-windows-amd64.exe` |
|
|
||||||
| `dns` | `coredns_*_linux_amd64.tgz` | `coredns_*_windows_amd64.tgz` |
|
|
||||||
| `meet` | `livekit_*_linux_amd64.tar.gz` | `livekit_*_windows_amd64.zip` |
|
|
||||||
| `table_editor` | `nocodb-linux-x64` | `nocodb-win-x64.exe` |
|
|
||||||
| `vector_db` | `qdrant-*-linux-gnu.tar.gz` | `qdrant-*-windows-msvc.zip` |
|
|
||||||
| `timeseries_db` | `influxdb2-*-linux-amd64.tar.gz` | `influxdb2-*-windows-amd64.zip` |
|
|
||||||
| `vault` | `vault_*_linux_amd64.zip` | `vault_*_windows_amd64.zip` |
|
|
||||||
| `observability` | `vector-*-linux-gnu.tar.gz` | `vector-*-windows-msvc.zip` |
|
|
||||||
|
|
||||||
A seleção é automática via `get_component_url()` em `installer.rs` que busca `{name}_windows` primeiro.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 4. Adaptações de Código para Windows
|
|
||||||
|
|
||||||
### 4.1 Execução de Comandos Shell
|
|
||||||
|
|
||||||
| Contexto | Linux | Windows |
|
|
||||||
|----------|-------|---------|
|
|
||||||
| `run_commands_with_password()` | `bash -c "{cmd}"` | `powershell -NoProfile -Command "{cmd}"` |
|
|
||||||
| `start()` (spawnar processos) | `sh -c "{exec_cmd}"` | `powershell -NoProfile -Command "{exec_cmd}"` |
|
|
||||||
| `safe_sh_command()` | `sh -c` | `powershell -NoProfile -Command` |
|
|
||||||
|
|
||||||
### 4.2 Gerenciamento de Processos
|
|
||||||
|
|
||||||
| Ação | Linux | Windows |
|
|
||||||
|------|-------|---------|
|
|
||||||
| Matar processos | `pkill -9 -f {name}` | `taskkill /F /IM {name}*` |
|
|
||||||
| Verificar processo | `pgrep -f {name}` | `Get-Process \| Where-Object { $_.ProcessName -like '*{name}*' }` |
|
|
||||||
|
|
||||||
### 4.3 Health Checks (fallback quando curl não está disponível)
|
|
||||||
|
|
||||||
| Check | Linux | Windows |
|
|
||||||
|-------|-------|---------|
|
|
||||||
| Porta aberta | `nc -z -w 1 127.0.0.1 {port}` | `(Test-NetConnection -ComputerName 127.0.0.1 -Port {port}).TcpTestSucceeded` |
|
|
||||||
|
|
||||||
### 4.4 Extração de Arquivos
|
|
||||||
|
|
||||||
| Formato | Linux | Windows |
|
|
||||||
|---------|-------|---------|
|
|
||||||
| `.zip` | `unzip -o -q {file}` | `Expand-Archive -Path '{file}' -DestinationPath '{dest}' -Force` |
|
|
||||||
| `.tar.gz` | `tar -xzf {file}` | `tar -xzf {file}` (Windows 10+ tem tar nativo) |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 5. Execução
|
|
||||||
|
|
||||||
### 5.1 Modo CLI (gerenciar componentes)
|
|
||||||
```powershell
|
|
||||||
$env:PQ_LIB_DIR = "C:\pgsql\pgsql\lib"
|
|
||||||
$env:PATH = "C:\pgsql\pgsql\bin;$env:PATH"
|
|
||||||
|
|
||||||
# Instalar um componente específico
|
|
||||||
.\target\debug\botserver.exe install vault
|
|
||||||
|
|
||||||
# Listar componentes
|
|
||||||
.\target\debug\botserver.exe list
|
|
||||||
|
|
||||||
# Iniciar componentes
|
|
||||||
.\target\debug\botserver.exe start
|
|
||||||
|
|
||||||
# Ver status
|
|
||||||
.\target\debug\botserver.exe status
|
|
||||||
```
|
|
||||||
|
|
||||||
### 5.2 Modo Servidor (bootstrap completo + HTTP)
|
|
||||||
```powershell
|
|
||||||
$env:PQ_LIB_DIR = "C:\pgsql\pgsql\lib"
|
|
||||||
$env:PATH = "C:\pgsql\pgsql\bin;$env:PATH"
|
|
||||||
|
|
||||||
# Executa bootstrap (baixa/instala todos os componentes) + inicia servidor HTTP
|
|
||||||
.\target\debug\botserver.exe
|
|
||||||
```
|
|
||||||
|
|
||||||
O bootstrap automático:
|
|
||||||
1. Baixa e instala Vault, PostgreSQL, Valkey, MinIO, Zitadel, LLM, etc.
|
|
||||||
2. Gera certificados TLS
|
|
||||||
3. Inicializa o banco de dados
|
|
||||||
4. Inicia o servidor HTTP na porta **5858**
|
|
||||||
|
|
||||||
Acesse: **http://localhost:5858**
|
|
||||||
|
|
||||||
### 5.3 Via restart.ps1
|
|
||||||
```powershell
|
|
||||||
.\restart.ps1
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 6. Detecção Automática de GPU (LLM)
|
|
||||||
|
|
||||||
O sistema detecta automaticamente:
|
|
||||||
- **CUDA 13.x** → `llama-*-win-cuda-13.1-x64.zip`
|
|
||||||
- **CUDA 12.x** → `llama-*-win-cuda-12.4-x64.zip`
|
|
||||||
- **Vulkan SDK** → `llama-*-win-vulkan-x64.zip`
|
|
||||||
- **CPU only** → `llama-*-win-cpu-x64.zip`
|
|
||||||
- **ARM64** → `llama-*-win-cpu-arm64.zip`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 7. Estrutura de Diretórios
|
|
||||||
|
|
||||||
```
|
|
||||||
botserver-stack/
|
|
||||||
├── bin/ # Binários dos componentes
|
|
||||||
│ ├── vault/
|
|
||||||
│ ├── tables/ # PostgreSQL
|
|
||||||
│ ├── cache/ # Valkey/Memurai
|
|
||||||
│ ├── drive/ # MinIO
|
|
||||||
│ ├── directory/ # Zitadel
|
|
||||||
│ ├── llm/ # llama.cpp
|
|
||||||
│ └── ...
|
|
||||||
├── conf/ # Configurações
|
|
||||||
│ ├── vault/
|
|
||||||
│ ├── system/certificates/
|
|
||||||
│ └── ...
|
|
||||||
├── data/ # Dados persistentes
|
|
||||||
│ ├── vault/
|
|
||||||
│ ├── tables/pgdata/
|
|
||||||
│ └── ...
|
|
||||||
└── logs/ # Logs de cada componente
|
|
||||||
├── vault/
|
|
||||||
├── tables/
|
|
||||||
└── ...
|
|
||||||
|
|
||||||
botserver-installers/ # Cache de downloads (reutilizado)
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 8. Troubleshooting
|
|
||||||
|
|
||||||
### Erro: `LNK1181: libpq.lib não encontrado`
|
|
||||||
```powershell
|
|
||||||
$env:PQ_LIB_DIR = "C:\pgsql\pgsql\lib"
|
|
||||||
# Ou execute .\DEPENDENCIES.ps1
|
|
||||||
```
|
|
||||||
|
|
||||||
### Erro: `sccache não encontrado`
|
|
||||||
Comente no `.cargo/config.toml`:
|
|
||||||
```toml
|
|
||||||
# [build]
|
|
||||||
# rustc-wrapper = "sccache"
|
|
||||||
```
|
|
||||||
|
|
||||||
### Erro: `Path traversal detected`
|
|
||||||
Limpe o cache e recompile:
|
|
||||||
```powershell
|
|
||||||
Remove-Item -Path ".\botserver-stack" -Recurse -Force
|
|
||||||
Remove-Item -Path ".\botserver-installers" -Recurse -Force
|
|
||||||
cargo clean -p botserver
|
|
||||||
cargo build -p botserver
|
|
||||||
```
|
|
||||||
|
|
||||||
### Componentes baixam versão Linux
|
|
||||||
Recompile o botserver para que o `3rdparty.toml` embutido seja atualizado:
|
|
||||||
```powershell
|
|
||||||
cargo clean -p botserver
|
|
||||||
cargo build -p botserver
|
|
||||||
```
|
|
||||||
|
|
@ -1,242 +0,0 @@
|
||||||
# BASIC Workflow Engine Plan
|
|
||||||
|
|
||||||
## Current State
|
|
||||||
|
|
||||||
- `workflow_executions` and `workflow_events` tables exist in DB
|
|
||||||
- `WorkflowExecution` / `WorkflowEvent` models exist in `core/shared/models/workflow_models.rs`
|
|
||||||
- `ORCHESTRATE WORKFLOW` keyword exists in `basic/keywords/orchestration.rs` (stub)
|
|
||||||
- `STEP` keyword registered but not durable
|
|
||||||
- Compiler (`basic/mod.rs`) produces Rhai AST and runs it in one shot via `engine.eval_ast_with_scope`
|
|
||||||
- `HEAR` currently blocks a thread (new) — works but not crash-safe
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Goal
|
|
||||||
|
|
||||||
BASIC scripts run as **durable step sequences**. Each keyword is a step. On crash/restart, execution resumes from the last completed step. No re-run. No Rhai for control flow.
|
|
||||||
|
|
||||||
```basic
|
|
||||||
' ticket.bas
|
|
||||||
TALK "Describe the issue" ← Step 1
|
|
||||||
HEAR description ← Step 2 (suspends, waits)
|
|
||||||
SET ticket = CREATE(description) ← Step 3
|
|
||||||
TALK "Ticket #{ticket} created" ← Step 4
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Two Execution Modes
|
|
||||||
|
|
||||||
The compiler serves both modes via a pragma at the top of the `.bas` file:
|
|
||||||
|
|
||||||
```basic
|
|
||||||
' Default: Rhai mode (current behavior, fast, no durability)
|
|
||||||
TALK "Hello"
|
|
||||||
|
|
||||||
' Workflow mode (durable, crash-safe)
|
|
||||||
#workflow
|
|
||||||
TALK "Hello"
|
|
||||||
HEAR name
|
|
||||||
```
|
|
||||||
|
|
||||||
`ScriptService::compile()` detects `#workflow` and returns either:
|
|
||||||
- `ExecutionPlan::Rhai(AST)` — current path, unchanged
|
|
||||||
- `ExecutionPlan::Workflow(Vec<Step>)` — new path
|
|
||||||
|
|
||||||
`ScriptService::run()` dispatches accordingly.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Architecture
|
|
||||||
|
|
||||||
### 1. Compiler changes (`basic/mod.rs`, `basic/compiler/`)
|
|
||||||
|
|
||||||
Add `compile_to_steps(script: &str) -> Result<Vec<Step>>`:
|
|
||||||
|
|
||||||
```rust
|
|
||||||
pub enum Step {
|
|
||||||
Talk { template: String },
|
|
||||||
Hear { variable: String, input_type: String },
|
|
||||||
Set { variable: String, expression: String },
|
|
||||||
If { condition: String, then_steps: Vec<Step>, else_steps: Vec<Step> },
|
|
||||||
Call { function: String, args: Vec<String>, result_var: Option<String> },
|
|
||||||
// ... one variant per keyword
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Expressions inside steps (`condition`, `expression`, `template`) are still evaluated by Rhai — but only as **pure expression evaluator**, no custom syntax, no side effects. This keeps Rhai as a math/string engine only.
|
|
||||||
|
|
||||||
### 2. WorkflowEngine (`basic/workflow/engine.rs`)
|
|
||||||
|
|
||||||
```rust
|
|
||||||
pub struct WorkflowEngine {
|
|
||||||
state: Arc<AppState>,
|
|
||||||
session: UserSession,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl WorkflowEngine {
|
|
||||||
/// Start a new workflow or resume existing one for this session+script
|
|
||||||
pub async fn run(&self, script_path: &str, steps: Vec<Step>) -> Result<()>
|
|
||||||
|
|
||||||
/// Execute one step, persist result, return next action
|
|
||||||
async fn execute_step(&self, exec_id: Uuid, step: &Step, vars: &mut Variables) -> StepResult
|
|
||||||
|
|
||||||
/// Load execution state from DB
|
|
||||||
async fn load_state(&self, exec_id: Uuid) -> (usize, Variables)
|
|
||||||
|
|
||||||
/// Persist step completion
|
|
||||||
async fn save_state(&self, exec_id: Uuid, step_index: usize, vars: &Variables, status: &str)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub enum StepResult {
|
|
||||||
Continue, // go to next step
|
|
||||||
Suspend, // HEAR — save state, return, wait for next message
|
|
||||||
Done, // script finished
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. HEAR in workflow mode
|
|
||||||
|
|
||||||
No thread blocking. Instead:
|
|
||||||
|
|
||||||
1. `execute_step(Hear)` saves state to `workflow_executions` with `status = "waiting"`, `current_step = N`
|
|
||||||
2. Returns `StepResult::Suspend` → engine returns to caller
|
|
||||||
3. Next user message → `stream_response` checks `workflow_executions` for `session_id` with `status = "waiting"`
|
|
||||||
4. Loads variables, sets `variables["description"] = user_input`, advances `current_step`, resumes
|
|
||||||
|
|
||||||
### 4. `stream_response` dispatch (`core/bot/mod.rs`)
|
|
||||||
|
|
||||||
```rust
|
|
||||||
// At top of stream_response, before LLM:
|
|
||||||
if let Some(exec) = WorkflowEngine::find_waiting(state, session_id).await {
|
|
||||||
WorkflowEngine::resume(state, exec, message_content).await?;
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 5. DB schema (already exists, minor additions)
|
|
||||||
|
|
||||||
```sql
|
|
||||||
-- Already exists:
|
|
||||||
workflow_executions (id, bot_id, workflow_name, current_step, state_json, status, ...)
|
|
||||||
|
|
||||||
-- Add:
|
|
||||||
ALTER TABLE workflow_executions ADD COLUMN session_id UUID;
|
|
||||||
ALTER TABLE workflow_executions ADD COLUMN script_path TEXT;
|
|
||||||
-- state_json stores: { "variables": {...}, "step_index": N }
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Migration Path
|
|
||||||
|
|
||||||
### Phase 1 — Parallel mode (no breaking changes)
|
|
||||||
- Add `compile_to_steps()` alongside existing `compile()`
|
|
||||||
- Add `WorkflowEngine` as new struct
|
|
||||||
- `#workflow` pragma routes to new path
|
|
||||||
- All existing `.bas` files unchanged, run via Rhai as before
|
|
||||||
|
|
||||||
### Phase 2 — Keyword parity
|
|
||||||
Implement step variants for all keywords used in practice:
|
|
||||||
`TALK`, `HEAR`, `SET`, `IF/ELSE/END IF`, `CALL` (HTTP, LLM, tool), `SEND MAIL`, `SCHEDULE`
|
|
||||||
|
|
||||||
### Phase 3 — Default for new scripts
|
|
||||||
New `.bas` files default to workflow mode. Rhai mode kept for backwards compat and tool scripts (short-lived, no HEAR).
|
|
||||||
|
|
||||||
### Phase 4 — Rhai scope reduction
|
|
||||||
Remove Rhai custom syntax registrations. Keep Rhai only as expression evaluator:
|
|
||||||
```rust
|
|
||||||
engine.eval_expression::<Dynamic>(&expr, &scope)
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## File Map
|
|
||||||
|
|
||||||
```
|
|
||||||
basic/
|
|
||||||
mod.rs ← add compile_to_steps(), ExecutionPlan enum
|
|
||||||
compiler/
|
|
||||||
mod.rs ← existing Rhai compiler, unchanged
|
|
||||||
step_compiler.rs ← NEW: BASIC → Vec<Step>
|
|
||||||
workflow/
|
|
||||||
mod.rs ← NEW: WorkflowEngine
|
|
||||||
engine.rs ← NEW: execute_step, load/save state
|
|
||||||
variables.rs ← NEW: Variables (HashMap<String, Dynamic>)
|
|
||||||
steps.rs ← NEW: Step enum
|
|
||||||
keywords/ ← existing, unchanged in Phase 1
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Keyword Compatibility
|
|
||||||
|
|
||||||
### Category A — Workflow steps (implement as `Step` variants)
|
|
||||||
`TALK`, `HEAR`, `SET`, `IF/ELSE/END IF`, `SEND MAIL`, `SEND TEMPLATE`, `SCHEDULE`,
|
|
||||||
`SAVE`/`INSERT`/`UPDATE`, `GET`, `FIND`, `SEARCH`, `USE KB`, `USE TOOL`, `REMEMBER`,
|
|
||||||
`HTTP GET/POST/PUT/DELETE`, `WAIT`, `TRANSFER TO HUMAN`, `CREATE TASK`, `BOOK`, `SCORE LEAD`
|
|
||||||
|
|
||||||
### Category B — Pure expressions (Rhai as calculator, no step boundary)
|
|
||||||
`math/*`, `datetime/*`, `string_functions`, `arrays/*`, `core_functions`, `validation/*`, `FORMAT`
|
|
||||||
→ Stored as expression strings in Step, evaluated at runtime via `engine.eval_expression_with_scope()`
|
|
||||||
|
|
||||||
### Category C — Rhai-only (scripts using these stay in Rhai mode, no `#workflow`)
|
|
||||||
`code_sandbox`, `use_website`, `face_api`, `on_change`, `on_email`, `webhook`,
|
|
||||||
`procedures` (FUNCTION/SUB/CALL), `for_next` (FOR EACH loops), `switch_case`, `events`, `orchestration`
|
|
||||||
|
|
||||||
A script with any Category C keyword cannot use `#workflow`. The compiler detects this and errors early.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## How Compilation Works Without Rhai
|
|
||||||
|
|
||||||
Workflow compiler is a **line-by-line parser**, not a Rhai AST walk:
|
|
||||||
|
|
||||||
```
|
|
||||||
Input line → Step variant
|
|
||||||
─────────────────────────────────────────────────────
|
|
||||||
TALK "Hello ${name}" → Step::Talk { template }
|
|
||||||
HEAR description → Step::Hear { var, input_type }
|
|
||||||
SET x = score + 1 → Step::Set { var, expr: "score + 1" }
|
|
||||||
IF score > 10 THEN → Step::If { cond: "score > 10", then_steps, else_steps }
|
|
||||||
SEND MAIL to, s, b → Step::SendMail { to, subject, body }
|
|
||||||
USE TOOL path → Step::UseTool { path, args }
|
|
||||||
```
|
|
||||||
|
|
||||||
Expressions (`score + 1`, `score > 10`) are stored as **raw strings** in the Step struct.
|
|
||||||
At runtime, Rhai evaluates them as pure expressions — no custom syntax, no side effects:
|
|
||||||
|
|
||||||
```rust
|
|
||||||
let mut engine = Engine::new(); // no register_custom_syntax calls
|
|
||||||
let mut scope = Scope::new();
|
|
||||||
for (k, v) in &variables { scope.push_dynamic(k, v.clone()); }
|
|
||||||
let result = engine.eval_expression_with_scope::<Dynamic>(&mut scope, expr)?;
|
|
||||||
```
|
|
||||||
|
|
||||||
Rhai remains a dependency but is used only as a math/string expression evaluator (~5 lines of code at runtime). All custom keyword machinery is bypassed entirely.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
| Engine | Lang | Latency | RAM | Rust SDK | Verdict |
|
|
||||||
|--------|------|---------|-----|----------|---------|
|
|
||||||
| **Custom (this plan)** | Rust | ~1ms | 0 extra | Native | ✅ Best fit |
|
|
||||||
| **[Restate](https://restate.dev)** | Rust server | ~5ms | ~50MB | ✅ official | Fallback option |
|
|
||||||
| **[Rhythm](https://github.com/maxnorth/rhythm)** | Rust | ~2ms | ~10MB | Native | Experimental |
|
|
||||||
| **Temporal** | Go+Java | ~20ms | ~500MB | ❌ | Too heavy |
|
|
||||||
| **Windmill** | Rust+TS | ~10ms | ~200MB | ❌ | Wrong abstraction |
|
|
||||||
|
|
||||||
**Why custom over Restate:** Restate requires its own server as a proxy between HTTP requests and handlers — adds a network hop and an extra process. The custom plan uses PostgreSQL already running in the stack, zero extra infrastructure.
|
|
||||||
|
|
||||||
**Escape hatch:** The `Step` enum in this plan maps 1:1 to Restate workflow steps. If the custom engine proves too complex to maintain, migration to Restate is mechanical — swap `WorkflowEngine::execute_step` internals, keep the compiler and Step enum unchanged.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
- **No re-run ever.** Steps before current_step are skipped on resume.
|
|
||||||
- **Rhai never removed entirely** — used for expression eval only.
|
|
||||||
- **Backwards compatible** — no `#workflow` = Rhai mode, existing bots unaffected.
|
|
||||||
- **HEAR in workflow mode = zero threads held.** State in DB, not RAM.
|
|
||||||
- **Tool scripts** (called by LLM) stay in Rhai mode — they're short-lived, no HEAR needed.
|
|
||||||
23
restart.sh
23
restart.sh
|
|
@ -4,21 +4,38 @@ set -e
|
||||||
echo "Stopping..."
|
echo "Stopping..."
|
||||||
pkill -f botserver || true
|
pkill -f botserver || true
|
||||||
pkill -f botui || true
|
pkill -f botui || true
|
||||||
|
pkill -f botmodels || true
|
||||||
pkill -f rustc || true
|
pkill -f rustc || true
|
||||||
|
|
||||||
echo "Cleaning..."
|
echo "Cleaning..."
|
||||||
rm -f botserver.log botui.log
|
rm -f botserver.log botui.log botmodels.log
|
||||||
|
|
||||||
echo "Building..."
|
echo "Building..."
|
||||||
cargo build -p botserver
|
cargo build -p botserver
|
||||||
cargo build -p botui
|
cargo build -p botui
|
||||||
|
|
||||||
|
echo "Starting botmodels..."
|
||||||
|
cd botmodels
|
||||||
|
source venv/bin/activate
|
||||||
|
uvicorn src.main:app --host 0.0.0.0 --port 8085 > ../botmodels.log 2>&1 &
|
||||||
|
echo " PID: $!"
|
||||||
|
cd ..
|
||||||
|
|
||||||
|
echo "Waiting for botmodels..."
|
||||||
|
for i in $(seq 1 30); do
|
||||||
|
if curl -s http://localhost:8085/api/health > /dev/null 2>&1; then
|
||||||
|
echo " botmodels ready"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
|
|
||||||
echo "Starting botserver..."
|
echo "Starting botserver..."
|
||||||
RUST_LOG=debug ./target/debug/botserver --noconsole > botserver.log 2>&1 &
|
BOTMODELS_HOST="http://localhost:8085" BOTMODELS_API_KEY="starter" RUST_LOG=debug ./target/debug/botserver --noconsole > botserver.log 2>&1 &
|
||||||
echo " PID: $!"
|
echo " PID: $!"
|
||||||
|
|
||||||
echo "Starting botui..."
|
echo "Starting botui..."
|
||||||
BOTSERVER_URL="http://localhost:8080" ./target/debug/botui > botui.log 2>&1 &
|
BOTSERVER_URL="http://localhost:8080" ./target/debug/botui > botui.log 2>&1 &
|
||||||
echo " PID: $!"
|
echo " PID: $!"
|
||||||
|
|
||||||
echo "Done. Logs: tail -f botserver.log botui.log"
|
echo "Done. Logs: tail -f botserver.log botui.log botmodels.log"
|
||||||
|
|
|
||||||
154
usekb2.md
Normal file
154
usekb2.md
Normal file
|
|
@ -0,0 +1,154 @@
|
||||||
|
# USE KB 2.0: Group-Based Knowledge Base Access
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
Modify the USE KB keyword to respect user group permissions, ensuring that THINK KB queries only return answers from knowledge base folders that belong to groups the logged-in user is a member of.
|
||||||
|
|
||||||
|
## Current Architecture
|
||||||
|
|
||||||
|
### USE KB Flow
|
||||||
|
1. User executes `USE KB "kb_name"` in BASIC script
|
||||||
|
2. `use_kb.rs:add_kb_to_session()` checks if KB exists in `kb_collections`
|
||||||
|
3. Creates default KB entry if not found
|
||||||
|
4. Adds association to `session_kb_associations` table
|
||||||
|
5. KB becomes active for the session
|
||||||
|
|
||||||
|
### THINK KB Flow
|
||||||
|
1. User executes `THINK KB "query"`
|
||||||
|
2. `think_kb.rs:think_kb_search()` gets all active KBs from `session_kb_associations`
|
||||||
|
3. For each active KB, calls `KnowledgeBaseManager.search()` on its Qdrant collection
|
||||||
|
4. Returns combined results from all active KBs
|
||||||
|
|
||||||
|
### Group System
|
||||||
|
- Groups stored in `rbac_groups` table
|
||||||
|
- User membership in `rbac_user_groups` table
|
||||||
|
- Group permissions via `rbac_group_roles` table
|
||||||
|
|
||||||
|
## Proposed Changes
|
||||||
|
|
||||||
|
### 1. Database Schema Changes
|
||||||
|
|
||||||
|
Add new table `kb_group_associations`:
|
||||||
|
|
||||||
|
```sql
|
||||||
|
CREATE TABLE kb_group_associations (
|
||||||
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
|
kb_id UUID NOT NULL REFERENCES kb_collections(id) ON DELETE CASCADE,
|
||||||
|
group_id UUID NOT NULL REFERENCES rbac_groups(id) ON DELETE CASCADE,
|
||||||
|
granted_by UUID REFERENCES users(id) ON DELETE SET NULL,
|
||||||
|
granted_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||||
|
UNIQUE(kb_id, group_id)
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
Migration file: `botserver/migrations/6.2.0-01-kb-groups/up.sql`
|
||||||
|
|
||||||
|
### 2. Backend Logic Changes
|
||||||
|
|
||||||
|
#### Modify `think_kb_search()` in `think_kb.rs`
|
||||||
|
- Add user group lookup before searching
|
||||||
|
- Filter active KBs to only those accessible by user's groups
|
||||||
|
- Allow access if KB has no group associations (public KBs) OR user is in associated groups
|
||||||
|
|
||||||
|
```rust
|
||||||
|
async fn think_kb_search(
|
||||||
|
kb_manager: Arc<KnowledgeBaseManager>,
|
||||||
|
db_pool: DbPool,
|
||||||
|
session_id: Uuid,
|
||||||
|
bot_id: Uuid,
|
||||||
|
user_id: Uuid, // Add user_id parameter
|
||||||
|
query: &str,
|
||||||
|
) -> Result<serde_json::Value, String> {
|
||||||
|
// Get user's groups
|
||||||
|
let user_groups = get_user_groups(&db_pool, user_id)?;
|
||||||
|
|
||||||
|
// Get active KBs filtered by groups
|
||||||
|
let accessible_kbs = get_accessible_kbs_for_session(&db_pool, session_id, &user_groups)?;
|
||||||
|
|
||||||
|
// Search only accessible KBs
|
||||||
|
// ... rest of search logic
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Add `get_accessible_kbs_for_session()` function
|
||||||
|
```rust
|
||||||
|
fn get_accessible_kbs_for_session(
|
||||||
|
conn_pool: &DbPool,
|
||||||
|
session_id: Uuid,
|
||||||
|
user_groups: &[String],
|
||||||
|
) -> Result<Vec<(String, String, String)>, String> {
|
||||||
|
// Query that joins session_kb_associations with kb_group_associations
|
||||||
|
// Returns KBs where group_id IS NULL (public) OR group_id IN user_groups
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Modify `add_kb_to_session()` in `use_kb.rs`
|
||||||
|
- Add optional group access check
|
||||||
|
- Allow USE KB if user has access to the KB's groups
|
||||||
|
|
||||||
|
### 3. API Changes
|
||||||
|
|
||||||
|
Add new endpoints in `rbac.rs` for KB-group management:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// Assign KB to group
|
||||||
|
POST /api/rbac/kbs/{kb_id}/groups/{group_id}
|
||||||
|
|
||||||
|
// Remove KB from group
|
||||||
|
DELETE /api/rbac/kbs/{kb_id}/groups/{group_id}
|
||||||
|
|
||||||
|
// Get groups for KB
|
||||||
|
GET /api/rbac/kbs/{kb_id}/groups
|
||||||
|
|
||||||
|
// Get KBs accessible by user
|
||||||
|
GET /api/rbac/users/{user_id}/accessible-kbs
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Frontend Changes
|
||||||
|
|
||||||
|
#### Update `botui/ui/suite/admin/groups.html`
|
||||||
|
- Add "Knowledge Bases" tab to group detail panel
|
||||||
|
- Show list of KBs assigned to the group
|
||||||
|
- Allow adding/removing KB assignments
|
||||||
|
|
||||||
|
#### Update `botui/ui/suite/drive/drive.html`
|
||||||
|
- Add group visibility indicators for KB folders
|
||||||
|
- Show which groups have access to each KB
|
||||||
|
|
||||||
|
### 5. Migration Strategy
|
||||||
|
|
||||||
|
1. Create new migration for `kb_group_associations` table
|
||||||
|
2. Run migration to create table
|
||||||
|
3. Assign existing KBs to default groups (e.g., "all_users" group)
|
||||||
|
4. Update application code
|
||||||
|
5. Deploy and test
|
||||||
|
|
||||||
|
### 6. Backward Compatibility
|
||||||
|
|
||||||
|
- Existing KBs without group associations remain public
|
||||||
|
- Existing USE KB calls continue to work
|
||||||
|
- THINK KB will filter results based on new permissions
|
||||||
|
|
||||||
|
## Implementation Steps
|
||||||
|
|
||||||
|
1. ✅ Database migration for kb_group_associations
|
||||||
|
2. ✅ Modify think_kb_search to accept user_id and filter by groups
|
||||||
|
3. ✅ Update THINK KB keyword registration to pass user_id
|
||||||
|
4. ✅ Add group access check to USE KB
|
||||||
|
5. ✅ Add API endpoints for KB-group management
|
||||||
|
6. ✅ Update admin UI for group-KB assignment
|
||||||
|
7. ✅ Update drive UI to show group access
|
||||||
|
8. ✅ Add tests for group-based access control
|
||||||
|
|
||||||
|
## Security Considerations
|
||||||
|
|
||||||
|
- All KB access checks must happen at the database level
|
||||||
|
- No client-side filtering of search results
|
||||||
|
- Group membership verified on each request
|
||||||
|
- Audit logging for KB access attempts
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
- Unit tests for group access functions
|
||||||
|
- Integration tests for THINK KB with group filtering
|
||||||
|
- UI tests for admin group-KB management
|
||||||
|
- End-to-end tests with different user group scenarios
|
||||||
Loading…
Add table
Reference in a new issue