feat: Issue Types settings + MCP SSE + npm auto-publish #557
@@ -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 }}
|
||||
@@ -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")
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user