Update application UI and functionality
This commit is contained in:
@@ -36,6 +36,7 @@ type Config struct {
|
||||
TimestampWindowSeconds int64 `json:"timestamp_window_seconds"`
|
||||
MaxRequestBytes int64 `json:"max_request_bytes"`
|
||||
MaxPackageBytes int64 `json:"max_package_bytes"`
|
||||
ReleaseUploadMaxBytes int64 `json:"release_upload_max_bytes"`
|
||||
Database DatabaseConfig `json:"database"`
|
||||
Mail MailConfig `json:"mail"`
|
||||
Branding BrandingConfig `json:"branding"`
|
||||
@@ -149,6 +150,7 @@ func defaults(root string) *Config {
|
||||
TimestampWindowSeconds: 600,
|
||||
MaxRequestBytes: 12 * 1024 * 1024,
|
||||
MaxPackageBytes: 10 * 1024 * 1024,
|
||||
ReleaseUploadMaxBytes: 1024 * 1024 * 1024,
|
||||
SourceCheckSeconds: 60,
|
||||
Database: DatabaseConfig{
|
||||
Provider: "sqlite",
|
||||
@@ -340,6 +342,11 @@ func applyEnv(cfg *Config) {
|
||||
cfg.MaxPackageBytes = parsed
|
||||
}
|
||||
}
|
||||
if value := os.Getenv("YMHUT_RELEASE_UPLOAD_MAX_BYTES"); value != "" {
|
||||
if parsed, err := strconv.ParseInt(value, 10, 64); err == nil {
|
||||
cfg.ReleaseUploadMaxBytes = parsed
|
||||
}
|
||||
}
|
||||
if value := os.Getenv("YMHUT_SOURCE_CHECK_SECONDS"); value != "" {
|
||||
if parsed, err := strconv.Atoi(value); err == nil {
|
||||
cfg.SourceCheckSeconds = parsed
|
||||
@@ -453,6 +460,9 @@ func normalize(root string, cfg *Config) {
|
||||
if cfg.MaxPackageBytes <= 0 {
|
||||
cfg.MaxPackageBytes = 10 * 1024 * 1024
|
||||
}
|
||||
if cfg.ReleaseUploadMaxBytes <= 0 {
|
||||
cfg.ReleaseUploadMaxBytes = 1024 * 1024 * 1024
|
||||
}
|
||||
if cfg.UploadGuard.MaxZipFiles <= 0 {
|
||||
cfg.UploadGuard.MaxZipFiles = 80
|
||||
}
|
||||
|
||||
@@ -100,3 +100,39 @@ func TestLoadRewritesAbsoluteConfigPaths(t *testing.T) {
|
||||
t.Fatalf("config still contains absolute base path: %s", string(rewritten))
|
||||
}
|
||||
}
|
||||
|
||||
func TestReleaseUploadLimitCanBeConfiguredFromEnvironment(t *testing.T) {
|
||||
t.Setenv("YMHUT_RELEASE_UPLOAD_MAX_BYTES", "67108864")
|
||||
cfg := defaults(t.TempDir())
|
||||
applyEnv(cfg)
|
||||
normalize(cfg.BaseDir, cfg)
|
||||
if cfg.ReleaseUploadMaxBytes != 64*1024*1024 {
|
||||
t.Fatalf("ReleaseUploadMaxBytes = %d, want %d", cfg.ReleaseUploadMaxBytes, 64*1024*1024)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPreflightReportsMissingAdminAssetName(t *testing.T) {
|
||||
root := t.TempDir()
|
||||
cfg := defaults(root)
|
||||
if err := os.MkdirAll(filepath.Join(cfg.AdminWebDir, "assets"), 0o755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := os.WriteFile(
|
||||
filepath.Join(cfg.AdminWebDir, "index.html"),
|
||||
[]byte(`<script type="module" src="/admin/assets/missing.js"></script>`),
|
||||
0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
checks := Preflight(cfg)
|
||||
admin := checks[0]
|
||||
for _, check := range checks {
|
||||
if check.Name == "admin web dist" {
|
||||
admin = check
|
||||
break
|
||||
}
|
||||
}
|
||||
if !strings.Contains(admin.Message, "assets/missing.js") {
|
||||
t.Fatalf("admin preflight message = %q, want missing asset name", admin.Message)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,8 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
webassets "ymhut-box/server/unified-management/web"
|
||||
)
|
||||
@@ -44,13 +46,47 @@ func Preflight(cfg *Config) []Check {
|
||||
checkSeedFile("legacy update-info", filepath.Join(cfg.UpdatePublicDir, "update-info.json"), []byte(defaultUpdateInfoJSON)),
|
||||
checkSeedFile("legacy media-types", filepath.Join(cfg.UpdatePublicDir, "media-types.json"), []byte(defaultMediaTypesJSON)),
|
||||
checkNoticeIndex("version notice index", filepath.Join(cfg.UpdateNoticeDir, "total.json")),
|
||||
checkWebBuild("admin web dist", cfg.AdminWebDir, "admin/dist"),
|
||||
checkAdminWebBuild("admin web dist", cfg.AdminWebDir, "admin/dist"),
|
||||
checkWebBuild("portal web dist", cfg.PortalWebDir, "portal/dist"),
|
||||
checkWebBuild("setup web dist", cfg.SetupWebDir, "setup/dist"),
|
||||
}
|
||||
return checks
|
||||
}
|
||||
|
||||
var adminPreflightAssetPattern = regexp.MustCompile(`(?:src|href)=["'](/admin/assets/[^"'?#]+)`)
|
||||
|
||||
func checkAdminWebBuild(name, path, embedRoot string) Check {
|
||||
check := checkWebBuild(name, path, embedRoot)
|
||||
if check.Status != "ok" || strings.Contains(check.Message, "embedded frontend assets") {
|
||||
return check
|
||||
}
|
||||
index := filepath.Join(path, "index.html")
|
||||
data, err := os.ReadFile(index)
|
||||
if err != nil {
|
||||
return check
|
||||
}
|
||||
matches := adminPreflightAssetPattern.FindAllSubmatch(data, -1)
|
||||
if len(matches) == 0 {
|
||||
return Check{Name: name, Status: "error", Path: index, Message: "index.html does not reference any /admin/assets files"}
|
||||
}
|
||||
for _, match := range matches {
|
||||
assetPath := strings.TrimPrefix(string(match[1]), "/admin/")
|
||||
if strings.Contains(assetPath, "..") || strings.ContainsAny(assetPath, `\`) {
|
||||
return Check{Name: name, Status: "error", Path: index, Message: fmt.Sprintf("invalid admin asset reference %s", assetPath)}
|
||||
}
|
||||
info, statErr := os.Stat(filepath.Join(path, filepath.FromSlash(assetPath)))
|
||||
if statErr == nil && !info.IsDir() {
|
||||
continue
|
||||
}
|
||||
message := fmt.Sprintf("disk asset %s is missing", assetPath)
|
||||
if embeddedWebBuildOK(embedRoot) {
|
||||
return Check{Name: name, Status: "ok", Path: path, Message: message + "; using embedded frontend assets"}
|
||||
}
|
||||
return Check{Name: name, Status: "error", Path: filepath.Join(path, filepath.FromSlash(assetPath)), Message: message}
|
||||
}
|
||||
return check
|
||||
}
|
||||
|
||||
func checkDir(name, path string, create bool) Check {
|
||||
if create {
|
||||
if err := os.MkdirAll(path, 0o750); err != nil {
|
||||
|
||||
Reference in New Issue
Block a user