Eliminate Python dependency: embed frontend assets in odoo-go

- Copy all OWL frontend assets (JS/CSS/XML/fonts/images) into frontend/
  directory (2925 files, 43MB) — no more runtime reads from Python Odoo
- Replace OdooAddonsPath config with FrontendDir pointing to local frontend/
- Rewire bundle.go, static.go, templates.go, webclient.go to read from
  frontend/ instead of external Python Odoo addons directory
- Auto-detect frontend/ and build/ dirs relative to binary in main.go
- Delete obsolete Python helper scripts (tools/*.py)

The Go server is now fully self-contained: single binary + frontend/ folder.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Marc
2026-03-31 23:09:12 +02:00
parent 0ed29fe2fd
commit 8741282322
2933 changed files with 280644 additions and 264 deletions

View File

@@ -9,6 +9,7 @@ import (
"os"
"path/filepath"
"strings"
"time"
"odoo-go/pkg/tools"
)
@@ -32,15 +33,16 @@ func init() {
xmlFiles = loadAssetList("assets_xml.txt", assetsXMLFile)
}
// loadXMLTemplate reads an XML template file from the Odoo addons paths.
// loadXMLTemplate reads an XML template file from the frontend directory.
func loadXMLTemplate(cfg *tools.Config, urlPath string) string {
if cfg.FrontendDir == "" {
return ""
}
rel := strings.TrimPrefix(urlPath, "/")
for _, addonsDir := range cfg.OdooAddonsPath {
fullPath := filepath.Join(addonsDir, rel)
data, err := os.ReadFile(fullPath)
if err == nil {
return string(data)
}
fullPath := filepath.Join(cfg.FrontendDir, rel)
data, err := os.ReadFile(fullPath)
if err == nil {
return string(data)
}
return ""
}
@@ -92,15 +94,25 @@ func (s *Server) handleWebClient(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/html; charset=utf-8")
w.Header().Set("Cache-Control", "no-store")
// Build script tags for all JS files (with cache buster)
// Build script tags: module_loader must load first (defines odoo.loader),
// then the concatenated bundle serves everything else in one request.
// We suppress transient missing-dependency errors during loading by
// temporarily replacing reportErrors with a no-op, then restore it
// after the bundle has loaded.
var scriptTags strings.Builder
cacheBuster := "?v=odoo-go-1"
for _, src := range jsFiles {
if strings.HasSuffix(src, ".scss") {
continue
}
scriptTags.WriteString(fmt.Sprintf(" <script type=\"text/javascript\" src=\"%s%s\"></script>\n", src, cacheBuster))
}
cacheBuster := fmt.Sprintf("?v=%d", time.Now().Unix())
// 1) module_loader.js — must run first to define odoo.define/odoo.loader
scriptTags.WriteString(fmt.Sprintf(" <script type=\"text/javascript\" src=\"/web/static/src/module_loader.js%s\"></script>\n", cacheBuster))
// 2) Suppress transient reportErrors while the bundle loads
scriptTags.WriteString(" <script>if (odoo.loader) { odoo.loader.__origReportErrors = odoo.loader.reportErrors.bind(odoo.loader); odoo.loader.reportErrors = function() {}; }</script>\n")
// 3) The concatenated JS bundle (all other modules + XML templates)
scriptTags.WriteString(fmt.Sprintf(" <script type=\"text/javascript\" src=\"/web/assets/bundle.js%s\"></script>\n", cacheBuster))
// 4) Restore reportErrors and run a final check for genuine errors
scriptTags.WriteString(" <script>if (odoo.loader && odoo.loader.__origReportErrors) { odoo.loader.reportErrors = odoo.loader.__origReportErrors; odoo.loader.reportErrors(odoo.loader.findErrors()); }</script>\n")
// Build link tags for CSS: compiled SCSS bundle + individual CSS files
var linkTags strings.Builder
@@ -140,20 +152,23 @@ func (s *Server) handleWebClient(w http.ResponseWriter, r *http.Request) {
};
odoo.loadMenusPromise = odoo.reloadMenus();
// Catch unhandled errors and log them
window.addEventListener('unhandledrejection', function(e) {
console.error('[odoo-go] Unhandled rejection:', e.reason);
});
// Patch OWL to prevent infinite error-dialog recursion.
// When ErrorDialog itself fails to render, stop retrying.
window.__errorDialogCount = 0;
var _origHandleError = null;
document.addEventListener('DOMContentLoaded', function() {
if (typeof owl !== 'undefined' && owl.App) {
_origHandleError = owl.App.prototype.handleError;
var _orig = owl.App.prototype.handleError;
owl.App.prototype.handleError = function() {
window.__errorDialogCount++;
if (window.__errorDialogCount > 3) {
console.error('[odoo-go] Error dialog recursion stopped. Check earlier errors for root cause.');
console.error('[odoo-go] Error dialog recursion stopped.');
return;
}
return _origHandleError.apply(this, arguments);
return _orig.apply(this, arguments);
};
}
});
@@ -180,7 +195,7 @@ func (s *Server) buildSessionInfo(sess *Session) map[string]interface{} {
"allowed_company_ids": []int64{sess.CompanyID},
},
"db": s.config.DBName,
"registry_hash": "odoo-go-static",
"registry_hash": fmt.Sprintf("odoo-go-%d", time.Now().Unix()),
"server_version": "19.0-go",
"server_version_info": []interface{}{19, 0, 0, "final", 0, "g"},
"name": sess.Login,
@@ -192,7 +207,8 @@ func (s *Server) buildSessionInfo(sess *Session) map[string]interface{} {
"web.base.url": fmt.Sprintf("http://localhost:%d", s.config.HTTPPort),
"active_ids_limit": 20000,
"max_file_upload_size": 134217728,
"home_action_id": false,
"home_action_id": 1,
"current_menu": 1,
"support_url": "",
"test_mode": false,
"show_effect": true,