package lkgateway // branding.go — настройка логотипа организации в шапке отчётов (загрузка своего // изображения через UI). Хранится в настройках (Branding.LogoDataURI) как data-URI; // пусто → встроенный логотип по умолчанию. См. orgLogoIMG в fontvielle_logo.go. import ( "encoding/base64" "io" "net/http" "strings" ) const maxLogoBytes = 512 << 10 // 512 КБ — логотип встраивается в каждый отчёт // saveLogo принимает загруженное изображение и сохраняет его как логотип отчётов. func (h *setupHandlers) saveLogo(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { http.Error(w, "method", http.StatusMethodNotAllowed) return } if err := r.ParseMultipartForm(maxLogoBytes + (64 << 10)); err != nil { setupFlash(w, r, "Логотип: не удалось прочитать форму: "+err.Error()) return } file, header, err := r.FormFile("logo") if err != nil { setupFlash(w, r, "Логотип: файл не выбран") return } defer file.Close() data, err := io.ReadAll(io.LimitReader(file, maxLogoBytes+1)) if err != nil { setupFlash(w, r, "Логотип: ошибка чтения файла: "+err.Error()) return } if len(data) == 0 { setupFlash(w, r, "Логотип: файл пустой") return } if len(data) > maxLogoBytes { setupFlash(w, r, "Логотип: файл слишком большой (максимум 512 КБ)") return } mime := logoMIME(header.Filename, data) if mime == "" { setupFlash(w, r, "Логотип: нужен файл-изображение (PNG, JPG, GIF, WEBP или SVG)") return } dataURI := "data:" + mime + ";base64," + base64.StdEncoding.EncodeToString(data) if err := h.rc.UpdateBranding(BrandingSettings{LogoDataURI: dataURI, LogoName: header.Filename}); err != nil { setupFlash(w, r, "Логотип: ошибка сохранения: "+err.Error()) return } setupFlash(w, r, "Логотип обновлён: "+header.Filename+" — появится в шапке отчётов справа.") } // resetLogo сбрасывает логотип на встроенный по умолчанию. func (h *setupHandlers) resetLogo(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { http.Error(w, "method", http.StatusMethodNotAllowed) return } if err := h.rc.UpdateBranding(BrandingSettings{}); err != nil { setupFlash(w, r, "Логотип: ошибка сброса: "+err.Error()) return } setupFlash(w, r, "Логотип сброшен на стандартный (встроенный Фонтвьель).") } // logoMIME определяет MIME изображения по расширению и содержимому; "" — не картинка. func logoMIME(filename string, data []byte) string { switch { case strings.HasSuffix(strings.ToLower(filename), ".svg"): return "image/svg+xml" } ct := http.DetectContentType(data) head := data if len(head) > 200 { head = head[:200] } switch { case strings.HasPrefix(ct, "image/"): return ct case strings.Contains(ct, "xml") && strings.Contains(strings.ToLower(string(head)), "