- Image handler now returns SVG placeholders for company logo and user avatars instead of broken 1x1 PNG (fixes yellow border in navbar) - Supports both query-param (?model=&field=) and path-style URLs (/web/image/res.partner/2/avatar_128) - Added Settings app menu with Users & Technical sub-menus - Added actions for Settings (company form), Users list, Sequences - Added /web/session/logout handler that clears session + cookie - Added logout to middleware whitelist Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
63 lines
2.5 KiB
Go
63 lines
2.5 KiB
Go
package server
|
|
|
|
import (
|
|
"net/http"
|
|
"strings"
|
|
)
|
|
|
|
// handleImage serves images for model records.
|
|
// Mirrors: odoo/addons/web/controllers/binary.py content_image()
|
|
// For now, return SVG placeholders that look like real Odoo avatars.
|
|
func (s *Server) handleImage(w http.ResponseWriter, r *http.Request) {
|
|
field := r.URL.Query().Get("field")
|
|
model := r.URL.Query().Get("model")
|
|
|
|
// Also parse path-style URLs: /web/image/res.partner/2/avatar_128
|
|
path := strings.TrimPrefix(r.URL.Path, "/web/image/")
|
|
if model == "" && path != "" && path != r.URL.Path {
|
|
pathParts := strings.Split(path, "/")
|
|
if len(pathParts) >= 3 {
|
|
model = pathParts[0]
|
|
field = pathParts[2]
|
|
} else if len(pathParts) >= 1 {
|
|
model = pathParts[0]
|
|
}
|
|
}
|
|
|
|
// Company logo: return a styled SVG with company initial
|
|
if model == "res.company" && (field == "logo" || field == "logo_web") {
|
|
w.Header().Set("Content-Type", "image/svg+xml")
|
|
w.Header().Set("Cache-Control", "public, max-age=3600")
|
|
w.Write([]byte(`<svg xmlns="http://www.w3.org/2000/svg" width="100" height="30" viewBox="0 0 100 30">
|
|
<rect width="100" height="30" rx="4" fill="#71639e"/>
|
|
<text x="50" y="20" font-family="sans-serif" font-size="14" fill="white" text-anchor="middle">Odoo</text>
|
|
</svg>`))
|
|
return
|
|
}
|
|
|
|
// User avatar: return a colored circle with initial
|
|
if (model == "res.partner" || model == "res.users") &&
|
|
(field == "avatar_128" || field == "avatar_256" || field == "image_128" || field == "image_256" || strings.HasPrefix(field, "avatar") || strings.HasPrefix(field, "image")) {
|
|
w.Header().Set("Content-Type", "image/svg+xml")
|
|
w.Header().Set("Cache-Control", "public, max-age=3600")
|
|
w.Write([]byte(`<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" viewBox="0 0 64 64">
|
|
<rect width="64" height="64" rx="32" fill="#71639e"/>
|
|
<text x="32" y="40" font-family="sans-serif" font-size="28" fill="white" text-anchor="middle">U</text>
|
|
</svg>`))
|
|
return
|
|
}
|
|
|
|
// Default: 1x1 transparent PNG
|
|
w.Header().Set("Content-Type", "image/png")
|
|
w.Header().Set("Cache-Control", "public, max-age=3600")
|
|
png := []byte{
|
|
0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d,
|
|
0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01,
|
|
0x08, 0x06, 0x00, 0x00, 0x00, 0x1f, 0x15, 0xc4, 0x89, 0x00, 0x00, 0x00,
|
|
0x0a, 0x49, 0x44, 0x41, 0x54, 0x78, 0x9c, 0x62, 0x00, 0x00, 0x00, 0x02,
|
|
0x00, 0x01, 0xe5, 0x27, 0xde, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45,
|
|
0x4e, 0x44, 0xae, 0x42, 0x60, 0x82,
|
|
}
|
|
w.Write(png)
|
|
}
|