chore(deps): update dependency eslint to v9 - abandoned (#3594)

Co-authored-by: qwerty287 <qwerty287@posteo.de>
Co-authored-by: qwerty287 <80460567+qwerty287@users.noreply.github.com>
Co-authored-by: Anbraten <6918444+anbraten@users.noreply.github.com>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
This commit is contained in:
renovate[bot] 2024-06-06 15:16:59 +02:00 committed by GitHub
parent c72468478d
commit 22414744b0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
110 changed files with 4623 additions and 3689 deletions

View file

@ -13,6 +13,7 @@
"words": [ "words": [
"abool", "abool",
"anbraten", "anbraten",
"antfu",
"apimachinery", "apimachinery",
"autoincr", "autoincr",
"autoscaler", "autoscaler",
@ -67,6 +68,7 @@
"httpsig", "httpsig",
"HTTPURL", "HTTPURL",
"httputil", "httputil",
"ianvs",
"iconify", "iconify",
"Infof", "Infof",
"Informatyka", "Informatyka",
@ -144,6 +146,7 @@
"tmpfs", "tmpfs",
"tmpl", "tmpl",
"tolerations", "tolerations",
"tseslint",
"ttlcache", "ttlcache",
"typecheck", "typecheck",
"Typeflag", "Typeflag",

10
.vscode/settings.json vendored
View file

@ -9,5 +9,13 @@
"go.lintTool": "golangci-lint", "go.lintTool": "golangci-lint",
"go.lintFlags": ["--fast"], "go.lintFlags": ["--fast"],
"eslint.workingDirectories": ["./web"], "eslint.workingDirectories": ["./web"],
"prettier.ignorePath": "./web/.prettierignore" "prettier.ignorePath": "./web/.prettierignore",
// Enable the ESlint flat config support
// (remove this if your ESLint extension above v3.0.5)
"eslint.experimental.useFlatConfig": true,
// Auto fix
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit",
"source.organizeImports": "never"
}
} }

View file

@ -1,9 +0,0 @@
# don't lint build output (make sure it's set to your correct build folder name)
dist
coverage/
package.json
tsconfig.eslint.json
tsconfig.json
src/assets/locales/
src/assets/dayjsLocales/
components.d.ts

View file

@ -1,161 +0,0 @@
// cSpell:ignore TSES
// @ts-check
/** @type {import('@typescript-eslint/experimental-utils').TSESLint.Linter.Config} */
/* eslint-env node */
module.exports = {
env: {
browser: true,
},
reportUnusedDisableDirectives: true,
parser: 'vue-eslint-parser',
parserOptions: {
project: ['./tsconfig.eslint.json'],
tsconfigRootDir: __dirname,
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore see https://github.com/vuejs/vue-eslint-parser#parseroptionsparser
parser: '@typescript-eslint/parser',
sourceType: 'module',
extraFileExtensions: ['.vue'],
},
plugins: ['@typescript-eslint', 'import', 'simple-import-sort'],
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'airbnb-base',
'airbnb-typescript/base',
'plugin:import/errors',
'plugin:import/warnings',
'plugin:import/typescript',
'plugin:promise/recommended',
'plugin:vue/vue3-recommended',
'plugin:prettier/recommended',
'plugin:vue-scoped-css/recommended',
],
rules: {
// enable scope analysis rules
'no-unused-vars': 'off',
'@typescript-eslint/no-unused-vars': 'error',
'no-use-before-define': 'off',
'@typescript-eslint/no-use-before-define': 'error',
'no-shadow': 'off',
'@typescript-eslint/no-shadow': 'error',
'no-redeclare': 'off',
'@typescript-eslint/no-redeclare': 'error',
// make typescript eslint rules even more strict
'@typescript-eslint/no-explicit-any': 'error',
'@typescript-eslint/explicit-module-boundary-types': 'off',
'@typescript-eslint/no-non-null-assertion': 'error',
// SOURCE: https://github.com/iamturns/eslint-config-airbnb-typescript/blob/4aec5702be5b4e74e0e2f40bc78b4bc961681de1/lib/shared.js#L41
'@typescript-eslint/naming-convention': [
'error',
// Allow camelCase variables (23.2), PascalCase variables (23.8), and UPPER_CASE variables (23.10)
{
selector: 'variable',
format: ['camelCase', 'PascalCase', 'UPPER_CASE'],
leadingUnderscore: 'allow',
},
// Allow camelCase functions (23.2), and PascalCase functions (23.8)
{
selector: 'function',
format: ['camelCase', 'PascalCase'],
},
// Airbnb recommends PascalCase for classes (23.3), and although Airbnb does not make TypeScript recommendations, we are assuming this rule would similarly apply to anything "type like", including interfaces, type aliases, and enums
{
selector: 'typeLike',
format: ['PascalCase'],
},
],
'import/no-unresolved': 'off', // disable as this is handled by tsc itself
'import/first': 'error',
'import/newline-after-import': 'error',
'import/no-cycle': 'error',
'import/no-relative-parent-imports': 'error',
'import/no-duplicates': 'error',
'import/no-extraneous-dependencies': 'error',
'import/extensions': 'off',
'import/prefer-default-export': 'off',
'simple-import-sort/imports': 'error',
'simple-import-sort/exports': 'error',
'promise/prefer-await-to-then': 'error',
'promise/prefer-await-to-callbacks': 'error',
'no-underscore-dangle': 'off',
'no-else-return': ['error', { allowElseIf: false }],
'no-return-assign': ['error', 'always'],
'no-return-await': 'error',
'no-useless-return': 'error',
'no-restricted-imports': [
'error',
{
patterns: ['src', 'dist'],
},
],
'no-console': 'warn',
'no-useless-concat': 'error',
'prefer-const': 'error',
'spaced-comment': ['error', 'always'],
'object-shorthand': ['error', 'always'],
'no-useless-rename': 'error',
eqeqeq: 'error',
'vue/attribute-hyphenation': 'error',
// enable in accordance with https://github.com/prettier/eslint-config-prettier#vuehtml-self-closing
'vue/html-self-closing': [
'error',
{
html: {
void: 'any',
},
},
],
'vue/no-static-inline-styles': 'error',
'vue/v-on-function-call': 'error',
'vue/no-useless-v-bind': 'error',
'vue/no-useless-mustaches': 'error',
'vue/no-useless-concat': 'error',
'vue/no-boolean-default': 'error',
'vue/html-button-has-type': 'error',
'vue/component-name-in-template-casing': 'error',
'vue/match-component-file-name': [
'error',
{
extensions: ['vue'],
shouldMatchCase: true,
},
],
'vue/require-name-property': 'error',
'vue/v-for-delimiter-style': 'error',
'vue/no-empty-component-block': 'error',
'vue/no-duplicate-attr-inheritance': 'error',
'vue/no-unused-properties': [
'error',
{
groups: ['props', 'data', 'computed', 'methods', 'setup'],
},
],
'vue/new-line-between-multi-line-property': 'error',
'vue/padding-line-between-blocks': 'error',
'vue/multi-word-component-names': 'off',
'vue/no-reserved-component-names': 'off',
// css rules
'vue-scoped-css/no-unused-selector': 'error',
'vue-scoped-css/no-parsing-error': 'error',
'vue-scoped-css/require-scoped': 'error',
// enable in accordance with https://github.com/prettier/eslint-config-prettier#curly
curly: ['error', 'all'],
// risky because of https://github.com/prettier/eslint-plugin-prettier#arrow-body-style-and-prefer-arrow-callback-issue
'arrow-body-style': 'error',
'prefer-arrow-callback': 'error',
},
};

15
web/.prettierrc.js Normal file
View file

@ -0,0 +1,15 @@
import { readFile } from 'node:fs/promises';
const config = JSON.parse(await readFile(new URL('../.prettierrc.json', import.meta.url)));
export default {
...config,
plugins: ['@ianvs/prettier-plugin-sort-imports'],
importOrder: [
'<THIRD_PARTY_MODULES>', // Imports not matched by other special words or groups.
'', // Empty string will match any import not matched by other special words or groups.
'^(#|@|~|\\$)(/.*)$',
'',
'^[./]',
],
};

119
web/eslint.config.js Normal file
View file

@ -0,0 +1,119 @@
// cSpell:ignore tseslint
// @ts-check
import antfu from '@antfu/eslint-config';
import js from '@eslint/js';
import vueI18n from '@intlify/eslint-plugin-vue-i18n';
import eslintPluginVueScopedCSS from 'eslint-plugin-vue-scoped-css';
export default antfu(
{
stylistic: false,
typescript: {
tsconfigPath: './tsconfig.json',
},
vue: true,
// Disable jsonc and yaml support
jsonc: false,
yaml: false,
},
js.configs.recommended,
// eslintPromise.configs.recommended,
// TypeScript
//...tseslint.configs.recommended,
//...tseslint.configs.recommendedTypeChecked,
//...tseslint.configs.strictTypeChecked,
//...tseslint.configs.stylisticTypeChecked,
{
rules: {
'import/order': 'off',
'sort-imports': 'off',
},
},
...eslintPluginVueScopedCSS.configs['flat/recommended'],
// Vue
{
files: ['**/*.vue'],
rules: {
'vue/multi-word-component-names': 'off',
'vue/html-self-closing': [
'error',
{
html: {
void: 'always',
normal: 'always',
component: 'always',
},
svg: 'always',
math: 'always',
},
],
'vue/block-order': [
'error',
{
order: ['template', 'script', 'style'],
},
],
'vue/singleline-html-element-content-newline': ['off'],
},
},
// Vue I18n
...vueI18n.configs['flat/recommended'],
{
rules: {
'@intlify/vue-i18n/no-raw-text': [
'error',
{
attributes: {
'/.+/': ['label'],
},
},
],
'@intlify/vue-i18n/key-format-style': ['error', 'snake_case'],
'@intlify/vue-i18n/no-duplicate-keys-in-locale': 'error',
'@intlify/vue-i18n/no-dynamic-keys': 'error',
'@intlify/vue-i18n/no-deprecated-i18n-component': 'error',
'@intlify/vue-i18n/no-deprecated-tc': 'error',
'@intlify/vue-i18n/no-i18n-t-path-prop': 'error',
'@intlify/vue-i18n/no-missing-keys-in-other-locales': 'off',
'@intlify/vue-i18n/valid-message-syntax': 'error',
'@intlify/vue-i18n/no-missing-keys': 'error',
'@intlify/vue-i18n/no-unknown-locale': 'error',
'@intlify/vue-i18n/no-unused-keys': ['error', { extensions: ['.ts', '.vue'] }],
'@intlify/vue-i18n/prefer-sfc-lang-attr': 'error',
'@intlify/vue-i18n/no-html-messages': 'error',
'@intlify/vue-i18n/prefer-linked-key-with-paren': 'error',
'@intlify/vue-i18n/sfc-locale-attr': 'error',
},
settings: {
'vue-i18n': {
localeDir: './src/assets/locales/en.json',
// Specify the version of `vue-i18n` you are using.
// If not specified, the message will be parsed twice.
messageSyntaxVersion: '^9.0.0',
},
},
},
// Ignore list
{
ignores: [
'dist',
'coverage/',
'package.json',
'tsconfig.eslint.json',
'tsconfig.json',
'src/assets/locales/**/*',
'!src/assets/locales/en.json',
'src/assets/dayjsLocales/',
'components.d.ts',
],
},
);

View file

