Compare commits

..

12 Commits

Author SHA1 Message Date
jmiller bec7b70ff5 Merge branch 'main' into dev
Universal: PR Check / Build RC Package (pull_request) Blocked by required conditions
Branch Policy Check / Verify merge target (pull_request) Successful in 5s
Universal: PR Check / Branch Policy (pull_request) Successful in 5s
Universal: PR Check / Validate PR (pull_request) Failing after 9s
Branch Cleanup / Delete merged branch (pull_request) Has been skipped
PR RC Release / Build RC Release (pull_request) Successful in 29s
2026-05-31 04:22:29 +00:00
jmiller 92b4cd61c2 Merge pull request 'fix(ui): details/summary toggle for create package' (#294) from fix/admin-delete-only into dev
Universal: PR Check / Build RC Package (pull_request) Blocked by required conditions
Branch Policy Check / Verify merge target (pull_request) Successful in 5s
Universal: PR Check / Branch Policy (pull_request) Successful in 6s
Universal: PR Check / Validate PR (pull_request) Failing after 18s
PR RC Release / Build RC Release (pull_request) Successful in 32s
2026-05-31 04:22:08 +00:00
Jonathan Miller dc2647977c fix(ui): use HTML details/summary for package create toggle
Universal: PR Check / Build RC Package (pull_request) Blocked by required conditions
Branch Policy Check / Verify merge target (pull_request) Successful in 2s
Universal: PR Check / Branch Policy (pull_request) Successful in 2s
PR RC Release / Build RC Release (pull_request) Successful in 5s
Universal: PR Check / Validate PR (pull_request) Failing after 11s
Branch Cleanup / Delete merged branch (pull_request) Successful in 4s
Replace broken JavaScript onclick toggle with native HTML
<details>/<summary> element. Works without JS, accessible,
and styled as a Fomantic UI button.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-30 23:21:42 -05:00
jmiller 685d89acf9 Merge pull request 'chore: merge dev into main — admin permissions' (#293) from dev into main
Deploy MokoGitea / deploy (push) Failing after 4m9s
2026-05-31 04:19:17 +00:00
jmiller 8ceddefbdb Merge branch 'main' into dev
Universal: PR Check / Build RC Package (pull_request) Blocked by required conditions
Branch Policy Check / Verify merge target (pull_request) Successful in 1s
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Universal: PR Check / Validate PR (pull_request) Failing after 7s
Branch Cleanup / Delete merged branch (pull_request) Has been skipped
PR RC Release / Build RC Release (pull_request) Successful in 25s
2026-05-31 04:19:04 +00:00
jmiller ea2666948e Merge pull request 'feat(permissions): site admin only for delete' (#292) from fix/admin-delete-only into dev
Universal: PR Check / Build RC Package (pull_request) Blocked by required conditions
Branch Policy Check / Verify merge target (pull_request) Successful in 2s
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Universal: PR Check / Validate PR (pull_request) Failing after 6s
PR RC Release / Build RC Release (pull_request) Successful in 25s
2026-05-31 04:18:53 +00:00
Jonathan Miller 3aabd1b1f9 feat(permissions): only site admins can delete license packages
Universal: PR Check / Build RC Package (pull_request) Blocked by required conditions
Branch Policy Check / Verify merge target (pull_request) Successful in 1s
Universal: PR Check / Branch Policy (pull_request) Successful in 2s
PR RC Release / Build RC Release (pull_request) Successful in 2s
Universal: PR Check / Validate PR (pull_request) Failing after 6s
Branch Cleanup / Delete merged branch (pull_request) Successful in 1s
- Delete button only visible to site admins (super admins)
- Delete handler checks ctx.IsUserSiteAdmin() and returns 404 otherwise
- Repo admins can still create, edit, revoke — but not delete
- IsSiteAdmin set in both repo and org context data

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-30 23:18:31 -05:00
jmiller 0328258529 Merge pull request 'chore: merge dev into main — org update streams' (#291) from dev into main
Deploy MokoGitea / deploy (push) Failing after 4m3s
2026-05-31 04:09:53 +00:00
jmiller e6ff9a99f9 Merge branch 'main' into dev
Universal: PR Check / Build RC Package (pull_request) Blocked by required conditions
Branch Policy Check / Verify merge target (pull_request) Successful in 1s
Universal: PR Check / Branch Policy (pull_request) Successful in 2s
Universal: PR Check / Validate PR (pull_request) Failing after 6s
Branch Cleanup / Delete merged branch (pull_request) Has been skipped
PR RC Release / Build RC Release (pull_request) Successful in 24s
2026-05-31 04:09:42 +00:00
jmiller 4138ab7d47 Merge pull request 'feat(org): Update Streams settings page + package edit/delete' (#290) from feat/repo-update-settings into dev
Universal: PR Check / Build RC Package (pull_request) Blocked by required conditions
Branch Policy Check / Verify merge target (pull_request) Successful in 2s
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Universal: PR Check / Validate PR (pull_request) Failing after 7s
PR RC Release / Build RC Release (pull_request) Successful in 25s
2026-05-31 04:09:30 +00:00
Jonathan Miller d75e648970 feat(org): add Update Streams settings page in org settings
Universal: PR Check / Build RC Package (pull_request) Blocked by required conditions
Branch Policy Check / Verify merge target (pull_request) Successful in 1s
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
PR RC Release / Build RC Release (pull_request) Successful in 2s
Universal: PR Check / Validate PR (pull_request) Failing after 5s
Branch Cleanup / Delete merged branch (pull_request) Successful in 2s
Add "Licenses & Update Streams" tab to org settings sidebar with:
- Stream mode: Joomla standard or Custom
- Active streams table showing name, suffix, description
- Custom streams JSON editor
- Saves org-level defaults that repos inherit

Ref #265

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-30 23:09:07 -05:00
jmiller 06a382e82e Merge pull request 'chore: merge dev into main — package edit/delete' (#289) from dev into main
Deploy MokoGitea / deploy (push) Failing after 3m39s
2026-05-31 04:04:41 +00:00
9 changed files with 209 additions and 73 deletions
+1
View File
@@ -67,6 +67,7 @@ func Licenses(ctx *context.Context) {
}
ctx.Data["LicenseKeys"] = keys
ctx.Data["IsRepoAdmin"] = ctx.Org.IsOwner
ctx.Data["IsSiteAdmin"] = ctx.IsUserSiteAdmin()
ctx.HTML(http.StatusOK, tplOrgLicenses)
}
+57
View File
@@ -0,0 +1,57 @@
// Copyright 2026 Moko Consulting <hello@mokoconsulting.tech>
// SPDX-License-Identifier: GPL-3.0-or-later
package org
import (
"net/http"
"git.mokoconsulting.tech/MokoConsulting/MokoGitea/models/licenses"
"git.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/templates"
"git.mokoconsulting.tech/MokoConsulting/MokoGitea/services/context"
)
const tplSettingsUpdateStreams templates.TplName = "org/settings/update_streams"
// SettingsUpdateStreams shows the org-level update stream settings.
func SettingsUpdateStreams(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("org.settings.update_streams")
ctx.Data["PageIsOrgSettings"] = true
ctx.Data["PageIsSettingsUpdateStreams"] = true
orgID := ctx.Org.Organization.ID
cfg, err := licenses.GetOrgConfig(ctx, orgID)
if err != nil {
ctx.ServerError("GetOrgConfig", err)
return
}
ctx.Data["StreamConfig"] = cfg
ctx.Data["EffectiveStreams"] = cfg.GetActiveStreams()
ctx.HTML(http.StatusOK, tplSettingsUpdateStreams)
}
// SettingsUpdateStreamsPost saves the org-level update stream settings.
func SettingsUpdateStreamsPost(ctx *context.Context) {
orgID := ctx.Org.Organization.ID
cfg := &licenses.UpdateStreamConfig{
OwnerID: orgID,
RepoID: 0,
StreamMode: ctx.FormString("stream_mode"),
CustomStreams: ctx.FormString("custom_streams"),
}
if cfg.StreamMode == "" {
cfg.StreamMode = "joomla"
}
if err := licenses.SaveConfig(ctx, cfg); err != nil {
ctx.ServerError("SaveConfig", err)
return
}
ctx.Flash.Success(ctx.Tr("org.settings.update_streams_saved"))
ctx.Redirect(ctx.Org.OrgLink + "/settings/update-streams")
}
+5 -1
View File
@@ -225,8 +225,12 @@ func LicensesEditPackagePost(ctx *context.Context) {
ctx.Redirect(ctx.Repo.RepoLink + "/licenses")
}
// LicensesDeletePackage deletes a license package.
// LicensesDeletePackage deletes a license package. Site admin only.
func LicensesDeletePackage(ctx *context.Context) {
if !ctx.IsUserSiteAdmin() {
ctx.NotFound(nil)
return
}
pkgID := ctx.PathParamInt64("id")
if err := licenses.DeleteLicensePackage(ctx, pkgID); err != nil {
ctx.ServerError("DeleteLicensePackage", err)
+5
View File
@@ -1057,6 +1057,11 @@ func registerWebRoutes(m *web.Router, webAuth *AuthMiddleware) {
m.Get("", org.BlockedUsers)
m.Post("", web.Bind(forms.BlockUserForm{}), org.BlockedUsersPost)
})
m.Group("/update-streams", func() {
m.Get("", org.SettingsUpdateStreams)
m.Post("", org.SettingsUpdateStreamsPost)
})
}, ctxDataSet("EnableOAuth2", setting.OAuth2.Enabled, "EnablePackages", setting.Packages.Enabled, "PageIsOrgSettings", true))
}, context.OrgAssignment(context.OrgAssignmentOptions{RequireOwner: true}))
}, reqSignIn)
+1
View File
@@ -613,6 +613,7 @@ func repoAssignmentPrepareTemplateData(ctx *Context, data *repoAssignmentPrepare
ctx.Data["NumLicensePackages"] = numLicensePackages
ctx.Data["EnableLicenses"] = numLicensePackages > 0
ctx.Data["IsRepoAdmin"] = ctx.Repo.Permission.IsAdmin()
ctx.Data["IsSiteAdmin"] = ctx.IsUserSiteAdmin()
// Load repo update config for platform-aware UI.
repoUpdateCfg, _ := licenses_model.GetRepoConfig(ctx, repo.ID)
+36 -37
View File
@@ -19,49 +19,48 @@
</div>
{{end}}
<h4 class="ui top attached header tw-flex tw-justify-between tw-items-center">
<span>{{svg "octicon-key" 16}} {{ctx.Locale.Tr "repo.licenses.packages"}}</span>
{{if .IsRepoAdmin}}
<a class="ui small primary button" href="#new-package-form" onclick="this.parentElement.parentElement.nextElementSibling.querySelector('#new-package-form').style.display='block'; return false;">
{{ctx.Locale.Tr "repo.licenses.new_package"}}
</a>
{{end}}
<h4 class="ui top attached header">
{{svg "octicon-key" 16}} {{ctx.Locale.Tr "repo.licenses.packages"}}
</h4>
<div class="ui attached segment">
{{if .IsRepoAdmin}}
<div id="new-package-form" style="display: none;" class="tw-mb-4">
<form class="ui form" method="post" action="{{$.Org.HomeLink}}/-/licenses/packages">
{{.CsrfTokenHtml}}
<div class="two fields">
<div class="required field">
<label>{{ctx.Locale.Tr "repo.licenses.package_name"}}</label>
<input name="name" required placeholder="Pro Annual">
<details class="tw-mb-4">
<summary class="ui primary button">{{svg "octicon-plus" 14}} {{ctx.Locale.Tr "repo.licenses.new_package"}}</summary>
<div class="tw-mt-4">
<form class="ui form" method="post" action="{{$.Org.HomeLink}}/-/licenses/packages">
{{.CsrfTokenHtml}}
<div class="two fields">
<div class="required field">
<label>{{ctx.Locale.Tr "repo.licenses.package_name"}}</label>
<input name="name" required placeholder="e.g. Pro Annual, Basic Monthly">
</div>
<div class="field">
<label>{{ctx.Locale.Tr "repo.licenses.description"}}</label>
<input name="description" placeholder="e.g. Annual pro subscription">
</div>
</div>
<div class="field">
<label>{{ctx.Locale.Tr "repo.licenses.description"}}</label>
<input name="description" placeholder="Annual pro subscription">
<div class="three fields">
<div class="field">
<label>{{ctx.Locale.Tr "repo.licenses.duration"}} ({{ctx.Locale.Tr "repo.licenses.days"}})</label>
<input name="duration_days" type="number" value="0" min="0">
<p class="help">0 = {{ctx.Locale.Tr "repo.licenses.lifetime"}}</p>
</div>
<div class="field">
<label>{{ctx.Locale.Tr "repo.licenses.max_sites"}}</label>
<input name="max_sites" type="number" value="0" min="0">
<p class="help">0 = unlimited</p>
</div>
<div class="field">
<label>{{ctx.Locale.Tr "repo.licenses.channels"}}</label>
<input name="allowed_channels" placeholder="stable,release-candidate">
<p class="help">{{ctx.Locale.Tr "repo.licenses.channels_help"}}</p>
</div>
</div>
</div>
<div class="three fields">
<div class="field">
<label>{{ctx.Locale.Tr "repo.licenses.duration"}} ({{ctx.Locale.Tr "repo.licenses.days"}})</label>
<input name="duration_days" type="number" value="0" min="0">
</div>
<div class="field">
<label>{{ctx.Locale.Tr "repo.licenses.max_sites"}}</label>
<input name="max_sites" type="number" value="0" min="0">
</div>
<div class="field">
<label>{{ctx.Locale.Tr "repo.licenses.channels"}}</label>
<input name="allowed_channels" placeholder="stable,release-candidate">
</div>
</div>
<button class="ui primary button" type="submit">{{ctx.Locale.Tr "repo.licenses.create_package"}}</button>
</form>
<div class="divider"></div>
</div>
<button class="ui primary button" type="submit">{{ctx.Locale.Tr "repo.licenses.create_package"}}</button>
</form>
</div>
</details>
{{end}}
{{if .LicensePackages}}
<table class="ui table">
<thead>
+3
View File
@@ -25,6 +25,9 @@
{{ctx.Locale.Tr "packages.title"}}
</a>
{{end}}
<a class="{{if .PageIsSettingsUpdateStreams}}active {{end}}item" href="{{.OrgLink}}/settings/update-streams">
{{ctx.Locale.Tr "org.settings.update_streams"}}
</a>
{{if .EnableActions}}
<details class="item toggleable-item" {{if or .PageIsOrgSettingsActionsGeneral .PageIsSharedSettingsRunners .PageIsSharedSettingsSecrets .PageIsSharedSettingsVariables}}open{{end}}>
<summary>{{ctx.Locale.Tr "actions.actions"}}</summary>
@@ -0,0 +1,62 @@
{{template "org/settings/layout_head" (dict "pageClass" "organization settings")}}
<div class="org-setting-content">
<h4 class="ui top attached header">
{{ctx.Locale.Tr "org.settings.update_streams"}}
</h4>
<div class="ui attached segment">
<p>{{ctx.Locale.Tr "org.settings.update_streams_desc"}}</p>
<form class="ui form" method="post" action="{{.OrgLink}}/settings/update-streams">
{{.CsrfTokenHtml}}
<div class="grouped fields">
<label>{{ctx.Locale.Tr "org.settings.stream_mode"}}</label>
<div class="field">
<div class="ui radio checkbox">
<input name="stream_mode" type="radio" value="joomla" {{if or (eq .StreamConfig.StreamMode "") (eq .StreamConfig.StreamMode "joomla")}}checked{{end}}>
<label>{{ctx.Locale.Tr "org.settings.stream_mode_joomla"}}</label>
</div>
</div>
<div class="field">
<div class="ui radio checkbox">
<input name="stream_mode" type="radio" value="custom" {{if eq .StreamConfig.StreamMode "custom"}}checked{{end}}>
<label>{{ctx.Locale.Tr "org.settings.stream_mode_custom"}}</label>
</div>
</div>
</div>
<div class="field">
<label>{{ctx.Locale.Tr "org.settings.default_streams"}}</label>
<p class="help">{{ctx.Locale.Tr "org.settings.default_streams_joomla"}}</p>
<table class="ui small table">
<thead>
<tr>
<th>{{ctx.Locale.Tr "org.settings.stream_name"}}</th>
<th>{{ctx.Locale.Tr "org.settings.stream_suffix"}}</th>
<th>{{ctx.Locale.Tr "repo.licenses.description"}}</th>
</tr>
</thead>
<tbody>
{{range .EffectiveStreams}}
<tr>
<td><code>{{.Name}}</code></td>
<td>{{if .Suffix}}<code>{{.Suffix}}</code>{{else}}<em>(no suffix)</em>{{end}}</td>
<td>{{.Description}}</td>
</tr>
{{end}}
</tbody>
</table>
</div>
<div class="field">
<label>{{ctx.Locale.Tr "org.settings.custom_streams"}}</label>
<textarea name="custom_streams" rows="6" placeholder='[{"name":"lts","suffix":"-lts","description":"Long-term support"}]'>{{.StreamConfig.CustomStreams}}</textarea>
<p class="help">{{ctx.Locale.Tr "org.settings.custom_streams_help"}}</p>
</div>
<div class="field">
<button class="ui primary button">{{ctx.Locale.Tr "save"}}</button>
</div>
</form>
</div>
</div>
{{template "org/settings/layout_footer" .}}
+39 -35
View File
@@ -56,12 +56,14 @@
<a class="ui tiny button" href="{{$.RepoLink}}/licenses/packages/{{.ID}}/edit" title="{{ctx.Locale.Tr "repo.licenses.edit_package"}}">
{{svg "octicon-pencil" 14}}
</a>
<form method="post" action="{{$.RepoLink}}/licenses/packages/{{.ID}}/delete" class="tw-inline" onsubmit="return confirm('Delete this package?')">
{{if $.IsSiteAdmin}}
<form method="post" action="{{$.RepoLink}}/licenses/packages/{{.ID}}/delete" class="tw-inline" onsubmit="return confirm('Delete this package? This action cannot be undone.')">
{{$.CsrfTokenHtml}}
<button class="ui tiny red button" type="submit" title="{{ctx.Locale.Tr "repo.licenses.delete_package"}}">
{{svg "octicon-trash" 14}}
</button>
</form>
{{end}}
</td>
{{end}}
</tr>
@@ -79,41 +81,43 @@
{{/* Create New License Package */}}
{{if .IsRepoAdmin}}
<h4 class="ui top attached header tw-mt-4">
{{svg "octicon-plus" 16}} {{ctx.Locale.Tr "repo.licenses.create_new_package"}}
</h4>
<div class="ui attached segment">
<form class="ui form" method="post" action="{{.RepoLink}}/licenses/packages">
{{.CsrfTokenHtml}}
<div class="two fields">
<div class="required field">
<label>{{ctx.Locale.Tr "repo.licenses.package_name"}}</label>
<input name="name" required placeholder="e.g. Pro Annual, Basic Monthly">
</div>
<div class="field">
<label>{{ctx.Locale.Tr "repo.licenses.description"}}</label>
<input name="description" placeholder="e.g. Annual pro subscription with all channels">
</div>
<div class="tw-mt-4">
<details>
<summary class="ui primary button">{{svg "octicon-plus" 14}} {{ctx.Locale.Tr "repo.licenses.new_package"}}</summary>
<div class="ui segment tw-mt-2">
<form class="ui form" method="post" action="{{.RepoLink}}/licenses/packages">
{{.CsrfTokenHtml}}
<div class="two fields">
<div class="required field">
<label>{{ctx.Locale.Tr "repo.licenses.package_name"}}</label>
<input name="name" required placeholder="e.g. Pro Annual, Basic Monthly">
</div>
<div class="field">
<label>{{ctx.Locale.Tr "repo.licenses.description"}}</label>
<input name="description" placeholder="e.g. Annual pro subscription with all channels">
</div>
</div>
<div class="three fields">
<div class="field">
<label>{{ctx.Locale.Tr "repo.licenses.duration"}} ({{ctx.Locale.Tr "repo.licenses.days"}})</label>
<input name="duration_days" type="number" value="0" min="0">
<p class="help">0 = {{ctx.Locale.Tr "repo.licenses.lifetime"}}</p>
</div>
<div class="field">
<label>{{ctx.Locale.Tr "repo.licenses.max_sites"}}</label>
<input name="max_sites" type="number" value="0" min="0">
<p class="help">0 = unlimited</p>
</div>
<div class="field">
<label>{{ctx.Locale.Tr "repo.licenses.channels"}}</label>
<input name="allowed_channels" placeholder="stable,release-candidate">
<p class="help">{{ctx.Locale.Tr "repo.licenses.channels_help"}}</p>
</div>
</div>
<button class="ui primary button" type="submit">{{ctx.Locale.Tr "repo.licenses.create_package"}}</button>
</form>
</div>
<div class="three fields">
<div class="field">
<label>{{ctx.Locale.Tr "repo.licenses.duration"}} ({{ctx.Locale.Tr "repo.licenses.days"}})</label>
<input name="duration_days" type="number" value="0" min="0">
<p class="help">0 = {{ctx.Locale.Tr "repo.licenses.lifetime"}}</p>
</div>
<div class="field">
<label>{{ctx.Locale.Tr "repo.licenses.max_sites"}}</label>
<input name="max_sites" type="number" value="0" min="0">
<p class="help">0 = unlimited</p>
</div>
<div class="field">
<label>{{ctx.Locale.Tr "repo.licenses.channels"}}</label>
<input name="allowed_channels" placeholder="stable,release-candidate">
<p class="help">{{ctx.Locale.Tr "repo.licenses.channels_help"}}</p>
</div>
</div>
<button class="ui primary button" type="submit">{{ctx.Locale.Tr "repo.licenses.create_package"}}</button>
</form>
</details>
</div>
{{end}}