Compare commits
16 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f3ce51d629 | |||
| 1f505b48c7 | |||
| afda7abcbe | |||
| 798d9c3ae0 | |||
| e0f22dd397 | |||
| ecda05aa46 | |||
| 9eadade2f4 | |||
| 2cc4f7c047 | |||
| 74a5fe2b80 | |||
| 50c472991a | |||
| 7dabf844a8 | |||
| 7d03541201 | |||
| d4a2c33c37 | |||
| e59290802a | |||
| 1d857d8205 | |||
| 4f9aeb7b85 |
@@ -445,9 +445,10 @@ server.tool(
|
||||
assignees: z.array(z.string()).optional().describe('Usernames to assign'),
|
||||
status_id: z.number().optional().describe('Custom status definition ID'),
|
||||
priority_id: z.number().optional().describe('Custom priority definition ID'),
|
||||
type_id: z.number().optional().describe('Custom type definition ID'),
|
||||
...ConnectionParam,
|
||||
},
|
||||
async ({ owner, repo, title, body: issueBody, labels, milestone, assignees, status_id, priority_id, connection }) => {
|
||||
async ({ owner, repo, title, body: issueBody, labels, milestone, assignees, status_id, priority_id, type_id, connection }) => {
|
||||
const c = clientFor(connection);
|
||||
|
||||
// Search for existing issue with same title to prevent duplicates
|
||||
@@ -483,6 +484,7 @@ server.tool(
|
||||
if (issueData?.id) {
|
||||
if (status_id !== undefined) await c.post(`/repos/${owner}/${repo}/issues/${issueData.id}/custom-status`, { status_id });
|
||||
if (priority_id !== undefined) await c.post(`/repos/${owner}/${repo}/issues/${issueData.id}/custom-priority`, { priority_id });
|
||||
if (type_id !== undefined) await c.post(`/repos/${owner}/${repo}/issues/${issueData.id}/custom-type`, { type_id });
|
||||
}
|
||||
const out = formatResponse(res);
|
||||
out.content[0].text = `Updated existing issue #${existing.number} (duplicate prevented)\n${out.content[0].text}`;
|
||||
@@ -501,6 +503,7 @@ server.tool(
|
||||
if (newIssue?.id) {
|
||||
if (status_id !== undefined) await c.post(`/repos/${owner}/${repo}/issues/${newIssue.id}/custom-status`, { status_id });
|
||||
if (priority_id !== undefined) await c.post(`/repos/${owner}/${repo}/issues/${newIssue.id}/custom-priority`, { priority_id });
|
||||
if (type_id !== undefined) await c.post(`/repos/${owner}/${repo}/issues/${newIssue.id}/custom-type`, { type_id });
|
||||
}
|
||||
return formatResponse(res);
|
||||
},
|
||||
@@ -2006,6 +2009,52 @@ server.tool(
|
||||
},
|
||||
);
|
||||
|
||||
// ── Issue Types (org-level) ──────────────────────────────────────────────
|
||||
|
||||
server.tool(
|
||||
'gitea_org_issue_types_list',
|
||||
'List custom issue type definitions for an organization',
|
||||
{
|
||||
org: z.string().describe('Organization name'),
|
||||
...ConnectionParam,
|
||||
},
|
||||
async ({ org, connection }) => {
|
||||
const c = clientFor(connection);
|
||||
return formatResponse(await c.get(`/orgs/${org}/issue-types`));
|
||||
},
|
||||
);
|
||||
|
||||
server.tool(
|
||||
'gitea_issue_set_type',
|
||||
'Set custom type on an issue',
|
||||
{
|
||||
owner: z.string().describe('Repository owner'),
|
||||
repo: z.string().describe('Repository name'),
|
||||
issue_id: z.number().describe('Internal issue ID'),
|
||||
type_id: z.number().describe('Type definition ID (0 to clear)'),
|
||||
...ConnectionParam,
|
||||
},
|
||||
async ({ owner, repo, issue_id, type_id, connection }) => {
|
||||
const c = clientFor(connection);
|
||||
return formatResponse(await c.post(`/repos/${owner}/${repo}/issues/${issue_id}/custom-type`, { type_id }));
|
||||
},
|
||||
);
|
||||
|
||||
// ── Security ────────────────────────────────────────────────────────────
|
||||
|
||||
server.tool(
|
||||
'gitea_security_alerts',
|
||||
'List security alerts for a repository',
|
||||
{
|
||||
...OwnerRepo,
|
||||
...ConnectionParam,
|
||||
},
|
||||
async ({ owner, repo, connection }) => {
|
||||
const c = clientFor(connection);
|
||||
return formatResponse(await c.get(`/repos/${owner}/${repo}/security/alerts`));
|
||||
},
|
||||
);
|
||||
|
||||
// ── Start Server ────────────────────────────────────────────────────────
|
||||
|
||||
async function main(): Promise<void> {
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
name: Publish MCP to npm
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
paths:
|
||||
- '.mokogitea/mcp/**'
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
registry-url: 'https://registry.npmjs.org'
|
||||
|
||||
- name: Install and build
|
||||
working-directory: .mokogitea/mcp
|
||||
run: |
|
||||
npm ci
|
||||
npx tsc
|
||||
|
||||
- name: Check version change
|
||||
id: version
|
||||
working-directory: .mokogitea/mcp
|
||||
run: |
|
||||
LOCAL_VERSION=$(node -p "require('./package.json').version")
|
||||
NPM_VERSION=$(npm view @mokoconsulting/mokogitea-mcp version 2>/dev/null || echo "0.0.0")
|
||||
if [ "$LOCAL_VERSION" != "$NPM_VERSION" ]; then
|
||||
echo "changed=true" >> $GITHUB_OUTPUT
|
||||
echo "Version changed: $NPM_VERSION -> $LOCAL_VERSION"
|
||||
else
|
||||
echo "changed=false" >> $GITHUB_OUTPUT
|
||||
echo "Version unchanged: $LOCAL_VERSION"
|
||||
fi
|
||||
|
||||
- name: Publish to npm
|
||||
if: steps.version.outputs.changed == 'true'
|
||||
working-directory: .mokogitea/mcp
|
||||
run: npm publish --access public
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
|
||||
- name: Publish to Gitea registry
|
||||
if: steps.version.outputs.changed == 'true'
|
||||
working-directory: .mokogitea/mcp
|
||||
run: |
|
||||
npm publish --registry ${{ github.server_url }}/api/packages/${{ github.repository_owner }}/npm/ \
|
||||
--//$(echo "${{ github.server_url }}" | sed 's|https://||')/api/packages/${{ github.repository_owner }}/npm/:_authToken=${{ secrets.GITEA_TOKEN }}
|
||||
@@ -3,6 +3,46 @@
|
||||
All notable changes to MokoGitea are documented here. Versions follow the format
|
||||
`v{upstream}-moko.{major}.{minor}` (e.g. `v1.26.1-moko.06.03`).
|
||||
|
||||
## [v1.26.1-moko.06.10] - 2026-06-06
|
||||
|
||||
* FEATURES
|
||||
* feat(issues): first-class Type field with 12 auto-seeded defaults (Bug, Feature, Enhancement, Task, Documentation, Security, Roadmap, Client, Dolibarr, Infrastructure, Joomla, WaaS)
|
||||
* feat(issues): first-class Status field with 13 auto-seeded defaults including 7 Pending states
|
||||
* feat(issues): first-class Priority field with 4 auto-seeded defaults (Critical, High, Medium, Low)
|
||||
* feat(issues): Type/Status/Priority colored badges in issue list view
|
||||
* feat(issues): status dropdown replaces close/reopen button in comment form
|
||||
* feat(security): built-in security scanning platform with secret scanner (15 patterns)
|
||||
* feat(security): Security tab in repo navigation with alerts, scan controls
|
||||
* feat(wiki): hierarchical folder navigation with sidebar tree and breadcrumbs
|
||||
* feat(ui): well-known file tabs (README/LICENSE/CONTRIBUTING/SECURITY/CHANGELOG)
|
||||
* feat(settings): repo manifest settings with REST API and auto-sync on push
|
||||
* feat(mcp): public MCP server published to npm (@mokoconsulting/mokogitea-mcp)
|
||||
* feat(mcp): SSE transport, env var config, Docker support, 120+ tools
|
||||
* feat(mcp): issue dedup on create, type_id/status_id/priority_id params
|
||||
|
||||
* MIGRATIONS
|
||||
* All org labels migrated to first-class Type/Status/Priority fields and deleted
|
||||
* Type custom field (id=9) migrated to type_id and deleted
|
||||
* Status custom field (id=1) deleted (replaced by first-class field)
|
||||
* Priority labels migrated to priority_id
|
||||
* Pending labels migrated to status definitions
|
||||
* Scope labels migrated to type definitions
|
||||
* Manifests populated for all 61 repos via API
|
||||
|
||||
* FIXES
|
||||
* fix(ui): dashboard issue count badges use label spans instead of strong tags
|
||||
* fix(wiki): directory check before raw redirect for folder navigation
|
||||
* fix(wiki): proper display names in sidebar tree (strip dash markers)
|
||||
* fix: replace non-ASCII em dashes with hyphens for hook compatibility
|
||||
* fix: hookify __init__.py for stop hook JSON validation
|
||||
|
||||
* INFRASTRUCTURE
|
||||
* npm: @mokoconsulting/mokogitea-mcp@1.1.0 and @mokoconsulting/mokowaas-mcp@1.0.0
|
||||
* MCP servers consolidated under moko-platform/mcp/servers/
|
||||
* Remote MCP repos renamed to hyphens
|
||||
* Wiki restructured into features/, api/, operations/ folders
|
||||
* Swagger API docs enabled at /api/swagger
|
||||
|
||||
## [v1.26.1-moko.06.04] - 2026-06-06
|
||||
|
||||
* FEATURES
|
||||
|
||||
@@ -2992,6 +2992,18 @@
|
||||
"org.settings.issue_priority_created": "Issue priority created.",
|
||||
"org.settings.issue_priority_updated": "Issue priority updated.",
|
||||
"org.settings.issue_priority_deleted": "Issue priority deleted.",
|
||||
"org.settings.issue_types": "Issue Types",
|
||||
"org.settings.issue_types_desc": "Define issue types for all repositories in this organization.",
|
||||
"org.settings.issue_types_empty": "No custom issue types defined yet.",
|
||||
"org.settings.issue_type_add": "Add Type",
|
||||
"org.settings.issue_type_name": "Type Name",
|
||||
"org.settings.issue_type_color": "Color",
|
||||
"org.settings.issue_type_description": "Description",
|
||||
"org.settings.issue_type_default": "Default",
|
||||
"org.settings.issue_type_sort_order": "Sort Order",
|
||||
"org.settings.issue_type_created": "Issue type created.",
|
||||
"org.settings.issue_type_updated": "Issue type updated.",
|
||||
"org.settings.issue_type_deleted": "Issue type deleted.",
|
||||
"org.settings.update_streams": "Update Server",
|
||||
"org.settings.licensing": "Update Server",
|
||||
"org.settings.licensing_desc": "Manage update feeds and optional license key gating across all repositories in this organization.",
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
// Copyright 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
package org
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
issues_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/issues"
|
||||
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/templates"
|
||||
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/services/context"
|
||||
)
|
||||
|
||||
const tplOrgIssueTypes templates.TplName = "org/settings/issue_types"
|
||||
|
||||
func SettingsIssueTypes(ctx *context.Context) {
|
||||
ctx.Data["Title"] = ctx.Tr("org.settings.issue_types")
|
||||
ctx.Data["PageIsOrgSettings"] = true
|
||||
ctx.Data["PageIsSettingsIssueTypes"] = true
|
||||
|
||||
defs, err := issues_model.GetAllIssueTypeDefsByOrg(ctx, ctx.Org.Organization.ID)
|
||||
if err != nil {
|
||||
ctx.ServerError("GetAllIssueTypeDefsByOrg", err)
|
||||
return
|
||||
}
|
||||
ctx.Data["IssueTypes"] = defs
|
||||
ctx.HTML(http.StatusOK, tplOrgIssueTypes)
|
||||
}
|
||||
|
||||
func SettingsIssueTypesCreatePost(ctx *context.Context) {
|
||||
sortOrder, _ := strconv.Atoi(ctx.FormString("sort_order"))
|
||||
def := &issues_model.IssueTypeDef{
|
||||
OrgID: ctx.Org.Organization.ID,
|
||||
Name: ctx.FormString("name"),
|
||||
Color: ctx.FormString("color"),
|
||||
Description: ctx.FormString("description"),
|
||||
SortOrder: sortOrder,
|
||||
IsDefault: ctx.FormString("is_default") == "on",
|
||||
IsActive: true,
|
||||
}
|
||||
if def.Name == "" {
|
||||
ctx.Flash.Error("Type name is required")
|
||||
ctx.Redirect(ctx.Org.OrgLink + "/settings/issue-types")
|
||||
return
|
||||
}
|
||||
if err := issues_model.CreateIssueTypeDef(ctx, def); err != nil {
|
||||
ctx.ServerError("CreateIssueTypeDef", err)
|
||||
return
|
||||
}
|
||||
ctx.Flash.Success(ctx.Tr("org.settings.issue_type_created"))
|
||||
ctx.Redirect(ctx.Org.OrgLink + "/settings/issue-types")
|
||||
}
|
||||
|
||||
func SettingsIssueTypesEditPost(ctx *context.Context) {
|
||||
id := ctx.PathParamInt64("id")
|
||||
def, err := issues_model.GetIssueTypeDefByID(ctx, id)
|
||||
if err != nil {
|
||||
ctx.ServerError("GetIssueTypeDefByID", err)
|
||||
return
|
||||
}
|
||||
if def.OrgID != ctx.Org.Organization.ID {
|
||||
ctx.NotFound(nil)
|
||||
return
|
||||
}
|
||||
def.Name = ctx.FormString("name")
|
||||
def.Color = ctx.FormString("color")
|
||||
def.Description = ctx.FormString("description")
|
||||
def.IsDefault = ctx.FormString("is_default") == "on"
|
||||
def.IsActive = ctx.FormString("is_active") == "on"
|
||||
sortOrder, _ := strconv.Atoi(ctx.FormString("sort_order"))
|
||||
def.SortOrder = sortOrder
|
||||
if err := issues_model.UpdateIssueTypeDef(ctx, def); err != nil {
|
||||
ctx.ServerError("UpdateIssueTypeDef", err)
|
||||
return
|
||||
}
|
||||
ctx.Flash.Success(ctx.Tr("org.settings.issue_type_updated"))
|
||||
ctx.Redirect(ctx.Org.OrgLink + "/settings/issue-types")
|
||||
}
|
||||
|
||||
func SettingsIssueTypesDeletePost(ctx *context.Context) {
|
||||
id := ctx.PathParamInt64("id")
|
||||
def, err := issues_model.GetIssueTypeDefByID(ctx, id)
|
||||
if err != nil {
|
||||
ctx.ServerError("GetIssueTypeDefByID", err)
|
||||
return
|
||||
}
|
||||
if def.OrgID != ctx.Org.Organization.ID {
|
||||
ctx.NotFound(nil)
|
||||
return
|
||||
}
|
||||
if err := issues_model.DeleteIssueTypeDef(ctx, id); err != nil {
|
||||
ctx.ServerError("DeleteIssueTypeDef", err)
|
||||
return
|
||||
}
|
||||
ctx.Flash.Success(ctx.Tr("org.settings.issue_type_deleted"))
|
||||
ctx.Redirect(ctx.Org.OrgLink + "/settings/issue-types")
|
||||
}
|
||||
@@ -88,7 +88,7 @@ func ManifestSettingsPost(ctx *context.Context) {
|
||||
RepoID: ctx.Repo.Repository.ID,
|
||||
Name: ctx.FormString("name"),
|
||||
Org: ctx.FormString("org"),
|
||||
Description: ctx.FormString("description"),
|
||||
Description: ctx.Repo.Repository.Description,
|
||||
Version: ctx.FormString("version"),
|
||||
LicenseSPDX: ctx.FormString("license_spdx"),
|
||||
LicenseName: ctx.FormString("license_name"),
|
||||
|
||||
@@ -1079,6 +1079,12 @@ func registerWebRoutes(m *web.Router, webAuth *AuthMiddleware) {
|
||||
m.Post("/{id}/edit", org.SettingsIssuePrioritiesEditPost)
|
||||
m.Post("/{id}/delete", org.SettingsIssuePrioritiesDeletePost)
|
||||
})
|
||||
m.Group("/issue-types", func() {
|
||||
m.Get("", org.SettingsIssueTypes)
|
||||
m.Post("", org.SettingsIssueTypesCreatePost)
|
||||
m.Post("/{id}/edit", org.SettingsIssueTypesEditPost)
|
||||
m.Post("/{id}/delete", org.SettingsIssueTypesDeletePost)
|
||||
})
|
||||
}, ctxDataSet("EnableOAuth2", setting.OAuth2.Enabled, "EnablePackages", setting.Packages.Enabled, "PageIsOrgSettings", true))
|
||||
}, context.OrgAssignment(context.OrgAssignmentOptions{RequireOwner: true}))
|
||||
}, reqSignIn)
|
||||
|
||||
@@ -16,8 +16,11 @@ import (
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
auth_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/auth"
|
||||
user_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/user"
|
||||
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/httplib"
|
||||
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/optional"
|
||||
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/services/auth/source/oauth2"
|
||||
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/log"
|
||||
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/setting"
|
||||
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/structs"
|
||||
@@ -166,6 +169,18 @@ func (ctx *Context) notFoundInternal(logMsg string, logErr error) {
|
||||
ctx.Data["IsRepo"] = ctx.Repo.Repository != nil
|
||||
ctx.Data["Title"] = "Page Not Found"
|
||||
ctx.Data["ErrorMsg"] = "" // FIXME: the template never renders this message, need to fix in the future (and show safe messages to end users)
|
||||
ctx.Data["CurrentURL"] = ctx.Req.URL.RequestURI()
|
||||
|
||||
// Load OAuth2 providers for the login form on error pages
|
||||
if !ctx.IsSigned {
|
||||
oauth2Providers, err := oauth2.GetOAuth2Providers(ctx, optional.Some(true))
|
||||
if err != nil {
|
||||
log.Error("NotFound: GetOAuth2Providers: %v", err)
|
||||
}
|
||||
ctx.Data["OAuth2Providers"] = oauth2Providers
|
||||
ctx.Data["EnableSSPI"] = auth_model.IsSSPIEnabled(ctx)
|
||||
}
|
||||
|
||||
ctx.HTML(http.StatusNotFound, "status/404")
|
||||
}
|
||||
|
||||
@@ -187,6 +202,17 @@ func (ctx *Context) Forbidden() {
|
||||
ctx.Data["IsRepo"] = ctx.Repo.Repository != nil
|
||||
ctx.Data["Title"] = "Access Denied"
|
||||
ctx.Data["CurrentURL"] = ctx.Req.URL.RequestURI()
|
||||
|
||||
// Load OAuth2 providers for the login form on the 403 page
|
||||
if !ctx.IsSigned {
|
||||
oauth2Providers, err := oauth2.GetOAuth2Providers(ctx, optional.Some(true))
|
||||
if err != nil {
|
||||
log.Error("Forbidden: GetOAuth2Providers: %v", err)
|
||||
}
|
||||
ctx.Data["OAuth2Providers"] = oauth2Providers
|
||||
ctx.Data["EnableSSPI"] = auth_model.IsSSPIEnabled(ctx)
|
||||
}
|
||||
|
||||
ctx.HTML(http.StatusForbidden, "status/403")
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
{{template "org/settings/layout_head" (dict "ctxData" . "pageClass" "organization settings issue-types")}}
|
||||
<h4 class="ui top attached header">
|
||||
{{ctx.Locale.Tr "org.settings.issue_types"}}
|
||||
</h4>
|
||||
<div class="ui attached segment">
|
||||
<p class="text grey">{{ctx.Locale.Tr "org.settings.issue_types_desc"}}</p>
|
||||
|
||||
{{if .IssueTypes}}
|
||||
<table class="ui compact table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{{ctx.Locale.Tr "org.settings.issue_type_color"}}</th>
|
||||
<th>{{ctx.Locale.Tr "org.settings.issue_type_name"}}</th>
|
||||
<th>{{ctx.Locale.Tr "org.settings.issue_type_default"}}</th>
|
||||
<th>{{ctx.Locale.Tr "org.settings.issue_type_sort_order"}}</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{range .IssueTypes}}
|
||||
<tr {{if not .IsActive}}class="tw-opacity-50"{{end}}>
|
||||
<td>
|
||||
{{if .Color}}<span class="tw-inline-block tw-w-4 tw-h-4 tw-rounded" style="background-color: {{.Color}}"></span>{{else}}<span class="text grey">-</span>{{end}}
|
||||
</td>
|
||||
<td>
|
||||
<strong>{{.Name}}</strong>
|
||||
{{if not .IsActive}}<span class="ui mini grey label">Inactive</span>{{end}}
|
||||
{{if .Description}}<br><small class="text grey">{{.Description}}</small>{{end}}
|
||||
</td>
|
||||
<td>
|
||||
{{if .IsDefault}}<span class="ui mini blue label">{{ctx.Locale.Tr "org.settings.issue_type_default"}}</span>{{else}}<span class="text grey">-</span>{{end}}
|
||||
</td>
|
||||
<td>{{.SortOrder}}</td>
|
||||
<td class="tw-text-right">
|
||||
<form method="post" action="{{$.OrgLink}}/settings/issue-types/{{.ID}}/delete" class="tw-inline">
|
||||
{{$.CsrfTokenHtml}}
|
||||
<button class="ui tiny red icon button" type="submit" title="{{ctx.Locale.Tr "remove"}}">{{svg "octicon-trash" 14}}</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
{{end}}
|
||||
</tbody>
|
||||
</table>
|
||||
{{else}}
|
||||
<div class="empty-placeholder"><p>{{ctx.Locale.Tr "org.settings.issue_types_empty"}}</p></div>
|
||||
{{end}}
|
||||
|
||||
<div class="divider"></div>
|
||||
<h5>{{ctx.Locale.Tr "org.settings.issue_type_add"}}</h5>
|
||||
<form class="ui form" method="post" action="{{.OrgLink}}/settings/issue-types">
|
||||
{{.CsrfTokenHtml}}
|
||||
<div class="three fields">
|
||||
<div class="required field">
|
||||
<label>{{ctx.Locale.Tr "org.settings.issue_type_name"}}</label>
|
||||
<input name="name" required placeholder="e.g. Bug, Feature, Task">
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>{{ctx.Locale.Tr "org.settings.issue_type_color"}}</label>
|
||||
<input name="color" type="color" value="#2563eb">
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>{{ctx.Locale.Tr "org.settings.issue_type_sort_order"}}</label>
|
||||
<input name="sort_order" type="number" value="0" min="0">
|
||||
</div>
|
||||
</div>
|
||||
<div class="two fields">
|
||||
<div class="field">
|
||||
<label>{{ctx.Locale.Tr "org.settings.issue_type_description"}}</label>
|
||||
<input name="description" placeholder="Help text">
|
||||
</div>
|
||||
<div class="field">
|
||||
<div class="ui checkbox tw-mt-4">
|
||||
<input name="is_default" type="checkbox">
|
||||
<label>{{ctx.Locale.Tr "org.settings.issue_type_default"}}</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button class="ui primary button" type="submit">{{ctx.Locale.Tr "org.settings.issue_type_add"}}</button>
|
||||
</form>
|
||||
</div>
|
||||
{{template "org/settings/layout_footer" .}}
|
||||
@@ -37,6 +37,9 @@
|
||||
<a class="{{if .PageIsSettingsIssuePriorities}}active {{end}}item" href="{{.OrgLink}}/settings/issue-priorities">
|
||||
{{svg "octicon-flame"}} {{ctx.Locale.Tr "org.settings.issue_priorities"}}
|
||||
</a>
|
||||
<a class="{{if .PageIsSettingsIssueTypes}}active {{end}}item" href="{{.OrgLink}}/settings/issue-types">
|
||||
{{svg "octicon-tag"}} {{ctx.Locale.Tr "org.settings.issue_types"}}
|
||||
</a>
|
||||
{{if .EnableActions}}
|
||||
<details class="item toggleable-item" {{if or .PageIsOrgSettingsActionsGeneral .PageIsSharedSettingsRunners .PageIsSharedSettingsSecrets .PageIsSharedSettingsVariables}}open{{end}}>
|
||||
<summary>{{svg "octicon-play"}} {{ctx.Locale.Tr "actions.actions"}}</summary>
|
||||
|
||||
@@ -19,10 +19,6 @@
|
||||
<input name="org" value="{{.Manifest.Org}}" placeholder="Organization">
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>{{ctx.Locale.Tr "repo.settings.manifest_description"}}</label>
|
||||
<input name="description" value="{{.Manifest.Description}}" placeholder="Project description">
|
||||
</div>
|
||||
<div class="three fields">
|
||||
<div class="field">
|
||||
<label>{{ctx.Locale.Tr "repo.settings.manifest_version"}}</label>
|
||||
|
||||
@@ -23,6 +23,10 @@
|
||||
</div>
|
||||
<button class="ui primary fluid button tw-mt-2" type="submit">{{ctx.Locale.Tr "sign_in"}}</button>
|
||||
</form>
|
||||
{{if or .OAuth2Providers .EnableSSPI}}
|
||||
<div class="divider"></div>
|
||||
{{template "user/auth/external_auth_methods" .}}
|
||||
{{end}}
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
|
||||
@@ -11,6 +11,27 @@
|
||||
<a class="tw-block tw-my-4" href="{{.NotFoundGoBackURL}}">{{ctx.Locale.Tr "go_back"}}</a>
|
||||
{{end}}
|
||||
</div>
|
||||
{{if not .IsSigned}}
|
||||
<div class="tw-max-w-sm tw-mx-auto tw-mt-4">
|
||||
<form class="ui form" action="{{AppSubUrl}}/user/login" method="post">
|
||||
{{.CsrfTokenHtml}}
|
||||
<input type="hidden" name="redirect_to" value="{{.CurrentURL}}">
|
||||
<div class="required field">
|
||||
<label>{{ctx.Locale.Tr "home.uname_holder"}}</label>
|
||||
<input type="text" name="user_name" required autofocus>
|
||||
</div>
|
||||
<div class="required field">
|
||||
<label>{{ctx.Locale.Tr "password"}}</label>
|
||||
<input type="password" name="password" required>
|
||||
</div>
|
||||
<button class="ui primary fluid button tw-mt-2" type="submit">{{ctx.Locale.Tr "sign_in"}}</button>
|
||||
</form>
|
||||
{{if or .OAuth2Providers .EnableSSPI}}
|
||||
<div class="divider"></div>
|
||||
{{template "user/auth/external_auth_methods" .}}
|
||||
{{end}}
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -28,23 +28,42 @@ Each status has:
|
||||
| Sort Order | Controls display order in dropdowns (ascending) |
|
||||
| Is Active | Inactive statuses are hidden from dropdowns but preserved on existing issues |
|
||||
|
||||
### Example Statuses
|
||||
### Default Statuses (auto-seeded)
|
||||
|
||||
| Status | Color | Closes Issue | Use Case |
|
||||
|--------|-------|:------------:|----------|
|
||||
| In Progress | Blue | No | Work is actively being done |
|
||||
| Needs Info | Yellow | No | Waiting for more information from reporter |
|
||||
| Blocked | Red | No | Cannot proceed due to external dependency |
|
||||
| Won't Fix | Gray | Yes | Decided not to address this issue |
|
||||
| Duplicate | Purple | Yes | Already tracked in another issue |
|
||||
| Resolved | Green | Yes | Fix has been implemented and verified |
|
||||
| Needs Info | Yellow | No | Waiting for more information |
|
||||
| Blocked | Red | No | Cannot proceed due to dependency |
|
||||
| Resolved | Green | Yes | Fix implemented and verified |
|
||||
| Won't Fix | Gray | Yes | Decided not to address |
|
||||
| Duplicate | Purple | Yes | Already tracked elsewhere |
|
||||
| Pending: Design | Lavender | No | Waiting on design work |
|
||||
| Pending: Testing | Yellow | No | Waiting for testing |
|
||||
| Pending: Review | Green | No | Waiting for code review |
|
||||
| Pending: Feedback | Pink | No | Waiting for feedback |
|
||||
| Pending: Documentation | Purple | No | Waiting for docs |
|
||||
| Pending: Deployment | Blue | No | Ready to deploy |
|
||||
| Pending: Dependency | Light Blue | No | Blocked by external dependency |
|
||||
|
||||
Statuses are auto-seeded when an org first accesses them. Admins can add, edit, reorder, or deactivate statuses.
|
||||
|
||||
## Comment Form Integration
|
||||
|
||||
The status dropdown **replaces the close/reopen button** in the comment form footer for issues with org statuses:
|
||||
- Open issues show all statuses plus a "Close" option
|
||||
- Selecting a status with `closes_issue = true` auto-closes the issue
|
||||
- Closed issues show only "Reopen" for non-admin users
|
||||
- Admins see the full dropdown on closed issues including "Reopen"
|
||||
- PRs still use the standard close/reopen button
|
||||
|
||||
## Issue List Badges
|
||||
|
||||
Status shows as a colored badge on each issue in the issue list view, alongside Type and Priority badges.
|
||||
|
||||
## Issue Sidebar
|
||||
|
||||
When an organization has custom statuses defined, a **Status** dropdown appears in the issue sidebar between labels and custom fields. The dropdown:
|
||||
|
||||
- Shows all active status definitions for the repo's organization
|
||||
- Auto-submits on change (no save button needed)
|
||||
Status also appears as a read-only display in the sidebar (the editable control is in the comment form). The dropdown:
|
||||
- Displays a colored left border on each option
|
||||
- Shows a power symbol on statuses that close the issue
|
||||
- Selecting "—" (empty) clears the status
|
||||
|
||||
+7
-3
@@ -7,7 +7,7 @@ Moko Consulting's custom fork of [Gitea](https://gitea.com), extending the self-
|
||||
| **Language** | Go |
|
||||
| **License** | MIT |
|
||||
| **Upstream** | Gitea 1.26.1 |
|
||||
| **Version** | v1.26.1-moko.06.07.03 |
|
||||
| **Version** | v1.26.1-moko.06.11.01 |
|
||||
| **Platform** | [Gitea](https://git.mokoconsulting.tech/MokoConsulting/MokoGitea) |
|
||||
|
||||
---
|
||||
@@ -16,10 +16,13 @@ Moko Consulting's custom fork of [Gitea](https://gitea.com), extending the self-
|
||||
|
||||
- **Commercial License System** — Package-based license keys with download gating, domain restriction, key expiry, and payment webhook API
|
||||
- **Update Server** — Built-in update feeds for Joomla, WordPress, Dolibarr, Composer, Drupal, PrestaShop, and WHMCS
|
||||
- **Custom Issue Statuses** — Org-defined workflow states (In Progress, Blocked, Won't Fix) with auto close/reopen
|
||||
- **First-Class Issue Fields** — Type (12 types), Status (13 statuses), Priority (4 levels) as built-in fields with auto-seed defaults, colored badges in issue list, and sidebar dropdowns
|
||||
- **Security Scanning** — Built-in security scanner with secret detection (15 patterns), push-time scanning, alert management, and Security tab in repo navigation
|
||||
- **Custom Fields** — Org-level field definitions for issues (sidebar) and repos (metadata) with dropdown, text, number, date, checkbox, and URL types
|
||||
- **Manifest Settings** — Per-repo identity/governance/build metadata with REST API for CI/CD integration
|
||||
- **Manifest Settings** — Per-repo identity/governance/build metadata with REST API and auto-sync on push
|
||||
- **Wiki Folders** — Hierarchical folder navigation with sidebar tree, breadcrumbs, and index page fallback
|
||||
- **Well-Known File Tabs** — README/LICENSE/CONTRIBUTING/SECURITY/CHANGELOG tabs on repo home page
|
||||
- **MCP Server** — 120+ tool MCP server published to npm (@mokoconsulting/mokogitea-mcp) with SSE transport
|
||||
- **Org-Level Branch Protection** — Organization-scoped rulesets that cascade to all repos. Supports glob patterns. Full CRUD API
|
||||
- **Enterprise Sub-Orgs** — Parent-child organization hierarchy
|
||||
- **Three-Level Visibility** — Public (200), Private (403), Hidden (404) for repositories
|
||||
@@ -39,6 +42,7 @@ Moko Consulting's custom fork of [Gitea](https://gitea.com), extending the self-
|
||||
| [Org Branch Protection API](Org-Branch-Protection-API) | Org-level branch protection rulesets and API reference |
|
||||
| [Project API](Project-API) | Custom API endpoint reference for project boards |
|
||||
| [Roadmap](Roadmap) | Development roadmap and planned features |
|
||||
| [features/](features) | Feature documentation folder |
|
||||
|
||||
---
|
||||
|
||||
|
||||
+24
-17
@@ -1,42 +1,49 @@
|
||||
# MokoGitea Roadmap
|
||||
|
||||
## Recently Completed (v1.26.1-moko.06.07)
|
||||
## Recently Completed (v1.26.1-moko.06.11)
|
||||
|
||||
- First-class Type field (12 types) replacing labels and custom fields
|
||||
- First-class Status field (13 statuses) with auto close/reopen
|
||||
- First-class Priority field (4 levels) with auto-seed defaults
|
||||
- All org labels migrated to first-class fields and deleted
|
||||
- Type/Status/Priority colored badges in issue list view
|
||||
- Security scanning platform with 15 secret detection patterns
|
||||
- Security tab in repo navigation (admin-only)
|
||||
- Wiki hierarchical folder navigation with sidebar tree
|
||||
- Well-known file tabs (README/LICENSE/CONTRIBUTING/SECURITY/CHANGELOG)
|
||||
- Custom issue statuses with auto close/reopen
|
||||
- Org-level issue priorities (Critical/High/Medium/Low)
|
||||
- Repo manifest settings with REST API
|
||||
- Manifest auto-sync on push to default branch
|
||||
- Status dropdown replaces close button
|
||||
- Auto-seed default statuses and priorities for orgs
|
||||
- MCP server published to npm (@mokoconsulting/mokogitea-mcp)
|
||||
- MCP SSE transport for hosted deployments
|
||||
- Repo manifest settings with REST API and auto-sync on push
|
||||
- MCP server published to npm (@mokoconsulting/mokogitea-mcp) with SSE transport
|
||||
- Dashboard issue count badges fixed
|
||||
- Status dropdown replaces close/reopen button
|
||||
- Org settings page for Issue Types
|
||||
- MCP SSE endpoint hosted at git.mokoconsulting.tech/mcp/
|
||||
- npm auto-publish workflow on MCP source changes
|
||||
- OAuth providers on 403/404 error pages
|
||||
- All stale branches cleaned up (main + dev only)
|
||||
|
||||
## In Progress
|
||||
|
||||
- Rename moko-platform to MokoPlatform
|
||||
- Granular role-based permissions for all features (#9)
|
||||
- Built-in secret scanning (#508)
|
||||
- Enterprise Wiki with hierarchical folder navigation (#79)
|
||||
- Wire moko-platform CLI to manifest API (#505)
|
||||
- Bulk migrate remaining 41 flat wikis to folders
|
||||
|
||||
## Planned
|
||||
|
||||
- Standard status presets and cross-org migration (#507)
|
||||
- Auto-create default teams on org creation (#513)
|
||||
- Update server reads from repo_manifest (#512)
|
||||
- Dependency vulnerability scanner module
|
||||
- Code security analysis scanner module
|
||||
- Payment gateways for license keys (#135)
|
||||
- Independent visibility controls for issues/wiki/projects (#133)
|
||||
|
||||
## Infrastructure
|
||||
|
||||
- MCP SSE endpoint at git.mokoconsulting.tech/mcp
|
||||
- MCP SSE endpoint hosted at git.mokoconsulting.tech/mcp
|
||||
- Smithery/Claude Code marketplace listing
|
||||
- Docker image for MCP server
|
||||
- npm auto-publish workflow on release
|
||||
|
||||
---
|
||||
|
||||
| Revision | Date | Author | Description |
|
||||
|---|---|---|---|
|
||||
| 3.0 | 2026-06-06 | Jonathan Miller (@jmiller) | First-class fields, security scanning, wiki folders, MCP release |
|
||||
| 2.0 | 2026-06-06 | Jonathan Miller (@jmiller) | Complete rewrite with current features and priorities |
|
||||
| 1.0 | 2026-05-09 | Jonathan Miller (@jmiller) | Initial version |
|
||||
|
||||
Reference in New Issue
Block a user