@ -3,6 +3,7 @@
"author": "Woodpecker CI", "author": "Woodpecker CI",
"version": "0.0.0", "version": "0.0.0",
"license": "Apache-2.0", "license": "Apache-2.0",
"type": "module",
"engines": { "engines": {
"node": ">=14" "node": ">=14"
}, },
@ -10,7 +11,7 @@
"start": "vite", "start": "vite",
"build": "vite build --base=/BASE_PATH", "build": "vite build --base=/BASE_PATH",
"serve": "vite preview", "serve": "vite preview",
"lint": "eslint --max-warnings 0 --ext .js,.ts,.vue,.json .", "lint": "eslint --max-warnings 0 .",
"format": "prettier --write .", "format": "prettier --write .",
"format:check": "prettier -c .", "format:check": "prettier -c .",
"typecheck": "vue-tsc --noEmit", "typecheck": "vue-tsc --noEmit",
@ -18,57 +19,54 @@
}, },
"dependencies": { "dependencies": {
"@intlify/unplugin-vue-i18n": "^4.0.0", "@intlify/unplugin-vue-i18n": "^4.0.0",
"@kyvg/vue3-notification": "^3.1.3", "@kyvg/vue3-notification": "^3.2.1",
"@vueuse/core": "^10.7.2", "@vueuse/core": "^10.10.0",
"ansi_up": "^6.0.2", "ansi_up": "^6.0.2",
"dayjs": "^1.11.10", "dayjs": "^1.11.11",
"fuse.js": "^7.0.0", "fuse.js": "^7.0.0",
"js-base64": "^3.7.6", "js-base64": "^3.7.7",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"node-emoji": "^2.1.3", "node-emoji": "^2.1.3",
"pinia": "^2.1.7", "pinia": "^2.1.7",
"prismjs": "^1.29.0", "prismjs": "^1.29.0",
"semver": "^7.5.4", "semver": "^7.6.2",
"vue": "^3.4.15", "vue": "^3.4.27",
"vue-i18n": "^9.9.0", "vue-i18n": "^9.13.1",
"vue-router": "^4.2.5" "vue-router": "^4.3.2"
}, },
"devDependencies": { "devDependencies": {
"@iconify/json": "^2.2.171", "@antfu/eslint-config": "^2.20.0",
"@types/lodash": "^4.14.202", "@eslint/js": "^9.4.0",
"@types/node": "^20.11.5", "@ianvs/prettier-plugin-sort-imports": "^4.2.1",
"@types/node-emoji": "^2.0.0", "@iconify/json": "^2.2.216",
"@types/prismjs": "^1.26.3", "@intlify/eslint-plugin-vue-i18n": "3.0.0-next.13",
"@types/semver": "^7.5.6", "@types/eslint__js": "^8.42.3",
"@typescript-eslint/eslint-plugin": "^7.0.0", "@types/lodash": "^4.17.4",
"@typescript-eslint/parser": "^7.0.0", "@types/node": "^20.14.2",
"@vitejs/plugin-vue": "^5.0.3", "@types/node-emoji": "^2.1.0",
"@vue/compiler-sfc": "^3.4.15", "@types/prismjs": "^1.26.4",
"@vue/test-utils": "^2.4.5", "@types/semver": "^7.5.8",
"eslint": "^8.56.0", "@types/tinycolor2": "^1.4.6",
"eslint-config-airbnb-base": "^15.0.0", "@vitejs/plugin-vue": "^5.0.5",
"eslint-config-airbnb-typescript": "^18.0.0", "@vue/compiler-sfc": "^3.4.27",
"eslint-config-prettier": "^9.1.0", "@vue/test-utils": "^2.4.6",
"eslint-plugin-import": "^2.29.1", "eslint": "^9.4.0",
"eslint-plugin-prettier": "^5.1.3", "eslint-plugin-promise": "^6.2.0",
"eslint-plugin-promise": "^6.1.1", "eslint-plugin-vue-scoped-css": "^2.8.0",
"eslint-plugin-simple-import-sort": "^12.0.0", "jsdom": "^24.1.0",
"eslint-plugin-vue": "^9.20.1", "prettier": "^3.3.0",
"eslint-plugin-vue-scoped-css": "^2.7.2", "replace-in-file": "^7.2.0",
"jsdom": "^24.0.0",
"prettier": "^3.2.4",
"replace-in-file": "^7.1.0",
"tinycolor2": "^1.6.0", "tinycolor2": "^1.6.0",
"typescript": "5.4.5", "typescript": "5.4.5",
"unplugin-icons": "^0.18.2", "typescript-eslint": "^7.12.0",
"unplugin-icons": "^0.18.5",
"unplugin-vue-components": "^0.26.0", "unplugin-vue-components": "^0.26.0",
"vite": "^5.0.12", "vite": "^5.2.12",
"vite-plugin-prismjs": "^0.0.11", "vite-plugin-prismjs": "^0.0.11",
"vite-plugin-windicss": "^1.9.3", "vite-plugin-windicss": "^1.9.3",
"vite-svg-loader": "^5.1.0", "vite-svg-loader": "^5.1.0",
"vitest": "^1.5.0", "vitest": "^1.6.0",
"vue-eslint-parser": "^9.4.0", "vue-tsc": "^2.0.19",
"vue-tsc": "^2.0.0",
"windicss": "^3.5.6" "windicss": "^3.5.6"
}, },
"pnpm": { "pnpm": {

File diff suppressed because it is too large Load diff

View file

@ -31,7 +31,7 @@ const apiClient = useApiClient();
const { notify } = useNotifications(); const { notify } = useNotifications();
const i18n = useI18n(); const i18n = useI18n();
// eslint-disable-next-line promise/prefer-await-to-callbacks // TODO reenable with eslint-plugin-promise eslint-disable-next-line promise/prefer-await-to-callbacks
apiClient.setErrorHandler((err) => { apiClient.setErrorHandler((err) => {
if (err.status === 404) { if (err.status === 404) {
notify({ title: i18n.t('errors.not_found'), type: 'error' }); notify({ title: i18n.t('errors.not_found'), type: 'error' });

View file

@ -10,7 +10,6 @@
"search": "Search…", "search": "Search…",
"username": "Username", "username": "Username",
"password": "Password", "password": "Password",
"url": "URL",
"back": "Back", "back": "Back",
"unknown_error": "An unknown error occurred", "unknown_error": "An unknown error occurred",
"documentation_for": "Documentation for \"{topic}\"", "documentation_for": "Documentation for \"{topic}\"",
@ -25,11 +24,6 @@
}, },
"time": { "time": {
"template": "MMM D, YYYY, HH:mm z", "template": "MMM D, YYYY, HH:mm z",
"weeks_short": "w",
"days_short": "d",
"hours_short": "h",
"min_short": "min",
"sec_short": "sec",
"not_started": "not started yet" "not_started": "not started yet"
}, },
"repo": { "repo": {
@ -65,12 +59,10 @@
"user_none": "This organization / user does not have any projects yet.", "user_none": "This organization / user does not have any projects yet.",
"not_allowed": "You are not allowed to access this repository", "not_allowed": "You are not allowed to access this repository",
"enable": { "enable": {
"reload": "Reload repositories",
"enable": "Enable", "enable": "Enable",
"enabled": "Already enabled", "enabled": "Already enabled",
"disabled": "Disabled", "disabled": "Disabled",
"success": "Repository enabled", "success": "Repository enabled"
"list_reloaded": "Repository list reloaded"
}, },
"open_in_forge": "Open repository in forge", "open_in_forge": "Open repository in forge",
"settings": { "settings": {
@ -209,7 +201,6 @@
"tasks": "Tasks", "tasks": "Tasks",
"config": "Config", "config": "Config",
"files": "Changed files ({files})", "files": "Changed files ({files})",
"no_files": "No files have been changed.",
"no_pipelines": "No pipelines have been started yet.", "no_pipelines": "No pipelines have been started yet.",
"no_pipeline_steps": "No pipeline steps available!", "no_pipeline_steps": "No pipeline steps available!",
"step_not_started": "This step hasn't started yet.", "step_not_started": "This step hasn't started yet.",
@ -444,7 +435,6 @@
"events": "Available at following events", "events": "Available at following events",
"pr_warning": "Please be careful with this option as a bad actor can submit a malicious pull request that exposes your secrets." "pr_warning": "Please be careful with this option as a bad actor can submit a malicious pull request that exposes your secrets."
}, },
"plugins_only": "Only available for plugins",
"edit": "Edit secret", "edit": "Edit secret",
"delete": "Delete secret" "delete": "Delete secret"
}, },

View file

@ -147,7 +147,7 @@ import { useAsyncAction } from '~/compositions/useAsyncAction';
import { useDate } from '~/compositions/useDate'; import { useDate } from '~/compositions/useDate';
import useNotifications from '~/compositions/useNotifications'; import useNotifications from '~/compositions/useNotifications';
import { usePagination } from '~/compositions/usePaginate'; import { usePagination } from '~/compositions/usePaginate';
import { Agent } from '~/lib/api/types'; import type { Agent } from '~/lib/api/types';
const apiClient = useApiClient(); const apiClient = useApiClient();
const notifications = useNotifications(); const notifications = useNotifications();
@ -175,14 +175,14 @@ const { doSubmit: saveAgent, isLoading: isSaving } = useAsyncAction(async () =>
selectedAgent.value = await apiClient.createAgent(selectedAgent.value); selectedAgent.value = await apiClient.createAgent(selectedAgent.value);
} }
notifications.notify({ notifications.notify({
title: t(isEditingAgent.value ? 'admin.settings.agents.saved' : 'admin.settings.agents.created'), title: isEditingAgent.value ? t('admin.settings.agents.saved') : t('admin.settings.agents.created'),
type: 'success', type: 'success',
}); });
resetPage(); resetPage();
}); });
const { doSubmit: deleteAgent, isLoading: isDeleting } = useAsyncAction(async (_agent: Agent) => { const { doSubmit: deleteAgent, isLoading: isDeleting } = useAsyncAction(async (_agent: Agent) => {
// eslint-disable-next-line no-restricted-globals, no-alert // eslint-disable-next-line no-alert
if (!confirm(t('admin.settings.agents.delete_confirm'))) { if (!confirm(t('admin.settings.agents.delete_confirm'))) {
return; return;
} }

View file

@ -15,8 +15,9 @@
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
class="underline" class="underline"
>{{ version.latest }}</a
> >
{{ version.latest }}
</a>
<span v-else> <span v-else>
{{ version.latest }} {{ version.latest }}
</span> </span>

View file

@ -43,7 +43,7 @@ import useApiClient from '~/compositions/useApiClient';
import { useAsyncAction } from '~/compositions/useAsyncAction'; import { useAsyncAction } from '~/compositions/useAsyncAction';
import useNotifications from '~/compositions/useNotifications'; import useNotifications from '~/compositions/useNotifications';
import { usePagination } from '~/compositions/usePaginate'; import { usePagination } from '~/compositions/usePaginate';
import { Org } from '~/lib/api/types'; import type { Org } from '~/lib/api/types';
const apiClient = useApiClient(); const apiClient = useApiClient();
const notifications = useNotifications(); const notifications = useNotifications();
@ -56,7 +56,7 @@ async function loadOrgs(page: number): Promise<Org[] | null> {
const { resetPage, data: orgs } = usePagination(loadOrgs); const { resetPage, data: orgs } = usePagination(loadOrgs);
const { doSubmit: deleteOrg, isLoading: isDeleting } = useAsyncAction(async (_org: Org) => { const { doSubmit: deleteOrg, isLoading: isDeleting } = useAsyncAction(async (_org: Org) => {
// eslint-disable-next-line no-restricted-globals, no-alert // eslint-disable-next-line no-alert
if (!confirm(t('admin.settings.orgs.delete_confirm'))) { if (!confirm(t('admin.settings.orgs.delete_confirm'))) {
return; return;
} }

View file

@ -85,7 +85,7 @@ import ListItem from '~/components/atomic/ListItem.vue';
import Settings from '~/components/layout/Settings.vue'; import Settings from '~/components/layout/Settings.vue';
import useApiClient from '~/compositions/useApiClient'; import useApiClient from '~/compositions/useApiClient';
import useNotifications from '~/compositions/useNotifications'; import useNotifications from '~/compositions/useNotifications';
import { QueueInfo } from '~/lib/api/types/queue'; import type { QueueInfo } from '~/lib/api/types';
import AdminQueueStats from './queue/AdminQueueStats.vue'; import AdminQueueStats from './queue/AdminQueueStats.vue';

View file

@ -33,7 +33,7 @@
</div> </div>
</ListItem> </ListItem>
<div v-if="repos?.length === 0" class="ml-2">{{ $t('admin.settings.orgs.none') }}</div> <div v-if="repos?.length === 0" class="ml-2">{{ $t('admin.settings.repos.none') }}</div>
</div> </div>
</Settings> </Settings>
</template> </template>
@ -49,7 +49,7 @@ import useApiClient from '~/compositions/useApiClient';
import { useAsyncAction } from '~/compositions/useAsyncAction'; import { useAsyncAction } from '~/compositions/useAsyncAction';
import useNotifications from '~/compositions/useNotifications'; import useNotifications from '~/compositions/useNotifications';
import { usePagination } from '~/compositions/usePaginate'; import { usePagination } from '~/compositions/usePaginate';
import { Repo } from '~/lib/api/types'; import type { Repo } from '~/lib/api/types';
const apiClient = useApiClient(); const apiClient = useApiClient();
const notifications = useNotifications(); const notifications = useNotifications();

View file

@ -41,7 +41,8 @@ import useApiClient from '~/compositions/useApiClient';
import { useAsyncAction } from '~/compositions/useAsyncAction'; import { useAsyncAction } from '~/compositions/useAsyncAction';
import useNotifications from '~/compositions/useNotifications'; import useNotifications from '~/compositions/useNotifications';
import { usePagination } from '~/compositions/usePaginate'; import { usePagination } from '~/compositions/usePaginate';
import { Secret, WebhookEvents } from '~/lib/api/types'; import type { Secret } from '~/lib/api/types';
import { WebhookEvents } from '~/lib/api/types';
const emptySecret: Partial<Secret> = { const emptySecret: Partial<Secret> = {
name: '', name: '',
@ -74,7 +75,7 @@ const { doSubmit: createSecret, isLoading: isSaving } = useAsyncAction(async ()
await apiClient.createGlobalSecret(selectedSecret.value); await apiClient.createGlobalSecret(selectedSecret.value);
} }
notifications.notify({ notifications.notify({
title: i18n.t(isEditingSecret.value ? 'secrets.saved' : 'secrets.created'), title: isEditingSecret.value ? i18n.t('secrets.saved') : i18n.t('secrets.created'),
type: 'success', type: 'success',
}); });
selectedSecret.value = undefined; selectedSecret.value = undefined;

View file

@ -97,7 +97,7 @@ import useApiClient from '~/compositions/useApiClient';
import { useAsyncAction } from '~/compositions/useAsyncAction'; import { useAsyncAction } from '~/compositions/useAsyncAction';
import useNotifications from '~/compositions/useNotifications'; import useNotifications from '~/compositions/useNotifications';
import { usePagination } from '~/compositions/usePaginate'; import { usePagination } from '~/compositions/usePaginate';
import { User } from '~/lib/api/types'; import type { User } from '~/lib/api/types';
const apiClient = useApiClient(); const apiClient = useApiClient();
const notifications = useNotifications(); const notifications = useNotifications();
@ -135,7 +135,7 @@ const { doSubmit: saveUser, isLoading: isSaving } = useAsyncAction(async () => {
}); });
const { doSubmit: deleteUser, isLoading: isDeleting } = useAsyncAction(async (_user: User) => { const { doSubmit: deleteUser, isLoading: isDeleting } = useAsyncAction(async (_user: User) => {
// eslint-disable-next-line no-restricted-globals, no-alert // eslint-disable-next-line no-alert
if (!confirm(t('admin.settings.users.delete_confirm'))) { if (!confirm(t('admin.settings.users.delete_confirm'))) {
return; return;
} }

View file

@ -54,14 +54,14 @@
import { computed } from 'vue'; import { computed } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { QueueStats } from '~/lib/api/types/queue'; import type { QueueStats } from '~/lib/api/types/queue';
const { t } = useI18n();
const props = defineProps<{ const props = defineProps<{
stats?: QueueStats; stats?: QueueStats;
}>(); }>();
const { t } = useI18n();
const total = computed(() => { const total = computed(() => {
if (!props.stats) { if (!props.stats) {
return 0; return 0;

View file

@ -36,9 +36,10 @@
<script lang="ts" setup> <script lang="ts" setup>
import { computed, useAttrs } from 'vue'; import { computed, useAttrs } from 'vue';
import { RouteLocationRaw } from 'vue-router'; import type { RouteLocationRaw } from 'vue-router';
import Icon, { IconNames } from '~/components/atomic/Icon.vue'; import type { IconNames } from '~/components/atomic/Icon.vue';
import Icon from '~/components/atomic/Icon.vue';
const props = withDefaults( const props = withDefaults(
defineProps<{ defineProps<{

View file

@ -1,11 +1,12 @@
<template> <template>
<a <a
:href="`${docsUrl}`" :href="`${docsUrl}`"
:title="$t('documentation_for', { topic: topic })" :title="$t('documentation_for', { topic })"
target="_blank" target="_blank"
class="text-wp-link-100 hover:text-wp-link-200 cursor-pointer mt-1" class="text-wp-link-100 hover:text-wp-link-200 cursor-pointer mt-1"
><Icon name="question" class="!w-4 !h-4" >
/></a> <Icon name="question" class="!w-4 !h-4" />
</a>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>

View file

@ -28,9 +28,9 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { RouteLocationRaw } from 'vue-router'; import type { RouteLocationRaw } from 'vue-router';
import Icon, { IconNames } from '~/components/atomic/Icon.vue'; import Icon, { type IconNames } from '~/components/atomic/Icon.vue';
defineProps<{ defineProps<{
icon?: IconNames; icon?: IconNames;

View file

@ -12,7 +12,7 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { RouteLocationRaw } from 'vue-router'; import type { RouteLocationRaw } from 'vue-router';
defineProps<{ defineProps<{
clickable?: boolean; clickable?: boolean;

View file

@ -1,7 +1,7 @@
import '~/style/prism.css'; import '~/style/prism.css';
import Prism from 'prismjs'; import Prism from 'prismjs';
import { computed, defineComponent, h, toRef, VNode } from 'vue'; import { computed, defineComponent, h, toRef, type VNode } from 'vue';
declare type Data = Record<string, unknown>; declare type Data = Record<string, unknown>;

View file

@ -14,7 +14,7 @@
import { computed, toRef } from 'vue'; import { computed, toRef } from 'vue';
import Checkbox from './Checkbox.vue'; import Checkbox from './Checkbox.vue';
import { CheckboxOption } from './form.types'; import type { CheckboxOption } from './form.types';
const props = defineProps<{ const props = defineProps<{
modelValue?: CheckboxOption['value'][]; modelValue?: CheckboxOption['value'][];

View file

@ -3,10 +3,10 @@
<div class="flex items-center mb-2"> <div class="flex items-center mb-2">
<label class="text-wp-text-100 font-bold" :for="id" v-bind="$attrs">{{ label }}</label> <label class="text-wp-text-100 font-bold" :for="id" v-bind="$attrs">{{ label }}</label>
<DocsLink v-if="docsUrl" :topic="label" :url="docsUrl" class="ml-2" /> <DocsLink v-if="docsUrl" :topic="label" :url="docsUrl" class="ml-2" />
<slot v-else-if="$slots['titleActions']" name="titleActions" /> <slot v-else-if="$slots.titleActions" name="titleActions" />
</div> </div>
<slot :id="id" /> <slot :id="id" />
<div v-if="$slots['description']" class="ml-1 text-wp-text-alt-100"> <div v-if="$slots.description" class="ml-1 text-wp-text-alt-100">
<slot name="description" /> <slot name="description" />
</div> </div>
</div> </div>

View file

@ -20,7 +20,7 @@ const modelValue = toRef(props, 'modelValue');
const innerValue = computed({ const innerValue = computed({
get: () => modelValue.value.toString(), get: () => modelValue.value.toString(),
set: (value) => { set: (value) => {
emit('update:modelValue', parseFloat(value)); emit('update:modelValue', Number.parseFloat(value));
}, },
}); });
</script> </script>

View file

@ -18,7 +18,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import { computed, toRef } from 'vue'; import { computed, toRef } from 'vue';
import { RadioOption } from './form.types'; import type { RadioOption } from './form.types';
const props = defineProps<{ const props = defineProps<{
modelValue: string; modelValue: string;

View file

@ -13,7 +13,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import { computed, toRef } from 'vue'; import { computed, toRef } from 'vue';
import { SelectOption } from './form.types'; import type { SelectOption } from './form.types';
const props = defineProps<{ const props = defineProps<{
modelValue: string; modelValue: string;

View file

@ -1,8 +1,8 @@
export type SelectOption = { export interface SelectOption {
value: string; value: string;
text: string; text: string;
description?: string; description?: string;
}; }
export type RadioOption = SelectOption; export type RadioOption = SelectOption;

View file

@ -7,8 +7,8 @@
<script setup lang="ts"> <script setup lang="ts">
import { computed, onMounted, ref } from 'vue'; import { computed, onMounted, ref } from 'vue';
import { IconNames } from '~/components/atomic/Icon.vue'; import type { IconNames } from '~/components/atomic/Icon.vue';
import { Tab, useTabsClient } from '~/compositions/useTabs'; import { useTabsClient, type Tab } from '~/compositions/useTabs';
const props = defineProps<{ const props = defineProps<{
id?: string; id?: string;

View file

@ -24,7 +24,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { useRoute, useRouter } from 'vue-router'; import { useRoute, useRouter } from 'vue-router';
import { Tab, useTabsClient } from '~/compositions/useTabs'; import { useTabsClient, type Tab } from '~/compositions/useTabs';
const router = useRouter(); const router = useRouter();
const route = useRoute(); const route = useRoute();

View file

@ -25,7 +25,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import { cloneDeep } from 'lodash'; import { cloneDeep } from 'lodash';
import { computed, inject, Ref, ref } from 'vue'; import { computed, inject, ref, type Ref } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import Button from '~/components/atomic/Button.vue'; import Button from '~/components/atomic/Button.vue';
@ -36,7 +36,7 @@ import useApiClient from '~/compositions/useApiClient';
import { useAsyncAction } from '~/compositions/useAsyncAction'; import { useAsyncAction } from '~/compositions/useAsyncAction';
import useNotifications from '~/compositions/useNotifications'; import useNotifications from '~/compositions/useNotifications';
import { usePagination } from '~/compositions/usePaginate'; import { usePagination } from '~/compositions/usePaginate';
import { Org, Secret, WebhookEvents } from '~/lib/api/types'; import { WebhookEvents, type Org, type Secret } from '~/lib/api/types';
const emptySecret: Partial<Secret> = { const emptySecret: Partial<Secret> = {
name: '', name: '',
@ -78,7 +78,7 @@ const { doSubmit: createSecret, isLoading: isSaving } = useAsyncAction(async ()
await apiClient.createOrgSecret(org.value.id, selectedSecret.value); await apiClient.createOrgSecret(org.value.id, selectedSecret.value);
} }
notifications.notify({ notifications.notify({
title: i18n.t(isEditingSecret.value ? 'secrets.saved' : 'secrets.created'), title: isEditingSecret.value ? i18n.t('secrets.saved') : i18n.t('secrets.created'),
type: 'success', type: 'success',
}); });
selectedSecret.value = undefined; selectedSecret.value = undefined;

View file

@ -8,8 +8,10 @@
params: { repoId: pipeline.repo_id }, params: { repoId: pipeline.repo_id },
}" }"
class="underline" class="underline"
>{{ repo?.owner }} / {{ repo?.name }}</router-link
> >
<!-- eslint-disable-next-line @intlify/vue-i18n/no-raw-text -->
{{ repo?.owner }} / {{ repo?.name }}
</router-link>
<span class="whitespace-nowrap overflow-hidden overflow-ellipsis" :title="message">{{ title }}</span> <span class="whitespace-nowrap overflow-hidden overflow-ellipsis" :title="message">{{ title }}</span>
<div class="flex flex-col mt-2"> <div class="flex flex-col mt-2">
<div class="flex space-x-2 items-center" :title="created"> <div class="flex space-x-2 items-center" :title="created">
@ -31,7 +33,7 @@ import { computed, toRef } from 'vue';
import Icon from '~/components/atomic/Icon.vue'; import Icon from '~/components/atomic/Icon.vue';
import PipelineStatusIcon from '~/components/repo/pipeline/PipelineStatusIcon.vue'; import PipelineStatusIcon from '~/components/repo/pipeline/PipelineStatusIcon.vue';
import usePipeline from '~/compositions/usePipeline'; import usePipeline from '~/compositions/usePipeline';
import { PipelineFeed } from '~/lib/api/types'; import type { PipelineFeed } from '~/lib/api/types';
import { useRepoStore } from '~/store/repos'; import { useRepoStore } from '~/store/repos';
const props = defineProps<{ const props = defineProps<{

View file

@ -24,13 +24,16 @@
</div> </div>
<div class="w-full md:w-auto md:mx-4 flex items-center min-w-0"> <div class="w-full md:w-auto md:mx-4 flex items-center min-w-0">
<!-- eslint-disable-next-line @intlify/vue-i18n/no-raw-text -->
<span class="text-wp-text-alt-100 <md:hidden">#{{ pipeline.number }}</span> <span class="text-wp-text-alt-100 <md:hidden">#{{ pipeline.number }}</span>
<!-- eslint-disable-next-line @intlify/vue-i18n/no-raw-text -->
<span class="text-wp-text-alt-100 <md:hidden mx-2">-</span> <span class="text-wp-text-alt-100 <md:hidden mx-2">-</span>
<span <span
class="text-wp-text-100 <md:underline whitespace-nowrap overflow-hidden overflow-ellipsis" class="text-wp-text-100 <md:underline whitespace-nowrap overflow-hidden overflow-ellipsis"
:title="message" :title="message"
>{{ title }}</span
> >
{{ title }}
</span>
</div> </div>
<div <div
@ -75,7 +78,7 @@ import { pipelineStatusColors } from '~/components/repo/pipeline/pipeline-status
import PipelineRunningIcon from '~/components/repo/pipeline/PipelineRunningIcon.vue'; import PipelineRunningIcon from '~/components/repo/pipeline/PipelineRunningIcon.vue';
import PipelineStatusIcon from '~/components/repo/pipeline/PipelineStatusIcon.vue'; import PipelineStatusIcon from '~/components/repo/pipeline/PipelineStatusIcon.vue';
import usePipeline from '~/compositions/usePipeline'; import usePipeline from '~/compositions/usePipeline';
import { Pipeline } from '~/lib/api/types'; import type { Pipeline } from '~/lib/api/types';
const props = defineProps<{ const props = defineProps<{
pipeline: Pipeline; pipeline: Pipeline;

View file

@ -18,7 +18,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import Panel from '~/components/layout/Panel.vue'; import Panel from '~/components/layout/Panel.vue';
import PipelineItem from '~/components/repo/pipeline/PipelineItem.vue'; import PipelineItem from '~/components/repo/pipeline/PipelineItem.vue';
import { Pipeline } from '~/lib/api/types'; import type { Pipeline } from '~/lib/api/types';
defineProps<{ defineProps<{
pipelines: Pipeline[] | undefined; pipelines: Pipeline[] | undefined;

View file

@ -60,8 +60,9 @@
'bg-opacity-30 bg-blue-600': isSelected(line), 'bg-opacity-30 bg-blue-600': isSelected(line),
underline: isSelected(line), underline: isSelected(line),
}" }"
>{{ line.number }}</a
> >
{{ line.number }}
</a>
<!-- eslint-disable vue/no-v-html --> <!-- eslint-disable vue/no-v-html -->
<span <span
class="align-top whitespace-pre-wrap break-words" class="align-top whitespace-pre-wrap break-words"
@ -80,8 +81,9 @@
'bg-opacity-40 dark:bg-opacity-50 bg-yellow-600 dark:bg-yellow-800': line.type === 'warning', 'bg-opacity-40 dark:bg-opacity-50 bg-yellow-600 dark:bg-yellow-800': line.type === 'warning',
'bg-opacity-30 bg-blue-600': isSelected(line), 'bg-opacity-30 bg-blue-600': isSelected(line),
}" }"
>{{ formatTime(line.time) }}</span
> >
{{ formatTime(line.time) }}
</span>
</div> </div>
</div> </div>
@ -110,7 +112,7 @@ import { useStorage } from '@vueuse/core';
import { AnsiUp } from 'ansi_up'; import { AnsiUp } from 'ansi_up';
import { decode } from 'js-base64'; import { decode } from 'js-base64';
import { debounce } from 'lodash'; import { debounce } from 'lodash';
import { computed, inject, nextTick, onMounted, Ref, ref, toRef, watch } from 'vue'; import { computed, inject, nextTick, onMounted, ref, toRef, watch, type Ref } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
@ -118,16 +120,16 @@ import IconButton from '~/components/atomic/IconButton.vue';
import PipelineStatusIcon from '~/components/repo/pipeline/PipelineStatusIcon.vue'; import PipelineStatusIcon from '~/components/repo/pipeline/PipelineStatusIcon.vue';
import useApiClient from '~/compositions/useApiClient'; import useApiClient from '~/compositions/useApiClient';
import useNotifications from '~/compositions/useNotifications'; import useNotifications from '~/compositions/useNotifications';
import { Pipeline, Repo, RepoPermissions } from '~/lib/api/types'; import type { Pipeline, Repo, RepoPermissions } from '~/lib/api/types';
import { findStep, isStepFinished, isStepRunning } from '~/utils/helpers'; import { findStep, isStepFinished, isStepRunning } from '~/utils/helpers';
type LogLine = { interface LogLine {
index: number; index: number;
number: number; number: number;
text: string; text: string;
time?: number; time?: number;
type: 'error' | 'warning' | null; type: 'error' | 'warning' | null;
}; }
const props = defineProps<{ const props = defineProps<{
pipeline: Pipeline; pipeline: Pipeline;
@ -246,7 +248,7 @@ async function download() {
downloadInProgress.value = true; downloadInProgress.value = true;
logs = await apiClient.getLogs(repo.value.id, pipeline.value.number, step.value.id); logs = await apiClient.getLogs(repo.value.id, pipeline.value.number, step.value.id);
} catch (e) { } catch (e) {
notifications.notifyError(e, i18n.t('repo.pipeline.log_download_error')); notifications.notifyError(e as Error, i18n.t('repo.pipeline.log_download_error'));
return; return;
} finally { } finally {
downloadInProgress.value = false; downloadInProgress.value = false;
@ -312,7 +314,7 @@ async function deleteLogs() {
} }
// TODO: use proper dialog (copy-pasted from web/src/components/secrets/SecretList.vue:deleteSecret) // TODO: use proper dialog (copy-pasted from web/src/components/secrets/SecretList.vue:deleteSecret)
// eslint-disable-next-line no-alert, no-restricted-globals // eslint-disable-next-line no-alert
if (!confirm(i18n.t('repo.pipeline.log_delete_confirm'))) { if (!confirm(i18n.t('repo.pipeline.log_delete_confirm'))) {
return; return;
} }
@ -321,7 +323,7 @@ async function deleteLogs() {
await apiClient.deleteLogs(repo.value.id, pipeline.value.number, step.value.id); await apiClient.deleteLogs(repo.value.id, pipeline.value.number, step.value.id);
log.value = []; log.value = [];
} catch (e) { } catch (e) {
notifications.notifyError(e, i18n.t('repo.pipeline.log_delete_error')); notifications.notifyError(e as Error, i18n.t('repo.pipeline.log_delete_error'));
} }
} }

View file

@ -1,7 +1,7 @@
<template> <template>
<div <div
class="flex items-center justify-center" class="flex items-center justify-center"
:title="$t('repo.pipeline.status.status', { status: $t(`repo.pipeline.status.${status}`) })" :title="$t('repo.pipeline.status.status', { status: statusDescriptions[status] })"
> >
<Icon <Icon
:name="service ? 'settings' : `status-${status}`" :name="service ? 'settings' : `status-${status}`"
@ -18,8 +18,10 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { useI18n } from 'vue-i18n';
import Icon from '~/components/atomic/Icon.vue'; import Icon from '~/components/atomic/Icon.vue';
import { PipelineStatus } from '~/lib/api/types'; import type { PipelineStatus } from '~/lib/api/types';
import { pipelineStatusColors } from './pipeline-status'; import { pipelineStatusColors } from './pipeline-status';
@ -27,4 +29,22 @@ defineProps<{
status: PipelineStatus; status: PipelineStatus;
service?: boolean; service?: boolean;
}>(); }>();
const { t } = useI18n();
const statusDescriptions = {
blocked: t('repo.pipeline.status.blocked'),
declined: t('repo.pipeline.status.declined'),
error: t('repo.pipeline.status.error'),
failure: t('repo.pipeline.status.failure'),
killed: t('repo.pipeline.status.killed'),
pending: t('repo.pipeline.status.pending'),
running: t('repo.pipeline.status.running'),
skipped: t('repo.pipeline.status.skipped'),
started: t('repo.pipeline.status.started'),
success: t('repo.pipeline.status.success'),
} satisfies {
// eslint-disable-next-line no-unused-vars
[_ in PipelineStatus]: string;
};
</script> </script>

View file

@ -7,7 +7,7 @@ import { computed, toRef } from 'vue';
import { useDate } from '~/compositions/useDate'; import { useDate } from '~/compositions/useDate';
import { useElapsedTime } from '~/compositions/useElapsedTime'; import { useElapsedTime } from '~/compositions/useElapsedTime';
import { PipelineStep, PipelineWorkflow } from '~/lib/api/types'; import type { PipelineStep, PipelineWorkflow } from '~/lib/api/types';
const props = defineProps<{ const props = defineProps<{
step?: PipelineStep; step?: PipelineStep;

View file

@ -119,7 +119,7 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { computed, inject, Ref, ref, toRef } from 'vue'; import { computed, inject, ref, toRef, type Ref } from 'vue';
import Badge from '~/components/atomic/Badge.vue'; import Badge from '~/components/atomic/Badge.vue';
import Icon from '~/components/atomic/Icon.vue'; import Icon from '~/components/atomic/Icon.vue';
@ -127,7 +127,7 @@ import Panel from '~/components/layout/Panel.vue';
import PipelineStatusIcon from '~/components/repo/pipeline/PipelineStatusIcon.vue'; import PipelineStatusIcon from '~/components/repo/pipeline/PipelineStatusIcon.vue';
import PipelineStepDuration from '~/components/repo/pipeline/PipelineStepDuration.vue'; import PipelineStepDuration from '~/components/repo/pipeline/PipelineStepDuration.vue';
import usePipeline from '~/compositions/usePipeline'; import usePipeline from '~/compositions/usePipeline';
import { Pipeline, PipelineConfig, PipelineStep, StepType } from '~/lib/api/types'; import { StepType, type Pipeline, type PipelineConfig, type PipelineStep } from '~/lib/api/types';
const props = defineProps<{ const props = defineProps<{
pipeline: Pipeline; pipeline: Pipeline;

View file

@ -1,4 +1,4 @@
import { PipelineStatus } from '~/lib/api/types'; import type { PipelineStatus } from '~/lib/api/types';
export const pipelineStatusColors: Record<PipelineStatus, 'green' | 'gray' | 'red' | 'blue' | 'orange'> = { export const pipelineStatusColors: Record<PipelineStatus, 'green' | 'gray' | 'red' | 'blue' | 'orange'> = {
blocked: 'gray', blocked: 'gray',

View file

@ -42,7 +42,7 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { computed, inject, Ref } from 'vue'; import { computed, inject, type Ref } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useRouter } from 'vue-router'; import { useRouter } from 'vue-router';
@ -51,7 +51,7 @@ import Settings from '~/components/layout/Settings.vue';
import useApiClient from '~/compositions/useApiClient'; import useApiClient from '~/compositions/useApiClient';
import { useAsyncAction } from '~/compositions/useAsyncAction'; import { useAsyncAction } from '~/compositions/useAsyncAction';
import useNotifications from '~/compositions/useNotifications'; import useNotifications from '~/compositions/useNotifications';
import { Repo } from '~/lib/api/types'; import type { Repo } from '~/lib/api/types';
const apiClient = useApiClient(); const apiClient = useApiClient();
const router = useRouter(); const router = useRouter();
@ -75,7 +75,7 @@ const { doSubmit: deleteRepo, isLoading: isDeletingRepo } = useAsyncAction(async
} }
// TODO: use proper dialog // TODO: use proper dialog
// eslint-disable-next-line no-alert, no-restricted-globals // eslint-disable-next-line no-alert
if (!confirm(i18n.t('repo.settings.actions.delete.confirm'))) { if (!confirm(i18n.t('repo.settings.actions.delete.confirm'))) {
return; return;
} }

View file

@ -41,16 +41,16 @@
<script lang="ts" setup> <script lang="ts" setup>
import { useStorage } from '@vueuse/core'; import { useStorage } from '@vueuse/core';
import { computed, inject, onMounted, Ref, ref, watch } from 'vue'; import { computed, inject, onMounted, ref, watch, type Ref } from 'vue';
import { SelectOption } from '~/components/form/form.types'; import type { SelectOption } from '~/components/form/form.types';
import InputField from '~/components/form/InputField.vue'; import InputField from '~/components/form/InputField.vue';
import SelectField from '~/components/form/SelectField.vue'; import SelectField from '~/components/form/SelectField.vue';
import Settings from '~/components/layout/Settings.vue'; import Settings from '~/components/layout/Settings.vue';
import useApiClient from '~/compositions/useApiClient'; import useApiClient from '~/compositions/useApiClient';
import useConfig from '~/compositions/useConfig'; import useConfig from '~/compositions/useConfig';
import { usePaginate } from '~/compositions/usePaginate'; import { usePaginate } from '~/compositions/usePaginate';
import { Repo } from '~/lib/api/types'; import type { Repo } from '~/lib/api/types';
const apiClient = useApiClient(); const apiClient = useApiClient();
const repo = inject<Ref<Repo>>('repo'); const repo = inject<Ref<Repo>>('repo');

View file

@ -18,8 +18,9 @@
> >
<span>{{ cron.name }}</span> <span>{{ cron.name }}</span>
<span v-if="cron.next_exec && cron.next_exec > 0" class="ml-auto"> <span v-if="cron.next_exec && cron.next_exec > 0" class="ml-auto">
{{ $t('repo.settings.crons.next_exec') }}: {{ date.toLocaleString(new Date(cron.next_exec * 1000)) }}</span <!-- eslint-disable-next-line @intlify/vue-i18n/no-raw-text -->
> {{ $t('repo.settings.crons.next_exec') }}: {{ date.toLocaleString(new Date(cron.next_exec * 1000)) }}
</span>
<span v-else class="ml-auto">{{ $t('repo.settings.crons.not_executed_yet') }}</span> <span v-else class="ml-auto">{{ $t('repo.settings.crons.not_executed_yet') }}</span>
<IconButton icon="play" class="ml-auto w-8 h-8" :title="$t('repo.settings.crons.run')" @click="runCron(cron)" /> <IconButton icon="play" class="ml-auto w-8 h-8" :title="$t('repo.settings.crons.run')" @click="runCron(cron)" />
<IconButton icon="edit" class="w-8 h-8" :title="$t('repo.settings.crons.edit')" @click="selectedCron = cron" /> <IconButton icon="edit" class="w-8 h-8" :title="$t('repo.settings.crons.edit')" @click="selectedCron = cron" />
@ -69,6 +70,7 @@
<div v-if="isEditingCron" class="ml-auto mb-4"> <div v-if="isEditingCron" class="ml-auto mb-4">
<span v-if="selectedCron.next_exec && selectedCron.next_exec > 0" class="text-wp-text-100"> <span v-if="selectedCron.next_exec && selectedCron.next_exec > 0" class="text-wp-text-100">
<!-- eslint-disable-next-line @intlify/vue-i18n/no-raw-text -->
{{ $t('repo.settings.crons.next_exec') }}: {{ $t('repo.settings.crons.next_exec') }}:
{{ date.toLocaleString(new Date(selectedCron.next_exec * 1000)) }} {{ date.toLocaleString(new Date(selectedCron.next_exec * 1000)) }}
</span> </span>
@ -90,7 +92,7 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { computed, inject, Ref, ref } from 'vue'; import { computed, inject, ref, type Ref } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import Button from '~/components/atomic/Button.vue'; import Button from '~/components/atomic/Button.vue';
@ -104,7 +106,7 @@ import { useAsyncAction } from '~/compositions/useAsyncAction';
import { useDate } from '~/compositions/useDate'; import { useDate } from '~/compositions/useDate';
import useNotifications from '~/compositions/useNotifications'; import useNotifications from '~/compositions/useNotifications';
import { usePagination } from '~/compositions/usePaginate'; import { usePagination } from '~/compositions/usePaginate';
import { Cron, Repo } from '~/lib/api/types'; import type { Cron, Repo } from '~/lib/api/types';
import router from '~/router'; import router from '~/router';
const apiClient = useApiClient(); const apiClient = useApiClient();
@ -141,7 +143,7 @@ const { doSubmit: createCron, isLoading: isSaving } = useAsyncAction(async () =>
await apiClient.createCron(repo.value.id, selectedCron.value); await apiClient.createCron(repo.value.id, selectedCron.value);
} }
notifications.notify({ notifications.notify({
title: i18n.t(isEditingCron.value ? 'repo.settings.crons.saved' : i18n.t('repo.settings.crons.created')), title: isEditingCron.value ? i18n.t('repo.settings.crons.saved') : i18n.t('repo.settings.crons.created'),
type: 'success', type: 'success',
}); });
selectedCron.value = undefined; selectedCron.value = undefined;

View file

@ -15,6 +15,7 @@
<template #description> <template #description>
<i18n-t keypath="repo.settings.general.pipeline_path.desc" tag="p" class="text-sm text-wp-text-alt-100"> <i18n-t keypath="repo.settings.general.pipeline_path.desc" tag="p" class="text-sm text-wp-text-alt-100">
<span class="code-box-inline px-1">{{ $t('repo.settings.general.pipeline_path.desc_path_example') }}</span> <span class="code-box-inline px-1">{{ $t('repo.settings.general.pipeline_path.desc_path_example') }}</span>
<!-- eslint-disable-next-line @intlify/vue-i18n/no-raw-text -->
<span class="code-box-inline px-1">/</span> <span class="code-box-inline px-1">/</span>
</i18n-t> </i18n-t>
</template> </template>
@ -97,13 +98,13 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { inject, onMounted, Ref, ref } from 'vue'; import { inject, onMounted, ref, type Ref } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import Button from '~/components/atomic/Button.vue'; import Button from '~/components/atomic/Button.vue';
import Checkbox from '~/components/form/Checkbox.vue'; import Checkbox from '~/components/form/Checkbox.vue';
import CheckboxesField from '~/components/form/CheckboxesField.vue'; import CheckboxesField from '~/components/form/CheckboxesField.vue';
import { CheckboxOption, RadioOption } from '~/components/form/form.types'; import type { CheckboxOption, RadioOption } from '~/components/form/form.types';
import InputField from '~/components/form/InputField.vue'; import InputField from '~/components/form/InputField.vue';
import NumberField from '~/components/form/NumberField.vue'; import NumberField from '~/components/form/NumberField.vue';
import RadioField from '~/components/form/RadioField.vue'; import RadioField from '~/components/form/RadioField.vue';
@ -113,7 +114,7 @@ import useApiClient from '~/compositions/useApiClient';
import { useAsyncAction } from '~/compositions/useAsyncAction'; import { useAsyncAction } from '~/compositions/useAsyncAction';
import useAuthentication from '~/compositions/useAuthentication'; import useAuthentication from '~/compositions/useAuthentication';
import useNotifications from '~/compositions/useNotifications'; import useNotifications from '~/compositions/useNotifications';
import { Repo, RepoSettings, RepoVisibility, WebhookEvents } from '~/lib/api/types'; import { RepoVisibility, WebhookEvents, type Repo, type RepoSettings } from '~/lib/api/types';
import { useRepoStore } from '~/store/repos'; import { useRepoStore } from '~/store/repos';
const apiClient = useApiClient(); const apiClient = useApiClient();

View file

@ -75,7 +75,7 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { computed, inject, Ref, ref } from 'vue'; import { computed, inject, ref, type Ref } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import Button from '~/components/atomic/Button.vue'; import Button from '~/components/atomic/Button.vue';
@ -88,8 +88,7 @@ import useApiClient from '~/compositions/useApiClient';
import { useAsyncAction } from '~/compositions/useAsyncAction'; import { useAsyncAction } from '~/compositions/useAsyncAction';
import useNotifications from '~/compositions/useNotifications'; import useNotifications from '~/compositions/useNotifications';
import { usePagination } from '~/compositions/usePaginate'; import { usePagination } from '~/compositions/usePaginate';
import { Repo } from '~/lib/api/types'; import type { Registry, Repo } from '~/lib/api/types';
import { Registry } from '~/lib/api/types/registry';
const apiClient = useApiClient(); const apiClient = useApiClient();
const notifications = useNotifications(); const notifications = useNotifications();
@ -124,9 +123,9 @@ const { doSubmit: createRegistry, isLoading: isSaving } = useAsyncAction(async (
await apiClient.createRegistry(repo.value.id, selectedRegistry.value); await apiClient.createRegistry(repo.value.id, selectedRegistry.value);
} }
notifications.notify({ notifications.notify({
title: i18n.t( title: isEditingRegistry.value
isEditingRegistry.value ? 'repo.settings.registries.saved' : i18n.t('repo.settings.registries.created'), ? i18n.t('repo.settings.registries.saved')
), : i18n.t('repo.settings.registries.created'),
type: 'success', type: 'success',
}); });
selectedRegistry.value = undefined; selectedRegistry.value = undefined;

View file

@ -25,7 +25,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import { cloneDeep } from 'lodash'; import { cloneDeep } from 'lodash';
import { computed, inject, Ref, ref } from 'vue'; import { computed, inject, ref, type Ref } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import Button from '~/components/atomic/Button.vue'; import Button from '~/components/atomic/Button.vue';
@ -36,7 +36,7 @@ import useApiClient from '~/compositions/useApiClient';
import { useAsyncAction } from '~/compositions/useAsyncAction'; import { useAsyncAction } from '~/compositions/useAsyncAction';
import useNotifications from '~/compositions/useNotifications'; import useNotifications from '~/compositions/useNotifications';
import { usePagination } from '~/compositions/usePaginate'; import { usePagination } from '~/compositions/usePaginate';
import { Repo, Secret, WebhookEvents } from '~/lib/api/types'; import { WebhookEvents, type Repo, type Secret } from '~/lib/api/types';
const emptySecret: Partial<Secret> = { const emptySecret: Partial<Secret> = {
name: '', name: '',
@ -77,9 +77,7 @@ const { resetPage, data: _secrets } = usePagination(loadSecrets, () => !selected
const secrets = computed(() => { const secrets = computed(() => {
const secretsList: Record<string, Secret & { edit?: boolean; level: 'repo' | 'org' | 'global' }> = {}; const secretsList: Record<string, Secret & { edit?: boolean; level: 'repo' | 'org' | 'global' }> = {};
// eslint-disable-next-line no-restricted-syntax
for (const level of ['repo', 'org', 'global']) { for (const level of ['repo', 'org', 'global']) {
// eslint-disable-next-line no-restricted-syntax
for (const secret of _secrets.value) { for (const secret of _secrets.value) {
if ( if (
((level === 'repo' && secret.repo_id !== 0 && secret.org_id === 0) || ((level === 'repo' && secret.repo_id !== 0 && secret.org_id === 0) ||
@ -118,7 +116,7 @@ const { doSubmit: createSecret, isLoading: isSaving } = useAsyncAction(async ()
await apiClient.createSecret(repo.value.id, selectedSecret.value); await apiClient.createSecret(repo.value.id, selectedSecret.value);
} }
notifications.notify({ notifications.notify({
title: i18n.t(isEditingSecret.value ? 'secrets.saved' : 'secrets.created'), title: isEditingSecret.value ? i18n.t('secrets.saved') : i18n.t('secrets.created'),
type: 'success', type: 'success',
}); });
selectedSecret.value = undefined; selectedSecret.value = undefined;

View file

@ -59,10 +59,10 @@ import { useI18n } from 'vue-i18n';
import Button from '~/components/atomic/Button.vue'; import Button from '~/components/atomic/Button.vue';
import CheckboxesField from '~/components/form/CheckboxesField.vue'; import CheckboxesField from '~/components/form/CheckboxesField.vue';
import { CheckboxOption } from '~/components/form/form.types'; import type { CheckboxOption } from '~/components/form/form.types';
import InputField from '~/components/form/InputField.vue'; import InputField from '~/components/form/InputField.vue';
import TextField from '~/components/form/TextField.vue'; import TextField from '~/components/form/TextField.vue';
import { Secret, WebhookEvents } from '~/lib/api/types'; import { WebhookEvents, type Secret } from '~/lib/api/types';
const props = defineProps<{ const props = defineProps<{
modelValue: Partial<Secret>; modelValue: Partial<Secret>;

View file

@ -42,7 +42,7 @@ import { useI18n } from 'vue-i18n';
import Badge from '~/components/atomic/Badge.vue'; import Badge from '~/components/atomic/Badge.vue';
import IconButton from '~/components/atomic/IconButton.vue'; import IconButton from '~/components/atomic/IconButton.vue';
import ListItem from '~/components/atomic/ListItem.vue'; import ListItem from '~/components/atomic/ListItem.vue';
import { Secret } from '~/lib/api/types'; import type { Secret } from '~/lib/api/types';
const props = defineProps<{ const props = defineProps<{
modelValue: (Secret & { edit?: boolean })[]; modelValue: (Secret & { edit?: boolean })[];
@ -64,8 +64,8 @@ function editSecret(secret: Secret) {
function deleteSecret(secret: Secret) { function deleteSecret(secret: Secret) {
// TODO: use proper dialog // TODO: use proper dialog
// eslint-disable-next-line no-alert, no-restricted-globals // eslint-disable-next-line no-alert
if (!confirm(i18n.t('repo.settings.secrets.delete_confirm'))) { if (!confirm(i18n.t('secrets.delete_confirm'))) {
return; return;
} }
emit('delete', secret); emit('delete', secret);

View file

@ -23,8 +23,9 @@
:href="`${address}/swagger/index.html`" :href="`${address}/swagger/index.html`"
target="_blank" target="_blank"
class="ml-4 text-wp-link-100 hover:text-wp-link-200" class="ml-4 text-wp-link-100 hover:text-wp-link-200"
>{{ $t('user.settings.cli_and_api.swagger_ui') }}</a
> >
{{ $t('user.settings.cli_and_api.swagger_ui') }}
</a>
</template> </template>
<pre class="code-box">{{ usageWithCurl }}</pre> <pre class="code-box">{{ usageWithCurl }}</pre>
</InputField> </InputField>

View file

@ -4,7 +4,7 @@
<div class="ml-2"> <div class="ml-2">
<h1 class="text-xl text-wp-text-100">{{ $t('secrets.secrets') }}</h1> <h1 class="text-xl text-wp-text-100">{{ $t('secrets.secrets') }}</h1>
<p class="text-sm text-wp-text-alt-100"> <p class="text-sm text-wp-text-alt-100">
{{ $t('secrets.desc') }} {{ $t('user.settings.secrets.desc') }}
<DocsLink :topic="$t('secrets.secrets')" url="docs/usage/secrets" /> <DocsLink :topic="$t('secrets.secrets')" url="docs/usage/secrets" />
</p> </p>
</div> </div>
@ -51,7 +51,7 @@ import { useAsyncAction } from '~/compositions/useAsyncAction';
import useAuthentication from '~/compositions/useAuthentication'; import useAuthentication from '~/compositions/useAuthentication';
import useNotifications from '~/compositions/useNotifications'; import useNotifications from '~/compositions/useNotifications';
import { usePagination } from '~/compositions/usePaginate'; import { usePagination } from '~/compositions/usePaginate';
import { Secret, WebhookEvents } from '~/lib/api/types'; import { WebhookEvents, type Secret } from '~/lib/api/types';
const emptySecret: Partial<Secret> = { const emptySecret: Partial<Secret> = {
name: '', name: '',
@ -92,7 +92,7 @@ const { doSubmit: createSecret, isLoading: isSaving } = useAsyncAction(async ()
await apiClient.createOrgSecret(user.org_id, selectedSecret.value); await apiClient.createOrgSecret(user.org_id, selectedSecret.value);
} }
notifications.notify({ notifications.notify({
title: i18n.t(isEditingSecret.value ? 'user.settings.secrets.saved' : 'user.settings.secrets.created'), title: isEditingSecret.value ? i18n.t('secrets.saved') : i18n.t('secrets.created'),
type: 'success', type: 'success',
}); });
selectedSecret.value = undefined; selectedSecret.value = undefined;
@ -101,7 +101,7 @@ const { doSubmit: createSecret, isLoading: isSaving } = useAsyncAction(async ()
const { doSubmit: deleteSecret, isLoading: isDeleting } = useAsyncAction(async (_secret: Secret) => { const { doSubmit: deleteSecret, isLoading: isDeleting } = useAsyncAction(async (_secret: Secret) => {
await apiClient.deleteOrgSecret(user.org_id, _secret.name); await apiClient.deleteOrgSecret(user.org_id, _secret.name);
notifications.notify({ title: i18n.t('user.settings.secrets.deleted'), type: 'success' }); notifications.notify({ title: i18n.t('secrets.deleted'), type: 'success' });
resetPage(); resetPage();
}); });

View file

@ -9,7 +9,7 @@ export default (): WoodpeckerClient => {
const config = useConfig(); const config = useConfig();
const server = config.rootPath; const server = config.rootPath;
const token = null; const token = null;
const csrf = config.csrf || null; const csrf = config.csrf ?? null;
apiClient = new WoodpeckerClient(server, token, csrf); apiClient = new WoodpeckerClient(server, token, csrf);
} }

View file

@ -4,14 +4,7 @@ import useNotifications from '~/compositions/useNotifications';
const notifications = useNotifications(); const notifications = useNotifications();
export type UseSubmitOptions = { export function useAsyncAction<T extends unknown[]>(action: (...a: T) => void | Promise<void>) {
showErrorNotification: false;
};
export function useAsyncAction<T extends unknown[]>(
action: (...a: T) => void | Promise<void>,
options?: UseSubmitOptions,
) {
const isLoading = ref(false); const isLoading = ref(false);
async function doSubmit(...a: T) { async function doSubmit(...a: T) {
@ -23,9 +16,7 @@ export function useAsyncAction<T extends unknown[]>(
try { try {
await action(...a); await action(...a);
} catch (error) { } catch (error) {
if (options?.showErrorNotification) { notifications.notify({ title: (error as Error).message, type: 'error' });
notifications.notify({ title: (error as Error).message, type: 'error' });
}
} }
isLoading.value = false; isLoading.value = false;
} }

View file

@ -8,7 +8,7 @@ export default () =>
user: useConfig().user, user: useConfig().user,
authenticate(url?: string) { authenticate(url?: string) {
if (url) { if (url !== undefined) {
const config = useUserConfig(); const config = useUserConfig();
config.setUserConfig('redirectUrl', url); config.setUserConfig('redirectUrl', url);
} }

View file

@ -1,4 +1,4 @@
import { User } from '~/lib/api/types'; import type { User } from '~/lib/api/types';
declare global { declare global {
interface Window { interface Window {
@ -13,11 +13,11 @@ declare global {
} }
export default () => ({ export default () => ({
user: window.WOODPECKER_USER || null, user: window.WOODPECKER_USER ?? null,
version: window.WOODPECKER_VERSION, version: window.WOODPECKER_VERSION,
skipVersionCheck: window.WOODPECKER_SKIP_VERSION_CHECK || false, skipVersionCheck: window.WOODPECKER_SKIP_VERSION_CHECK === true || false,
csrf: window.WOODPECKER_CSRF || null, csrf: window.WOODPECKER_CSRF ?? null,
forge: window.WOODPECKER_FORGE || null, forge: window.WOODPECKER_FORGE ?? null,
rootPath: window.WOODPECKER_ROOT_PATH || '', rootPath: window.WOODPECKER_ROOT_PATH ?? '',
enableSwagger: window.WOODPECKER_ENABLE_SWAGGER || false, enableSwagger: window.WOODPECKER_ENABLE_SWAGGER === true || false,
}); });

View file

@ -29,7 +29,7 @@ export function useDate() {
async function setDayjsLocale(locale: string) { async function setDayjsLocale(locale: string) {
if (!addedLocales.includes(locale)) { if (!addedLocales.includes(locale)) {
const l = await import(`~/assets/dayjsLocales/${locale}.js`); const l = (await import(`~/assets/dayjsLocales/${locale}.js`)) as { default: string };
dayjs.locale(l.default); dayjs.locale(l.default);
} else { } else {
dayjs.locale(locale); dayjs.locale(locale);

View file

@ -1,4 +1,4 @@
import { computed, onBeforeUnmount, onMounted, Ref, ref, watch } from 'vue'; import { computed, onBeforeUnmount, onMounted, ref, watch, type Ref } from 'vue';
export function useElapsedTime(running: Ref<boolean>, startTime: Ref<number | undefined>) { export function useElapsedTime(running: Ref<boolean>, startTime: Ref<number | undefined>) {
const time = ref<number | undefined>(startTime.value); const time = ref<number | undefined>(startTime.value);

View file

@ -2,7 +2,7 @@ import { computed, ref, watch } from 'vue';
import useConfig from '~/compositions/useConfig'; import useConfig from '~/compositions/useConfig';
import { useTheme } from '~/compositions/useTheme'; import { useTheme } from '~/compositions/useTheme';
import { PipelineStatus } from '~/lib/api/types'; import type { PipelineStatus } from '~/lib/api/types';
const { theme } = useTheme(); const { theme } = useTheme();
const darkMode = computed(() => theme.value); const darkMode = computed(() => theme.value);

View file

@ -16,9 +16,9 @@ export const i18n = createI18n({
}); });
const loadLocaleMessages = async (locale: string) => { const loadLocaleMessages = async (locale: string) => {
const { default: messages } = await import(`~/assets/locales/${locale}.json`); const messages = (await import(`~/assets/locales/${locale}.json`)) as { default: any };
i18n.global.setLocaleMessage(locale, messages); i18n.global.setLocaleMessage(locale, messages.default);
return nextTick(); return nextTick();
}; };
@ -31,6 +31,6 @@ export const setI18nLanguage = async (lang: string): Promise<void> => {
await setDayjsLocale(lang); await setDayjsLocale(lang);
}; };
loadLocaleMessages(fallbackLocale); loadLocaleMessages(fallbackLocale).catch(console.error);
loadLocaleMessages(userLanguage); loadLocaleMessages(userLanguage).catch(console.error);
setDayjsLocale(userLanguage); setDayjsLocale(userLanguage).catch(console.error);

View file

@ -1,12 +1,13 @@
import { inject as vueInject, InjectionKey, provide as vueProvide, Ref } from 'vue'; import type { InjectionKey, Ref } from 'vue';
import { inject as vueInject, provide as vueProvide } from 'vue';
import { Org, OrgPermissions, Repo } from '~/lib/api/types'; import type { Org, OrgPermissions, Repo } from '~/lib/api/types';
export type InjectKeys = { export interface InjectKeys {
repo: Ref<Repo>; repo: Ref<Repo>;
org: Ref<Org | undefined>; org: Ref<Org | undefined>;
'org-permissions': Ref<OrgPermissions | undefined>; 'org-permissions': Ref<OrgPermissions | undefined>;
}; }
export function inject<T extends keyof InjectKeys>(key: T): InjectKeys[T] { export function inject<T extends keyof InjectKeys>(key: T): InjectKeys[T] {
const value = vueInject<InjectKeys[T]>(key); const value = vueInject<InjectKeys[T]>(key);

View file

@ -1,13 +1,12 @@
import Notifications, { NotificationsOptions, notify } from '@kyvg/vue3-notification'; import Notifications, { notify, type NotificationsOptions } from '@kyvg/vue3-notification';
export const notifications = Notifications; export const notifications = Notifications;
function notifyError(err: unknown, args: NotificationsOptions | string = {}): void { function notifyError(err: Error, args: NotificationsOptions | string = {}): void {
// eslint-disable-next-line no-console
console.error(err); console.error(err);
const mArgs = typeof args === 'string' ? { title: args } : args; const mArgs = typeof args === 'string' ? { title: args } : args;
const title = mArgs?.title || (err as Error)?.message || `${err}`; const title = mArgs?.title ?? err?.message ?? err?.toString();
notify({ type: 'error', ...mArgs, title }); notify({ type: 'error', ...mArgs, title });
} }

View file

@ -1,6 +1,6 @@
import { shallowMount } from '@vue/test-utils'; import { shallowMount } from '@vue/test-utils';
import { describe, expect, it } from 'vitest'; import { describe, expect, it } from 'vitest';
import { type Ref, watch } from 'vue'; import { watch, type Ref } from 'vue';
import { usePagination } from './usePaginate'; import { usePagination } from './usePaginate';
@ -18,11 +18,11 @@ async function waitForState<T>(ref: Ref<T>, expected: T): Promise<void> {
}); });
} }
// eslint-disable-next-line promise/prefer-await-to-callbacks // TODO enable again with eslint-plugin-promise eslint-disable-next-line promise/prefer-await-to-callbacks
export const mountComposition = (cb: () => void) => { export const mountComposition = (cb: () => void) => {
const wrapper = shallowMount({ const wrapper = shallowMount({
setup() { setup() {
// eslint-disable-next-line promise/prefer-await-to-callbacks // TODO enable again with eslint-plugin-promise eslint-disable-next-line promise/prefer-await-to-callbacks
cb(); cb();
return {}; return {};
}, },
@ -119,7 +119,7 @@ describe('usePaginate', () => {
usePaginationComposition.nextPage(); usePaginationComposition.nextPage();
await waitForState(usePaginationComposition.loading, false); await waitForState(usePaginationComposition.loading, false);
usePaginationComposition.resetPage(); void usePaginationComposition.resetPage();
await waitForState(usePaginationComposition.loading, false); await waitForState(usePaginationComposition.loading, false);
expect(usePaginationComposition.data.value.length).toBe(3); expect(usePaginationComposition.data.value.length).toBe(3);

View file

@ -1,12 +1,11 @@
import { useInfiniteScroll } from '@vueuse/core'; import { useInfiniteScroll } from '@vueuse/core';
import { onMounted, Ref, ref, UnwrapRef, watch } from 'vue'; import { onMounted, ref, watch, type Ref, type UnwrapRef } from 'vue';
export async function usePaginate<T>(getSingle: (page: number) => Promise<T[]>): Promise<T[]> { export async function usePaginate<T>(getSingle: (page: number) => Promise<T[]>): Promise<T[]> {
let hasMore = true; let hasMore = true;
let page = 1; let page = 1;
const result: T[] = []; const result: T[] = [];
while (hasMore) { while (hasMore) {
// eslint-disable-next-line no-await-in-loop
const singleRes = await getSingle(page); const singleRes = await getSingle(page);
result.push(...singleRes); result.push(...singleRes);
hasMore = singleRes.length !== 0; hasMore = singleRes.length !== 0;

View file

@ -1,9 +1,9 @@
import { computed, Ref } from 'vue'; import { computed, type Ref } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useDate } from '~/compositions/useDate'; import { useDate } from '~/compositions/useDate';
import { useElapsedTime } from '~/compositions/useElapsedTime'; import { useElapsedTime } from '~/compositions/useElapsedTime';
import { Pipeline } from '~/lib/api/types'; import type { Pipeline } from '~/lib/api/types';
import { convertEmojis } from '~/utils/emoji'; import { convertEmojis } from '~/utils/emoji';
const { toLocaleString, timeAgo, prettyDuration } = useDate(); const { toLocaleString, timeAgo, prettyDuration } = useDate();

View file

@ -5,20 +5,20 @@ import { usePipelineStore } from '~/store/pipelines';
import useAuthentication from './useAuthentication'; import useAuthentication from './useAuthentication';
const { userConfig, setUserConfig } = useUserConfig(); const userConfig = useUserConfig();
export default () => { export default () => {
const pipelineStore = usePipelineStore(); const pipelineStore = usePipelineStore();
const { isAuthenticated } = useAuthentication(); const { isAuthenticated } = useAuthentication();
const isOpen = computed(() => userConfig.value.isPipelineFeedOpen && !!isAuthenticated); const isOpen = computed(() => userConfig.userConfig.value.isPipelineFeedOpen && !!isAuthenticated);
function toggle() { function toggle() {
setUserConfig('isPipelineFeedOpen', !userConfig.value.isPipelineFeedOpen); userConfig.setUserConfig('isPipelineFeedOpen', !userConfig.userConfig.value.isPipelineFeedOpen);
} }
function close() { function close() {
setUserConfig('isPipelineFeedOpen', false); userConfig.setUserConfig('isPipelineFeedOpen', false);
} }
const sortedPipelines = toRef(pipelineStore, 'pipelineFeed'); const sortedPipelines = toRef(pipelineStore, 'pipelineFeed');

View file

@ -1,7 +1,7 @@
import Fuse from 'fuse.js'; import Fuse from 'fuse.js';
import { computed, Ref } from 'vue'; import { computed, type Ref } from 'vue';
import { Repo } from '~/lib/api/types'; import type { Repo } from '~/lib/api/types';
/* /*
* Compares Repos lexicographically using owner/name . * Compares Repos lexicographically using owner/name .
@ -9,7 +9,7 @@ import { Repo } from '~/lib/api/types';
function repoCompare(a: Repo, b: Repo) { function repoCompare(a: Repo, b: Repo) {
const x = `${a.owner}/${a.name}`; const x = `${a.owner}/${a.name}`;
const y = `${b.owner}/${b.name}`; const y = `${b.owner}/${b.name}`;
// eslint-disable-next-line no-nested-ternary
return x === y ? 0 : x > y ? 1 : -1; return x === y ? 0 : x > y ? 1 : -1;
} }

View file

@ -1,4 +1,4 @@
import { RouteLocationRaw, useRouter } from 'vue-router'; import { useRouter, type RouteLocationRaw } from 'vue-router';
export function useRouteBack(to: RouteLocationRaw) { export function useRouteBack(to: RouteLocationRaw) {
const router = useRouter(); const router = useRouter();

View file

@ -1,12 +1,12 @@
import { inject, onMounted, provide, Ref, ref } from 'vue'; import { inject, onMounted, provide, ref, type Ref } from 'vue';
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
export type Tab = { export interface Tab {
id: string; id: string;
title: string; title: string;
icon?: string; icon?: string;
iconClass?: string; iconClass?: string;
}; }
export function useTabsProvider({ export function useTabsProvider({
activeTab, activeTab,
@ -29,7 +29,7 @@ export function useTabsProvider({
} }
const hashTab = route.hash.replace(/^#/, ''); const hashTab = route.hash.replace(/^#/, '');
// eslint-disable-next-line no-param-reassign
activeTab.value = hashTab || tabs.value[0].id; activeTab.value = hashTab || tabs.value[0].id;
}); });
} }

View file

@ -2,10 +2,10 @@ import { computed, ref } from 'vue';
const USER_CONFIG_KEY = 'woodpecker-user-config'; const USER_CONFIG_KEY = 'woodpecker-user-config';
type UserConfig = { interface UserConfig {
isPipelineFeedOpen: boolean; isPipelineFeedOpen: boolean;
redirectUrl: string; redirectUrl: string;
}; }
const defaultUserConfig: UserConfig = { const defaultUserConfig: UserConfig = {
isPipelineFeedOpen: false, isPipelineFeedOpen: false,
@ -14,11 +14,11 @@ const defaultUserConfig: UserConfig = {
function loadUserConfig(): UserConfig { function loadUserConfig(): UserConfig {
const lsData = localStorage.getItem(USER_CONFIG_KEY); const lsData = localStorage.getItem(USER_CONFIG_KEY);
if (!lsData) { if (lsData === null) {
return defaultUserConfig; return defaultUserConfig;
} }
return JSON.parse(lsData); return JSON.parse(lsData) as UserConfig;
} }
const config = ref<UserConfig>(loadUserConfig()); const config = ref<UserConfig>(loadUserConfig());

View file

@ -5,11 +5,11 @@ import { onMounted, ref } from 'vue';
import useAuthentication from './useAuthentication'; import useAuthentication from './useAuthentication';
import useConfig from './useConfig'; import useConfig from './useConfig';
type VersionInfo = { interface VersionInfo {
latest: string; latest: string;
rc: string; rc: string;
next: string; next: string;
}; }
const version = ref<{ const version = ref<{
latest: string | undefined; latest: string | undefined;
@ -22,10 +22,9 @@ const version = ref<{
async function fetchVersion(): Promise<VersionInfo | undefined> { async function fetchVersion(): Promise<VersionInfo | undefined> {
try { try {
const resp = await fetch('https://woodpecker-ci.org/version.json'); const resp = await fetch('https://woodpecker-ci.org/version.json');
const json = await resp.json(); const json = (await resp.json()) as VersionInfo;
return json; return json;
} catch (error) { } catch (error) {
// eslint-disable-next-line no-console
console.error('Failed to fetch version info', error); console.error('Failed to fetch version info', error);
return undefined; return undefined;
} }
@ -45,7 +44,7 @@ export function useVersion() {
const usesNext = current.startsWith('next'); const usesNext = current.startsWith('next');
const { user } = useAuthentication(); const { user } = useAuthentication();
if (config.skipVersionCheck || !user?.admin) { if (config.skipVersionCheck || user?.admin !== true) {
version.value = { version.value = {
latest: undefined, latest: undefined,
current, current,

View file

@ -1,27 +1,28 @@
export type ApiError = { export interface ApiError {
status: number; status: number;
message: string; message: string;
}; }
export function encodeQueryString(_params: Record<string, string | number | boolean | undefined> = {}): string { type QueryParams = Record<string, string | number | boolean>;
const params: Record<string, string | number | boolean> = {};
Object.keys(_params).forEach((key) => { export function encodeQueryString(_params: unknown = {}): string {
const val = _params[key]; const __params = _params as QueryParams;
const params: QueryParams = {};
Object.keys(__params).forEach((key) => {
const val = __params[key];
if (val !== undefined) { if (val !== undefined) {
params[key] = val; params[key] = val;
} }
}); });
return params return Object.keys(params)
? Object.keys(params) .sort()
.sort() .map((key) => {
.map((key) => { const val = params[key];
const val = params[key]; return `${encodeURIComponent(key)}=${encodeURIComponent(val)}`;
return `${encodeURIComponent(key)}=${encodeURIComponent(val)}`; })
}) .join('&');
.join('&')
: '';
} }
export default class ApiClient { export default class ApiClient {
@ -43,11 +44,11 @@ export default class ApiClient {
const res = await fetch(`${this.server}${path}`, { const res = await fetch(`${this.server}${path}`, {
method, method,
headers: { headers: {
...(method !== 'GET' && this.csrf ? { 'X-CSRF-TOKEN': this.csrf } : {}), ...(method !== 'GET' && this.csrf !== null ? { 'X-CSRF-TOKEN': this.csrf } : {}),
...(this.token ? { Authorization: `Bearer ${this.token}` } : {}), ...(this.token !== null ? { Authorization: `Bearer ${this.token}` } : {}),
...(data ? { 'Content-Type': 'application/json' } : {}), ...(data !== undefined ? { 'Content-Type': 'application/json' } : {}),
}, },
body: data ? JSON.stringify(data) : undefined, body: data !== undefined ? JSON.stringify(data) : undefined,
}); });
if (!res.ok) { if (!res.ok) {
@ -62,7 +63,7 @@ export default class ApiClient {
} }
const contentType = res.headers.get('Content-Type'); const contentType = res.headers.get('Content-Type');
if (contentType && contentType.startsWith('application/json')) { if (contentType !== null && contentType.startsWith('application/json')) {
return res.json(); return res.json();
} }
@ -87,15 +88,15 @@ export default class ApiClient {
_subscribe<T>(path: string, callback: (data: T) => void, opts = { reconnect: true }) { _subscribe<T>(path: string, callback: (data: T) => void, opts = { reconnect: true }) {
const query = encodeQueryString({ const query = encodeQueryString({
access_token: this.token || undefined, access_token: this.token ?? undefined,
}); });
let _path = this.server ? this.server + path : path; let _path = this.server ? this.server + path : path;
_path = this.token ? `${_path}?${query}` : _path; _path = this.token !== null ? `${_path}?${query}` : _path;
const events = new EventSource(_path); const events = new EventSource(_path);
events.onmessage = (event) => { events.onmessage = (event) => {
const data = JSON.parse(event.data) as T; const data = JSON.parse(event.data as string) as T;
// eslint-disable-next-line promise/prefer-await-to-callbacks // TODO enable again with eslint-plugin-promise eslint-disable-next-line promise/prefer-await-to-callbacks
callback(data); callback(data);
}; };

View file

@ -1,5 +1,5 @@
import ApiClient, { encodeQueryString } from './client'; import ApiClient, { encodeQueryString } from './client';
import { import type {
Agent, Agent,
Cron, Cron,
Org, Org,
@ -18,27 +18,27 @@ import {
User, User,
} from './types'; } from './types';
type RepoListOptions = { interface RepoListOptions {
all?: boolean; all?: boolean;
}; }
// PipelineOptions is the data for creating a new pipeline // PipelineOptions is the data for creating a new pipeline
type PipelineOptions = { interface PipelineOptions {
branch: string; branch: string;
variables: Record<string, string>; variables: Record<string, string>;
}; }
type DeploymentOptions = { interface DeploymentOptions {
id: string; id: string;
environment: string; environment: string;
task: string; task: string;
variables: Record<string, string>; variables: Record<string, string>;
}; }
type PaginationOptions = { interface PaginationOptions {
page?: number; page?: number;
perPage?: number; perPage?: number;
}; }
export default class WoodpeckerClient extends ApiClient { export default class WoodpeckerClient extends ApiClient {
getRepoList(opts?: RepoListOptions): Promise<Repo[]> { getRepoList(opts?: RepoListOptions): Promise<Repo[]> {
@ -336,10 +336,10 @@ export default class WoodpeckerClient extends ApiClient {
} }
repairAllRepos(): Promise<unknown> { repairAllRepos(): Promise<unknown> {
return this._post(`/api/repos/repair`) as Promise<unknown>; return this._post(`/api/repos/repair`);
} }
// eslint-disable-next-line promise/prefer-await-to-callbacks // TODO enable again with eslint-plugin-promise eslint-disable-next-line promise/prefer-await-to-callbacks
on(callback: (data: { pipeline?: Pipeline; repo?: Repo }) => void): EventSource { on(callback: (data: { pipeline?: Pipeline; repo?: Repo }) => void): EventSource {
return this._subscribe('/api/stream/events', callback, { return this._subscribe('/api/stream/events', callback, {
reconnect: true, reconnect: true,
@ -350,7 +350,7 @@ export default class WoodpeckerClient extends ApiClient {
repoId: number, repoId: number,
pipeline: number, pipeline: number,
step: number, step: number,
// eslint-disable-next-line promise/prefer-await-to-callbacks // TODO enable again with eslint-plugin-promise eslint-disable-next-line promise/prefer-await-to-callbacks
callback: (data: PipelineLog) => void, callback: (data: PipelineLog) => void,
): EventSource { ): EventSource {
return this._subscribe(`/api/stream/logs/${repoId}/${pipeline}/${step}`, callback, { return this._subscribe(`/api/stream/logs/${repoId}/${pipeline}/${step}`, callback, {

View file

@ -1,4 +1,4 @@
export type Agent = { export interface Agent {
id: number; id: number;
name: string; name: string;
token: string; token: string;
@ -10,4 +10,4 @@ export type Agent = {
capacity: number; capacity: number;
version: string; version: string;
no_schedule: boolean; no_schedule: boolean;
}; }

View file

@ -1,7 +1,7 @@
export type Cron = { export interface Cron {
id: number; id: number;
name: string; name: string;
branch: string; branch: string;
schedule: string; schedule: string;
next_exec: number; next_exec: number;
}; }

View file

@ -1,12 +1,12 @@
// A version control organization. // A version control organization.
export type Org = { export interface Org {
// The name of the organization. // The name of the organization.
id: number; id: number;
name: string; name: string;
is_user: boolean; is_user: boolean;
}; }
export type OrgPermissions = { export interface OrgPermissions {
member: boolean; member: boolean;
admin: boolean; admin: boolean;
}; }

View file

@ -1,14 +1,14 @@
import { WebhookEvents } from './webhook'; import type { WebhookEvents } from './webhook';
export type PipelineError<D = unknown> = { export interface PipelineError<D = unknown> {
type: string; type: string;
message: string; message: string;
data?: D; data?: D;
is_warning: boolean; is_warning: boolean;
}; }
// A pipeline for a repository. // A pipeline for a repository.
export type Pipeline = { export interface Pipeline {
id: number; id: number;
// The pipeline number. // The pipeline number.
@ -89,7 +89,7 @@ export type Pipeline = {
workflows?: PipelineWorkflow[]; workflows?: PipelineWorkflow[];
changed_files?: string[]; changed_files?: string[];
}; }
export type PipelineStatus = export type PipelineStatus =
| 'blocked' | 'blocked'
@ -103,7 +103,7 @@ export type PipelineStatus =
| 'started' | 'started'
| 'success'; | 'success';
export type PipelineWorkflow = { export interface PipelineWorkflow {
id: number; id: number;
pipeline_id: number; pipeline_id: number;
pid: number; pid: number;
@ -115,9 +115,9 @@ export type PipelineWorkflow = {
agent_id?: number; agent_id?: number;
error?: string; error?: string;
children: PipelineStep[]; children: PipelineStep[];
}; }
export type PipelineStep = { export interface PipelineStep {
id: number; id: number;
uuid: string; uuid: string;
pipeline_id: number; pipeline_id: number;
@ -130,21 +130,22 @@ export type PipelineStep = {
end_time?: number; end_time?: number;
error?: string; error?: string;
type?: StepType; type?: StepType;
}; }
export type PipelineLog = { export interface PipelineLog {
id: number; id: number;
step_id: number; step_id: number;
time: number; time: number;
line: number; line: number;
data: string; // base64 encoded data: string; // base64 encoded
type: number; type: number;
}; }
export type PipelineFeed = Pipeline & { export type PipelineFeed = Pipeline & {
repo_id: number; repo_id: number;
}; };
/* eslint-disable no-unused-vars */
export enum StepType { export enum StepType {
Clone = 'clone', Clone = 'clone',
Service = 'service', Service = 'service',
@ -152,3 +153,4 @@ export enum StepType {
Commands = 'commands', Commands = 'commands',
Cache = 'cache', Cache = 'cache',
} }
/* eslint-enable */

View file

@ -1,6 +1,6 @@
// A config for a pipeline. // A config for a pipeline.
export type PipelineConfig = { export interface PipelineConfig {
hash: string; hash: string;
name: string; name: string;
data: string; data: string;
}; }

View file

@ -1,7 +1,7 @@
// A version control pull request. // A version control pull request.
export type PullRequest = { export interface PullRequest {
// The index of the pull request. // The index of the pull request.
index: string; index: string;
// The title of the pull request. // The title of the pull request.
title: string; title: string;
}; }

View file

@ -1,4 +1,4 @@
export type Task = { export interface Task {
id: number; id: number;
data: string; data: string;
labels: { [key: string]: string }; labels: { [key: string]: string };
@ -6,20 +6,20 @@ export type Task = {
dep_status: { [key: string]: string }; dep_status: { [key: string]: string };
run_on: string[]; run_on: string[];
agent_id: number; agent_id: number;
}; }
export type QueueStats = { export interface QueueStats {
worker_count: number; worker_count: number;
pending_count: number; pending_count: number;
waiting_on_deps_count: number; waiting_on_deps_count: number;
running_count: number; running_count: number;
completed_count: number; completed_count: number;
}; }
export type QueueInfo = { export interface QueueInfo {
pending: Task[]; pending: Task[];
waiting_on_deps: Task[]; waiting_on_deps: Task[];
running: Task[]; running: Task[];
stats: QueueStats; stats: QueueStats;
paused: boolean; paused: boolean;
}; }

View file

@ -1,6 +1,6 @@
export type Registry = { export interface Registry {
id: string; id: string;
address: string; address: string;
username: string; username: string;
password: string; password: string;
}; }

View file

@ -1,5 +1,5 @@
// A version control repository. // A version control repository.
export type Repo = { export interface Repo {
// Is the repo currently active or not // Is the repo currently active or not
active: boolean; active: boolean;
@ -70,13 +70,15 @@ export type Repo = {
cancel_previous_pipeline_events: string[]; cancel_previous_pipeline_events: string[];
netrc_only_trusted: boolean; netrc_only_trusted: boolean;
}; }
/* eslint-disable no-unused-vars */
export enum RepoVisibility { export enum RepoVisibility {
Public = 'public', Public = 'public',
Private = 'private', Private = 'private',
Internal = 'internal', Internal = 'internal',
} }
/* eslint-enable */
export type RepoSettings = Pick< export type RepoSettings = Pick<
Repo, Repo,
@ -91,9 +93,9 @@ export type RepoSettings = Pick<
| 'netrc_only_trusted' | 'netrc_only_trusted'
>; >;
export type RepoPermissions = { export interface RepoPermissions {
pull: boolean; pull: boolean;
push: boolean; push: boolean;
admin: boolean; admin: boolean;
synced: number; synced: number;
}; }

View file

@ -1,6 +1,6 @@
import { WebhookEvents } from './webhook'; import type { WebhookEvents } from './webhook';
export type Secret = { export interface Secret {
id: string; id: string;
repo_id: number; repo_id: number;
org_id: number; org_id: number;
@ -8,4 +8,4 @@ export type Secret = {
value: string; value: string;
events: WebhookEvents[]; events: WebhookEvents[];
images: string[]; images: string[];
}; }

View file

@ -1,5 +1,5 @@
// The user account. // The user account.
export type User = { export interface User {
id: number; id: number;
// The unique identifier for the account. // The unique identifier for the account.
@ -20,4 +20,4 @@ export type User = {
org_id: number; org_id: number;
// The ID of the org assigned to the user. // The ID of the org assigned to the user.
}; }

View file

@ -1,3 +1,4 @@
/* eslint-disable no-unused-vars */
export enum WebhookEvents { export enum WebhookEvents {
Push = 'push', Push = 'push',
Tag = 'tag', Tag = 'tag',
@ -8,3 +9,4 @@ export enum WebhookEvents {
Cron = 'cron', Cron = 'cron',
Manual = 'manual', Manual = 'manual',
} }
/* eslint-enable */

View file

@ -11,6 +11,7 @@ import { i18n } from '~/compositions/useI18n';
import { notifications } from '~/compositions/useNotifications'; import { notifications } from '~/compositions/useNotifications';
import router from '~/router'; import router from '~/router';
// eslint-disable-next-line ts/no-unsafe-argument
const app = createApp(App); const app = createApp(App);
app.use(router); app.use(router);

View file

@ -1,5 +1,5 @@
import { Component } from 'vue'; import type { Component } from 'vue';
import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router'; import { createRouter, createWebHistory, type RouteRecordRaw } from 'vue-router';
import useAuthentication from '~/compositions/useAuthentication'; import useAuthentication from '~/compositions/useAuthentication';
import useConfig from '~/compositions/useConfig'; import useConfig from '~/compositions/useConfig';

View file

@ -1,8 +1,8 @@
import { defineStore } from 'pinia'; import { defineStore } from 'pinia';
import { computed, reactive, Ref, ref } from 'vue'; import { computed, reactive, ref, type Ref } from 'vue';
import useApiClient from '~/compositions/useApiClient'; import useApiClient from '~/compositions/useApiClient';
import { Pipeline, PipelineFeed, PipelineWorkflow } from '~/lib/api/types'; import type { Pipeline, PipelineFeed, PipelineWorkflow } from '~/lib/api/types';
import { useRepoStore } from '~/store/repos'; import { useRepoStore } from '~/store/repos';
import { comparePipelines, comparePipelinesWithStatus, isPipelineActive } from '~/utils/helpers'; import { comparePipelines, comparePipelinesWithStatus, isPipelineActive } from '~/utils/helpers';
@ -13,7 +13,7 @@ export const usePipelineStore = defineStore('pipelines', () => {
const pipelines: Map<number, Map<number, Pipeline>> = reactive(new Map()); const pipelines: Map<number, Map<number, Pipeline>> = reactive(new Map());
function setPipeline(repoId: number, pipeline: Pipeline) { function setPipeline(repoId: number, pipeline: Pipeline) {
const repoPipelines = pipelines.get(repoId) || new Map(); const repoPipelines = pipelines.get(repoId) || new Map<number, Pipeline>();
repoPipelines.set(pipeline.number, { repoPipelines.set(pipeline.number, {
...(repoPipelines.get(pipeline.number) || {}), ...(repoPipelines.get(pipeline.number) || {}),
...pipeline, ...pipeline,
@ -27,7 +27,7 @@ export const usePipelineStore = defineStore('pipelines', () => {
function getPipeline(repoId: Ref<number>, _pipelineNumber: Ref<string>) { function getPipeline(repoId: Ref<number>, _pipelineNumber: Ref<string>) {
return computed(() => { return computed(() => {
const pipelineNumber = parseInt(_pipelineNumber.value, 10); const pipelineNumber = Number.parseInt(_pipelineNumber.value, 10);
return pipelines.get(repoId.value)?.get(pipelineNumber); return pipelines.get(repoId.value)?.get(pipelineNumber);
}); });
} }

View file

@ -1,8 +1,8 @@
import { defineStore } from 'pinia'; import { defineStore } from 'pinia';
import { computed, reactive, Ref, ref } from 'vue'; import { computed, reactive, ref, type Ref } from 'vue';
import useApiClient from '~/compositions/useApiClient'; import useApiClient from '~/compositions/useApiClient';
import { Repo } from '~/lib/api/types'; import type { Repo } from '~/lib/api/types';
export const useRepoStore = defineStore('repos', () => { export const useRepoStore = defineStore('repos', () => {
const apiClient = useApiClient(); const apiClient = useApiClient();

View file

@ -1,4 +1,4 @@
import { Pipeline, PipelineStep, PipelineWorkflow, Repo } from '~/lib/api/types'; import type { Pipeline, PipelineStep, PipelineWorkflow, Repo } from '~/lib/api/types';
export function findStep(workflows: PipelineWorkflow[], pid: number): PipelineStep | undefined { export function findStep(workflows: PipelineWorkflow[], pid: number): PipelineStep | undefined {
return workflows.reduce( return workflows.reduce(
@ -24,20 +24,16 @@ export function findStep(workflows: PipelineWorkflow[], pid: number): PipelineSt
} }
/** /**
* Returns true if the process is in a completed state. * @param {object} step - The process object.
* * @returns {boolean} true if the process is in a completed state
* @param {Object} step - The process object.
* @returns {boolean}
*/ */
export function isStepFinished(step: PipelineStep): boolean { export function isStepFinished(step: PipelineStep): boolean {
return step.state !== 'running' && step.state !== 'pending'; return step.state !== 'running' && step.state !== 'pending';
} }
/** /**
* Returns true if the process is running. * @param {object} step - The process object.
* * @returns {boolean} true if the process is running
* @param {Object} step - The process object.
* @returns {boolean}
*/ */
export function isStepRunning(step: PipelineStep): boolean { export function isStepRunning(step: PipelineStep): boolean {
return step.state === 'running'; return step.state === 'running';
@ -45,9 +41,9 @@ export function isStepRunning(step: PipelineStep): boolean {
/** /**
* Compare two pipelines by creation timestamp. * Compare two pipelines by creation timestamp.
* @param {Object} a - A pipeline. * @param {object} a - A pipeline.
* @param {Object} b - A pipeline. * @param {object} b - A pipeline.
* @returns {number} * @returns {number} 0 if created at the same time, < 0 if b was create before a, > 0 otherwise
*/ */
export function comparePipelines(a: Pipeline, b: Pipeline): number { export function comparePipelines(a: Pipeline, b: Pipeline): number {
return (b.created_at || -1) - (a.created_at || -1); return (b.created_at || -1) - (a.created_at || -1);
@ -56,9 +52,9 @@ export function comparePipelines(a: Pipeline, b: Pipeline): number {
/** /**
* Compare two pipelines by the status. * Compare two pipelines by the status.
* Giving pending, running, or started higher priority than other status * Giving pending, running, or started higher priority than other status
* @param {Object} a - A pipeline. * @param {object} a - A pipeline.
* @param {Object} b - A pipeline. * @param {object} b - A pipeline.
* @returns {number} * @returns {number} 0 if status same priority, < 0 if b has higher priority, > 0 otherwise
*/ */
export function comparePipelinesWithStatus(a: Pipeline, b: Pipeline): number { export function comparePipelinesWithStatus(a: Pipeline, b: Pipeline): number {
const bPriority = ['pending', 'running', 'started'].includes(b.status) ? 1 : 0; const bPriority = ['pending', 'running', 'started'].includes(b.status) ? 1 : 0;
@ -70,11 +66,9 @@ export function isPipelineActive(pipeline: Pipeline): boolean {
return ['pending', 'running', 'started'].includes(pipeline.status); return ['pending', 'running', 'started'].includes(pipeline.status);
} }
export function repoSlug(ownerOrRepo: Repo): string;
export function repoSlug(ownerOrRepo: string, name: string): string;
export function repoSlug(ownerOrRepo: string | Repo, name?: string): string { export function repoSlug(ownerOrRepo: string | Repo, name?: string): string {
if (typeof ownerOrRepo === 'string') { if (typeof ownerOrRepo === 'string') {
if (!name) { if (name === undefined) {
throw new Error('Please provide a name as well'); throw new Error('Please provide a name as well');
} }

View file

@ -1,10 +1,8 @@
<template> <template>
<div class="flex flex-col h-full w-full items-center justify-center"> <div class="flex flex-col h-full w-full items-center justify-center">
<p class="text-2xl mb-8">{{ $t('not_found.not_found') }}</p> <p class="text-2xl mb-8">{{ $t('not_found.not_found') }}</p>
<span <router-link class="text-blue-400" replace :to="{ name: 'home' }">
><router-link class="text-blue-400" replace :to="{ name: 'home' }">{{ {{ $t('not_found.back_home') }}
$t('not_found.back_home') </router-link>
}}</router-link></span
>
</div> </div>
</template> </template>

View file

@ -45,7 +45,7 @@ import { useAsyncAction } from '~/compositions/useAsyncAction';
import useNotifications from '~/compositions/useNotifications'; import useNotifications from '~/compositions/useNotifications';
import { useRepoSearch } from '~/compositions/useRepoSearch'; import { useRepoSearch } from '~/compositions/useRepoSearch';
import { useRouteBack } from '~/compositions/useRouteBack'; import { useRouteBack } from '~/compositions/useRouteBack';
import { Repo } from '~/lib/api/types'; import type { Repo } from '~/lib/api/types';
const router = useRouter(); const router = useRouter();
const apiClient = useApiClient(); const apiClient = useApiClient();

View file

@ -8,13 +8,12 @@ import { useRoute, useRouter } from 'vue-router';
import useApiClient from '~/compositions/useApiClient'; import useApiClient from '~/compositions/useApiClient';
const apiClient = useApiClient();
const route = useRoute();
const router = useRouter();
const props = defineProps<{ const props = defineProps<{
orgName: string; orgName: string;
}>(); }>();
const apiClient = useApiClient();
const route = useRoute();
const router = useRouter();
onMounted(async () => { onMounted(async () => {
const org = await apiClient.lookupOrg(props.orgName); const org = await apiClient.lookupOrg(props.orgName);

View file

@ -4,6 +4,7 @@
<span> <span>
<router-link :to="{ name: 'org' }" class="hover:underline"> <router-link :to="{ name: 'org' }" class="hover:underline">
{{ org.name }} {{ org.name }}
<!-- eslint-disable-next-line @intlify/vue-i18n/no-raw-text -->
</router-link> </router-link>
/ /
{{ $t('settings') }} {{ $t('settings') }}

View file

@ -25,13 +25,13 @@ import IconButton from '~/components/atomic/IconButton.vue';
import Scaffold from '~/components/layout/scaffold/Scaffold.vue'; import Scaffold from '~/components/layout/scaffold/Scaffold.vue';
import useApiClient from '~/compositions/useApiClient'; import useApiClient from '~/compositions/useApiClient';
import { provide } from '~/compositions/useInjectProvide'; import { provide } from '~/compositions/useInjectProvide';
import { Org, OrgPermissions } from '~/lib/api/types'; import type { Org, OrgPermissions } from '~/lib/api/types';
const props = defineProps<{ const props = defineProps<{
orgId: string; orgId: string;
}>(); }>();
const orgId = computed(() => parseInt(props.orgId, 10)); const orgId = computed(() => Number.parseInt(props.orgId, 10));
const apiClient = useApiClient(); const apiClient = useApiClient();
const org = ref<Org>(); const org = ref<Org>();

View file

@ -6,10 +6,10 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { computed, inject, Ref, toRef } from 'vue'; import { computed, inject, toRef, type Ref } from 'vue';
import PipelineList from '~/components/repo/pipeline/PipelineList.vue'; import PipelineList from '~/components/repo/pipeline/PipelineList.vue';
import { Pipeline, Repo, RepoPermissions } from '~/lib/api/types'; import type { Pipeline, Repo, RepoPermissions } from '~/lib/api/types';
const props = defineProps<{ const props = defineProps<{
branch: string; branch: string;

View file

@ -21,13 +21,13 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { computed, inject, Ref, watch } from 'vue'; import { computed, inject, watch, type Ref } from 'vue';
import Badge from '~/components/atomic/Badge.vue'; import Badge from '~/components/atomic/Badge.vue';
import ListItem from '~/components/atomic/ListItem.vue'; import ListItem from '~/components/atomic/ListItem.vue';
import useApiClient from '~/compositions/useApiClient'; import useApiClient from '~/compositions/useApiClient';
import { usePagination } from '~/compositions/usePaginate'; import { usePagination } from '~/compositions/usePaginate';
import { Repo } from '~/lib/api/types'; import type { Repo } from '~/lib/api/types';
const apiClient = useApiClient(); const apiClient = useApiClient();

View file

@ -8,14 +8,13 @@ import { useRoute, useRouter } from 'vue-router';
import useApiClient from '~/compositions/useApiClient'; import useApiClient from '~/compositions/useApiClient';
const apiClient = useApiClient();
const route = useRoute();
const router = useRouter();
const props = defineProps<{ const props = defineProps<{
repoOwner: string; repoOwner: string;
repoName: string; repoName: string;
}>(); }>();
const apiClient = useApiClient();
const route = useRoute();
const router = useRouter();
onMounted(async () => { onMounted(async () => {
const repo = await apiClient.lookupRepo(props.repoOwner, props.repoName); const repo = await apiClient.lookupRepo(props.repoOwner, props.repoName);

View file

@ -3,10 +3,10 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { inject, Ref } from 'vue'; import { inject, type Ref } from 'vue';
import PipelineList from '~/components/repo/pipeline/PipelineList.vue'; import PipelineList from '~/components/repo/pipeline/PipelineList.vue';
import { Pipeline, Repo, RepoPermissions } from '~/lib/api/types'; import type { Pipeline, Repo, RepoPermissions } from '~/lib/api/types';
const repo = inject<Ref<Repo>>('repo'); const repo = inject<Ref<Repo>>('repo');
const repoPermissions = inject<Ref<RepoPermissions>>('repo-permissions'); const repoPermissions = inject<Ref<RepoPermissions>>('repo-permissions');

View file

@ -6,10 +6,10 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { computed, inject, Ref, toRef } from 'vue'; import { computed, inject, toRef, type Ref } from 'vue';
import PipelineList from '~/components/repo/pipeline/PipelineList.vue'; import PipelineList from '~/components/repo/pipeline/PipelineList.vue';
import { Pipeline, Repo, RepoPermissions } from '~/lib/api/types'; import type { Pipeline, Repo, RepoPermissions } from '~/lib/api/types';
const props = defineProps<{ const props = defineProps<{
pullRequest: string; pullRequest: string;

View file

@ -7,7 +7,9 @@
class="text-wp-text-100" class="text-wp-text-100"
:to="{ name: 'repo-pull-request', params: { pullRequest: pullRequest.index } }" :to="{ name: 'repo-pull-request', params: { pullRequest: pullRequest.index } }"
> >
<!-- eslint-disable-next-line @intlify/vue-i18n/no-raw-text -->
<span class="text-wp-text-alt-100 <md:hidden">#{{ pullRequest.index }}</span> <span class="text-wp-text-alt-100 <md:hidden">#{{ pullRequest.index }}</span>
<!-- eslint-disable-next-line @intlify/vue-i18n/no-raw-text -->
<span class="text-wp-text-alt-100 <md:hidden mx-2">-</span> <span class="text-wp-text-alt-100 <md:hidden mx-2">-</span>
<span class="text-wp-text-100 <md:underline whitespace-nowrap overflow-hidden overflow-ellipsis">{{ <span class="text-wp-text-100 <md:underline whitespace-nowrap overflow-hidden overflow-ellipsis">{{
pullRequest.title pullRequest.title
@ -24,14 +26,14 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { inject, Ref, watch } from 'vue'; import { inject, watch, type Ref } from 'vue';
import Icon from '~/components/atomic/Icon.vue'; import Icon from '~/components/atomic/Icon.vue';
import ListItem from '~/components/atomic/ListItem.vue'; import ListItem from '~/components/atomic/ListItem.vue';
import Panel from '~/components/layout/Panel.vue'; import Panel from '~/components/layout/Panel.vue';
import useApiClient from '~/compositions/useApiClient'; import useApiClient from '~/compositions/useApiClient';
import { usePagination } from '~/compositions/usePaginate'; import { usePagination } from '~/compositions/usePaginate';
import { PullRequest, Repo } from '~/lib/api/types'; import type { PullRequest, Repo } from '~/lib/api/types';
const apiClient = useApiClient(); const apiClient = useApiClient();

View file

@ -4,10 +4,12 @@
<span> <span>
<router-link :to="{ name: 'org', params: { orgId: repo!.org_id } }" class="hover:underline"> <router-link :to="{ name: 'org', params: { orgId: repo!.org_id } }" class="hover:underline">
{{ repo!.owner }} {{ repo!.owner }}
<!-- eslint-disable-next-line @intlify/vue-i18n/no-raw-text -->
</router-link> </router-link>
/ /
<router-link :to="{ name: 'repo' }" class="hover:underline"> <router-link :to="{ name: 'repo' }" class="hover:underline">
{{ repo!.name }} {{ repo!.name }}
<!-- eslint-disable-next-line @intlify/vue-i18n/no-raw-text -->
</router-link> </router-link>
/ /
{{ $t('settings') }} {{ $t('settings') }}
@ -36,7 +38,7 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { inject, onMounted, Ref } from 'vue'; import { inject, onMounted, type Ref } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useRouter } from 'vue-router'; import { useRouter } from 'vue-router';
@ -50,7 +52,7 @@ import RegistriesTab from '~/components/repo/settings/RegistriesTab.vue';
import SecretsTab from '~/components/repo/settings/SecretsTab.vue'; import SecretsTab from '~/components/repo/settings/SecretsTab.vue';
import useNotifications from '~/compositions/useNotifications'; import useNotifications from '~/compositions/useNotifications';
import { useRouteBack } from '~/compositions/useRouteBack'; import { useRouteBack } from '~/compositions/useRouteBack';
import { Repo, RepoPermissions } from '~/lib/api/types'; import type { Repo, RepoPermissions } from '~/lib/api/types';
const notifications = useNotifications(); const notifications = useNotifications();
const router = useRouter(); const router = useRouter();

Some files were not shown because too many files have changed in this diff Show more