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>
81 lines
3.3 KiB
Go
81 lines
3.3 KiB
Go
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>`))
|
|
}
|