Odoo ERP ported to Go — complete backend + original OWL frontend
Full port of Odoo's ERP system from Python to Go, with the original Odoo JavaScript frontend (OWL framework) running against the Go server. Backend (10,691 LoC Go): - Custom ORM: CRUD, domains→SQL with JOINs, computed fields, sequences - 93 models across 14 modules (base, account, sale, stock, purchase, hr, project, crm, fleet, product, l10n_de, google_address/translate/calendar) - Auth with bcrypt + session cookies - Setup wizard (company, SKR03 chart, admin, demo data) - Double-entry bookkeeping constraint - Sale→Invoice workflow (confirm SO → generate invoice → post) - SKR03 chart of accounts (110 accounts) + German taxes (USt/VSt) - Record rules (multi-company filter) - Google integrations as opt-in modules (Maps, Translate, Calendar) Frontend: - Odoo's original OWL webclient (503 JS modules, 378 XML templates) - JS transpiled via Odoo's js_transpiler (ES modules → odoo.define) - SCSS compiled to CSS (675KB) via dart-sass - XML templates compiled to registerTemplate() JS calls - Static file serving from Odoo source addons - Login page, session management, menu navigation - Contacts list view renders with real data from PostgreSQL Infrastructure: - 14MB single binary (CGO_ENABLED=0) - Docker Compose (Go server + PostgreSQL 16) - Zero phone-home (no outbound calls to odoo.com) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
80
pkg/server/login.go
Normal file
80
pkg/server/login.go
Normal file
@@ -0,0 +1,80 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// handleLogin serves the login page.
|
||||
func (s *Server) handleLogin(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||
w.Write([]byte(`<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
||||
<title>Odoo - Login</title>
|
||||
<style>
|
||||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
||||
background: #f0eeee; display: flex; align-items: center; justify-content: center; min-height: 100vh; }
|
||||
.login-box { background: white; padding: 40px; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||||
width: 100%; max-width: 400px; }
|
||||
.login-box h1 { text-align: center; color: #71639e; margin-bottom: 30px; font-size: 28px; }
|
||||
.login-box label { display: block; margin-bottom: 6px; font-weight: 500; color: #333; }
|
||||
.login-box input { width: 100%; padding: 10px 12px; border: 1px solid #ddd; border-radius: 4px;
|
||||
font-size: 14px; margin-bottom: 16px; }
|
||||
.login-box input:focus { outline: none; border-color: #71639e; box-shadow: 0 0 0 2px rgba(113,99,158,0.2); }
|
||||
.login-box button { width: 100%; padding: 12px; background: #71639e; color: white; border: none;
|
||||
border-radius: 4px; font-size: 16px; cursor: pointer; }
|
||||
.login-box button:hover { background: #5f5387; }
|
||||
.error { color: #dc3545; margin-bottom: 16px; display: none; text-align: center; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="login-box">
|
||||
<h1>Odoo</h1>
|
||||
<div id="error" class="error"></div>
|
||||
<form id="loginForm">
|
||||
<label for="login">Email</label>
|
||||
<input type="text" id="login" name="login" value="admin" autofocus/>
|
||||
<label for="password">Password</label>
|
||||
<input type="password" id="password" name="password" value="admin"/>
|
||||
<button type="submit">Log in</button>
|
||||
</form>
|
||||
</div>
|
||||
<script>
|
||||
document.getElementById('loginForm').addEventListener('submit', function(e) {
|
||||
e.preventDefault();
|
||||
var login = document.getElementById('login').value;
|
||||
var password = document.getElementById('password').value;
|
||||
var errorEl = document.getElementById('error');
|
||||
errorEl.style.display = 'none';
|
||||
|
||||
fetch('/web/session/authenticate', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({
|
||||
jsonrpc: '2.0',
|
||||
method: 'call',
|
||||
id: 1,
|
||||
params: {db: '` + s.config.DBName + `', login: login, password: password}
|
||||
})
|
||||
})
|
||||
.then(function(r) { return r.json(); })
|
||||
.then(function(data) {
|
||||
if (data.error) {
|
||||
errorEl.textContent = data.error.message;
|
||||
errorEl.style.display = 'block';
|
||||
} else if (data.result && data.result.uid) {
|
||||
window.location.href = '/web';
|
||||
}
|
||||
})
|
||||
.catch(function(err) {
|
||||
errorEl.textContent = 'Connection error';
|
||||
errorEl.style.display = 'block';
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>`))
|
||||
}
|
||||
Reference in New Issue
Block a user