mirror of
https://github.com/woodpecker-ci/woodpecker.git
synced 2024-11-30 08:06:52 +02:00
Support localized web UI (#912)
* Add support for localization * Add docs & format code * Add lib to docs
This commit is contained in:
parent
687d57217d
commit
7d7d75d7e5
17
docs/docs/92-development/07-translations.md
Normal file
17
docs/docs/92-development/07-translations.md
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
# Translations
|
||||||
|
|
||||||
|
Woodpecker uses [Vue I18n](https://vue-i18n.intlify.dev/) as translation library, thus you can easily translate the web UI into your language. Therefore, copy the file `web/src/assets/locales/en.json` to the same path with your language's code and `.json` as name.
|
||||||
|
Then, translate content of this file, but only the values:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"dont_translate": "Only translate this text"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
To add support for time formatting, import the language into two files:
|
||||||
|
|
||||||
|
1. `web/src/compositions/useDate.ts`: Just add a line like `import 'dayjs/locale/en';` to the first block of `import` statements and replace `en` with your language's code.
|
||||||
|
2. `web/src/utils/timeAgo.ts`: Add a line like `import en from 'javascript-time-ago/locale/en.json';` to the other `import`-statements and replace both `en`s with your language's code. Then, add the line `TimeAgo.addDefaultLocale(en);` to the other lines of them, and replace `en` with your language's code.
|
||||||
|
|
||||||
|
Then, the web UI should be available in your language. You should open a pull request to our repository to get your changes into the next release.
|
@ -4,3 +4,4 @@ coverage/
|
|||||||
package.json
|
package.json
|
||||||
tsconfig.eslint.json
|
tsconfig.eslint.json
|
||||||
tsconfig.json
|
tsconfig.json
|
||||||
|
src/assets/locales/
|
||||||
|
@ -17,6 +17,7 @@
|
|||||||
"test": "echo 'No tests configured' && exit 0"
|
"test": "echo 'No tests configured' && exit 0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@intlify/vite-plugin-vue-i18n": "^3.4.0",
|
||||||
"@kyvg/vue3-notification": "2.3.4",
|
"@kyvg/vue3-notification": "2.3.4",
|
||||||
"@meforma/vue-toaster": "1.2.2",
|
"@meforma/vue-toaster": "1.2.2",
|
||||||
"ansi-to-html": "0.7.2",
|
"ansi-to-html": "0.7.2",
|
||||||
@ -29,6 +30,7 @@
|
|||||||
"node-emoji": "1.11.0",
|
"node-emoji": "1.11.0",
|
||||||
"pinia": "2.0.0",
|
"pinia": "2.0.0",
|
||||||
"vue": "v3.2.20",
|
"vue": "v3.2.20",
|
||||||
|
"vue-i18n": "9",
|
||||||
"vue-router": "4.0.10"
|
"vue-router": "4.0.10"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
208
web/src/assets/locales/en.json
Normal file
208
web/src/assets/locales/en.json
Normal file
@ -0,0 +1,208 @@
|
|||||||
|
{
|
||||||
|
"login": "Login",
|
||||||
|
"welcome": "Welcome to Woodpecker",
|
||||||
|
"repos": "Repos",
|
||||||
|
"repositories": "Repositories",
|
||||||
|
"logout": "Logout",
|
||||||
|
"search": "Search...",
|
||||||
|
"username": "Username",
|
||||||
|
"password": "Password",
|
||||||
|
"url": "URL",
|
||||||
|
|
||||||
|
"not_found": {
|
||||||
|
"not_found": "Whoa 404, either we broke something or you had a typing mishap :-/",
|
||||||
|
"back_home": "Back to home"
|
||||||
|
},
|
||||||
|
|
||||||
|
"time": {
|
||||||
|
"tmpl": "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"
|
||||||
|
},
|
||||||
|
|
||||||
|
"repo": {
|
||||||
|
"activity": "Activity",
|
||||||
|
"branches": "Branches",
|
||||||
|
"add": "Add repository",
|
||||||
|
"user_none": "This organization / user does not have any projects yet.",
|
||||||
|
"not_allowed": "Not allowed to access this repository",
|
||||||
|
|
||||||
|
"enable": {
|
||||||
|
"reload": "Reload repositories",
|
||||||
|
"enable": "Enable",
|
||||||
|
"enabled": "Already enabled",
|
||||||
|
"success": "Repository enabled",
|
||||||
|
"list_reloaded": "Repository list reloaded"
|
||||||
|
},
|
||||||
|
|
||||||
|
"settings": {
|
||||||
|
"settings": "Settings",
|
||||||
|
|
||||||
|
"general": {
|
||||||
|
"general": "General",
|
||||||
|
"project": "Project settings",
|
||||||
|
"save": "Save settings",
|
||||||
|
"success": "Repository settings updated",
|
||||||
|
|
||||||
|
"pipeline_path": {
|
||||||
|
"path": "Pipeline path",
|
||||||
|
"default": "By default: .woodpecker/*.yml -> .woodpecker.yml -> .drone.yml",
|
||||||
|
"desc": "Path to your pipeline config (for example<span class=\"bg-gray-300 dark:bg-dark-100 rounded-md px-1\">my/path/</span>). Folders should end with a<span class=\"bg-gray-300 dark:bg-dark-100 rounded-md px-1\">/</span>."
|
||||||
|
},
|
||||||
|
|
||||||
|
"allow_pr": {
|
||||||
|
"allow": "Allow Pull Requests",
|
||||||
|
"desc": "Pipelines can run on pull requests."
|
||||||
|
},
|
||||||
|
|
||||||
|
"protected": {
|
||||||
|
"protected": "Protected",
|
||||||
|
"desc": "Every pipeline needs to be approved before being executed."
|
||||||
|
},
|
||||||
|
"trusted": {
|
||||||
|
"trusted": "Trusted",
|
||||||
|
"desc": "Underlying pipeline containers get access to escalated capabilities like mounting volumes."
|
||||||
|
},
|
||||||
|
"visibility": {
|
||||||
|
"visibility": "Project visibility",
|
||||||
|
|
||||||
|
"public": {
|
||||||
|
"public": "Public",
|
||||||
|
"desc": "Every user can see your project without being logged in."
|
||||||
|
},
|
||||||
|
"private": {
|
||||||
|
"private": "Private",
|
||||||
|
"desc": "Only authenticated users of the Woodpecker instance can see this project."
|
||||||
|
},
|
||||||
|
"internal": {
|
||||||
|
"internal": "Internal",
|
||||||
|
"desc": "Only you and other owners of the repository can see this project."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
"timeout": {
|
||||||
|
"timeout": "Timeout",
|
||||||
|
"minutes": "minutes"
|
||||||
|
},
|
||||||
|
|
||||||
|
"cancel_prev": {
|
||||||
|
"cancel": "Cancel previous pipelines",
|
||||||
|
"desc": "Enable to cancel running pipelines of the same event and context before starting the newly triggered one."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
"secrets": {
|
||||||
|
"secrets": "Secrets",
|
||||||
|
"desc": "Secrets can be passed to individual pipeline steps at runtime as environmental variables.",
|
||||||
|
"none": "There are no secrets yet.",
|
||||||
|
"add": "Add secret",
|
||||||
|
"save": "Save secret",
|
||||||
|
"show": "Show secrets",
|
||||||
|
"name": "Name",
|
||||||
|
"value": "Value",
|
||||||
|
"deleted": "Secret deleted",
|
||||||
|
"created": "Secret created",
|
||||||
|
"saved": "Secret saved",
|
||||||
|
|
||||||
|
"images": {
|
||||||
|
"images": "Available for following images",
|
||||||
|
"desc": "Comma separated list of images where this secret is available, leave empty to allow all images"
|
||||||
|
},
|
||||||
|
"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."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
"registries": {
|
||||||
|
"registries": "Registries",
|
||||||
|
"creds": "Registry credentials",
|
||||||
|
"desc": "Registries credentials can be added to use private images for your pipeline.",
|
||||||
|
"show": "Show registries",
|
||||||
|
"add": "Add registry",
|
||||||
|
"none": "There are no registry credentials yet.",
|
||||||
|
"save": "Save registry",
|
||||||
|
"created": "Registry credentials created",
|
||||||
|
"saved": "Registry credentials saved",
|
||||||
|
"deleted": "Registry credentials deleted",
|
||||||
|
|
||||||
|
"address": {
|
||||||
|
"address": "Address",
|
||||||
|
"placeholder": "Registry Address (e.g. docker.io)"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
"badge": {
|
||||||
|
"badge": "Badge",
|
||||||
|
"url_branch": "URL for specific branch",
|
||||||
|
"markdown": "Markdown"
|
||||||
|
},
|
||||||
|
|
||||||
|
"actions": {
|
||||||
|
"actions": "Actions",
|
||||||
|
|
||||||
|
"repair": {
|
||||||
|
"repair": "Repair repository",
|
||||||
|
"success": "Repository repaired"
|
||||||
|
},
|
||||||
|
"disable": {
|
||||||
|
"disable": "Disable repository",
|
||||||
|
"success": "Repository disabled"
|
||||||
|
},
|
||||||
|
"delete": {
|
||||||
|
"delete": "Delete repository",
|
||||||
|
"confirm": "All data will be lost after this action!!!\n\nDo you really want to proceed?",
|
||||||
|
"success": "Repository deleted"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
"build": {
|
||||||
|
"created": "Created",
|
||||||
|
"tasks": "Tasks",
|
||||||
|
"config": "Config",
|
||||||
|
"files": "Changed files ({0})",
|
||||||
|
"no_files": "No files have been changed.",
|
||||||
|
"execution_error": "Execution error",
|
||||||
|
"no_pipelines": "No pipelines have been started yet.",
|
||||||
|
"step_not_started": "This step hasn't started yet.",
|
||||||
|
"pipelines_for": "Pipelines for branch \"{0}\"",
|
||||||
|
|
||||||
|
"actions": {
|
||||||
|
"cancel": "Cancel",
|
||||||
|
"restart": "Restart",
|
||||||
|
"canceled": "This step has been canceled.",
|
||||||
|
"cancel_success": "Pipeline canceled",
|
||||||
|
"restart_success": "Pipeline restarted"
|
||||||
|
},
|
||||||
|
"protected": {
|
||||||
|
"awaits": "This pipeline is awaiting approval by some maintainer!",
|
||||||
|
"approve": "Approve",
|
||||||
|
"decline": "Decline",
|
||||||
|
"declined": "This pipeline has been declined!"
|
||||||
|
},
|
||||||
|
"event": {
|
||||||
|
"push": "Push",
|
||||||
|
"tag": "Tag",
|
||||||
|
"pr": "Pull Request",
|
||||||
|
"deploy": "Deploy"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
"user": {
|
||||||
|
"oauth_error": "Error while authenticating against OAuth provider",
|
||||||
|
"internal_error": "Some internal error occurred",
|
||||||
|
"access_denied": "You are not allowed to login",
|
||||||
|
"token": "Your Personal Token",
|
||||||
|
"shell_setup": "Shell setup",
|
||||||
|
"api_usage": "Example API Usage",
|
||||||
|
"cli_usage": "Example CLI Usage",
|
||||||
|
"dl_cli": "Download CLI",
|
||||||
|
"shell_setup_before": "do shell setup steps before"
|
||||||
|
}
|
||||||
|
}
|
@ -9,7 +9,9 @@
|
|||||||
<Icon name="since" />
|
<Icon name="since" />
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
<span>{{ since }}</span>
|
<span>{{ since }}</span>
|
||||||
<template #popper><span class="font-bold">Created</span> {{ created }}</template>
|
<template #popper
|
||||||
|
><span class="font-bold">{{ $t('created') }}</span> {{ created }}</template
|
||||||
|
>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex space-x-2 items-center">
|
<div class="flex space-x-2 items-center">
|
||||||
|
@ -21,7 +21,7 @@
|
|||||||
<BuildFeedItem :build="build" />
|
<BuildFeedItem :build="build" />
|
||||||
</router-link>
|
</router-link>
|
||||||
|
|
||||||
<span v-if="sortedBuildFeed.length === 0" class="text-gray-500 m-4">No pipelines have been started yet.</span>
|
<span v-if="sortedBuildFeed.length === 0" class="text-gray-500 m-4">{{ $t('repo.build.no_pipelines') }}</span>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -10,8 +10,8 @@
|
|||||||
:to="{ name: 'repos' }"
|
:to="{ name: 'repos' }"
|
||||||
class="mx-4 hover:bg-lime-700 dark:hover:bg-gray-600 px-4 py-1 rounded-md"
|
class="mx-4 hover:bg-lime-700 dark:hover:bg-gray-600 px-4 py-1 rounded-md"
|
||||||
>
|
>
|
||||||
<span class="flex md:hidden">Repos</span>
|
<span class="flex md:hidden">{{ $t('repos') }}</span>
|
||||||
<span class="hidden md:flex">Repositories</span>
|
<span class="hidden md:flex">{{ $t('repositories') }}</span>
|
||||||
</router-link>
|
</router-link>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex ml-auto items-center space-x-4 text-white dark:text-gray-500">
|
<div class="flex ml-auto items-center space-x-4 text-white dark:text-gray-500">
|
||||||
@ -29,7 +29,7 @@
|
|||||||
<router-link v-if="user" :to="{ name: 'user' }">
|
<router-link v-if="user" :to="{ name: 'user' }">
|
||||||
<img v-if="user && user.avatar_url" class="w-8" :src="`${user.avatar_url}`" />
|
<img v-if="user && user.avatar_url" class="w-8" :src="`${user.avatar_url}`" />
|
||||||
</router-link>
|
</router-link>
|
||||||
<Button v-else text="Login" @click="doLogin" />
|
<Button v-else :text="$t('login')" @click="doLogin" />
|
||||||
<ActiveBuilds v-if="user" />
|
<ActiveBuilds v-if="user" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
<BuildItem :build="build" />
|
<BuildItem :build="build" />
|
||||||
</router-link>
|
</router-link>
|
||||||
<Panel v-if="builds.length === 0">
|
<Panel v-if="builds.length === 0">
|
||||||
<span class="text-gray-500">No pipelines have been started yet.</span>
|
<span class="text-gray-500">{{ $t('repo.build.no_pipelines') }}</span>
|
||||||
</Panel>
|
</Panel>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -22,10 +22,10 @@
|
|||||||
|
|
||||||
<div class="text-gray-300 mx-auto">
|
<div class="text-gray-300 mx-auto">
|
||||||
<span v-if="proc?.error" class="text-red-500">{{ proc.error }}</span>
|
<span v-if="proc?.error" class="text-red-500">{{ proc.error }}</span>
|
||||||
<span v-else-if="proc?.state === 'skipped'" class="text-orange-300 dark:text-orange-800"
|
<span v-else-if="proc?.state === 'skipped'" class="text-orange-300 dark:text-orange-800">
|
||||||
>This step has been canceled.</span
|
>{{ $t('repo.build.actions.canceled') }}</span
|
||||||
>
|
>
|
||||||
<span v-else-if="!proc?.start_time" class="dark:text-gray-500">This step hasn't started yet.</span>
|
<span v-else-if="!proc?.start_time" class="dark:text-gray-500">{{ $t('repo.build.step_not_started') }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<Panel>
|
<Panel>
|
||||||
<div class="flex flex-row border-b mb-4 pb-4 items-center dark:border-gray-600">
|
<div class="flex flex-row border-b mb-4 pb-4 items-center dark:border-gray-600">
|
||||||
<h1 class="text-xl ml-2 text-gray-500">Actions</h1>
|
<h1 class="text-xl ml-2 text-gray-500">{{ $t('repo.settings.actions.actions') }}</h1>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex flex-col">
|
<div class="flex flex-col">
|
||||||
@ -9,8 +9,8 @@
|
|||||||
class="mr-auto mt-4"
|
class="mr-auto mt-4"
|
||||||
color="blue"
|
color="blue"
|
||||||
start-icon="heal"
|
start-icon="heal"
|
||||||
text="Repair repository"
|
|
||||||
:is-loading="isRepairingRepo"
|
:is-loading="isRepairingRepo"
|
||||||
|
:text="$t('repo.settings.actions.repair.repair')"
|
||||||
@click="repairRepo"
|
@click="repairRepo"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@ -18,8 +18,8 @@
|
|||||||
class="mr-auto mt-4"
|
class="mr-auto mt-4"
|
||||||
color="blue"
|
color="blue"
|
||||||
start-icon="turn-off"
|
start-icon="turn-off"
|
||||||
text="Disable repository"
|
|
||||||
:is-loading="isDeactivatingRepo"
|
:is-loading="isDeactivatingRepo"
|
||||||
|
:text="$t('repo.settings.actions.disable.disable')"
|
||||||
@click="deactivateRepo"
|
@click="deactivateRepo"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@ -27,8 +27,8 @@
|
|||||||
class="mr-auto mt-4"
|
class="mr-auto mt-4"
|
||||||
color="red"
|
color="red"
|
||||||
start-icon="trash"
|
start-icon="trash"
|
||||||
text="Delete repository"
|
|
||||||
:is-loading="isDeletingRepo"
|
:is-loading="isDeletingRepo"
|
||||||
|
:text="$t('repo.settings.actions.delete.delete')"
|
||||||
@click="deleteRepo"
|
@click="deleteRepo"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -37,6 +37,7 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent, inject, Ref } from 'vue';
|
import { defineComponent, inject, Ref } from 'vue';
|
||||||
|
import { useI18n } from 'vue-i18n';
|
||||||
import { useRouter } from 'vue-router';
|
import { useRouter } from 'vue-router';
|
||||||
|
|
||||||
import Button from '~/components/atomic/Button.vue';
|
import Button from '~/components/atomic/Button.vue';
|
||||||
@ -55,6 +56,7 @@ export default defineComponent({
|
|||||||
const apiClient = useApiClient();
|
const apiClient = useApiClient();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const notifications = useNotifications();
|
const notifications = useNotifications();
|
||||||
|
const i18n = useI18n();
|
||||||
|
|
||||||
const repo = inject<Ref<Repo>>('repo');
|
const repo = inject<Ref<Repo>>('repo');
|
||||||
|
|
||||||
@ -64,7 +66,7 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
|
|
||||||
await apiClient.repairRepo(repo.value.owner, repo.value.name);
|
await apiClient.repairRepo(repo.value.owner, repo.value.name);
|
||||||
notifications.notify({ title: 'Repository repaired', type: 'success' });
|
notifications.notify({ title: i18n.t('repo.settings.actions.repair.success'), type: 'success' });
|
||||||
});
|
});
|
||||||
|
|
||||||
const { doSubmit: deleteRepo, isLoading: isDeletingRepo } = useAsyncAction(async () => {
|
const { doSubmit: deleteRepo, isLoading: isDeletingRepo } = useAsyncAction(async () => {
|
||||||
@ -74,12 +76,12 @@ export default defineComponent({
|
|||||||
|
|
||||||
// TODO use proper dialog
|
// TODO use proper dialog
|
||||||
// eslint-disable-next-line no-alert, no-restricted-globals
|
// eslint-disable-next-line no-alert, no-restricted-globals
|
||||||
if (!confirm('All data will be lost after this action!!!\n\nDo you really want to proceed?')) {
|
if (!confirm(i18n.t('repo.settings.actions.delete.confirm'))) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await apiClient.deleteRepo(repo.value.owner, repo.value.name);
|
await apiClient.deleteRepo(repo.value.owner, repo.value.name);
|
||||||
notifications.notify({ title: 'Repository deleted', type: 'success' });
|
notifications.notify({ title: i18n.t('repo.settings.actions.delete.success'), type: 'success' });
|
||||||
await router.replace({ name: 'repos' });
|
await router.replace({ name: 'repos' });
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -89,7 +91,7 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
|
|
||||||
await apiClient.deleteRepo(repo.value.owner, repo.value.name, false);
|
await apiClient.deleteRepo(repo.value.owner, repo.value.name, false);
|
||||||
notifications.notify({ title: 'Repository disabled', type: 'success' });
|
notifications.notify({ title: i18n.t('repo.settings.actions.disable.success'), type: 'success' });
|
||||||
await router.replace({ name: 'repos' });
|
await router.replace({ name: 'repos' });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<Panel>
|
<Panel>
|
||||||
<div class="flex flex-row border-b mb-4 pb-4 items-center dark:border-gray-600">
|
<div class="flex flex-row border-b mb-4 pb-4 items-center dark:border-gray-600">
|
||||||
<h1 class="text-xl ml-2 text-gray-500">Badge</h1>
|
<h1 class="text-xl ml-2 text-gray-500">{{ $t('repo.settings.badge.badge') }}</h1>
|
||||||
<a v-if="badgeUrl" :href="badgeUrl" target="_blank" class="ml-auto">
|
<a v-if="badgeUrl" :href="badgeUrl" target="_blank" class="ml-auto">
|
||||||
<img :src="badgeUrl" />
|
<img :src="badgeUrl" />
|
||||||
</a>
|
</a>
|
||||||
@ -9,17 +9,17 @@
|
|||||||
|
|
||||||
<div class="flex flex-col space-y-4">
|
<div class="flex flex-col space-y-4">
|
||||||
<div>
|
<div>
|
||||||
<h2 class="text-lg text-gray-500 ml-2">Url</h2>
|
<h2 class="text-lg text-gray-500 ml-2">{{ $t('url') }}</h2>
|
||||||
<pre class="box">{{ baseUrl }}{{ badgeUrl }}</pre>
|
<pre class="box">{{ baseUrl }}{{ badgeUrl }}</pre>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<h2 class="text-lg text-gray-500 ml-2">Url for specific branch</h2>
|
<h2 class="text-lg text-gray-500 ml-2">{{ $t('repo.settings.badge.url_branch') }}</h2>
|
||||||
<pre class="box">{{ baseUrl }}{{ badgeUrl }}?branch=<span class="font-bold"><branch></span></pre>
|
<pre class="box">{{ baseUrl }}{{ badgeUrl }}?branch=<span class="font-bold"><branch></span></pre>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<h2 class="text-lg text-gray-500 ml-2">Markdown</h2>
|
<h2 class="text-lg text-gray-500 ml-2">{{ $t('repo.settings.badge.markdown') }}</h2>
|
||||||
<pre class="box">[![status-badge]({{ baseUrl }}{{ badgeUrl }})]({{ baseUrl }}{{ repoUrl }})</pre>
|
<pre class="box">[![status-badge]({{ baseUrl }}{{ badgeUrl }})]({{ baseUrl }}{{ repoUrl }})</pre>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,74 +1,90 @@
|
|||||||
<template>
|
<template>
|
||||||
<Panel>
|
<Panel>
|
||||||
<div class="flex flex-row border-b mb-4 pb-4 items-center dark:border-gray-600">
|
<div class="flex flex-row border-b mb-4 pb-4 items-center dark:border-gray-600">
|
||||||
<h1 class="text-xl ml-2 text-gray-500">General</h1>
|
<h1 class="text-xl ml-2 text-gray-500">{{ $t('general') }}</h1>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="repoSettings" class="flex flex-col">
|
<div v-if="repoSettings" class="flex flex-col">
|
||||||
<InputField label="Pipeline path" docs-url="docs/usage/project-settings#pipeline-path">
|
<InputField
|
||||||
|
docs-url="docs/usage/project-settings#pipeline-path"
|
||||||
|
:label="$t('repo.settings.general.pipeline_path.path')"
|
||||||
|
>
|
||||||
<TextField
|
<TextField
|
||||||
v-model="repoSettings.config_file"
|
v-model="repoSettings.config_file"
|
||||||
class="max-w-124"
|
class="max-w-124"
|
||||||
placeholder="By default: .woodpecker/*.yml -> .woodpecker.yml -> .drone.yml"
|
:placeholder="$t('repo.settings.general.pipeline_path.default')"
|
||||||
/>
|
/>
|
||||||
<template #description>
|
<template #description>
|
||||||
<p class="text-sm text-gray-400 dark:text-gray-600">
|
<!-- eslint-disable-next-line vue/no-v-html -->
|
||||||
Path to your pipeline config (for example
|
<p class="text-sm text-gray-400 dark:text-gray-600" v-html="$t('repo.settings.general.pipeline_path.desc')" />
|
||||||
<span class="bg-gray-300 dark:bg-dark-100 rounded-md px-1">my/path/</span>). Folders should end with a
|
|
||||||
<span class="bg-gray-300 dark:bg-dark-100 rounded-md px-1">/</span>.
|
|
||||||
</p>
|
|
||||||
</template>
|
</template>
|
||||||
</InputField>
|
</InputField>
|
||||||
|
|
||||||
<InputField label="Project settings" docs-url="docs/usage/project-settings#project-settings-1">
|
<InputField
|
||||||
|
docs-url="docs/usage/project-settings#project-settings-1"
|
||||||
|
:label="$t('repo.settings.general.project')"
|
||||||
|
>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
v-model="repoSettings.allow_pr"
|
v-model="repoSettings.allow_pr"
|
||||||
label="Allow Pull Request"
|
:label="$t('repo.settings.general.allow_pr.allow')"
|
||||||
description="Pipelines can run on pull requests."
|
@description="$t('repo.settings.general.allow_pr.desc')"
|
||||||
/>
|
/>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
v-model="repoSettings.gated"
|
v-model="repoSettings.gated"
|
||||||
label="Protected"
|
:label="$t('repo.settings.general.protected.protected')"
|
||||||
description="Every pipeline needs to be approved before being executed."
|
@description="$t('repo.settings.general.protected.desc')"
|
||||||
/>
|
/>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
v-if="user?.admin"
|
v-if="user?.admin"
|
||||||
v-model="repoSettings.trusted"
|
v-model="repoSettings.trusted"
|
||||||
label="Trusted"
|
:label="$t('repo.settings.general.trusted.trusted')"
|
||||||
description="Underlying pipeline containers get access to escalated capabilities like mounting volumes."
|
:description="$t('repo.settings.general.trusted.desc')"
|
||||||
/>
|
/>
|
||||||
</InputField>
|
</InputField>
|
||||||
|
|
||||||
<InputField label="Project visibility" docs-url="docs/usage/project-settings#project-visibility">
|
<InputField
|
||||||
|
docs-url="docs/usage/project-settings#project-visibility"
|
||||||
|
:label="$t('repo.settings.general.visibility.visibility')"
|
||||||
|
>
|
||||||
<RadioField v-model="repoSettings.visibility" :options="projectVisibilityOptions" />
|
<RadioField v-model="repoSettings.visibility" :options="projectVisibilityOptions" />
|
||||||
</InputField>
|
</InputField>
|
||||||
|
|
||||||
<InputField label="Timeout" docs-url="docs/usage/project-settings#timeout">
|
<InputField docs-url="docs/usage/project-settings#timeout" :label="$t('repo.settings.general.timeout.timeout')">
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<NumberField v-model="repoSettings.timeout" class="w-24" />
|
<NumberField v-model="repoSettings.timeout" class="w-24" />
|
||||||
<span class="ml-4 text-gray-600">minutes</span>
|
<span class="ml-4 text-gray-600">{{ $t('repo.settings.general.timeout.minutes') }}</span>
|
||||||
</div>
|
</div>
|
||||||
</InputField>
|
</InputField>
|
||||||
|
|
||||||
<InputField label="Cancel previous pipelines" docs-url="docs/usage/project-settings#cancel-previous-pipelines">
|
<InputField
|
||||||
|
docs-url="docs/usage/project-settings#cancel-previous-pipelines"
|
||||||
|
:label="$t('repo.settings.general.cancel_prev.cancel')"
|
||||||
|
>
|
||||||
<CheckboxesField
|
<CheckboxesField
|
||||||
v-model="repoSettings.cancel_previous_pipeline_events"
|
v-model="repoSettings.cancel_previous_pipeline_events"
|
||||||
:options="cancelPreviousBuildEventsOptions"
|
:options="cancelPreviousBuildEventsOptions"
|
||||||
/>
|
/>
|
||||||
<template #description>
|
<template #description>
|
||||||
<p class="text-sm text-gray-400 dark:text-gray-600">
|
<p class="text-sm text-gray-400 dark:text-gray-600">
|
||||||
Enable to cancel running pipelines of the same event and context before starting the newly triggered one.
|
{{ $t('repo.settings.general.cancel_prev.desc') }}
|
||||||
</p>
|
</p>
|
||||||
</template>
|
</template>
|
||||||
</InputField>
|
</InputField>
|
||||||
|
|
||||||
<Button class="mr-auto" color="green" text="Save settings" :is-loading="isSaving" @click="saveRepoSettings" />
|
<Button
|
||||||
|
class="mr-auto"
|
||||||
|
color="green"
|
||||||
|
:is-loading="isSaving"
|
||||||
|
:text="$t('repo.settings.general.save')"
|
||||||
|
@click="saveRepoSettings"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</Panel>
|
</Panel>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent, inject, onMounted, Ref, ref } from 'vue';
|
import { defineComponent, inject, onMounted, Ref, ref } from 'vue';
|
||||||
|
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';
|
||||||
@ -86,34 +102,6 @@ import useNotifications from '~/compositions/useNotifications';
|
|||||||
import { Repo, RepoSettings, RepoVisibility, WebhookEvents } from '~/lib/api/types';
|
import { Repo, RepoSettings, RepoVisibility, WebhookEvents } from '~/lib/api/types';
|
||||||
import RepoStore from '~/store/repos';
|
import RepoStore from '~/store/repos';
|
||||||
|
|
||||||
const projectVisibilityOptions: RadioOption[] = [
|
|
||||||
{
|
|
||||||
value: RepoVisibility.Public,
|
|
||||||
text: 'Public',
|
|
||||||
description: 'Every user can see your project without being logged in.',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: RepoVisibility.Private,
|
|
||||||
text: 'Private',
|
|
||||||
description: 'Only authenticated users of the Woodpecker instance can see this project.',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: RepoVisibility.Internal,
|
|
||||||
text: 'Internal',
|
|
||||||
description: 'Only you and other owners of the repository can see this project.',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const cancelPreviousBuildEventsOptions: CheckboxOption[] = [
|
|
||||||
{ value: WebhookEvents.Push, text: 'Push' },
|
|
||||||
{ value: WebhookEvents.Tag, text: 'Tag' },
|
|
||||||
{
|
|
||||||
value: WebhookEvents.PullRequest,
|
|
||||||
text: 'Pull Request',
|
|
||||||
},
|
|
||||||
{ value: WebhookEvents.Deploy, text: 'Deploy' },
|
|
||||||
];
|
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'GeneralTab',
|
name: 'GeneralTab',
|
||||||
|
|
||||||
@ -124,6 +112,7 @@ export default defineComponent({
|
|||||||
const notifications = useNotifications();
|
const notifications = useNotifications();
|
||||||
const { user } = useAuthentication();
|
const { user } = useAuthentication();
|
||||||
const repoStore = RepoStore();
|
const repoStore = RepoStore();
|
||||||
|
const i18n = useI18n();
|
||||||
|
|
||||||
const repo = inject<Ref<Repo>>('repo');
|
const repo = inject<Ref<Repo>>('repo');
|
||||||
const repoSettings = ref<RepoSettings>();
|
const repoSettings = ref<RepoSettings>();
|
||||||
@ -164,13 +153,41 @@ export default defineComponent({
|
|||||||
|
|
||||||
await apiClient.updateRepo(repo.value.owner, repo.value.name, repoSettings.value);
|
await apiClient.updateRepo(repo.value.owner, repo.value.name, repoSettings.value);
|
||||||
await loadRepo();
|
await loadRepo();
|
||||||
notifications.notify({ title: 'Repository settings updated', type: 'success' });
|
notifications.notify({ title: i18n.t('repo.settings.general.success'), type: 'success' });
|
||||||
});
|
});
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
loadRepoSettings();
|
loadRepoSettings();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const projectVisibilityOptions: RadioOption[] = [
|
||||||
|
{
|
||||||
|
value: RepoVisibility.Public,
|
||||||
|
text: i18n.t('repo.settings.general.visibility.public.public'),
|
||||||
|
description: i18n.t('repo.settings.general.visibility.public.desc'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: RepoVisibility.Private,
|
||||||
|
text: i18n.t('repo.settings.general.visibility.private.private'),
|
||||||
|
description: i18n.t('repo.settings.general.visibility.private.desc'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: RepoVisibility.Internal,
|
||||||
|
text: i18n.t('repo.settings.general.visibility.internal.internal'),
|
||||||
|
description: i18n.t('repo.settings.general.visibility.internal.desc'),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const cancelPreviousBuildEventsOptions: CheckboxOption[] = [
|
||||||
|
{ value: WebhookEvents.Push, text: i18n.t('repo.build.event.push') },
|
||||||
|
{ value: WebhookEvents.Tag, text: i18n.t('repo.build.event.tag') },
|
||||||
|
{
|
||||||
|
value: WebhookEvents.PullRequest,
|
||||||
|
text: i18n.t('repo.build.event.pr'),
|
||||||
|
},
|
||||||
|
{ value: WebhookEvents.Deploy, text: i18n.t('repo.build.event.deploy') },
|
||||||
|
];
|
||||||
|
|
||||||
return {
|
return {
|
||||||
user,
|
user,
|
||||||
repoSettings,
|
repoSettings,
|
||||||
|
@ -2,9 +2,9 @@
|
|||||||
<Panel>
|
<Panel>
|
||||||
<div class="flex flex-row border-b mb-4 pb-4 items-center dark:border-gray-600">
|
<div class="flex flex-row border-b mb-4 pb-4 items-center dark:border-gray-600">
|
||||||
<div class="ml-2">
|
<div class="ml-2">
|
||||||
<h1 class="text-xl text-gray-500">Registry credentials</h1>
|
<h1 class="text-xl text-gray-500">{{ $t('repo.settings.registries.creds') }}</h1>
|
||||||
<p class="text-sm text-gray-400 dark:text-gray-600">
|
<p class="text-sm text-gray-400 dark:text-gray-600">
|
||||||
Registries credentials can be added to use private images for your pipeline.
|
{{ $t('repo.settings.registries.desc') }}
|
||||||
<DocsLink url="docs/usage/registries" />
|
<DocsLink url="docs/usage/registries" />
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@ -12,10 +12,16 @@
|
|||||||
v-if="selectedRegistry"
|
v-if="selectedRegistry"
|
||||||
class="ml-auto"
|
class="ml-auto"
|
||||||
start-icon="back"
|
start-icon="back"
|
||||||
text="Show registries"
|
:text="$t('repo.settings.registries.show')"
|
||||||
@click="selectedRegistry = undefined"
|
@click="selectedRegistry = undefined"
|
||||||
/>
|
/>
|
||||||
<Button v-else class="ml-auto" start-icon="plus" text="Add registry" @click="selectedRegistry = {}" />
|
<Button
|
||||||
|
v-else
|
||||||
|
class="ml-auto"
|
||||||
|
start-icon="plus"
|
||||||
|
:text="$t('repo.settings.registries.add')"
|
||||||
|
@click="selectedRegistry = {}"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="!selectedRegistry" class="space-y-4 text-gray-500">
|
<div v-if="!selectedRegistry" class="space-y-4 text-gray-500">
|
||||||
@ -30,30 +36,34 @@
|
|||||||
/>
|
/>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
|
|
||||||
<div v-if="registries?.length === 0" class="ml-2">There are no registry credentials yet.</div>
|
<div v-if="registries?.length === 0" class="ml-2">{{ $t('repo.settings.registries.none') }}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-else class="space-y-4">
|
<div v-else class="space-y-4">
|
||||||
<form @submit.prevent="createRegistry">
|
<form @submit.prevent="createRegistry">
|
||||||
<InputField label="Address">
|
<InputField :label="$t('repo.settings.registries.address.address')">
|
||||||
<!-- TODO: check input field Address is a valid address -->
|
<!-- TODO: check input field Address is a valid address -->
|
||||||
<TextField
|
<TextField
|
||||||
v-model="selectedRegistry.address"
|
v-model="selectedRegistry.address"
|
||||||
placeholder="Registry Address (e.g. docker.io)"
|
:placeholder="$t('repo.settings.registries.address.placeholder')"
|
||||||
required
|
required
|
||||||
:disabled="isEditingRegistry"
|
:disabled="isEditingRegistry"
|
||||||
/>
|
/>
|
||||||
</InputField>
|
</InputField>
|
||||||
|
|
||||||
<InputField label="Username">
|
<InputField :label="$t('username')">
|
||||||
<TextField v-model="selectedRegistry.username" placeholder="Username" required />
|
<TextField v-model="selectedRegistry.username" :placeholder="$t('username')" required />
|
||||||
</InputField>
|
</InputField>
|
||||||
|
|
||||||
<InputField label="Password">
|
<InputField :label="$t('password')">
|
||||||
<TextField v-model="selectedRegistry.password" placeholder="Password" required />
|
<TextField v-model="selectedRegistry.password" :placeholder="$t('password')" required />
|
||||||
</InputField>
|
</InputField>
|
||||||
|
|
||||||
<Button type="submit" :is-loading="isSaving" :text="isEditingRegistry ? 'Save registy' : 'Add registry'" />
|
<Button
|
||||||
|
type="submit"
|
||||||
|
:is-loading="isSaving"
|
||||||
|
:text="isEditingRegistry ? $t('repo.settings.registries.save') : $t('repo.settings.registries.add')"
|
||||||
|
/>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</Panel>
|
</Panel>
|
||||||
@ -61,6 +71,7 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { computed, defineComponent, inject, onMounted, Ref, ref } from 'vue';
|
import { computed, defineComponent, inject, onMounted, Ref, ref } from 'vue';
|
||||||
|
import { useI18n } from 'vue-i18n';
|
||||||
|
|
||||||
import Button from '~/components/atomic/Button.vue';
|
import Button from '~/components/atomic/Button.vue';
|
||||||
import DocsLink from '~/components/atomic/DocsLink.vue';
|
import DocsLink from '~/components/atomic/DocsLink.vue';
|
||||||
@ -91,6 +102,7 @@ export default defineComponent({
|
|||||||
setup() {
|
setup() {
|
||||||
const apiClient = useApiClient();
|
const apiClient = useApiClient();
|
||||||
const notifications = useNotifications();
|
const notifications = useNotifications();
|
||||||
|
const i18n = useI18n();
|
||||||
|
|
||||||
const repo = inject<Ref<Repo>>('repo');
|
const repo = inject<Ref<Repo>>('repo');
|
||||||
const registries = ref<Registry[]>();
|
const registries = ref<Registry[]>();
|
||||||
@ -119,7 +131,12 @@ export default defineComponent({
|
|||||||
} else {
|
} else {
|
||||||
await apiClient.createRegistry(repo.value.owner, repo.value.name, selectedRegistry.value);
|
await apiClient.createRegistry(repo.value.owner, repo.value.name, selectedRegistry.value);
|
||||||
}
|
}
|
||||||
notifications.notify({ title: 'Registry credentials created', type: 'success' });
|
notifications.notify({
|
||||||
|
title: i18n.t(
|
||||||
|
isEditingRegistry.value ? 'repo.settings.registries.saved' : i18n.t('repo.settings.registries.created'),
|
||||||
|
),
|
||||||
|
type: 'success',
|
||||||
|
});
|
||||||
selectedRegistry.value = undefined;
|
selectedRegistry.value = undefined;
|
||||||
await loadRegistries();
|
await loadRegistries();
|
||||||
});
|
});
|
||||||
@ -131,7 +148,7 @@ export default defineComponent({
|
|||||||
|
|
||||||
const registryAddress = encodeURIComponent(_registry.address);
|
const registryAddress = encodeURIComponent(_registry.address);
|
||||||
await apiClient.deleteRegistry(repo.value.owner, repo.value.name, registryAddress);
|
await apiClient.deleteRegistry(repo.value.owner, repo.value.name, registryAddress);
|
||||||
notifications.notify({ title: 'Registry credentials deleted', type: 'success' });
|
notifications.notify({ title: i18n.t('repo.settings.registries.deleted'), type: 'success' });
|
||||||
await loadRegistries();
|
await loadRegistries();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -2,20 +2,20 @@
|
|||||||
<Panel>
|
<Panel>
|
||||||
<div class="flex flex-row border-b mb-4 pb-4 items-center dark:border-gray-600">
|
<div class="flex flex-row border-b mb-4 pb-4 items-center dark:border-gray-600">
|
||||||
<div class="ml-2">
|
<div class="ml-2">
|
||||||
<h1 class="text-xl text-gray-500">Secrets</h1>
|
<h1 class="text-xl text-gray-500">{{ $t('repo.settings.secrets.secrets') }}</h1>
|
||||||
<p class="text-sm text-gray-400 dark:text-gray-600">
|
<p class="text-sm text-gray-400 dark:text-gray-600">
|
||||||
Secrets can be passed to individual pipeline steps at runtime as environmental variables.
|
{{ $t('repo.settings.secrets.desc') }}
|
||||||
<DocsLink url="docs/usage/secrets" />
|
<DocsLink url="docs/usage/secrets" />
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<Button
|
<Button
|
||||||
v-if="selectedSecret"
|
v-if="selectedSecret"
|
||||||
class="ml-auto"
|
class="ml-auto"
|
||||||
text="Show secrets"
|
:text="$t('repo.settings.secrets.show')"
|
||||||
start-icon="back"
|
start-icon="back"
|
||||||
@click="selectedSecret = undefined"
|
@click="selectedSecret = undefined"
|
||||||
/>
|
/>
|
||||||
<Button v-else class="ml-auto" text="Add secret" start-icon="plus" @click="showAddSecret" />
|
<Button v-else class="ml-auto" :text="$t('repo.settings.secrets.add')" start-icon="plus" @click="showAddSecret" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="!selectedSecret" class="space-y-4 text-gray-500">
|
<div v-if="!selectedSecret" class="space-y-4 text-gray-500">
|
||||||
@ -38,31 +38,42 @@
|
|||||||
/>
|
/>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
|
|
||||||
<div v-if="secrets?.length === 0" class="ml-2">There are no secrets yet.</div>
|
<div v-if="secrets?.length === 0" class="ml-2">{{ $t('repo.settings.secrets.none') }}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-else class="space-y-4">
|
<div v-else class="space-y-4">
|
||||||
<form @submit.prevent="createSecret">
|
<form @submit.prevent="createSecret">
|
||||||
<InputField label="Name">
|
<InputField :label="$t('repo.settings.secrets.name')">
|
||||||
<TextField v-model="selectedSecret.name" placeholder="Name" required :disabled="isEditingSecret" />
|
|
||||||
</InputField>
|
|
||||||
|
|
||||||
<InputField label="Value">
|
|
||||||
<TextField v-model="selectedSecret.value" placeholder="Value" :lines="5" required />
|
|
||||||
</InputField>
|
|
||||||
|
|
||||||
<InputField label="Available for following images">
|
|
||||||
<TextField
|
<TextField
|
||||||
v-model="images"
|
v-model="selectedSecret.name"
|
||||||
placeholder="Comma separated list of images where this secret is available, leave empty to allow all images"
|
:placeholder="$t('repo.settings.secrets.name')"
|
||||||
|
required
|
||||||
|
:disabled="isEditingSecret"
|
||||||
/>
|
/>
|
||||||
</InputField>
|
</InputField>
|
||||||
|
|
||||||
<InputField label="Available at following events">
|
<InputField :label="$t('repo.settings.secrets.value')">
|
||||||
|
<TextField
|
||||||
|
v-model="selectedSecret.value"
|
||||||
|
:placeholder="$t('repo.settings.secrets.value')"
|
||||||
|
:lines="5"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</InputField>
|
||||||
|
|
||||||
|
<InputField :label="$t('repo.settings.secrets.images.images')">
|
||||||
|
<TextField v-model="images" :placeholder="$t('repo.settings.secrets.images.desc')" />
|
||||||
|
</InputField>
|
||||||
|
|
||||||
|
<InputField :label="$t('repo.settings.secrets.events.events')">
|
||||||
<CheckboxesField v-model="selectedSecret.event" :options="secretEventsOptions" />
|
<CheckboxesField v-model="selectedSecret.event" :options="secretEventsOptions" />
|
||||||
</InputField>
|
</InputField>
|
||||||
|
|
||||||
<Button :is-loading="isSaving" type="submit" :text="isEditingSecret ? 'Save secret' : 'Add secret'" />
|
<Button
|
||||||
|
:is-loading="isSaving"
|
||||||
|
type="submit"
|
||||||
|
:text="isEditingSecret ? $t('repo.settings.secrets.save') : $t('repo.settings.secrets.add')"
|
||||||
|
/>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</Panel>
|
</Panel>
|
||||||
@ -71,6 +82,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { cloneDeep } from 'lodash';
|
import { cloneDeep } from 'lodash';
|
||||||
import { computed, defineComponent, inject, onMounted, Ref, ref } from 'vue';
|
import { computed, defineComponent, inject, onMounted, Ref, ref } from 'vue';
|
||||||
|
import { useI18n } from 'vue-i18n';
|
||||||
|
|
||||||
import Button from '~/components/atomic/Button.vue';
|
import Button from '~/components/atomic/Button.vue';
|
||||||
import DocsLink from '~/components/atomic/DocsLink.vue';
|
import DocsLink from '~/components/atomic/DocsLink.vue';
|
||||||
@ -93,18 +105,6 @@ const emptySecret = {
|
|||||||
event: [WebhookEvents.Push],
|
event: [WebhookEvents.Push],
|
||||||
};
|
};
|
||||||
|
|
||||||
const secretEventsOptions: CheckboxOption[] = [
|
|
||||||
{ value: WebhookEvents.Push, text: 'Push' },
|
|
||||||
{ value: WebhookEvents.Tag, text: 'Tag' },
|
|
||||||
{
|
|
||||||
value: WebhookEvents.PullRequest,
|
|
||||||
text: 'Pull Request',
|
|
||||||
description:
|
|
||||||
'Please be careful with this option as a bad actor can submit a malicious pull request that exposes your secrets.',
|
|
||||||
},
|
|
||||||
{ value: WebhookEvents.Deploy, text: 'Deploy' },
|
|
||||||
];
|
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'SecretsTab',
|
name: 'SecretsTab',
|
||||||
|
|
||||||
@ -122,6 +122,7 @@ export default defineComponent({
|
|||||||
setup() {
|
setup() {
|
||||||
const apiClient = useApiClient();
|
const apiClient = useApiClient();
|
||||||
const notifications = useNotifications();
|
const notifications = useNotifications();
|
||||||
|
const i18n = useI18n();
|
||||||
|
|
||||||
const repo = inject<Ref<Repo>>('repo');
|
const repo = inject<Ref<Repo>>('repo');
|
||||||
const secrets = ref<Secret[]>();
|
const secrets = ref<Secret[]>();
|
||||||
@ -163,7 +164,10 @@ export default defineComponent({
|
|||||||
} else {
|
} else {
|
||||||
await apiClient.createSecret(repo.value.owner, repo.value.name, selectedSecret.value);
|
await apiClient.createSecret(repo.value.owner, repo.value.name, selectedSecret.value);
|
||||||
}
|
}
|
||||||
notifications.notify({ title: 'Secret created', type: 'success' });
|
notifications.notify({
|
||||||
|
title: i18n.t(isEditingSecret.value ? 'repo.settings.secrets.saved' : 'repo.settings.secrets.created'),
|
||||||
|
type: 'success',
|
||||||
|
});
|
||||||
selectedSecret.value = undefined;
|
selectedSecret.value = undefined;
|
||||||
await loadSecrets();
|
await loadSecrets();
|
||||||
});
|
});
|
||||||
@ -174,7 +178,7 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
|
|
||||||
await apiClient.deleteSecret(repo.value.owner, repo.value.name, _secret.name);
|
await apiClient.deleteSecret(repo.value.owner, repo.value.name, _secret.name);
|
||||||
notifications.notify({ title: 'Secret deleted', type: 'success' });
|
notifications.notify({ title: i18n.t('repo.settings.secrets.deleted'), type: 'success' });
|
||||||
await loadSecrets();
|
await loadSecrets();
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -186,6 +190,17 @@ export default defineComponent({
|
|||||||
await loadSecrets();
|
await loadSecrets();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const secretEventsOptions: CheckboxOption[] = [
|
||||||
|
{ value: WebhookEvents.Push, text: i18n.t('repo.build.event.push') },
|
||||||
|
{ value: WebhookEvents.Tag, text: i18n.t('repo.build.event.tag') },
|
||||||
|
{
|
||||||
|
value: WebhookEvents.PullRequest,
|
||||||
|
text: i18n.t('repo.build.event.pr'),
|
||||||
|
description: i18n.t('repo.settings.secrets.events.pr_warning'),
|
||||||
|
},
|
||||||
|
{ value: WebhookEvents.Deploy, text: i18n.t('repo.build.event.deploy') },
|
||||||
|
];
|
||||||
|
|
||||||
return {
|
return {
|
||||||
secretEventsOptions,
|
secretEventsOptions,
|
||||||
selectedSecret,
|
selectedSecret,
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { computed, Ref } from 'vue';
|
import { computed, Ref } from 'vue';
|
||||||
|
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';
|
||||||
@ -25,9 +26,10 @@ export default (build: Ref<Build | undefined>) => {
|
|||||||
);
|
);
|
||||||
const { time: sinceElapsed } = useElapsedTime(sinceUnderOneHour, sinceRaw);
|
const { time: sinceElapsed } = useElapsedTime(sinceUnderOneHour, sinceRaw);
|
||||||
|
|
||||||
|
const i18n = useI18n();
|
||||||
const since = computed(() => {
|
const since = computed(() => {
|
||||||
if (sinceRaw.value === 0) {
|
if (sinceRaw.value === 0) {
|
||||||
return 'not started yet';
|
return i18n.t('time.not_started');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sinceElapsed.value === undefined) {
|
if (sinceElapsed.value === undefined) {
|
||||||
@ -66,7 +68,7 @@ export default (build: Ref<Build | undefined>) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (durationRaw.value === 0) {
|
if (durationRaw.value === 0) {
|
||||||
return 'not started yet';
|
return i18n.t('time.not_started');
|
||||||
}
|
}
|
||||||
|
|
||||||
return prettyDuration(durationElapsed.value);
|
return prettyDuration(durationElapsed.value);
|
||||||
|
@ -1,15 +1,19 @@
|
|||||||
|
import 'dayjs/locale/en';
|
||||||
|
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import advancedFormat from 'dayjs/plugin/advancedFormat';
|
import advancedFormat from 'dayjs/plugin/advancedFormat';
|
||||||
import timezone from 'dayjs/plugin/timezone';
|
import timezone from 'dayjs/plugin/timezone';
|
||||||
import utc from 'dayjs/plugin/utc';
|
import utc from 'dayjs/plugin/utc';
|
||||||
|
import { useI18n } from 'vue-i18n';
|
||||||
|
|
||||||
dayjs.extend(timezone);
|
dayjs.extend(timezone);
|
||||||
dayjs.extend(utc);
|
dayjs.extend(utc);
|
||||||
dayjs.extend(advancedFormat);
|
dayjs.extend(advancedFormat);
|
||||||
|
dayjs.locale(navigator.language.split('-')[0]);
|
||||||
|
|
||||||
export function useDate() {
|
export function useDate() {
|
||||||
function toLocaleString(date: Date) {
|
function toLocaleString(date: Date) {
|
||||||
return dayjs(date).format('MMM D, YYYY, HH:mm z');
|
return dayjs(date).format(useI18n().t('time.tmpl'));
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
12
web/src/compositions/useI18n.ts
Normal file
12
web/src/compositions/useI18n.ts
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore
|
||||||
|
import messages from '@intlify/vite-plugin-vue-i18n/messages';
|
||||||
|
import { createI18n } from 'vue-i18n';
|
||||||
|
|
||||||
|
export const i18n = createI18n({
|
||||||
|
locale: navigator.language.split('-')[0],
|
||||||
|
legacy: false,
|
||||||
|
globalInjection: true,
|
||||||
|
fallbackLocale: 'en',
|
||||||
|
messages,
|
||||||
|
});
|
@ -7,6 +7,7 @@ import { createApp } from 'vue';
|
|||||||
|
|
||||||
import App from '~/App.vue';
|
import App from '~/App.vue';
|
||||||
import useEvents from '~/compositions/useEvents';
|
import useEvents from '~/compositions/useEvents';
|
||||||
|
import { i18n } from '~/compositions/useI18n';
|
||||||
import { notifications } from '~/compositions/useNotifications';
|
import { notifications } from '~/compositions/useNotifications';
|
||||||
import router from '~/router';
|
import router from '~/router';
|
||||||
|
|
||||||
@ -14,6 +15,7 @@ const app = createApp(App);
|
|||||||
|
|
||||||
app.use(router);
|
app.use(router);
|
||||||
app.use(notifications);
|
app.use(notifications);
|
||||||
|
app.use(i18n);
|
||||||
|
|
||||||
app.use(createPinia());
|
app.use(createPinia());
|
||||||
app.mount('#app');
|
app.mount('#app');
|
||||||
|
@ -1,19 +1,21 @@
|
|||||||
import humanizeDuration from 'humanize-duration';
|
import humanizeDuration from 'humanize-duration';
|
||||||
|
import { useI18n } from 'vue-i18n';
|
||||||
const enShort = {
|
|
||||||
w: () => 'w',
|
|
||||||
d: () => 'd',
|
|
||||||
h: () => 'h',
|
|
||||||
m: () => 'min',
|
|
||||||
s: () => 'sec',
|
|
||||||
};
|
|
||||||
const durationOptions: humanizeDuration.HumanizerOptions = {
|
|
||||||
round: true,
|
|
||||||
languages: { en_short: enShort },
|
|
||||||
language: 'en_short',
|
|
||||||
};
|
|
||||||
|
|
||||||
export function prettyDuration(durationMs: number): string {
|
export function prettyDuration(durationMs: number): string {
|
||||||
|
const i18n = useI18n();
|
||||||
|
const short = {
|
||||||
|
w: () => i18n.t('time.weeks_short'),
|
||||||
|
d: () => i18n.t('time.days_short'),
|
||||||
|
h: () => i18n.t('time.hours_short'),
|
||||||
|
m: () => i18n.t('time.min_short'),
|
||||||
|
s: () => i18n.t('time.sec_short'),
|
||||||
|
};
|
||||||
|
const durationOptions: humanizeDuration.HumanizerOptions = {
|
||||||
|
round: true,
|
||||||
|
languages: { short },
|
||||||
|
language: 'short',
|
||||||
|
};
|
||||||
|
|
||||||
if (durationMs < 1000 * 60 * 60) {
|
if (durationMs < 1000 * 60 * 60) {
|
||||||
return humanizeDuration(durationMs, durationOptions);
|
return humanizeDuration(durationMs, durationOptions);
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,6 @@ import en from 'javascript-time-ago/locale/en.json';
|
|||||||
|
|
||||||
TimeAgo.addDefaultLocale(en);
|
TimeAgo.addDefaultLocale(en);
|
||||||
|
|
||||||
const timeAgo = new TimeAgo('en-US');
|
const timeAgo = new TimeAgo(navigator.language);
|
||||||
|
|
||||||
export default timeAgo;
|
export default timeAgo;
|
||||||
|
@ -19,8 +19,8 @@
|
|||||||
<img class="w-48 h-48" src="../assets/logo.svg?url" />
|
<img class="w-48 h-48" src="../assets/logo.svg?url" />
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col my-8 md:w-2/5 p-4 items-center justify-center">
|
<div class="flex flex-col my-8 md:w-2/5 p-4 items-center justify-center">
|
||||||
<h1 class="text-xl text-gray-600 dark:text-gray-500">Welcome to Woodpecker</h1>
|
<h1 class="text-xl text-gray-600 dark:text-gray-500">{{ $t('welcome') }}</h1>
|
||||||
<Button class="mt-4" @click="doLogin">Login</Button>
|
<Button class="mt-4" @click="doLogin">{{ $t('login') }}</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -28,17 +28,12 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent, onMounted, ref } from 'vue';
|
import { defineComponent, onMounted, ref } from 'vue';
|
||||||
|
import { useI18n } from 'vue-i18n';
|
||||||
import { useRoute, useRouter } from 'vue-router';
|
import { useRoute, useRouter } from 'vue-router';
|
||||||
|
|
||||||
import Button from '~/components/atomic/Button.vue';
|
import Button from '~/components/atomic/Button.vue';
|
||||||
import useAuthentication from '~/compositions/useAuthentication';
|
import useAuthentication from '~/compositions/useAuthentication';
|
||||||
|
|
||||||
const authErrorMessages = {
|
|
||||||
oauth_error: 'Error while authenticating against OAuth provider',
|
|
||||||
internal_error: 'Some internal error occured',
|
|
||||||
access_denied: 'You are not allowed to login',
|
|
||||||
};
|
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'Login',
|
name: 'Login',
|
||||||
|
|
||||||
@ -51,12 +46,19 @@ export default defineComponent({
|
|||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const authentication = useAuthentication();
|
const authentication = useAuthentication();
|
||||||
const errorMessage = ref<string>();
|
const errorMessage = ref<string>();
|
||||||
|
const i18n = useI18n();
|
||||||
|
|
||||||
function doLogin() {
|
function doLogin() {
|
||||||
const url = typeof route.query.url === 'string' ? route.query.url : '';
|
const url = typeof route.query.url === 'string' ? route.query.url : '';
|
||||||
authentication.authenticate(url);
|
authentication.authenticate(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const authErrorMessages = {
|
||||||
|
oauth_error: i18n.t('user.oauth_error'),
|
||||||
|
internal_error: i18n.t('user.internal_error'),
|
||||||
|
access_denied: i18n.t('user.access_denied'),
|
||||||
|
};
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
if (authentication.isAuthenticated) {
|
if (authentication.isAuthenticated) {
|
||||||
await router.replace({ name: 'home' });
|
await router.replace({ name: 'home' });
|
||||||
|
@ -1,7 +1,11 @@
|
|||||||
<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">Whoa 404, either we broke something or you had a typing mishap :-/</p>
|
<p class="text-2xl mb-8">{{ $t('not_found.not_found') }}</p>
|
||||||
<span>Back to <router-link class="text-blue-400" replace :to="{ name: 'home' }">home</router-link></span>
|
<span
|
||||||
|
><router-link class="text-blue-400" replace :to="{ name: 'home' }">{{
|
||||||
|
$t('not_found.back_home')
|
||||||
|
}}</router-link></span
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -2,12 +2,12 @@
|
|||||||
<FluidContainer class="flex flex-col">
|
<FluidContainer class="flex flex-col">
|
||||||
<div class="flex flex-row border-b mb-4 pb-4 items-center dark:border-dark-200">
|
<div class="flex flex-row border-b mb-4 pb-4 items-center dark:border-dark-200">
|
||||||
<IconButton :to="{ name: 'repos' }" icon="back" />
|
<IconButton :to="{ name: 'repos' }" icon="back" />
|
||||||
<h1 class="text-xl ml-2 text-gray-500">Add repository</h1>
|
<h1 class="text-xl ml-2 text-gray-500">{{ $t('repo.add') }}</h1>
|
||||||
<TextField v-model="search" class="w-auto ml-auto" placeholder="Search ..." />
|
<TextField v-model="search" class="w-auto ml-auto" :placeholder="$t('search')" />
|
||||||
<Button
|
<Button
|
||||||
class="ml-auto"
|
class="ml-auto"
|
||||||
start-icon="sync"
|
start-icon="sync"
|
||||||
text="Reload repositories"
|
:text="$t('repo.enable.reload')"
|
||||||
:is-loading="isReloadingRepos"
|
:is-loading="isReloadingRepos"
|
||||||
@click="reloadRepos"
|
@click="reloadRepos"
|
||||||
/>
|
/>
|
||||||
@ -22,11 +22,11 @@
|
|||||||
@click="repo.active && $router.push({ name: 'repo', params: { repoOwner: repo.owner, repoName: repo.name } })"
|
@click="repo.active && $router.push({ name: 'repo', params: { repoOwner: repo.owner, repoName: repo.name } })"
|
||||||
>
|
>
|
||||||
<span class="text-gray-500">{{ repo.full_name }}</span>
|
<span class="text-gray-500">{{ repo.full_name }}</span>
|
||||||
<span v-if="repo.active" class="ml-auto text-gray-500">Already enabled</span>
|
<span v-if="repo.active" class="ml-auto text-gray-500">{{ $t('repo.enable.enabled') }}</span>
|
||||||
<Button
|
<Button
|
||||||
v-if="!repo.active"
|
v-if="!repo.active"
|
||||||
class="ml-auto"
|
class="ml-auto"
|
||||||
text="Enable"
|
:text="$t('repo.enable.enable')"
|
||||||
:is-loading="isActivatingRepo && repoToActivate?.id === repo.id"
|
:is-loading="isActivatingRepo && repoToActivate?.id === repo.id"
|
||||||
@click="activateRepo(repo)"
|
@click="activateRepo(repo)"
|
||||||
/>
|
/>
|
||||||
@ -37,6 +37,7 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent, onMounted, ref } from 'vue';
|
import { defineComponent, onMounted, ref } from 'vue';
|
||||||
|
import { useI18n } from 'vue-i18n';
|
||||||
import { useRouter } from 'vue-router';
|
import { useRouter } from 'vue-router';
|
||||||
|
|
||||||
import Button from '~/components/atomic/Button.vue';
|
import Button from '~/components/atomic/Button.vue';
|
||||||
@ -68,6 +69,7 @@ export default defineComponent({
|
|||||||
const repos = ref<Repo[]>();
|
const repos = ref<Repo[]>();
|
||||||
const repoToActivate = ref<Repo>();
|
const repoToActivate = ref<Repo>();
|
||||||
const search = ref('');
|
const search = ref('');
|
||||||
|
const i18n = useI18n();
|
||||||
|
|
||||||
const { searchedRepos } = useRepoSearch(repos, search);
|
const { searchedRepos } = useRepoSearch(repos, search);
|
||||||
|
|
||||||
@ -78,13 +80,13 @@ export default defineComponent({
|
|||||||
const { doSubmit: reloadRepos, isLoading: isReloadingRepos } = useAsyncAction(async () => {
|
const { doSubmit: reloadRepos, isLoading: isReloadingRepos } = useAsyncAction(async () => {
|
||||||
repos.value = undefined;
|
repos.value = undefined;
|
||||||
repos.value = await apiClient.getRepoList({ all: true, flush: true });
|
repos.value = await apiClient.getRepoList({ all: true, flush: true });
|
||||||
notifications.notify({ title: 'Repository list reloaded', type: 'success' });
|
notifications.notify({ title: i18n.t('repo.enable.list_reloaded'), type: 'success' });
|
||||||
});
|
});
|
||||||
|
|
||||||
const { doSubmit: activateRepo, isLoading: isActivatingRepo } = useAsyncAction(async (repo: Repo) => {
|
const { doSubmit: activateRepo, isLoading: isActivatingRepo } = useAsyncAction(async (repo: Repo) => {
|
||||||
repoToActivate.value = repo;
|
repoToActivate.value = repo;
|
||||||
await apiClient.activateRepo(repo.owner, repo.name);
|
await apiClient.activateRepo(repo.owner, repo.name);
|
||||||
notifications.notify({ title: 'Repository enabled', type: 'success' });
|
notifications.notify({ title: i18n.t('repo.enabled.success'), type: 'success' });
|
||||||
repoToActivate.value = undefined;
|
repoToActivate.value = undefined;
|
||||||
await router.push({ name: 'repo', params: { repoName: repo.name, repoOwner: repo.owner } });
|
await router.push({ name: 'repo', params: { repoName: repo.name, repoOwner: repo.owner } });
|
||||||
});
|
});
|
||||||
|
@ -2,8 +2,8 @@
|
|||||||
<FluidContainer class="flex flex-col">
|
<FluidContainer class="flex flex-col">
|
||||||
<div class="flex flex-row flex-wrap md:grid md:grid-cols-3 border-b pb-4 mb-4 dark:border-dark-200">
|
<div class="flex flex-row flex-wrap md:grid md:grid-cols-3 border-b pb-4 mb-4 dark:border-dark-200">
|
||||||
<h1 class="text-xl text-gray-500">Repositories</h1>
|
<h1 class="text-xl text-gray-500">Repositories</h1>
|
||||||
<TextField v-model="search" class="w-auto md:ml-auto md:mr-auto" placeholder="Search ..." />
|
<TextField v-model="search" class="w-auto md:ml-auto md:mr-auto" :placeholder="$t('search')" />
|
||||||
<Button class="md:ml-auto" :to="{ name: 'repo-add' }" start-icon="plus" text="Add repository" />
|
<Button class="md:ml-auto" :to="{ name: 'repo-add' }" start-icon="plus" :text="$t('repo.add')" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="space-y-4">
|
<div class="space-y-4">
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
<FluidContainer class="flex flex-col">
|
<FluidContainer class="flex flex-col">
|
||||||
<div class="flex flex-row flex-wrap md:grid md:grid-cols-3 border-b pb-4 mb-4 dark:border-dark-200">
|
<div class="flex flex-row flex-wrap md:grid md:grid-cols-3 border-b pb-4 mb-4 dark:border-dark-200">
|
||||||
<h1 class="text-xl text-gray-500">{{ repoOwner }}</h1>
|
<h1 class="text-xl text-gray-500">{{ repoOwner }}</h1>
|
||||||
<TextField v-model="search" class="w-auto md:ml-auto md:mr-auto" placeholder="Search ..." />
|
<TextField v-model="search" class="w-auto md:ml-auto md:mr-auto" :placeholder="$t('search')" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="space-y-4">
|
<div class="space-y-4">
|
||||||
@ -16,7 +16,7 @@
|
|||||||
</ListItem>
|
</ListItem>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="(searchedRepos || []).length <= 0" class="text-center">
|
<div v-if="(searchedRepos || []).length <= 0" class="text-center">
|
||||||
<span class="text-gray-500 m-auto">This organization / user does not have any projects yet.</span>
|
<span class="text-gray-500 m-auto">{{ $t('repo.user_none') }}</span>
|
||||||
</div>
|
</div>
|
||||||
</FluidContainer>
|
</FluidContainer>
|
||||||
</template>
|
</template>
|
||||||
|
@ -1,26 +1,26 @@
|
|||||||
<template>
|
<template>
|
||||||
<FluidContainer class="space-y-4 flex flex-col my-0">
|
<FluidContainer class="space-y-4 flex flex-col my-0">
|
||||||
<Button class="ml-auto" text="Logout" :to="`${address}/logout`" />
|
<Button class="ml-auto" :text="$t('logout')" :to="`${address}/logout`" />
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<h2 class="text-lg text-gray-500">Your Personal Token</h2>
|
<h2 class="text-lg text-gray-500">{{ $t('user.token') }}</h2>
|
||||||
<pre class="cli-box">{{ token }}</pre>
|
<pre class="cli-box">{{ token }}</pre>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<h2 class="text-lg text-gray-500">Shell setup</h2>
|
<h2 class="text-lg text-gray-500">{{ $t('user.shell_setup') }}</h2>
|
||||||
<pre class="cli-box">{{ usageWithShell }}</pre>
|
<pre class="cli-box">{{ usageWithShell }}</pre>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<h2 class="text-lg text-gray-500">Example API Usage</h2>
|
<h2 class="text-lg text-gray-500">{{ $t('user.api_usage') }}</h2>
|
||||||
<pre class="cli-box">{{ usageWithCurl }}</pre>
|
<pre class="cli-box">{{ usageWithCurl }}</pre>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<h2 class="text-lg text-gray-500">Example CLI Usage</h2>
|
<h2 class="text-lg text-gray-500">{{ $t('user.cli_usage') }}</h2>
|
||||||
<a :href="cliDownload" target="_blank" class="ml-4 text-link">Download CLI</a>
|
<a :href="cliDownload" target="_blank" class="ml-4 text-link">{{ $t('user.dl_cli') }}</a>
|
||||||
</div>
|
</div>
|
||||||
<pre class="cli-box">{{ usageWithCli }}</pre>
|
<pre class="cli-box">{{ usageWithCli }}</pre>
|
||||||
</div>
|
</div>
|
||||||
@ -29,6 +29,7 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { computed, defineComponent, onMounted, ref } from 'vue';
|
import { computed, defineComponent, onMounted, ref } from 'vue';
|
||||||
|
import { useI18n } from 'vue-i18n';
|
||||||
|
|
||||||
import Button from '~/components/atomic/Button.vue';
|
import Button from '~/components/atomic/Button.vue';
|
||||||
import FluidContainer from '~/components/layout/FluidContainer.vue';
|
import FluidContainer from '~/components/layout/FluidContainer.vue';
|
||||||
@ -61,9 +62,11 @@ export default defineComponent({
|
|||||||
|
|
||||||
const usageWithCurl =
|
const usageWithCurl =
|
||||||
// eslint-disable-next-line no-template-curly-in-string
|
// eslint-disable-next-line no-template-curly-in-string
|
||||||
'# do shell setup steps before\ncurl -i ${WOODPECKER_SERVER}/api/user -H "Authorization: Bearer ${WOODPECKER_TOKEN}"';
|
`# ${useI18n().t(
|
||||||
|
'user.shell_setup_before',
|
||||||
|
)}\ncurl -i \${WOODPECKER_SERVER}/api/user -H "Authorization: Bearer \${WOODPECKER_TOKEN}"`;
|
||||||
|
|
||||||
const usageWithCli = '# do shell setup steps before\nwoodpecker info';
|
const usageWithCli = `# ${useI18n().t('user.shell_setup_before')}\nwoodpecker info`;
|
||||||
|
|
||||||
const cliDownload = 'https://github.com/woodpecker-ci/woodpecker/releases';
|
const cliDownload = 'https://github.com/woodpecker-ci/woodpecker/releases';
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="flex w-full mb-4 justify-center">
|
<div class="flex w-full mb-4 justify-center">
|
||||||
<span class="text-gray-600 dark:text-gray-500 text-xl">Pipelines for branch "{{ branch }}"</span>
|
<span class="text-gray-600 dark:text-gray-500 text-xl">{{ $t('repo.build.pipelines_for', [branch]) }}</span>
|
||||||
</div>
|
</div>
|
||||||
<BuildList :builds="builds" :repo="repo" />
|
<BuildList :builds="builds" :repo="repo" />
|
||||||
</template>
|
</template>
|
||||||
|
@ -2,23 +2,23 @@
|
|||||||
<FluidContainer>
|
<FluidContainer>
|
||||||
<div class="flex border-b items-center pb-4 mb-4 dark:border-gray-600">
|
<div class="flex border-b items-center pb-4 mb-4 dark:border-gray-600">
|
||||||
<IconButton icon="back" @click="goBack" />
|
<IconButton icon="back" @click="goBack" />
|
||||||
<h1 class="text-xl ml-2 text-gray-500">Settings</h1>
|
<h1 class="text-xl ml-2 text-gray-500">{{ $t('repo.settings.settings') }}</h1>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Tabs>
|
<Tabs>
|
||||||
<Tab title="General">
|
<Tab :title="$t('repo.settings.general.general')">
|
||||||
<GeneralTab />
|
<GeneralTab />
|
||||||
</Tab>
|
</Tab>
|
||||||
<Tab title="Secrets">
|
<Tab :title="$t('repo.settings.secrets.secrets')">
|
||||||
<SecretsTab />
|
<SecretsTab />
|
||||||
</Tab>
|
</Tab>
|
||||||
<Tab title="Registries">
|
<Tab :title="$t('repo.settings.registries.registries')">
|
||||||
<RegistriesTab />
|
<RegistriesTab />
|
||||||
</Tab>
|
</Tab>
|
||||||
<Tab title="Badge">
|
<Tab :title="$t('repo.settings.badge.badge')">
|
||||||
<BadgeTab />
|
<BadgeTab />
|
||||||
</Tab>
|
</Tab>
|
||||||
<Tab title="Actions">
|
<Tab :title="$t('repo.settings.actions.actions')">
|
||||||
<ActionsTab />
|
<ActionsTab />
|
||||||
</Tab>
|
</Tab>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
|
@ -20,8 +20,8 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Tabs v-model="activeTab" disable-hash-mode class="mb-4">
|
<Tabs v-model="activeTab" disable-hash-mode class="mb-4">
|
||||||
<Tab title="Activity" />
|
<Tab :title="$t('repo.activity')" />
|
||||||
<Tab title="Branches" />
|
<Tab :title="$t('repo.branches')" />
|
||||||
</Tabs>
|
</Tabs>
|
||||||
|
|
||||||
<router-view />
|
<router-view />
|
||||||
@ -31,6 +31,7 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { computed, defineComponent, onMounted, provide, ref, toRef, watch } from 'vue';
|
import { computed, defineComponent, onMounted, provide, ref, toRef, watch } from 'vue';
|
||||||
|
import { useI18n } from 'vue-i18n';
|
||||||
import { useRoute, useRouter } from 'vue-router';
|
import { useRoute, useRouter } from 'vue-router';
|
||||||
|
|
||||||
import Icon from '~/components/atomic/Icon.vue';
|
import Icon from '~/components/atomic/Icon.vue';
|
||||||
@ -76,6 +77,7 @@ export default defineComponent({
|
|||||||
const { isAuthenticated } = useAuthentication();
|
const { isAuthenticated } = useAuthentication();
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
const i18n = useI18n();
|
||||||
|
|
||||||
const repo = repoStore.getRepo(repoOwner, repoName);
|
const repo = repoStore.getRepo(repoOwner, repoName);
|
||||||
const repoPermissions = ref<RepoPermissions>();
|
const repoPermissions = ref<RepoPermissions>();
|
||||||
@ -87,7 +89,7 @@ export default defineComponent({
|
|||||||
async function loadRepo() {
|
async function loadRepo() {
|
||||||
repoPermissions.value = await apiClient.getRepoPermissions(repoOwner.value, repoName.value);
|
repoPermissions.value = await apiClient.getRepoPermissions(repoOwner.value, repoName.value);
|
||||||
if (!repoPermissions.value.pull) {
|
if (!repoPermissions.value.pull) {
|
||||||
notifications.notify({ type: 'error', title: 'Not allowed to access this repository' });
|
notifications.notify({ type: 'error', title: i18n.t('repo.not_allowed') });
|
||||||
// no access and not authenticated, redirect to login
|
// no access and not authenticated, redirect to login
|
||||||
if (!isAuthenticated) {
|
if (!isAuthenticated) {
|
||||||
await router.replace({ name: 'login', query: { url: route.fullPath } });
|
await router.replace({ name: 'login', query: { url: route.fullPath } });
|
||||||
|
@ -5,22 +5,32 @@
|
|||||||
|
|
||||||
<div class="flex flex-grow relative">
|
<div class="flex flex-grow relative">
|
||||||
<div v-if="build.error" class="flex flex-col p-4">
|
<div v-if="build.error" class="flex flex-col p-4">
|
||||||
<span class="text-red-400 font-bold text-xl mb-2">Execution error</span>
|
<span class="text-red-400 font-bold text-xl mb-2">{{ $t('repo.build.execution_error') }}</span>
|
||||||
<span class="text-red-400">{{ build.error }}</span>
|
<span class="text-red-400">{{ build.error }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-else-if="build.status === 'blocked'" class="flex flex-col flex-grow justify-center items-center">
|
<div v-else-if="build.status === 'blocked'" class="flex flex-col flex-grow justify-center items-center">
|
||||||
<Icon name="status-blocked" class="w-32 h-32 text-gray-500" />
|
<Icon name="status-blocked" class="w-32 h-32 text-gray-500" />
|
||||||
<p class="text-xl text-gray-500">This pipeline is awaiting approval by some maintainer!</p>
|
<p class="text-xl text-gray-500">{{ $t('repo.build.protected.awaits') }}</p>
|
||||||
<div v-if="repoPermissions.push" class="flex mt-2 space-x-4">
|
<div v-if="repoPermissions.push" class="flex mt-2 space-x-4">
|
||||||
<Button color="green" text="Approve" :is-loading="isApprovingBuild" @click="approveBuild" />
|
<Button
|
||||||
<Button color="red" text="Decline" :is-loading="isDecliningBuild" @click="declineBuild" />
|
color="green"
|
||||||
|
:text="$t('repo.build.protected.approve')"
|
||||||
|
:is-loading="isApprovingBuild"
|
||||||
|
@click="approveBuild"
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
color="red"
|
||||||
|
:text="$t('repo.build.protected.decline')"
|
||||||
|
:is-loading="isDecliningBuild"
|
||||||
|
@click="declineBuild"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-else-if="build.status === 'declined'" class="flex flex-col flex-grow justify-center items-center">
|
<div v-else-if="build.status === 'declined'" class="flex flex-col flex-grow justify-center items-center">
|
||||||
<Icon name="status-blocked" class="w-32 h-32 text-gray-500" />
|
<Icon name="status-blocked" class="w-32 h-32 text-gray-500" />
|
||||||
<p class="text-xl text-gray-500">This pipeline has been declined!</p>
|
<p class="text-xl text-gray-500">{{ $t('repo.build.protected.declined') }}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<BuildLog
|
<BuildLog
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
<FluidContainer v-if="build" class="flex flex-col gap-y-6 text-gray-500 justify-between py-0">
|
<FluidContainer v-if="build" class="flex flex-col gap-y-6 text-gray-500 justify-between py-0">
|
||||||
<Panel>
|
<Panel>
|
||||||
<div v-if="build.changed_files === undefined || build.changed_files.length < 1" class="w-full">
|
<div v-if="build.changed_files === undefined || build.changed_files.length < 1" class="w-full">
|
||||||
<span class="text-gray-500">No files have been changed.</span>
|
<span class="text-gray-500">{{ $t('repo.build.no_files') }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div v-for="file in build.changed_files" v-else :key="file" class="w-full">
|
<div v-for="file in build.changed_files" v-else :key="file" class="w-full">
|
||||||
<div>- {{ file }}</div>
|
<div>- {{ file }}</div>
|
||||||
|
@ -27,14 +27,14 @@
|
|||||||
<Button
|
<Button
|
||||||
v-if="build.status === 'pending' || build.status === 'running'"
|
v-if="build.status === 'pending' || build.status === 'running'"
|
||||||
class="ml-4 flex-shrink-0"
|
class="ml-4 flex-shrink-0"
|
||||||
text="Cancel"
|
:text="$t('repo.build.actions.cancel')"
|
||||||
:is-loading="isCancelingBuild"
|
:is-loading="isCancelingBuild"
|
||||||
@click="cancelBuild"
|
@click="cancelBuild"
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
v-else-if="build.status !== 'blocked' && build.status !== 'declined'"
|
v-else-if="build.status !== 'blocked' && build.status !== 'declined'"
|
||||||
class="ml-4 flex-shrink-0"
|
class="ml-4 flex-shrink-0"
|
||||||
text="Restart"
|
:text="$t('repo.build.actions.restart')"
|
||||||
:is-loading="isRestartingBuild"
|
:is-loading="isRestartingBuild"
|
||||||
@click="restartBuild"
|
@click="restartBuild"
|
||||||
/>
|
/>
|
||||||
@ -43,9 +43,9 @@
|
|||||||
|
|
||||||
<div class="flex flex-wrap gap-y-2 items-center justify-between">
|
<div class="flex flex-wrap gap-y-2 items-center justify-between">
|
||||||
<Tabs v-model="activeTab" disable-hash-mode class="order-2 md:order-none">
|
<Tabs v-model="activeTab" disable-hash-mode class="order-2 md:order-none">
|
||||||
<Tab id="tasks" title="Tasks" />
|
<Tab id="tasks" :title="$t('repo.build.tasks')" />
|
||||||
<Tab id="config" title="Config" />
|
<Tab id="config" :title="$t('repo.build.config')" />
|
||||||
<Tab id="changed-files" :title="`Changed files (${build.changed_files?.length || 0})`" />
|
<Tab id="changed-files" :title="$t('repo.build.files', [build.changed_files?.length || 0])" />
|
||||||
</Tabs>
|
</Tabs>
|
||||||
|
|
||||||
<div class="flex justify-between gap-x-4 text-gray-500 flex-shrink-0 pb-2 md:p-0 mx-auto md:mr-0">
|
<div class="flex justify-between gap-x-4 text-gray-500 flex-shrink-0 pb-2 md:p-0 mx-auto md:mr-0">
|
||||||
@ -53,7 +53,9 @@
|
|||||||
<Icon name="since" />
|
<Icon name="since" />
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
<span>{{ since }}</span>
|
<span>{{ since }}</span>
|
||||||
<template #popper><span class="font-bold">Created</span> {{ created }}</template>
|
<template #popper
|
||||||
|
><span class="font-bold">{{ $t('repo.build.created') }}</span> {{ created }}</template
|
||||||
|
>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex space-x-1 items-center flex-shrink-0">
|
<div class="flex space-x-1 items-center flex-shrink-0">
|
||||||
@ -82,6 +84,7 @@ import {
|
|||||||
toRef,
|
toRef,
|
||||||
watch,
|
watch,
|
||||||
} from 'vue';
|
} from 'vue';
|
||||||
|
import { useI18n } from 'vue-i18n';
|
||||||
import { useRoute, useRouter } from 'vue-router';
|
import { useRoute, useRouter } from 'vue-router';
|
||||||
|
|
||||||
import Button from '~/components/atomic/Button.vue';
|
import Button from '~/components/atomic/Button.vue';
|
||||||
@ -146,6 +149,7 @@ export default defineComponent({
|
|||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const notifications = useNotifications();
|
const notifications = useNotifications();
|
||||||
const favicon = useFavicon();
|
const favicon = useFavicon();
|
||||||
|
const i18n = useI18n();
|
||||||
|
|
||||||
const buildStore = BuildStore();
|
const buildStore = BuildStore();
|
||||||
const buildId = toRef(props, 'buildId');
|
const buildId = toRef(props, 'buildId');
|
||||||
@ -190,7 +194,7 @@ export default defineComponent({
|
|||||||
// }
|
// }
|
||||||
|
|
||||||
await apiClient.cancelBuild(repo.value.owner, repo.value.name, parseInt(buildId.value, 10), 0);
|
await apiClient.cancelBuild(repo.value.owner, repo.value.name, parseInt(buildId.value, 10), 0);
|
||||||
notifications.notify({ title: 'Pipeline canceled', type: 'success' });
|
notifications.notify({ title: i18n.t('repo.build.actions.cancel_success'), type: 'success' });
|
||||||
});
|
});
|
||||||
|
|
||||||
const { doSubmit: restartBuild, isLoading: isRestartingBuild } = useAsyncAction(async () => {
|
const { doSubmit: restartBuild, isLoading: isRestartingBuild } = useAsyncAction(async () => {
|
||||||
@ -199,7 +203,7 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
|
|
||||||
await apiClient.restartBuild(repo.value.owner, repo.value.name, buildId.value, { fork: true });
|
await apiClient.restartBuild(repo.value.owner, repo.value.name, buildId.value, { fork: true });
|
||||||
notifications.notify({ title: 'Pipeline restarted', type: 'success' });
|
notifications.notify({ title: i18n.t('repo.build.actions.restart_success'), type: 'success' });
|
||||||
// TODO: directly send to newest build?
|
// TODO: directly send to newest build?
|
||||||
await router.push({ name: 'repo', params: { repoName: repo.value.name, repoOwner: repo.value.owner } });
|
await router.push({ name: 'repo', params: { repoName: repo.value.name, repoOwner: repo.value.owner } });
|
||||||
});
|
});
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
/* eslint-disable import/no-extraneous-dependencies */
|
/* eslint-disable import/no-extraneous-dependencies */
|
||||||
|
import vueI18n from '@intlify/vite-plugin-vue-i18n';
|
||||||
import vue from '@vitejs/plugin-vue';
|
import vue from '@vitejs/plugin-vue';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import IconsResolver from 'unplugin-icons/resolver';
|
import IconsResolver from 'unplugin-icons/resolver';
|
||||||
@ -25,6 +26,9 @@ function woodpeckerInfoPlugin() {
|
|||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [
|
plugins: [
|
||||||
vue(),
|
vue(),
|
||||||
|
vueI18n({
|
||||||
|
include: path.resolve(__dirname, 'src/assets/locales/**'),
|
||||||
|
}),
|
||||||
WindiCSS(),
|
WindiCSS(),
|
||||||
Icons(),
|
Icons(),
|
||||||
svgLoader(),
|
svgLoader(),
|
||||||
|
144
web/yarn.lock
144
web/yarn.lock
@ -133,6 +133,85 @@
|
|||||||
kolorist "^1.5.0"
|
kolorist "^1.5.0"
|
||||||
local-pkg "^0.4.0"
|
local-pkg "^0.4.0"
|
||||||
|
|
||||||
|
"@intlify/bundle-utils@^2.2.2":
|
||||||
|
version "2.2.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/@intlify/bundle-utils/-/bundle-utils-2.2.2.tgz#fe65ce2549a73b99b75f3e66209d741b4f4d61fd"
|
||||||
|
integrity sha512-vngkvlIVV8ZJoyC5VqMvqJd2nvsx+qMN7pQjPiPjOrVndeiR7Dlue0k86Q8FsFUzyksW3HJZZi833ldxwbFzTA==
|
||||||
|
dependencies:
|
||||||
|
"@intlify/message-compiler" "^9.1.0"
|
||||||
|
"@intlify/shared" "^9.1.0"
|
||||||
|
jsonc-eslint-parser "^1.0.1"
|
||||||
|
source-map "^0.6.1"
|
||||||
|
yaml-eslint-parser "^0.3.2"
|
||||||
|
|
||||||
|
"@intlify/core-base@9.1.10":
|
||||||
|
version "9.1.10"
|
||||||
|
resolved "https://registry.yarnpkg.com/@intlify/core-base/-/core-base-9.1.10.tgz#cbd3099f375c789a1b974f3ea79b6efb8bb148fa"
|
||||||
|
integrity sha512-So9CNUavB/IsZ+zBmk2Cv6McQp6vc2wbGi1S0XQmJ8Vz+UFcNn9MFXAe9gY67PreIHrbLsLxDD0cwo1qsxM1Nw==
|
||||||
|
dependencies:
|
||||||
|
"@intlify/devtools-if" "9.1.10"
|
||||||
|
"@intlify/message-compiler" "9.1.10"
|
||||||
|
"@intlify/message-resolver" "9.1.10"
|
||||||
|
"@intlify/runtime" "9.1.10"
|
||||||
|
"@intlify/shared" "9.1.10"
|
||||||
|
"@intlify/vue-devtools" "9.1.10"
|
||||||
|
|
||||||
|
"@intlify/devtools-if@9.1.10":
|
||||||
|
version "9.1.10"
|
||||||
|
resolved "https://registry.yarnpkg.com/@intlify/devtools-if/-/devtools-if-9.1.10.tgz#8704852a4fa547df43df71a16b1cc4b27e758aa3"
|
||||||
|
integrity sha512-SHaKoYu6sog3+Q8js1y3oXLywuogbH1sKuc7NSYkN3GElvXSBaMoCzW+we0ZSFqj/6c7vTNLg9nQ6rxhKqYwnQ==
|
||||||
|
dependencies:
|
||||||
|
"@intlify/shared" "9.1.10"
|
||||||
|
|
||||||
|
"@intlify/message-compiler@9.1.10", "@intlify/message-compiler@^9.1.0":
|
||||||
|
version "9.1.10"
|
||||||
|
resolved "https://registry.yarnpkg.com/@intlify/message-compiler/-/message-compiler-9.1.10.tgz#271f5e1cb65f3cec4b1fb243e50615747613f4be"
|
||||||
|
integrity sha512-+JiJpXff/XTb0EadYwdxOyRTB0hXNd4n1HaJ/a4yuV960uRmPXaklJsedW0LNdcptd/hYUZtCkI7Lc9J5C1gxg==
|
||||||
|
dependencies:
|
||||||
|
"@intlify/message-resolver" "9.1.10"
|
||||||
|
"@intlify/shared" "9.1.10"
|
||||||
|
source-map "0.6.1"
|
||||||
|
|
||||||
|
"@intlify/message-resolver@9.1.10":
|
||||||
|
version "9.1.10"
|
||||||
|
resolved "https://registry.yarnpkg.com/@intlify/message-resolver/-/message-resolver-9.1.10.tgz#fb1dabdec2e29942df26f47e19444278a6e2f070"
|
||||||
|
integrity sha512-5YixMG/M05m0cn9+gOzd4EZQTFRUu8RGhzxJbR1DWN21x/Z3bJ8QpDYj6hC4FwBj5uKsRfKpJQ3Xqg98KWoA+w==
|
||||||
|
|
||||||
|
"@intlify/runtime@9.1.10":
|
||||||
|
version "9.1.10"
|
||||||
|
resolved "https://registry.yarnpkg.com/@intlify/runtime/-/runtime-9.1.10.tgz#70582a16810f68953d1cbf7183c8107a9137b580"
|
||||||
|
integrity sha512-7QsuByNzpe3Gfmhwq6hzgXcMPpxz8Zxb/XFI6s9lQdPLPe5Lgw4U1ovRPZTOs6Y2hwitR3j/HD8BJNGWpJnOFA==
|
||||||
|
dependencies:
|
||||||
|
"@intlify/message-compiler" "9.1.10"
|
||||||
|
"@intlify/message-resolver" "9.1.10"
|
||||||
|
"@intlify/shared" "9.1.10"
|
||||||
|
|
||||||
|
"@intlify/shared@9.1.10", "@intlify/shared@^9.1.0":
|
||||||
|
version "9.1.10"
|
||||||
|
resolved "https://registry.yarnpkg.com/@intlify/shared/-/shared-9.1.10.tgz#9e2527276b43ae3f354c4015eb04f855d9d7a707"
|
||||||
|
integrity sha512-Om54xJeo1Vw+K1+wHYyXngE8cAbrxZHpWjYzMR9wCkqbhGtRV5VLhVc214Ze2YatPrWlS2WSMOWXR8JktX/IgA==
|
||||||
|
|
||||||
|
"@intlify/vite-plugin-vue-i18n@^3.4.0":
|
||||||
|
version "3.4.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@intlify/vite-plugin-vue-i18n/-/vite-plugin-vue-i18n-3.4.0.tgz#cf15b0d207a843227a5da0ac713f1a5b9d96e40b"
|
||||||
|
integrity sha512-XXcZBgwJ+3FRu11c4ARoY9N00kElPii0/jNZ49qR045Ka7/YGCwb1Ku14BBlMSEHiHDSjLQknLwrJKSQGVZLyA==
|
||||||
|
dependencies:
|
||||||
|
"@intlify/bundle-utils" "^2.2.2"
|
||||||
|
"@intlify/shared" "^9.1.0"
|
||||||
|
"@rollup/pluginutils" "^4.1.0"
|
||||||
|
debug "^4.3.1"
|
||||||
|
fast-glob "^3.2.5"
|
||||||
|
source-map "0.6.1"
|
||||||
|
|
||||||
|
"@intlify/vue-devtools@9.1.10":
|
||||||
|
version "9.1.10"
|
||||||
|
resolved "https://registry.yarnpkg.com/@intlify/vue-devtools/-/vue-devtools-9.1.10.tgz#c62535d86742bcd16593806a4fcae49f6fc8ae6d"
|
||||||
|
integrity sha512-5l3qYARVbkWAkagLu1XbDUWRJSL8br1Dj60wgMaKB0+HswVsrR6LloYZTg7ozyvM621V6+zsmwzbQxbVQyrytQ==
|
||||||
|
dependencies:
|
||||||
|
"@intlify/message-resolver" "9.1.10"
|
||||||
|
"@intlify/runtime" "9.1.10"
|
||||||
|
"@intlify/shared" "9.1.10"
|
||||||
|
|
||||||
"@kyvg/vue3-notification@2.3.4":
|
"@kyvg/vue3-notification@2.3.4":
|
||||||
version "2.3.4"
|
version "2.3.4"
|
||||||
resolved "https://registry.yarnpkg.com/@kyvg/vue3-notification/-/vue3-notification-2.3.4.tgz#7503647ae1d26a7c58bbf5182b505ca345c0882a"
|
resolved "https://registry.yarnpkg.com/@kyvg/vue3-notification/-/vue3-notification-2.3.4.tgz#7503647ae1d26a7c58bbf5182b505ca345c0882a"
|
||||||
@ -167,6 +246,14 @@
|
|||||||
"@nodelib/fs.scandir" "2.1.5"
|
"@nodelib/fs.scandir" "2.1.5"
|
||||||
fastq "^1.6.0"
|
fastq "^1.6.0"
|
||||||
|
|
||||||
|
"@rollup/pluginutils@^4.1.0":
|
||||||
|
version "4.2.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/@rollup/pluginutils/-/pluginutils-4.2.1.tgz#e6c6c3aba0744edce3fb2074922d3776c0af2a6d"
|
||||||
|
integrity sha512-iKnFXr7NkdZAIHiIWE+BX5ULi/ucVFYWD6TbAV+rZctiRTY2PL6tsIKhoIOaoskiWAkgu+VsbXgUVDNLHf+InQ==
|
||||||
|
dependencies:
|
||||||
|
estree-walker "^2.0.1"
|
||||||
|
picomatch "^2.2.2"
|
||||||
|
|
||||||
"@rollup/pluginutils@^4.1.1":
|
"@rollup/pluginutils@^4.1.1":
|
||||||
version "4.1.2"
|
version "4.1.2"
|
||||||
resolved "https://registry.yarnpkg.com/@rollup/pluginutils/-/pluginutils-4.1.2.tgz#ed5821c15e5e05e32816f5fb9ec607cdf5a75751"
|
resolved "https://registry.yarnpkg.com/@rollup/pluginutils/-/pluginutils-4.1.2.tgz#ed5821c15e5e05e32816f5fb9ec607cdf5a75751"
|
||||||
@ -438,6 +525,11 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@vue/devtools-api/-/devtools-api-6.0.1.tgz#c198200b9f84c7b6f7c4976b0206cbe1e7f61dc9"
|
resolved "https://registry.yarnpkg.com/@vue/devtools-api/-/devtools-api-6.0.1.tgz#c198200b9f84c7b6f7c4976b0206cbe1e7f61dc9"
|
||||||
integrity sha512-V2BKGa9pHf/sY2oBUr4uO8yF5MtgL2X96uJq2cBPxPqEUEkLfhJrbpU7t34JRjnanp2tkDJQrQsrsoMltHnFNQ==
|
integrity sha512-V2BKGa9pHf/sY2oBUr4uO8yF5MtgL2X96uJq2cBPxPqEUEkLfhJrbpU7t34JRjnanp2tkDJQrQsrsoMltHnFNQ==
|
||||||
|
|
||||||
|
"@vue/devtools-api@^6.0.0-beta.7":
|
||||||
|
version "6.1.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/@vue/devtools-api/-/devtools-api-6.1.4.tgz#b4aec2f4b4599e11ba774a50c67fa378c9824e53"
|
||||||
|
integrity sha512-IiA0SvDrJEgXvVxjNkHPFfDx6SXw0b/TUkqMcDZWNg9fnCAHbTpoo59YfJ9QLFkwa3raau5vSlRVzMSLDnfdtQ==
|
||||||
|
|
||||||
"@vue/reactivity-transform@3.2.30":
|
"@vue/reactivity-transform@3.2.30":
|
||||||
version "3.2.30"
|
version "3.2.30"
|
||||||
resolved "https://registry.yarnpkg.com/@vue/reactivity-transform/-/reactivity-transform-3.2.30.tgz#2006e9f4645777a481b78ae77fc486159afa8480"
|
resolved "https://registry.yarnpkg.com/@vue/reactivity-transform/-/reactivity-transform-3.2.30.tgz#2006e9f4645777a481b78ae77fc486159afa8480"
|
||||||
@ -536,7 +628,7 @@ acorn-jsx@^5.2.0, acorn-jsx@^5.3.1:
|
|||||||
resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937"
|
resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937"
|
||||||
integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==
|
integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==
|
||||||
|
|
||||||
acorn@^7.1.1, acorn@^7.4.0:
|
acorn@^7.1.1, acorn@^7.4.0, acorn@^7.4.1:
|
||||||
version "7.4.1"
|
version "7.4.1"
|
||||||
resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa"
|
resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa"
|
||||||
integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==
|
integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==
|
||||||
@ -893,6 +985,13 @@ debug@^4.0.1, debug@^4.1.1, debug@^4.3.2, debug@^4.3.3:
|
|||||||
dependencies:
|
dependencies:
|
||||||
ms "2.1.2"
|
ms "2.1.2"
|
||||||
|
|
||||||
|
debug@^4.3.1:
|
||||||
|
version "4.3.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865"
|
||||||
|
integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==
|
||||||
|
dependencies:
|
||||||
|
ms "2.1.2"
|
||||||
|
|
||||||
debug@~3.1.0:
|
debug@~3.1.0:
|
||||||
version "3.1.0"
|
version "3.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261"
|
resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261"
|
||||||
@ -1341,7 +1440,7 @@ eslint@7.32.0:
|
|||||||
text-table "^0.2.0"
|
text-table "^0.2.0"
|
||||||
v8-compile-cache "^2.0.3"
|
v8-compile-cache "^2.0.3"
|
||||||
|
|
||||||
espree@^6.2.1:
|
espree@^6.0.0, espree@^6.2.1:
|
||||||
version "6.2.1"
|
version "6.2.1"
|
||||||
resolved "https://registry.yarnpkg.com/espree/-/espree-6.2.1.tgz#77fc72e1fd744a2052c20f38a5b575832e82734a"
|
resolved "https://registry.yarnpkg.com/espree/-/espree-6.2.1.tgz#77fc72e1fd744a2052c20f38a5b575832e82734a"
|
||||||
integrity sha512-ysCxRQY3WaXJz9tdbWOwuWr5Y/XrPTGX9Kiz3yoUXwW0VZ4w30HTkQLaGx/+ttFjF8i+ACbArnB4ce68a9m5hw==
|
integrity sha512-ysCxRQY3WaXJz9tdbWOwuWr5Y/XrPTGX9Kiz3yoUXwW0VZ4w30HTkQLaGx/+ttFjF8i+ACbArnB4ce68a9m5hw==
|
||||||
@ -1423,7 +1522,7 @@ fast-diff@^1.1.2, fast-diff@^1.2.0:
|
|||||||
resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.2.0.tgz#73ee11982d86caaf7959828d519cfe927fac5f03"
|
resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.2.0.tgz#73ee11982d86caaf7959828d519cfe927fac5f03"
|
||||||
integrity sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==
|
integrity sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==
|
||||||
|
|
||||||
fast-glob@^3.2.7, fast-glob@^3.2.9:
|
fast-glob@^3.2.5, fast-glob@^3.2.7, fast-glob@^3.2.9:
|
||||||
version "3.2.11"
|
version "3.2.11"
|
||||||
resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.11.tgz#a1172ad95ceb8a16e20caa5c5e56480e5129c1d9"
|
resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.11.tgz#a1172ad95ceb8a16e20caa5c5e56480e5129c1d9"
|
||||||
integrity sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew==
|
integrity sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew==
|
||||||
@ -1876,6 +1975,17 @@ json5@^1.0.1:
|
|||||||
dependencies:
|
dependencies:
|
||||||
minimist "^1.2.0"
|
minimist "^1.2.0"
|
||||||
|
|
||||||
|
jsonc-eslint-parser@^1.0.1:
|
||||||
|
version "1.4.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/jsonc-eslint-parser/-/jsonc-eslint-parser-1.4.1.tgz#8cbe99f6f5199acbc5a823c4c0b6135411027fa6"
|
||||||
|
integrity sha512-hXBrvsR1rdjmB2kQmUjf1rEIa+TqHBGMge8pwi++C+Si1ad7EjZrJcpgwym+QGK/pqTx+K7keFAtLlVNdLRJOg==
|
||||||
|
dependencies:
|
||||||
|
acorn "^7.4.1"
|
||||||
|
eslint-utils "^2.1.0"
|
||||||
|
eslint-visitor-keys "^1.3.0"
|
||||||
|
espree "^6.0.0"
|
||||||
|
semver "^6.3.0"
|
||||||
|
|
||||||
jsonc-parser@^2.3.0:
|
jsonc-parser@^2.3.0:
|
||||||
version "2.3.1"
|
version "2.3.1"
|
||||||
resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-2.3.1.tgz#59549150b133f2efacca48fe9ce1ec0659af2342"
|
resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-2.3.1.tgz#59549150b133f2efacca48fe9ce1ec0659af2342"
|
||||||
@ -1963,7 +2073,7 @@ lodash.truncate@^4.4.2:
|
|||||||
resolved "https://registry.yarnpkg.com/lodash.truncate/-/lodash.truncate-4.4.2.tgz#5a350da0b1113b837ecfffd5812cbe58d6eae193"
|
resolved "https://registry.yarnpkg.com/lodash.truncate/-/lodash.truncate-4.4.2.tgz#5a350da0b1113b837ecfffd5812cbe58d6eae193"
|
||||||
integrity sha1-WjUNoLERO4N+z//VgSy+WNbq4ZM=
|
integrity sha1-WjUNoLERO4N+z//VgSy+WNbq4ZM=
|
||||||
|
|
||||||
lodash@4.17.21, lodash@^4.17.19, lodash@^4.17.21:
|
lodash@4.17.21, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.21:
|
||||||
version "4.17.21"
|
version "4.17.21"
|
||||||
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
|
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
|
||||||
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
|
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
|
||||||
@ -2579,7 +2689,7 @@ source-map-url@^0.4.0:
|
|||||||
resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.4.1.tgz#0af66605a745a5a2f91cf1bbf8a7afbc283dec56"
|
resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.4.1.tgz#0af66605a745a5a2f91cf1bbf8a7afbc283dec56"
|
||||||
integrity sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw==
|
integrity sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw==
|
||||||
|
|
||||||
source-map@^0.6.1:
|
source-map@0.6.1, source-map@^0.6.1:
|
||||||
version "0.6.1"
|
version "0.6.1"
|
||||||
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
|
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
|
||||||
integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==
|
integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==
|
||||||
@ -3033,6 +3143,16 @@ vue-eslint-parser@^7.10.0:
|
|||||||
lodash "^4.17.21"
|
lodash "^4.17.21"
|
||||||
semver "^6.3.0"
|
semver "^6.3.0"
|
||||||
|
|
||||||
|
vue-i18n@9:
|
||||||
|
version "9.1.10"
|
||||||
|
resolved "https://registry.yarnpkg.com/vue-i18n/-/vue-i18n-9.1.10.tgz#7ad516b89ba28debb90fc4181c9a2faec9ad97f9"
|
||||||
|
integrity sha512-jpr7gV5KPk4n+sSPdpZT8Qx3XzTcNDWffRlHV/cT2NUyEf+sEgTTmLvnBAibjOFJ0zsUyZlVTAWH5DDnYep+1g==
|
||||||
|
dependencies:
|
||||||
|
"@intlify/core-base" "9.1.10"
|
||||||
|
"@intlify/shared" "9.1.10"
|
||||||
|
"@intlify/vue-devtools" "9.1.10"
|
||||||
|
"@vue/devtools-api" "^6.0.0-beta.7"
|
||||||
|
|
||||||
vue-resize@^2.0.0-alpha.1:
|
vue-resize@^2.0.0-alpha.1:
|
||||||
version "2.0.0-alpha.1"
|
version "2.0.0-alpha.1"
|
||||||
resolved "https://registry.yarnpkg.com/vue-resize/-/vue-resize-2.0.0-alpha.1.tgz#43eeb79e74febe932b9b20c5c57e0ebc14e2df3a"
|
resolved "https://registry.yarnpkg.com/vue-resize/-/vue-resize-2.0.0-alpha.1.tgz#43eeb79e74febe932b9b20c5c57e0ebc14e2df3a"
|
||||||
@ -3127,6 +3247,20 @@ yallist@^4.0.0:
|
|||||||
resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72"
|
resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72"
|
||||||
integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==
|
integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==
|
||||||
|
|
||||||
|
yaml-eslint-parser@^0.3.2:
|
||||||
|
version "0.3.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/yaml-eslint-parser/-/yaml-eslint-parser-0.3.2.tgz#c7f5f3904f1c06ad55dc7131a731b018426b4898"
|
||||||
|
integrity sha512-32kYO6kJUuZzqte82t4M/gB6/+11WAuHiEnK7FreMo20xsCKPeFH5tDBU7iWxR7zeJpNnMXfJyXwne48D0hGrg==
|
||||||
|
dependencies:
|
||||||
|
eslint-visitor-keys "^1.3.0"
|
||||||
|
lodash "^4.17.20"
|
||||||
|
yaml "^1.10.0"
|
||||||
|
|
||||||
|
yaml@^1.10.0:
|
||||||
|
version "1.10.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b"
|
||||||
|
integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==
|
||||||
|
|
||||||
yocto-queue@^0.1.0:
|
yocto-queue@^0.1.0:
|
||||||
version "0.1.0"
|
version "0.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b"
|
resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b"
|
||||||
|
Loading…
Reference in New Issue
Block a user