diff --git a/TODO.md b/TODO.md deleted file mode 100644 index 163e179..0000000 --- a/TODO.md +++ /dev/null @@ -1,232 +0,0 @@ -# SMB Suite Implementation TODO - -## Overview -Complete sovereign SMB suite with CRM, Billing, Products, Tickets, and Forms. -Following Microsoft Dynamics nomenclature (simplified for SMB). - ---- - -## ✅ COMPLETED - -### Phase 1: Create HTML/CSS for New Apps ✅ - -#### 1.1 CRM (`/suite/crm/`) ✅ -- [x] `crm.html` - Pipeline view (Kanban style) -- [x] `crm.css` - Styling -- [x] Entities (Dynamics nomenclature): - - **Lead** - Unqualified prospect - - **Opportunity** - Qualified, in sales process - - **Account** - Company (converted customer) - - **Contact** - Person at Account - - **Activity** - Linked tasks/calls/emails - -#### 1.2 Billing (`/suite/billing/`) ✅ -- [x] `billing.html` - Invoice list + dashboard -- [x] `billing.css` - Styling -- [x] Entities: - - **Invoice** - Bill to customer - - **Payment** - Payment received - - **Quote** - Price quotation → converts to Invoice - -#### 1.3 Products (`/suite/products/`) ✅ -- [x] `products.html` - Product/Service catalog -- [x] `products.css` - Styling -- [x] Entities: - - **Product** - Physical/digital product - - **Service** - Service offering - - **PriceList** - Pricing tiers - -#### 1.4 Tickets (`/suite/tickets/`) ✅ -- [x] `tickets.html` - AI-assisted support tickets -- [x] `tickets.css` - Styling -- [x] Entities: - - **Case** - Support ticket (Dynamics term) - - **Resolution** - AI-suggested solutions - -#### 1.5 Forms (`/suite/forms/`) ✅ -- [x] `forms.html` - Redirect to Tasks with AI prompt -- [x] Behavior: "Create a form for me about [topic]" - -### Phase 2: Menu Integration (`/suite/index.html`) ✅ - -- [x] Add CRM to dropdown menu -- [x] Add Billing to dropdown menu -- [x] Add Products to dropdown menu -- [x] Add Tickets to dropdown menu -- [x] Add Forms to dropdown menu -- [x] Update header tabs (add CRM) -- [x] Update CSS breakpoints (`/suite/css/app.css`) - -### Phase 3: i18n Updates ✅ - -**NOTE:** Translations are stored in `.ftl` files in `botlib/locales/` - NOT in JS files. - -#### English (`botlib/locales/en/ui.ftl`) ✅ -- [x] nav-crm, nav-billing, nav-products, nav-tickets, nav-forms -- [x] CRM: lead, opportunity, account, contact, pipeline, qualify, convert, won, lost -- [x] Billing: invoice, payment, quote, due-date, overdue, paid, pending -- [x] Products: product, service, price, sku, category, unit -- [x] Tickets: case, priority, status, assigned, resolved, escalate - -#### Portuguese (`botlib/locales/pt-BR/ui.ftl`) ✅ -- [x] nav-crm: "CRM" -- [x] nav-billing: "Faturamento" -- [x] nav-products: "Produtos" -- [x] nav-tickets: "Chamados" -- [x] nav-forms: "Formulários" -- [x] All entity labels in Portuguese - -#### Spanish (`botlib/locales/es/ui.ftl`) ✅ -- [x] All navigation and entity labels in Spanish - ---- - -## 📋 TODO - -### Phase 4: Chat @ Mentions - -- [ ] Add @ autocomplete in chat input -- [ ] Entity types to reference: - - @lead:name - - @opportunity:name - - @account:name - - @contact:name - - @invoice:number - - @case:number - - @product:name -- [ ] Show entity card on hover -- [ ] Navigate to entity on click - -### Phase 5: Reports (in Analytics/Dashboards) - -#### CRM Reports -- [ ] Sales Pipeline (funnel) -- [ ] Lead Conversion Rate -- [ ] Opportunities by Stage -- [ ] Won/Lost Analysis -- [ ] Sales Forecast - -#### Billing Reports -- [ ] Revenue Summary -- [ ] Aging Report (overdue invoices) -- [ ] Payment History -- [ ] Monthly Revenue - -#### Support Reports -- [ ] Open Cases by Priority -- [ ] Resolution Time (avg) -- [ ] Cases by Category -- [ ] AI Resolution Rate - -### Phase 6: BotBook Documentation - -- [ ] Add CRM chapter -- [ ] Add Billing chapter -- [ ] Add Products chapter -- [ ] Add Tickets chapter -- [ ] Document @ mentions -- [ ] Update SUMMARY.md - ---- - -## File Structure - -### i18n Files (Fluent format .ftl): -``` -botlib/locales/ -├── en/ -│ └── ui.ftl # English translations -├── pt-BR/ -│ └── ui.ftl # Portuguese translations -└── es/ - └── ui.ftl # Spanish translations -``` - -### Suite Files: -``` -botui/ui/suite/ -├── crm/ -│ ├── crm.html -│ └── crm.css -├── billing/ -│ ├── billing.html -│ └── billing.css -├── products/ -│ ├── products.html -│ └── products.css -├── tickets/ -│ ├── tickets.html -│ └── tickets.css -├── forms/ -│ └── forms.html -├── index.html # Menu items + HTMX routes -└── css/ - └── app.css # Breakpoints for tabs -``` - -### BotBook Files (TODO): -``` -botbook/src/ -├── SUMMARY.md # Add new chapters -├── XX-crm/ # CRM documentation -├── XX-billing/ # Billing documentation -├── XX-products/ # Products documentation -└── XX-tickets/ # Tickets documentation -``` - ---- - -## Entity Relationships (Dynamics Style) - -``` -Lead ──(qualify)──► Opportunity ──(convert)──► Account + Contact - │ - ▼ - Quote ──(accept)──► Invoice ──(pay)──► Payment - │ - └── Product/Service (line items) - -Account ◄──► Contact (1:N) -Account ◄──► Case/Ticket (1:N) -Account ◄──► Invoice (1:N) -Account ◄──► Opportunity (1:N) -``` - ---- - -## HTMX Patterns to Use - -### List with selection -```html -
- Loading... -
-``` - -### Pipeline drag-drop -```html -
-``` - -### @ Mention autocomplete -```html - -``` - ---- - -## Notes - -- **i18n Location**: All translations in `botlib/locales/{locale}/ui.ftl` files (Fluent format) -- **Dynamics Nomenclature**: Lead, Opportunity, Account, Contact, Case, Quote, Invoice -- **SMB Focus**: Simple, not enterprise complexity -- **AI-First**: Tickets use AI suggestions, Forms use AI generation -- **HTMX**: All interactions via HTMX -- **Sovereign**: No external dependencies, all data local -- **@ Mentions**: Reference any entity in chat with @type:name \ No newline at end of file diff --git a/ui/suite/assets/icons/gb-security.svg b/ui/suite/assets/icons/gb-security.svg new file mode 100644 index 0000000..79f213f --- /dev/null +++ b/ui/suite/assets/icons/gb-security.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/ui/suite/css/home.css b/ui/suite/css/home.css index f4ca597..903893e 100644 --- a/ui/suite/css/home.css +++ b/ui/suite/css/home.css @@ -312,7 +312,7 @@ .app-icon.attendant { background: linear-gradient(135deg, #22c55e, #16a34a); } -.app-icon.compliance { +.app-icon.security { background: linear-gradient(135deg, #3b82f6, #1d4ed8); } .app-icon.monitoring { diff --git a/ui/suite/designer.html b/ui/suite/designer.html index 0ba665c..622a8e9 100644 --- a/ui/suite/designer.html +++ b/ui/suite/designer.html @@ -904,7 +904,7 @@
Untitled @@ -367,7 +367,7 @@
@@ -395,7 +395,7 @@
- - - - - -
- - - - -

- You'll be redirected to Tasks where AI will generate your form -

- - - - - - diff --git a/ui/suite/home.html b/ui/suite/home.html index 4927199..442f075 100644 --- a/ui/suite/home.html +++ b/ui/suite/home.html @@ -1001,13 +1001,13 @@ -
+
-

Compliance

+

Security

- Security, auditing, and compliance tools. Monitor - access, track changes, and ensure data protection. + Security tools, compliance scanning, and server + protection. Lynis, RKHunter, ClamAV, and more.

diff --git a/ui/suite/index.html b/ui/suite/index.html index f61ca84..9f8000b 100644 --- a/ui/suite/index.html +++ b/ui/suite/index.html @@ -908,37 +908,6 @@ Tickets - - - - Forms - - Sources - + - Compliance + Security diff --git a/ui/suite/js/i18n.js b/ui/suite/js/i18n.js new file mode 100644 index 0000000..25e14a1 --- /dev/null +++ b/ui/suite/js/i18n.js @@ -0,0 +1,293 @@ +(function () { + "use strict"; + + const DEFAULT_LOCALE = "en"; + const STORAGE_KEY = "gb-locale"; + const CACHE_TTL_MS = 3600000; + + const MINIMAL_FALLBACK = { + "label-loading": "Loading...", + "status-error": "Error", + "action-retry": "Retry", + }; + + let currentLocale = DEFAULT_LOCALE; + let translations = {}; + let isInitialized = false; + + function detectBrowserLocale() { + const stored = localStorage.getItem(STORAGE_KEY); + if (stored) { + return stored; + } + + const browserLang = + navigator.language || navigator.userLanguage || DEFAULT_LOCALE; + const shortLang = browserLang.split("-")[0]; + + const supportedLocales = ["en", "pt-BR", "es", "zh-CN"]; + + if (supportedLocales.includes(browserLang)) { + return browserLang; + } + + const match = supportedLocales.find((loc) => loc.startsWith(shortLang)); + return match || DEFAULT_LOCALE; + } + + function getCacheKey(locale) { + return `gb-i18n-cache-${locale}`; + } + + function getCachedTranslations(locale) { + try { + const cached = localStorage.getItem(getCacheKey(locale)); + if (cached) { + const { data, timestamp } = JSON.parse(cached); + if (Date.now() - timestamp < CACHE_TTL_MS) { + return data; + } + } + } catch (e) { + console.warn("i18n: Failed to read cache", e); + } + return null; + } + + function setCachedTranslations(locale, data) { + try { + localStorage.setItem( + getCacheKey(locale), + JSON.stringify({ + data, + timestamp: Date.now(), + }), + ); + } catch (e) { + console.warn("i18n: Failed to write cache", e); + } + } + + async function fetchTranslations(locale) { + try { + const response = await fetch(`/api/i18n/${locale}`, { + headers: { Accept: "application/json" }, + }); + + if (!response.ok) { + throw new Error(`HTTP ${response.status}`); + } + + const result = await response.json(); + return result.translations || {}; + } catch (e) { + console.warn(`i18n: Failed to fetch translations for ${locale}`, e); + return null; + } + } + + async function loadTranslations(locale) { + const cached = getCachedTranslations(locale); + if (cached) { + translations = cached; + currentLocale = locale; + return true; + } + + const fetched = await fetchTranslations(locale); + if (fetched && Object.keys(fetched).length > 0) { + translations = fetched; + currentLocale = locale; + setCachedTranslations(locale, fetched); + return true; + } + + if (locale !== DEFAULT_LOCALE) { + console.warn(`i18n: Falling back to ${DEFAULT_LOCALE}`); + return loadTranslations(DEFAULT_LOCALE); + } + + translations = MINIMAL_FALLBACK; + return false; + } + + function t(key, params) { + let text = translations[key] || MINIMAL_FALLBACK[key] || key; + + if (params && typeof params === "object") { + Object.keys(params).forEach((param) => { + text = text.replace( + new RegExp(`\\{\\s*\\$?${param}\\s*\\}`, "g"), + params[param], + ); + text = text.replace( + new RegExp(`\\{\\s*${param}\\s*\\}`, "g"), + params[param], + ); + }); + } + + return text; + } + + function translateElement(element) { + const key = element.getAttribute("data-i18n"); + if (key) { + const paramsAttr = element.getAttribute("data-i18n-params"); + let params = null; + + if (paramsAttr) { + try { + params = JSON.parse(paramsAttr); + } catch (e) { + console.warn("i18n: Invalid params JSON", paramsAttr); + } + } + + element.textContent = t(key, params); + } + + const placeholderKey = element.getAttribute("data-i18n-placeholder"); + if (placeholderKey) { + element.setAttribute("placeholder", t(placeholderKey)); + } + + const titleKey = element.getAttribute("data-i18n-title"); + if (titleKey) { + element.setAttribute("title", t(titleKey)); + } + + const ariaLabelKey = element.getAttribute("data-i18n-aria-label"); + if (ariaLabelKey) { + element.setAttribute("aria-label", t(ariaLabelKey)); + } + } + + function translatePage(root) { + const container = root || document; + + const elements = container.querySelectorAll( + "[data-i18n], [data-i18n-placeholder], [data-i18n-title], [data-i18n-aria-label]", + ); + + elements.forEach(translateElement); + } + + async function setLocale(locale) { + if (locale === currentLocale && isInitialized) { + return; + } + + localStorage.setItem(STORAGE_KEY, locale); + await loadTranslations(locale); + translatePage(); + + document.documentElement.setAttribute("lang", locale.split("-")[0]); + + window.dispatchEvent( + new CustomEvent("localeChanged", { + detail: { locale: currentLocale }, + }), + ); + } + + function setupBodyListeners() { + if (!document.body) { + return; + } + + document.body.addEventListener("htmx:afterSwap", (event) => { + translatePage(event.detail.target); + }); + + document.body.addEventListener("htmx:afterSettle", (event) => { + translatePage(event.detail.target); + }); + + const observer = new MutationObserver((mutations) => { + mutations.forEach((mutation) => { + mutation.addedNodes.forEach((node) => { + if (node.nodeType === Node.ELEMENT_NODE) { + if ( + node.hasAttribute && + (node.hasAttribute("data-i18n") || + node.hasAttribute("data-i18n-placeholder") || + node.hasAttribute("data-i18n-title") || + node.hasAttribute("data-i18n-aria-label")) + ) { + translateElement(node); + } + if (node.querySelectorAll) { + translatePage(node); + } + } + }); + }); + }); + + observer.observe(document.body, { + childList: true, + subtree: true, + }); + } + + async function init() { + if (isInitialized) { + return; + } + + const locale = detectBrowserLocale(); + await loadTranslations(locale); + + isInitialized = true; + + if (document.readyState === "loading") { + document.addEventListener("DOMContentLoaded", () => { + translatePage(); + setupBodyListeners(); + }); + } else { + translatePage(); + setupBodyListeners(); + } + } + + async function getAvailableLocales() { + try { + const response = await fetch("/api/i18n/locales"); + if (response.ok) { + const data = await response.json(); + return data.locales || ["en"]; + } + } catch (e) { + console.warn("i18n: Failed to fetch available locales", e); + } + return ["en", "pt-BR", "es"]; + } + + function getCurrentLocale() { + return currentLocale; + } + + function clearCache() { + const keys = Object.keys(localStorage); + keys.forEach((key) => { + if (key.startsWith("gb-i18n-cache-")) { + localStorage.removeItem(key); + } + }); + } + + window.i18n = { + t, + init, + setLocale, + getCurrentLocale, + getAvailableLocales, + translatePage, + translateElement, + clearCache, + }; + + init().catch((e) => console.error("i18n: Initialization failed", e)); +})(); diff --git a/ui/suite/tools/compliance.html b/ui/suite/tools/compliance.html index 2099e8b..c279655 100644 --- a/ui/suite/tools/compliance.html +++ b/ui/suite/tools/compliance.html @@ -512,7 +512,7 @@
+ +
+ + +
+
+ + +
+ + +
+
+
+ Critical +
+
0
+
+ Requires immediate action +
+
+
+
+ High +
+
0
+
+ Security risk +
+
+
+
+ Medium +
+
0
+
+ Should be addressed +
+
+
+
+ Low +
+
0
+
+ Best practice +
+
+
+
+ Info +
+
0
+
+ Informational +
+
+
+ + +
+
+ Severity: + +
+
+ Type: + +
+
+
+ +
+
+ + +
+
+ Compliance Issues + 5 issues found +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
SeverityIssue TypeLocationDescriptionAction
+ + + Critical + + +
+
+ + + + +
+
+
+ Password in Config +
+
+ Security +
+
+
+
+
+ marketing.gbai/poster.bas +
+
Line 12
+
+
+ Hardcoded password found in BASIC file. Move to + Vault. +
+ POST TO INSTAGRAM username, password, image +
+
+
+ +
+ + + High + + +
+
+ + + + + +
+
+
+ Hardcoded Secret +
+
+ Security +
+
+
+
+
+ api-client.gbai/msft-partner.bas +
+
Line 7
+
+
+ Client secret found in source code. Use + environment variables. +
+ client_secret = "abc123..." +
+
+
+ +
+ + + Medium + + +
+
+ + + + + +
+
+
+ Deprecated Keyword +
+
+ Code Quality +
+
+
+
+
+ default.gbai/start.bas +
+
Line 45
+
+
+ Using deprecated IF...input pattern. Use HEAR AS + instead. +
+ IF input = "yes" THEN +
+
+
+ +
+ + + Low + + +
+
+ + + + +
+
+
+ Underscore in Keyword +
+
+ Naming Convention +
+
+
+
+
+ crm.gbai/contacts.bas +
+
Line 23
+
+
+ Keywords should use spaces not underscores. +
+ GET_BOT_MEMORY → GET BOT MEMORY +
+
+
+ +
+ + + Info + + +
+
+ + + + +
+
+
+ Missing Vault Config +
+
+ Configuration +
+
+
+
+
+ bank.gbai/config.csv +
+
-
+
+
+ Bot is not configured to use Vault for secrets + management. Consider enabling for better + security. +
+
+ +
+
+
+ + +
+
+ +
+
+
+
🛡️
+
+

Lynis

+

Security auditing & compliance tool

+
+
+
+ + Installed +
+
+
+
+
+ Version + 3.0.9 +
+
+ Last Scan + 2 hours ago +
+
+ Hardening Index + 78/100 +
+
+ Warnings + 12 +
+
+
+ + +
+
+ +
+ + +
+
+
+
🔍
+
+

RKHunter

+

Rootkit detection scanner

+
+
+
+ + Installed +
+
+
+
+
+ Version + 1.4.6 +
+
+ Last Scan + 6 hours ago +
+
+ Status + Clean +
+
+ Warnings + 0 +
+
+
+ + +
+
+ +
+ + +
+
+
+
🔎
+
+

Chkrootkit

+

Check for rootkits locally

+
+
+
+ + Not Installed +
+
+
+
+
+ Version + -- +
+
+ Last Scan + Never +
+
+ Status + -- +
+
+ Infected + -- +
+
+
+ +
+
+ +
+ + +
+
+
+
🦈
+
+

Suricata

+

IDS/IPS network security monitoring

+
+
+
+ + Stopped +
+
+
+
+
+ Version + 7.0.3 +
+
+ Rules + 45,230 +
+
+ Alerts Today + 23 +
+
+ Blocked + 8 +
+
+
+ + +
+
+ +
+ + +
+
+
+
🦠
+
+

LMD

+

Linux Malware Detect scanner

+
+
+
+ + Installed +
+
+
+
+
+ Version + 1.6.5 +
+
+ Last Scan + Yesterday +
+
+ Signatures + 18,432 +
+
+ Threats Found + 0 +
+
+
+ + +
+
+ +
+ + +
+
+
+
🐚
+
+

ClamAV

+

Open source antivirus engine

+
+
+
+ + Running +
+
+
+
+
+ Version + 1.2.1 +
+
+ DB Updated + 3 hours ago +
+
+ Signatures + 8.7M +
+
+ Quarantined + 2 +
+
+
+ + + +
+
+ +
+
+
+ + +
+
+
+

+ Security Report +

+ +
+
+
+Loading report...
+
+
+
+
+ + + diff --git a/ui/suite/tools/tools.js b/ui/suite/tools/tools.js index 19f71e1..d4ea839 100644 --- a/ui/suite/tools/tools.js +++ b/ui/suite/tools/tools.js @@ -208,7 +208,7 @@ */ function exportReport() { if (typeof htmx !== 'undefined') { - htmx.ajax('GET', '/api/v1/compliance/export', { + htmx.ajax('GET', '/api/compliance/export', { swap: 'none' }); } @@ -219,7 +219,7 @@ */ function fixIssue(issueId) { if (typeof htmx !== 'undefined') { - htmx.ajax('POST', `/api/v1/compliance/fix/${issueId}`, { + htmx.ajax('POST', `/api/compliance/fix/${issueId}`, { swap: 'none' }).then(() => { // Refresh results