From dcde4701a5b31c88b3120722c3163af4214264d2 Mon Sep 17 00:00:00 2001 From: silverwind Date: Mon, 17 Apr 2023 12:10:22 +0200 Subject: [PATCH 01/21] Fix math and mermaid rendering bugs (#24049) 1. Fix multiple error display for math and mermaid: ![err](https://user-images.githubusercontent.com/115237/231126411-8a21a777-cd53-4b7e-ac67-5332623106e8.gif) 2. Fix height calculation of certain mermaid diagrams by reading the iframe inner height from it's document instead of parsing it from SVG: Before: Screenshot 2023-04-11 at 11 56 27 After: Screenshot 2023-04-11 at 11 56 35 3. Refactor error handling to a common function 4. Rename to `renderAsciicast` for consistency 5. Improve mermaid loading sequence Note: I did try `securityLevel: 'sandbox'` to make mermaid output a iframe directly, but that showed a bug in mermaid where the iframe style height was set incorrectly. Opened https://github.com/mermaid-js/mermaid/issues/4289 for this. --------- Co-authored-by: Giteabot --- web_src/css/base.css | 2 +- web_src/css/helpers.css | 3 +- web_src/css/markup/content.css | 2 +- web_src/js/markup/asciicast.js | 2 +- web_src/js/markup/common.js | 8 ++++++ web_src/js/markup/content.js | 4 +-- web_src/js/markup/math.js | 18 +++++------- web_src/js/markup/mermaid.js | 50 ++++++++++++++++------------------ 8 files changed, 46 insertions(+), 43 deletions(-) create mode 100644 web_src/js/markup/common.js diff --git a/web_src/css/base.css b/web_src/css/base.css index c48a36c854764..bdf601951b717 100644 --- a/web_src/css/base.css +++ b/web_src/css/base.css @@ -9,7 +9,7 @@ /* non-color variables */ --border-radius: 0.28571429rem; --opacity-disabled: 0.55; - --height-loading: 12rem; + --height-loading: 16rem; /* base colors */ --color-primary: #4183c4; --color-primary-contrast: #ffffff; diff --git a/web_src/css/helpers.css b/web_src/css/helpers.css index 8d64bd751b879..beb93e1e86d83 100644 --- a/web_src/css/helpers.css +++ b/web_src/css/helpers.css @@ -25,7 +25,8 @@ .gt-overflow-x-scroll { overflow-x: scroll !important; } .gt-cursor-default { cursor: default !important; } .gt-items-start { align-items: flex-start !important; } -.gt-whitespace-pre { white-space: pre !important } +.gt-whitespace-pre { white-space: pre !important; } +.gt-invisible { visibility: hidden !important; } .gt-mono { font-family: var(--fonts-monospace) !important; diff --git a/web_src/css/markup/content.css b/web_src/css/markup/content.css index 90f8c7091e9a6..d0f11e8e76500 100644 --- a/web_src/css/markup/content.css +++ b/web_src/css/markup/content.css @@ -542,7 +542,7 @@ .markup-block-error { display: block !important; /* override fomantic .ui.form .error.message {display: none} */ - border: 1px solid var(--color-error-border) !important; + border: none !important; margin-bottom: 0 !important; border-bottom-left-radius: 0 !important; border-bottom-right-radius: 0 !important; diff --git a/web_src/js/markup/asciicast.js b/web_src/js/markup/asciicast.js index 902cfcb7316bc..97b18743a15d3 100644 --- a/web_src/js/markup/asciicast.js +++ b/web_src/js/markup/asciicast.js @@ -1,4 +1,4 @@ -export async function renderAsciinemaPlayer() { +export async function renderAsciicast() { const els = document.querySelectorAll('.asciinema-player-container'); if (!els.length) return; diff --git a/web_src/js/markup/common.js b/web_src/js/markup/common.js new file mode 100644 index 0000000000000..aff4a3242368e --- /dev/null +++ b/web_src/js/markup/common.js @@ -0,0 +1,8 @@ +export function displayError(el, err) { + el.classList.remove('is-loading'); + const errorNode = document.createElement('pre'); + errorNode.setAttribute('class', 'ui message error markup-block-error'); + errorNode.textContent = err.str || err.message || String(err); + el.before(errorNode); + el.setAttribute('data-render-done', 'true'); +} diff --git a/web_src/js/markup/content.js b/web_src/js/markup/content.js index e4ec3d0b4baa6..1d29dc07f29ad 100644 --- a/web_src/js/markup/content.js +++ b/web_src/js/markup/content.js @@ -1,7 +1,7 @@ import {renderMermaid} from './mermaid.js'; import {renderMath} from './math.js'; import {renderCodeCopy} from './codecopy.js'; -import {renderAsciinemaPlayer} from './asciicast.js'; +import {renderAsciicast} from './asciicast.js'; import {initMarkupTasklist} from './tasklist.js'; // code that runs for all markup content @@ -9,7 +9,7 @@ export function initMarkupContent() { renderMermaid(); renderMath(); renderCodeCopy(); - renderAsciinemaPlayer(); + renderAsciicast(); } // code that only runs for comments diff --git a/web_src/js/markup/math.js b/web_src/js/markup/math.js index dcc656ea82cee..8427637a0f3d4 100644 --- a/web_src/js/markup/math.js +++ b/web_src/js/markup/math.js @@ -1,14 +1,8 @@ -function displayError(el, err) { - const target = targetElement(el); - target.classList.remove('is-loading'); - const errorNode = document.createElement('div'); - errorNode.setAttribute('class', 'ui message error markup-block-error gt-mono'); - errorNode.textContent = err.str || err.message || String(err); - target.before(errorNode); -} +import {displayError} from './common.js'; function targetElement(el) { - // The target element is either the current element if it has the `is-loading` class or the pre that contains it + // The target element is either the current element if it has the + // `is-loading` class or the pre that contains it return el.classList.contains('is-loading') ? el : el.closest('pre'); } @@ -22,6 +16,8 @@ export async function renderMath() { ]); for (const el of els) { + const target = targetElement(el); + if (target.hasAttribute('data-render-done')) continue; const source = el.textContent; const displayMode = el.classList.contains('display'); const nodeName = displayMode ? 'p' : 'span'; @@ -33,9 +29,9 @@ export async function renderMath() { maxExpand: 50, displayMode, }); - targetElement(el).replaceWith(tempEl); + target.replaceWith(tempEl); } catch (error) { - displayError(el, error); + displayError(target, error); } } } diff --git a/web_src/js/markup/mermaid.js b/web_src/js/markup/mermaid.js index b519e2dcdc352..865a414c93367 100644 --- a/web_src/js/markup/mermaid.js +++ b/web_src/js/markup/mermaid.js @@ -1,21 +1,12 @@ import {isDarkTheme} from '../utils.js'; import {makeCodeCopyButton} from './codecopy.js'; +import {displayError} from './common.js'; const {mermaidMaxSourceCharacters} = window.config; -const iframeCss = ` - :root {color-scheme: normal} - body {margin: 0; padding: 0; overflow: hidden} - #mermaid {display: block; margin: 0 auto} -`; - -function displayError(el, err) { - el.closest('pre').classList.remove('is-loading'); - const errorNode = document.createElement('div'); - errorNode.setAttribute('class', 'ui message error markup-block-error gt-mono'); - errorNode.textContent = err.str || err.message || String(err); - el.closest('pre').before(errorNode); -} +const iframeCss = `:root {color-scheme: normal} +body {margin: 0; padding: 0; overflow: hidden} +#mermaid {display: block; margin: 0 auto}`; export async function renderMermaid() { const els = document.querySelectorAll('.markup code.language-mermaid'); @@ -30,18 +21,19 @@ export async function renderMermaid() { }); for (const el of els) { - const source = el.textContent; + const pre = el.closest('pre'); + if (pre.hasAttribute('data-render-done')) continue; + const source = el.textContent; if (mermaidMaxSourceCharacters >= 0 && source.length > mermaidMaxSourceCharacters) { - displayError(el, new Error(`Mermaid source of ${source.length} characters exceeds the maximum allowed length of ${mermaidMaxSourceCharacters}.`)); + displayError(pre, new Error(`Mermaid source of ${source.length} characters exceeds the maximum allowed length of ${mermaidMaxSourceCharacters}.`)); continue; } try { await mermaid.parse(source); } catch (err) { - displayError(el, err); - el.closest('pre').classList.remove('is-loading'); + displayError(pre, err); continue; } @@ -49,26 +41,32 @@ export async function renderMermaid() { // can't use bindFunctions here because we can't cross the iframe boundary. This // means js-based interactions won't work but they aren't intended to work either const {svg} = await mermaid.render('mermaid', source); - const heightStr = (svg.match(/viewBox="(.+?)"/) || ['', ''])[1].split(/\s+/)[3]; - if (!heightStr) return displayError(el, new Error('Could not determine chart height')); const iframe = document.createElement('iframe'); - iframe.classList.add('markup-render'); - iframe.sandbox = 'allow-scripts'; - iframe.style.height = `${Math.ceil(parseFloat(heightStr))}px`; + iframe.classList.add('markup-render', 'gt-invisible'); iframe.srcdoc = `${svg}`; const mermaidBlock = document.createElement('div'); - mermaidBlock.classList.add('mermaid-block'); + mermaidBlock.classList.add('mermaid-block', 'is-loading', 'gt-hidden'); mermaidBlock.append(iframe); const btn = makeCodeCopyButton(); btn.setAttribute('data-clipboard-text', source); - mermaidBlock.append(btn); - el.closest('pre').replaceWith(mermaidBlock); + + iframe.addEventListener('load', () => { + pre.replaceWith(mermaidBlock); + mermaidBlock.classList.remove('gt-hidden'); + iframe.style.height = `${iframe.contentWindow.document.body.clientHeight}px`; + setTimeout(() => { // avoid flash of iframe background + mermaidBlock.classList.remove('is-loading'); + iframe.classList.remove('gt-invisible'); + }, 0); + }); + + document.body.append(mermaidBlock); } catch (err) { - displayError(el, err); + displayError(pre, err); } } } From cda84bec87f18fa5cbbbd62fc72bc3d997aba66c Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Mon, 17 Apr 2023 21:22:10 +0800 Subject: [PATCH 02/21] Support converting varchar to nvarchar for mssql database (#24105) --- cmd/convert.go | 27 ++++++++++++++++----------- models/db/convert.go | 27 +++++++++++++++++++++++++++ 2 files changed, 43 insertions(+), 11 deletions(-) diff --git a/cmd/convert.go b/cmd/convert.go index d9b89495c1bf0..8c7746fdf2970 100644 --- a/cmd/convert.go +++ b/cmd/convert.go @@ -17,7 +17,7 @@ import ( var CmdConvert = cli.Command{ Name: "convert", Usage: "Convert the database", - Description: "A command to convert an existing MySQL database from utf8 to utf8mb4", + Description: "A command to convert an existing MySQL database from utf8 to utf8mb4 or MSSQL database from varchar to nvarchar", Action: runConvert, } @@ -35,17 +35,22 @@ func runConvert(ctx *cli.Context) error { log.Info("Log path: %s", setting.Log.RootPath) log.Info("Configuration file: %s", setting.CustomConf) - if !setting.Database.Type.IsMySQL() { - fmt.Println("This command can only be used with a MySQL database") - return nil + switch { + case setting.Database.Type.IsMySQL(): + if err := db.ConvertUtf8ToUtf8mb4(); err != nil { + log.Fatal("Failed to convert database from utf8 to utf8mb4: %v", err) + return err + } + fmt.Println("Converted successfully, please confirm your database's character set is now utf8mb4") + case setting.Database.Type.IsMSSQL(): + if err := db.ConvertVarcharToNVarchar(); err != nil { + log.Fatal("Failed to convert database from varchar to nvarchar: %v", err) + return err + } + fmt.Println("Converted successfully, please confirm your database's all columns character is NVARCHAR now") + default: + fmt.Println("This command can only be used with a MySQL or MSSQL database") } - if err := db.ConvertUtf8ToUtf8mb4(); err != nil { - log.Fatal("Failed to convert database from utf8 to utf8mb4: %v", err) - return err - } - - fmt.Println("Converted successfully, please confirm your database's character set is now utf8mb4") - return nil } diff --git a/models/db/convert.go b/models/db/convert.go index b17e68c87e591..112c8575ca2c7 100644 --- a/models/db/convert.go +++ b/models/db/convert.go @@ -42,6 +42,33 @@ func ConvertUtf8ToUtf8mb4() error { return nil } +// ConvertVarcharToNVarchar converts database and tables from varchar to nvarchar if it's mssql +func ConvertVarcharToNVarchar() error { + if x.Dialect().URI().DBType != schemas.MSSQL { + return nil + } + + sess := x.NewSession() + defer sess.Close() + res, err := sess.QuerySliceString(`SELECT 'ALTER TABLE ' + OBJECT_NAME(SC.object_id) + ' MODIFY SC.name NVARCHAR(' + CONVERT(VARCHAR(5),SC.max_length) + ')' +FROM SYS.columns SC +JOIN SYS.types ST +ON SC.system_type_id = ST.system_type_id +AND SC.user_type_id = ST.user_type_id +WHERE ST.name ='varchar'`) + if err != nil { + return err + } + for _, row := range res { + if len(row) == 1 { + if _, err = sess.Exec(row[0]); err != nil { + return err + } + } + } + return err +} + // Cell2Int64 converts a xorm.Cell type to int64, // and handles possible irregular cases. func Cell2Int64(val xorm.Cell) int64 { From 4b1c6cd8e5745a945df0f72233964f6aede8a3a6 Mon Sep 17 00:00:00 2001 From: silverwind Date: Mon, 17 Apr 2023 16:46:25 +0200 Subject: [PATCH 03/21] Make HAS_GO a simply expanded variable (#24169) Avoid recursive expansion on this variable and simplify the value. [Reference](https://www.gnu.org/software/make/manual/html_node/Setting.html). --- Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index eb48766194f18..59cc27ee8a102 100644 --- a/Makefile +++ b/Makefile @@ -20,7 +20,7 @@ IMPORT := code.gitea.io/gitea GO ?= go SHASUM ?= shasum -a 256 -HAS_GO = $(shell hash $(GO) > /dev/null 2>&1 && echo "GO" || echo "NOGO" ) +HAS_GO := $(shell hash $(GO) > /dev/null 2>&1 && echo yes) COMMA := , XGO_VERSION := go-1.20.x @@ -41,7 +41,7 @@ DOCKER_IMAGE ?= gitea/gitea DOCKER_TAG ?= latest DOCKER_REF := $(DOCKER_IMAGE):$(DOCKER_TAG) -ifeq ($(HAS_GO), GO) +ifeq ($(HAS_GO), yes) GOPATH ?= $(shell $(GO) env GOPATH) export PATH := $(GOPATH)/bin:$(PATH) From f20057271def2240474d64c57eeba8b365642c08 Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Mon, 17 Apr 2023 23:35:57 +0800 Subject: [PATCH 04/21] Fix Org edit page bugs: renaming detection, maxlength (#24161) ## Before * The renaming detection is wrong (eg: pasting a new name into the input doesn't trigger the detection) * The renaming prompt layout is not good * Some MaxSize/maxlength rules is missing ![image](https://user-images.githubusercontent.com/2114189/232379191-5d0f6d10-56ca-4cec-ac52-7f77b9cb4a8a.png) ![image](https://user-images.githubusercontent.com/2114189/232379234-3289373b-9ddb-4627-ae86-f4d74589fa0c.png) ## After * Fix these problems ![image](https://user-images.githubusercontent.com/2114189/232379098-31c6fa21-c210-4e7f-a337-b38b99670835.png) --- modules/structs/org.go | 6 +++--- templates/org/create.tmpl | 2 +- templates/org/settings/options.tmpl | 15 ++++++++------- web_src/js/features/common-organization.js | 19 +++++-------------- 4 files changed, 17 insertions(+), 25 deletions(-) diff --git a/modules/structs/org.go b/modules/structs/org.go index b4c58623fd29c..7c83dcdee7e4d 100644 --- a/modules/structs/org.go +++ b/modules/structs/org.go @@ -30,8 +30,8 @@ type OrganizationPermissions struct { // CreateOrgOption options for creating an organization type CreateOrgOption struct { // required: true - UserName string `json:"username" binding:"Required"` - FullName string `json:"full_name"` + UserName string `json:"username" binding:"Required;Username;MaxSize(40)"` + FullName string `json:"full_name" binding:"MaxSize(100)"` Description string `json:"description" binding:"MaxSize(255)"` Website string `json:"website" binding:"ValidUrl;MaxSize(255)"` Location string `json:"location" binding:"MaxSize(50)"` @@ -45,7 +45,7 @@ type CreateOrgOption struct { // EditOrgOption options for editing an organization type EditOrgOption struct { - FullName string `json:"full_name"` + FullName string `json:"full_name" binding:"MaxSize(100)"` Description string `json:"description" binding:"MaxSize(255)"` Website string `json:"website" binding:"ValidUrl;MaxSize(255)"` Location string `json:"location" binding:"MaxSize(50)"` diff --git a/templates/org/create.tmpl b/templates/org/create.tmpl index f734a39a93724..7e988ba0c792a 100644 --- a/templates/org/create.tmpl +++ b/templates/org/create.tmpl @@ -11,7 +11,7 @@ {{template "base/alert" .}}
- + {{.locale.Tr "org.org_name_helper"}}
diff --git a/templates/org/settings/options.tmpl b/templates/org/settings/options.tmpl index 833b97e347253..1caa4210e62ca 100644 --- a/templates/org/settings/options.tmpl +++ b/templates/org/settings/options.tmpl @@ -14,26 +14,27 @@ {{.CsrfTokenHtml}}
- +
- +
- +
- +
- +
diff --git a/web_src/js/features/common-organization.js b/web_src/js/features/common-organization.js index 1796efc6a8cfd..352e824b05be4 100644 --- a/web_src/js/features/common-organization.js +++ b/web_src/js/features/common-organization.js @@ -1,25 +1,16 @@ import $ from 'jquery'; import {initCompLabelEdit} from './comp/LabelEdit.js'; -import {hideElem, showElem} from '../utils/dom.js'; +import {toggleElem} from '../utils/dom.js'; export function initCommonOrganization() { if ($('.organization').length === 0) { return; } - if ($('.organization.settings.options').length > 0) { - $('#org_name').on('keyup', function () { - const $prompt = $('#org-name-change-prompt'); - const $prompt_redirect = $('#org-name-change-redirect-prompt'); - if ($(this).val().toString().toLowerCase() !== $(this).data('org-name').toString().toLowerCase()) { - showElem($prompt); - showElem($prompt_redirect); - } else { - hideElem($prompt); - hideElem($prompt_redirect); - } - }); - } + $('.organization.settings.options #org_name').on('input', function () { + const nameChanged = $(this).val().toLowerCase() !== $(this).attr('data-org-name').toLowerCase(); + toggleElem('#org-name-change-prompt', nameChanged); + }); // Labels initCompLabelEdit('.organization.settings.labels'); From 1819c4b59b81ba4db2a38d3b3dc81f29102fde51 Mon Sep 17 00:00:00 2001 From: techknowlogick Date: Mon, 17 Apr 2023 12:36:50 -0400 Subject: [PATCH 05/21] Add new user types `reserved`, `bot`, and `remote` (#24026) This allows for usernames, and emails connected to them to be reserved and not reused. Use case, I manage an instance with open registration, and sometimes when users are deleted for spam (or other purposes), their usernames are freed up and they sign up again with the same information. This could also be used to reserve usernames, and block them from being registered (in case an instance would like to block certain things without hardcoding the list in code and compiling from scratch). This is an MVP, that will allow for future work where you can set something as reserved via the interface. --------- Co-authored-by: delvh Co-authored-by: John Olheiser --- models/user/user.go | 16 +++++++++++++++- services/auth/source/db/authenticate.go | 8 ++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/models/user/user.go b/models/user/user.go index 5709ed7ff27e9..5f152780bff04 100644 --- a/models/user/user.go +++ b/models/user/user.go @@ -41,6 +41,18 @@ const ( // UserTypeOrganization defines an organization UserTypeOrganization + + // UserTypeReserved reserves a (non-existing) user, i.e. to prevent a spam user from re-registering after being deleted, or to reserve the name until the user is actually created later on + UserTypeUserReserved + + // UserTypeOrganizationReserved reserves a (non-existing) organization, to be used in combination with UserTypeUserReserved + UserTypeOrganizationReserved + + // UserTypeBot defines a bot user + UserTypeBot + + // UserTypeRemoteUser defines a remote user for federated users + UserTypeRemoteUser ) const ( @@ -312,6 +324,7 @@ func GetUserFollowers(ctx context.Context, u, viewer *User, listOptions db.ListO Select("`user`.*"). Join("LEFT", "follow", "`user`.id=follow.user_id"). Where("follow.follow_id=?", u.ID). + And("`user`.type=?", UserTypeIndividual). And(isUserVisibleToViewerCond(viewer)) if listOptions.Page != 0 { @@ -333,6 +346,7 @@ func GetUserFollowing(ctx context.Context, u, viewer *User, listOptions db.ListO Select("`user`.*"). Join("LEFT", "follow", "`user`.id=follow.follow_id"). Where("follow.user_id=?", u.ID). + And("`user`.type=?", UserTypeIndividual). And(isUserVisibleToViewerCond(viewer)) if listOptions.Page != 0 { @@ -959,7 +973,7 @@ func GetUserByName(ctx context.Context, name string) (*User, error) { if len(name) == 0 { return nil, ErrUserNotExist{0, name, 0} } - u := &User{LowerName: strings.ToLower(name)} + u := &User{LowerName: strings.ToLower(name), Type: UserTypeIndividual} has, err := db.GetEngine(ctx).Get(u) if err != nil { return nil, err diff --git a/services/auth/source/db/authenticate.go b/services/auth/source/db/authenticate.go index ec89984499862..76445e0d6d53d 100644 --- a/services/auth/source/db/authenticate.go +++ b/services/auth/source/db/authenticate.go @@ -40,5 +40,13 @@ func Authenticate(user *user_model.User, login, password string) (*user_model.Us } } + // attempting to login as a non-user account + if user.Type != user_model.UserTypeIndividual { + return nil, user_model.ErrUserProhibitLogin{ + UID: user.ID, + Name: user.Name, + } + } + return user, nil } From 4014200021a1997283c779a815fe9e5febf1fda1 Mon Sep 17 00:00:00 2001 From: techknowlogick Date: Mon, 17 Apr 2023 13:07:13 -0400 Subject: [PATCH 06/21] add CLI command to register runner tokens (#23762) This is a CLI command to generate new tokens for the runners to register with Fix https://github.com/go-gitea/gitea/issues/23643 --------- Co-authored-by: delvh --- cmd/actions.go | 56 ++++++++++++ .../doc/administration/command-line.en-us.md | 25 +++++ main.go | 1 + modules/private/actions.go | 27 ++++++ routers/private/actions.go | 91 +++++++++++++++++++ routers/private/internal.go | 1 + 6 files changed, 201 insertions(+) create mode 100644 cmd/actions.go create mode 100644 modules/private/actions.go create mode 100644 routers/private/actions.go diff --git a/cmd/actions.go b/cmd/actions.go new file mode 100644 index 0000000000000..66ad336da508e --- /dev/null +++ b/cmd/actions.go @@ -0,0 +1,56 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package cmd + +import ( + "fmt" + + "code.gitea.io/gitea/modules/private" + "code.gitea.io/gitea/modules/setting" + + "github.com/urfave/cli" +) + +var ( + // CmdActions represents the available actions sub-commands. + CmdActions = cli.Command{ + Name: "actions", + Usage: "", + Description: "Commands for managing Gitea Actions", + Subcommands: []cli.Command{ + subcmdActionsGenRunnerToken, + }, + } + + subcmdActionsGenRunnerToken = cli.Command{ + Name: "generate-runner-token", + Usage: "Generate a new token for a runner to use to register with the server", + Action: runGenerateActionsRunnerToken, + Aliases: []string{"grt"}, + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "scope, s", + Value: "", + Usage: "{owner}[/{repo}] - leave empty for a global runner", + }, + }, + } +) + +func runGenerateActionsRunnerToken(c *cli.Context) error { + ctx, cancel := installSignals() + defer cancel() + + setting.InitProviderFromExistingFile() + setting.LoadCommonSettings() + + scope := c.String("scope") + + respText, extra := private.GenerateActionsRunnerToken(ctx, scope) + if extra.HasError() { + return handleCliResponseExtra(extra) + } + _, _ = fmt.Printf("%s\n", respText) + return nil +} diff --git a/docs/content/doc/administration/command-line.en-us.md b/docs/content/doc/administration/command-line.en-us.md index d3362e573138b..4d01d6e640ef0 100644 --- a/docs/content/doc/administration/command-line.en-us.md +++ b/docs/content/doc/administration/command-line.en-us.md @@ -551,3 +551,28 @@ Restore-repo restore repository data from disk dir: - `--owner_name lunny`: Restore destination owner name - `--repo_name tango`: Restore destination repository name - `--units `: Which items will be restored, one or more units should be separated as comma. wiki, issues, labels, releases, release_assets, milestones, pull_requests, comments are allowed. Empty means all units. + +### actions generate-runner-token + +Generate a new token for a runner to use to register with the server + +- Options: + - `--scope {owner}[/{repo}]`, `-s {owner}[/{repo}]`: To limit the scope of the runner, no scope means the runner can be used for all repos, but you can also limit it to a specific repo or owner + +To register a global runner: + +``` +gitea actions generate-runner-token +``` + +To register a runner for a specific organization, in this case `org`: + +``` +gitea actions generate-runner-token -s org +``` + +To register a runner for a specific repo, in this case `username/test-repo`: + +``` +gitea actions generate-runner-token -s username/test-repo +``` diff --git a/main.go b/main.go index eeedf54c27419..1589fa97db434 100644 --- a/main.go +++ b/main.go @@ -75,6 +75,7 @@ arguments - which can alternatively be run by running the subcommand web.` cmd.CmdDocs, cmd.CmdDumpRepository, cmd.CmdRestoreRepository, + cmd.CmdActions, } // Now adjust these commands to add our global configuration options diff --git a/modules/private/actions.go b/modules/private/actions.go new file mode 100644 index 0000000000000..be24e16d3ff3f --- /dev/null +++ b/modules/private/actions.go @@ -0,0 +1,27 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package private + +import ( + "context" + + "code.gitea.io/gitea/modules/setting" +) + +// Email structure holds a data for sending general emails +type GenerateTokenRequest struct { + Scope string +} + +// GenerateActionsRunnerToken calls the internal GenerateActionsRunnerToken function +func GenerateActionsRunnerToken(ctx context.Context, scope string) (string, ResponseExtra) { + reqURL := setting.LocalURL + "api/internal/actions/generate_actions_runner_token" + + req := newInternalRequest(ctx, reqURL, "POST", GenerateTokenRequest{ + Scope: scope, + }) + + resp, extra := requestJSONResp(req, &responseText{}) + return resp.Text, extra +} diff --git a/routers/private/actions.go b/routers/private/actions.go new file mode 100644 index 0000000000000..b7e416f56a33b --- /dev/null +++ b/routers/private/actions.go @@ -0,0 +1,91 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package private + +import ( + "errors" + "fmt" + "net/http" + "strings" + + actions_model "code.gitea.io/gitea/models/actions" + repo_model "code.gitea.io/gitea/models/repo" + user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/context" + "code.gitea.io/gitea/modules/json" + "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/private" + "code.gitea.io/gitea/modules/util" +) + +// GenerateActionsRunnerToken generates a new runner token for a given scope +func GenerateActionsRunnerToken(ctx *context.PrivateContext) { + var genRequest private.GenerateTokenRequest + rd := ctx.Req.Body + defer rd.Close() + + if err := json.NewDecoder(rd).Decode(&genRequest); err != nil { + log.Error("%v", err) + ctx.JSON(http.StatusInternalServerError, private.Response{ + Err: err.Error(), + }) + return + } + + owner, repo, err := parseScope(ctx, genRequest.Scope) + if err != nil { + log.Error("%v", err) + ctx.JSON(http.StatusInternalServerError, private.Response{ + Err: err.Error(), + }) + } + + token, err := actions_model.GetUnactivatedRunnerToken(ctx, owner, repo) + if errors.Is(err, util.ErrNotExist) { + token, err = actions_model.NewRunnerToken(ctx, owner, repo) + if err != nil { + err := fmt.Sprintf("error while creating runner token: %v", err) + log.Error("%v", err) + ctx.JSON(http.StatusInternalServerError, private.Response{ + Err: err, + }) + return + } + } else if err != nil { + err := fmt.Sprintf("could not get unactivated runner token: %v", err) + log.Error("%v", err) + ctx.JSON(http.StatusInternalServerError, private.Response{ + Err: err, + }) + return + } + + ctx.PlainText(http.StatusOK, token.Token) +} + +func parseScope(ctx *context.PrivateContext, scope string) (ownerID, repoID int64, err error) { + ownerID = 0 + repoID = 0 + if scope == "" { + return ownerID, repoID, nil + } + + ownerName, repoName, found := strings.Cut(scope, "/") + + u, err := user_model.GetUserByName(ctx, ownerName) + if err != nil { + return ownerID, repoID, err + } + + if !found { + return u.ID, repoID, nil + } + + r, err := repo_model.GetRepositoryByName(u.ID, repoName) + if err != nil { + return ownerID, repoID, err + } + repoID = r.ID + return ownerID, repoID, nil +} diff --git a/routers/private/internal.go b/routers/private/internal.go index 4acede33705d6..b4d32c37a6439 100644 --- a/routers/private/internal.go +++ b/routers/private/internal.go @@ -77,6 +77,7 @@ func Routes() *web.Route { r.Get("/manager/processes", Processes) r.Post("/mail/send", SendEmail) r.Post("/restore_repo", RestoreRepo) + r.Post("/actions/generate_actions_runner_token", GenerateActionsRunnerToken) return r } From 5eb4c6386709259a9280c5aad6e0488f381144c5 Mon Sep 17 00:00:00 2001 From: Zettat123 Date: Tue, 18 Apr 2023 01:49:47 +0800 Subject: [PATCH 07/21] Support triggering workflows by wiki related events (#24119) This PR is to support triggering workflows by wiki related events like creating, editing or deleting wiki pages. In GitHub, this event is called [gollum](https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#gollum) --- modules/actions/github.go | 5 +++++ modules/actions/workflows.go | 2 -- modules/actions/workflows_test.go | 7 +++++++ services/actions/notifier.go | 35 +++++++++++++++++++++++++++++++ 4 files changed, 47 insertions(+), 2 deletions(-) diff --git a/modules/actions/github.go b/modules/actions/github.go index 1148554139cd1..f3cb335da98c4 100644 --- a/modules/actions/github.go +++ b/modules/actions/github.go @@ -21,6 +21,7 @@ const ( githubEventIssueComment = "issue_comment" githubEventRelease = "release" githubEventPullRequestComment = "pull_request_comment" + githubEventGollum = "gollum" ) // canGithubEventMatch check if the input Github event can match any Gitea event. @@ -29,6 +30,10 @@ func canGithubEventMatch(eventName string, triggedEvent webhook_module.HookEvent case githubEventRegistryPackage: return triggedEvent == webhook_module.HookEventPackage + // See https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#gollum + case githubEventGollum: + return triggedEvent == webhook_module.HookEventWiki + case githubEventIssues: switch triggedEvent { case webhook_module.HookEventIssues, diff --git a/modules/actions/workflows.go b/modules/actions/workflows.go index d21dc1d809c01..f37f4f2878af7 100644 --- a/modules/actions/workflows.go +++ b/modules/actions/workflows.go @@ -119,8 +119,6 @@ func detectMatched(commit *git.Commit, triggedEvent webhook_module.HookEventType webhook_module.HookEventCreate, webhook_module.HookEventDelete, webhook_module.HookEventFork, - // FIXME: `wiki` event should match `gollum` event - // See https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#gollum webhook_module.HookEventWiki: if len(evt.Acts()) != 0 { log.Warn("Ignore unsupported %s event arguments %v", triggedEvent, evt.Acts()) diff --git a/modules/actions/workflows_test.go b/modules/actions/workflows_test.go index 6724abafd859e..6ef5d59942298 100644 --- a/modules/actions/workflows_test.go +++ b/modules/actions/workflows_test.go @@ -92,6 +92,13 @@ func TestDetectMatched(t *testing.T) { yamlOn: "on:\n registry_package:\n types: [updated]", expected: false, }, + { + desc: "HookEventWiki(wiki) matches githubEventGollum(gollum)", + triggedEvent: webhook_module.HookEventWiki, + payload: nil, + yamlOn: "on: gollum", + expected: true, + }, } for _, tc := range testCases { diff --git a/services/actions/notifier.go b/services/actions/notifier.go index 6956c25cee2fd..4ac77276ffe2e 100644 --- a/services/actions/notifier.go +++ b/services/actions/notifier.go @@ -526,3 +526,38 @@ func (n *actionsNotifier) NotifyPullRequestChangeTargetBranch(ctx context.Contex WithPullRequest(pr). Notify(ctx) } + +func (n *actionsNotifier) NotifyNewWikiPage(ctx context.Context, doer *user_model.User, repo *repo_model.Repository, page, comment string) { + ctx = withMethod(ctx, "NotifyNewWikiPage") + + newNotifyInput(repo, doer, webhook_module.HookEventWiki).WithPayload(&api.WikiPayload{ + Action: api.HookWikiCreated, + Repository: convert.ToRepo(ctx, repo, perm_model.AccessModeOwner), + Sender: convert.ToUser(ctx, doer, nil), + Page: page, + Comment: comment, + }).Notify(ctx) +} + +func (n *actionsNotifier) NotifyEditWikiPage(ctx context.Context, doer *user_model.User, repo *repo_model.Repository, page, comment string) { + ctx = withMethod(ctx, "NotifyEditWikiPage") + + newNotifyInput(repo, doer, webhook_module.HookEventWiki).WithPayload(&api.WikiPayload{ + Action: api.HookWikiEdited, + Repository: convert.ToRepo(ctx, repo, perm_model.AccessModeOwner), + Sender: convert.ToUser(ctx, doer, nil), + Page: page, + Comment: comment, + }).Notify(ctx) +} + +func (n *actionsNotifier) NotifyDeleteWikiPage(ctx context.Context, doer *user_model.User, repo *repo_model.Repository, page string) { + ctx = withMethod(ctx, "NotifyDeleteWikiPage") + + newNotifyInput(repo, doer, webhook_module.HookEventWiki).WithPayload(&api.WikiPayload{ + Action: api.HookWikiDeleted, + Repository: convert.ToRepo(ctx, repo, perm_model.AccessModeOwner), + Sender: convert.ToUser(ctx, doer, nil), + Page: page, + }).Notify(ctx) +} From f045e58cc7ba076a0ac4b0b5bf0702fa155eaa59 Mon Sep 17 00:00:00 2001 From: Yarden Shoham Date: Mon, 17 Apr 2023 21:26:01 +0300 Subject: [PATCH 08/21] Localize activity heatmap (except tooltip) (#24131) The calculation of the total sum is moved to the backend so a full HTML string could be sent. ![image](https://user-images.githubusercontent.com/20454870/232112381-c11d896b-ba47-40f8-b2a3-71cf4b3208de.png) - Closes #10669 - 2nd attempt (the first was in #21570) --------- Signed-off-by: Yarden Shoham Co-authored-by: Giteabot --- models/activities/user_heatmap.go | 9 +++++++++ options/locale/locale_en-US.ini | 6 ++++++ routers/web/user/home.go | 1 + routers/web/user/profile.go | 1 + templates/user/heatmap.tmpl | 8 +++++++- web_src/js/components/ActivityHeatmap.vue | 11 +---------- web_src/js/features/heatmap.js | 6 +++++- 7 files changed, 30 insertions(+), 12 deletions(-) diff --git a/models/activities/user_heatmap.go b/models/activities/user_heatmap.go index d3f0f0db73b27..33207995267b6 100644 --- a/models/activities/user_heatmap.go +++ b/models/activities/user_heatmap.go @@ -69,3 +69,12 @@ func getUserHeatmapData(user *user_model.User, team *organization.Team, doer *us OrderBy("timestamp"). Find(&hdata) } + +// GetTotalContributionsInHeatmap returns the total number of contributions in a heatmap +func GetTotalContributionsInHeatmap(hdata []*UserHeatmapData) int64 { + var total int64 + for _, v := range hdata { + total += v.Contributions + } + return total +} diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index c2c8f1e120c1f..2b0260a615e6f 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -118,6 +118,12 @@ footer = Footer footer.software = About Software footer.links = Links +[heatmap] +number_of_contributions_in_the_last_12_months = %s contributions in the last 12 months +no_contributions = No contributions +less = Less +more = More + [editor] buttons.heading.tooltip = Add heading buttons.bold.tooltip = Add bold text diff --git a/routers/web/user/home.go b/routers/web/user/home.go index a0a5dc3c4b9ba..1f77379044afe 100644 --- a/routers/web/user/home.go +++ b/routers/web/user/home.go @@ -107,6 +107,7 @@ func Dashboard(ctx *context.Context) { return } ctx.Data["HeatmapData"] = data + ctx.Data["HeatmapTotalContributions"] = activities_model.GetTotalContributionsInHeatmap(data) } feeds, count, err := activities_model.GetFeeds(ctx, activities_model.GetFeedsOptions{ diff --git a/routers/web/user/profile.go b/routers/web/user/profile.go index d690fa4d0113d..b39ba58f12455 100644 --- a/routers/web/user/profile.go +++ b/routers/web/user/profile.go @@ -74,6 +74,7 @@ func Profile(ctx *context.Context) { return } ctx.Data["HeatmapData"] = data + ctx.Data["HeatmapTotalContributions"] = activities_model.GetTotalContributionsInHeatmap(data) } if len(ctx.ContextUser.Description) != 0 { diff --git a/templates/user/heatmap.tmpl b/templates/user/heatmap.tmpl index 9d58bc8fc5e51..5d42a5435bb7b 100644 --- a/templates/user/heatmap.tmpl +++ b/templates/user/heatmap.tmpl @@ -1,5 +1,11 @@ {{if .HeatmapData}} -
+
{{.locale.Tr "user.heatmap.loading"}}
diff --git a/web_src/js/components/ActivityHeatmap.vue b/web_src/js/components/ActivityHeatmap.vue index 98ffce44b5579..7834ebe82ccac 100644 --- a/web_src/js/components/ActivityHeatmap.vue +++ b/web_src/js/components/ActivityHeatmap.vue @@ -1,7 +1,7 @@