Compare commits

..

7 Commits

Author SHA1 Message Date
jmiller d1b964235a Merge pull request 'rc(v05.02.00): org sidebar fix, admin branding' (#185) from rc/05.02.00 into main 2026-05-26 02:03:42 +00:00
Jonathan Miller 299ec57b52 fix(ci): correct updates.xml regex to target stable channel block only
The previous regex matched across channel boundaries because <version>
appears before <tags> in the XML. Fixed by matching the entire <update>
block containing the target channel tag, then replacing fields within
that block only.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-25 20:10:27 -05:00
jmiller 34e2a460f9 fix(ci): correct updates.xml stable channel to 05.01.02 2026-05-26 01:08:29 +00:00
jmiller 7921e007f1 chore(ci): update updates.xml to 05.01.02 2026-05-26 01:02:14 +00:00
jmiller c21df45434 Merge pull request 'rc(v05.01.02): auto-update updates.xml' (#180) from rc/05.01.02 into main 2026-05-26 00:56:39 +00:00
jmiller e279f8dbe8 Merge pull request 'rc(v05.01.01): maintenance mode deploy + checksums' (#178) from rc/05.01.01 into main 2026-05-26 00:35:25 +00:00
jmiller 3b57aaff10 Merge pull request 'rc(v05.01.00): update checker channels + SHA256 release checksums' (#176) from rc/05.01.00 into main 2026-05-26 00:28:10 +00:00
11 changed files with 22 additions and 232 deletions
+11 -11
View File
@@ -183,18 +183,18 @@ jobs:
sha = data["sha"]
content = base64.b64decode(data["content"]).decode("utf-8")
# Update stable channel version, infourl, and docker tag
content = re.sub(
r"(<tags><tag>stable</tag></tags>[\s\S]*?<version>)[^<]*(</version>)",
rf"\g<1>{moko_ver}\2", content)
content = re.sub(
r"(<tags><tag>stable</tag></tags>[\s\S]*?<infourl[^>]*>)[^<]*(</infourl>)",
rf"\g<1>{release_url}\2", content)
content = re.sub(
r"(<tags><tag>stable</tag></tags>[\s\S]*?<downloadurl[^>]*>)[^<]*(</downloadurl>)",
rf"\g<1>{docker_img}\2", content)
# Update stable channel — match the <update> block containing <tag>stable</tag>
def replace_channel(xml, channel, ver, url, docker):
pattern = rf"(<update>\s*<name>MokoGitea</name>[\s\S]*?<tags><tag>{channel}</tag></tags>[\s\S]*?</update>)"
def replacer(m):
block = m.group(1)
block = re.sub(r"<version>[^<]*</version>", f"<version>{ver}</version>", block)
block = re.sub(r"(<infourl[^>]*>)[^<]*(</infourl>)", rf"\1{url}\2", block)
block = re.sub(r"(<downloadurl[^>]*>)[^<]*(</downloadurl>)", rf"\1{docker}\2", block)
return block
return re.sub(pattern, replacer, xml)
# Also update VERSION comment at top
content = replace_channel(content, "stable", moko_ver, release_url, docker_img)
content = re.sub(r"VERSION: [^\n]*", f"VERSION: {moko_ver}", content)
# Push updated file
Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

-118
View File
@@ -1,118 +0,0 @@
// Copyright 2026 Moko Consulting <hello@mokoconsulting.tech>
// SPDX-License-Identifier: GPL-3.0-or-later
package admin
import (
"io"
"net/http"
"os"
"path/filepath"
"git.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/log"
"git.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/setting"
"git.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/templates"
"git.mokoconsulting.tech/MokoConsulting/MokoGitea/services/context"
)
const tplBranding templates.TplName = "admin/branding"
// brandingImageDir returns the path to the custom branding images directory.
func brandingImageDir() string {
return filepath.Join(setting.CustomPath, "public", "assets", "img")
}
// Branding shows the admin branding page.
func Branding(ctx *context.Context) {
ctx.Data["Title"] = "Branding"
ctx.Data["PageIsAdminBranding"] = true
imgDir := brandingImageDir()
ctx.Data["HasNavIcon"] = fileExists(filepath.Join(imgDir, "logo-small.png"))
ctx.Data["HasLogo"] = fileExists(filepath.Join(imgDir, "logo.png"))
ctx.Data["HasFavicon"] = fileExists(filepath.Join(imgDir, "favicon.png"))
ctx.HTML(http.StatusOK, tplBranding)
}
// BrandingUpload handles branding image uploads.
func BrandingUpload(ctx *context.Context) {
imageType := ctx.FormString("type")
if imageType == "" {
ctx.Flash.Error("No image type specified")
ctx.Redirect(setting.AppSubURL + "/-/admin/branding")
return
}
var filename string
switch imageType {
case "nav-icon":
filename = "logo-small.png"
case "logo":
filename = "logo.png"
case "favicon":
filename = "favicon.png"
default:
ctx.Flash.Error("Invalid image type: " + imageType)
ctx.Redirect(setting.AppSubURL + "/-/admin/branding")
return
}
file, header, err := ctx.Req.FormFile("file")
if err != nil {
ctx.Flash.Error("Upload failed: " + err.Error())
ctx.Redirect(setting.AppSubURL + "/-/admin/branding")
return
}
defer file.Close()
// Validate file size (max 2MB)
if header.Size > 2*1024*1024 {
ctx.Flash.Error("File too large (max 2MB)")
ctx.Redirect(setting.AppSubURL + "/-/admin/branding")
return
}
// Ensure the custom image directory exists
imgDir := brandingImageDir()
if err := os.MkdirAll(imgDir, 0o755); err != nil {
ctx.Flash.Error("Failed to create image directory")
log.Error("MkdirAll %s: %v", imgDir, err)
ctx.Redirect(setting.AppSubURL + "/-/admin/branding")
return
}
// Write the file
destPath := filepath.Join(imgDir, filename)
dest, err := os.Create(destPath)
if err != nil {
ctx.Flash.Error("Failed to save image")
log.Error("Create %s: %v", destPath, err)
ctx.Redirect(setting.AppSubURL + "/-/admin/branding")
return
}
defer dest.Close()
if _, err := io.Copy(dest, file); err != nil {
ctx.Flash.Error("Failed to write image")
log.Error("Copy to %s: %v", destPath, err)
ctx.Redirect(setting.AppSubURL + "/-/admin/branding")
return
}
// Also remove SVG override if present (PNG should take priority)
svgPath := filepath.Join(imgDir, filename[:len(filename)-4]+".svg")
if fileExists(svgPath) {
os.Remove(svgPath)
log.Info("Removed SVG override: %s", svgPath)
}
ctx.Flash.Success("Branding image updated: " + imageType)
log.Info("Branding image uploaded: %s (%d bytes)", filename, header.Size)
ctx.Redirect(setting.AppSubURL + "/-/admin/branding")
}
func fileExists(path string) bool {
_, err := os.Stat(path)
return err == nil
}
-5
View File
@@ -765,11 +765,6 @@ func registerWebRoutes(m *web.Router, webAuth *AuthMiddleware) {
m.Get("/settings", admin.ConfigSettings)
})
m.Group("/branding", func() {
m.Get("", admin.Branding)
m.Post("/upload", admin.BrandingUpload)
})
m.Group("/monitor", func() {
m.Get("/stats", admin.MonitorStats)
m.Get("/cron", admin.CronTasks)
-78
View File
@@ -1,78 +0,0 @@
{{template "admin/layout_head" (dict "pageClass" "admin branding")}}
<div class="admin-setting-content">
<h4 class="ui top attached header">
{{svg "octicon-paintbrush" 16}} Branding
</h4>
<div class="ui attached segment">
<p>Upload custom branding images. Changes take effect immediately — no restart required.</p>
<div class="ui three stackable cards">
<!-- Nav Icon -->
<div class="card">
<div class="content">
<div class="header">Nav Icon</div>
<div class="meta">Top-left corner (30x30px recommended)</div>
</div>
<div class="image tw-p-4 tw-text-center" style="background: var(--color-body);">
<img src="{{AssetUrlPrefix}}/img/logo-small.png?v={{ctx.CspScriptNonce}}" style="max-height: 64px;" onerror="this.src='{{AssetUrlPrefix}}/img/logo.png'">
</div>
<div class="extra content">
<form method="post" action="{{AppSubUrl}}/-/admin/branding/upload" enctype="multipart/form-data">
{{.CsrfTokenHtml}}
<input type="hidden" name="type" value="nav-icon">
<div class="ui action input tw-w-full">
<input type="file" name="file" accept="image/png,image/svg+xml" required>
<button type="submit" class="ui primary button">{{svg "octicon-upload" 14}} Upload</button>
</div>
</form>
{{if .HasNavIcon}}<span class="ui green label tw-mt-2">Custom</span>{{else}}<span class="ui grey label tw-mt-2">Default</span>{{end}}
</div>
</div>
<!-- Login Logo -->
<div class="card">
<div class="content">
<div class="header">Login Logo</div>
<div class="meta">Login page and homepage (wide format recommended)</div>
</div>
<div class="image tw-p-4 tw-text-center" style="background: var(--color-body);">
<img src="{{AssetUrlPrefix}}/img/logo.png?v={{ctx.CspScriptNonce}}" style="max-height: 64px;">
</div>
<div class="extra content">
<form method="post" action="{{AppSubUrl}}/-/admin/branding/upload" enctype="multipart/form-data">
{{.CsrfTokenHtml}}
<input type="hidden" name="type" value="logo">
<div class="ui action input tw-w-full">
<input type="file" name="file" accept="image/png,image/svg+xml" required>
<button type="submit" class="ui primary button">{{svg "octicon-upload" 14}} Upload</button>
</div>
</form>
{{if .HasLogo}}<span class="ui green label tw-mt-2">Custom</span>{{else}}<span class="ui grey label tw-mt-2">Default</span>{{end}}
</div>
</div>
<!-- Favicon -->
<div class="card">
<div class="content">
<div class="header">Favicon</div>
<div class="meta">Browser tab icon (256x256px recommended)</div>
</div>
<div class="image tw-p-4 tw-text-center" style="background: var(--color-body);">
<img src="{{AssetUrlPrefix}}/img/favicon.png?v={{ctx.CspScriptNonce}}" style="max-height: 64px;">
</div>
<div class="extra content">
<form method="post" action="{{AppSubUrl}}/-/admin/branding/upload" enctype="multipart/form-data">
{{.CsrfTokenHtml}}
<input type="hidden" name="type" value="favicon">
<div class="ui action input tw-w-full">
<input type="file" name="file" accept="image/png,image/svg+xml,image/x-icon" required>
<button type="submit" class="ui primary button">{{svg "octicon-upload" 14}} Upload</button>
</div>
</form>
{{if .HasFavicon}}<span class="ui green label tw-mt-2">Custom</span>{{else}}<span class="ui grey label tw-mt-2">Default</span>{{end}}
</div>
</div>
</div>
</div>
</div>
{{template "admin/layout_tail" .}}
-3
View File
@@ -84,9 +84,6 @@
</div>
</details>
{{end}}
<a class="{{if .PageIsAdminBranding}}active {{end}}item" href="{{AppSubUrl}}/-/admin/branding">
{{svg "octicon-paintbrush" 16}} Branding
</a>
<details class="item toggleable-item" {{if or .PageIsAdminConfig}}open{{end}}>
<summary>{{ctx.Locale.Tr "admin.config"}}</summary>
<div class="menu">
+1 -1
View File
@@ -2,7 +2,7 @@
<div class="navbar-left">
<!-- the logo -->
<a class="item" id="navbar-logo" href="{{AppSubUrl}}/" aria-label="{{if .IsSigned}}{{ctx.Locale.Tr "dashboard"}}{{else}}{{ctx.Locale.Tr "home_title"}}{{end}}">
<img width="30" height="30" src="{{AssetUrlPrefix}}/img/logo-small.png" alt="{{ctx.Locale.Tr "logo"}}" aria-hidden="true" onerror="this.src='{{AssetUrlPrefix}}/img/logo.png'">
<img width="30" height="30" src="https://mokoconsulting.tech/images/branding/logo.png" alt="{{ctx.Locale.Tr "logo"}}" aria-hidden="true">
</a>
<!-- mobile right menu, it must be here because in mobile view, each item is a flex column, the first item is a full row column -->
+1 -1
View File
@@ -2,7 +2,7 @@
<div role="main" aria-label="{{if .IsSigned}}{{ctx.Locale.Tr "dashboard"}}{{else}}{{ctx.Locale.Tr "home_title"}}{{end}}" class="page-content home">
<div class="tw-mb-8 tw-px-8">
<div class="center">
<img class="logo" width="220" height="220" src="{{AssetUrlPrefix}}/img/logo.png" alt="{{ctx.Locale.Tr "logo"}}">
<img class="logo" width="220" height="220" src="https://mokoconsulting.tech/images/branding/logo.png" alt="{{ctx.Locale.Tr "logo"}}">
<div class="hero">
<h1 class="ui icon header title tw-text-balance">
{{AppName}}
+1 -1
View File
@@ -21,7 +21,7 @@
<div class="ui container tw-flex">
<div class="item tw-flex-1">
<a href="{{AppSubUrl}}/" aria-label="{{ctx.Locale.Tr "home_title"}}">
<img width="30" height="30" src="{{AssetUrlPrefix}}/img/logo-small.png" alt="{{ctx.Locale.Tr "logo"}}" aria-hidden="true">
<img width="30" height="30" src="https://mokoconsulting.tech/images/branding/logo.png" alt="{{ctx.Locale.Tr "logo"}}" aria-hidden="true">
</a>
</div>
<div class="item">
+7 -7
View File
@@ -1,7 +1,7 @@
<?xml version='1.0' encoding='UTF-8'?>
<!-- Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
SPDX-License-Identifier: GPL-3.0-or-later
VERSION: 05.00.00
VERSION: 05.01.02
-->
<updates>
@@ -10,12 +10,12 @@
<description>MokoGitea update</description>
<element>mokogitea</element>
<type>application</type>
<version>05.00.00</version>
<version>05.01.02</version>
<client>server</client>
<tags><tag>stable</tag></tags>
<infourl title="MokoGitea">https://git.mokoconsulting.tech/MokoConsulting/MokoGitea/releases/tag/v1.26.1-moko.05.00.00</infourl>
<infourl title="MokoGitea">https://git.mokoconsulting.tech/MokoConsulting/MokoGitea/releases/tag/v1.26.1-moko.05.01.02</infourl>
<downloads>
<downloadurl type="full" format="docker">git.mokoconsulting.tech/mokoconsulting/mokogitea:v1.26.1-moko.05.00.00</downloadurl>
<downloadurl type="full" format="docker">git.mokoconsulting.tech/mokoconsulting/mokogitea:v1.26.1-moko.05.01.02</downloadurl>
</downloads>
<sha256></sha256>
<targetplatform name="mokogitea" version="((1\.25\.)|(1\.26\.))" />
@@ -27,12 +27,12 @@
<description>MokoGitea update</description>
<element>mokogitea</element>
<type>application</type>
<version>05.00.00</version>
<version>05.01.02</version>
<client>server</client>
<tags><tag>rc</tag></tags>
<infourl title="MokoGitea RC">https://git.mokoconsulting.tech/MokoConsulting/MokoGitea/releases/tag/v1.26.1-moko.05.00.00</infourl>
<infourl title="MokoGitea RC">https://git.mokoconsulting.tech/MokoConsulting/MokoGitea/releases/tag/v1.26.1-moko.05.01.02</infourl>
<downloads>
<downloadurl type="full" format="docker">git.mokoconsulting.tech/mokoconsulting/mokogitea:v1.26.1-moko.05.00.00</downloadurl>
<downloadurl type="full" format="docker">git.mokoconsulting.tech/mokoconsulting/mokogitea:v1.26.1-moko.05.01.02</downloadurl>
</downloads>
<sha256></sha256>
<targetplatform name="mokogitea" version="((1\.25\.)|(1\.26\.))" />
+1 -7
View File
@@ -9,8 +9,8 @@ details.toggleable-item .menu {
details.toggleable-item summary {
display: flex;
justify-content: space-between;
align-items: center;
gap: 0.5em;
padding: 0.92857143em 1.14285714em;
}
@@ -20,7 +20,6 @@ details.toggleable-item summary::-webkit-details-marker /* Safari */ {
}
details.toggleable-item summary::after {
margin-left: auto;
transition: transform 0.25s ease;
content: "";
width: 14px;
@@ -36,8 +35,3 @@ details.toggleable-item summary::after {
details.toggleable-item[open] summary::after {
transform: rotate(90deg);
}
.flex-container-nav .ui.menu .item {
text-align: left !important;
justify-content: flex-start !important;
}