mirror of
https://github.com/woodpecker-ci/woodpecker.git
synced 2024-11-25 19:31:05 +00:00
Use separate routes instead of anchors (#4285)
Co-authored-by: Anbraten <anton@ju60.de>
This commit is contained in:
parent
5c2204716c
commit
95e464b7cd
39 changed files with 339 additions and 369 deletions
|
@ -18,8 +18,6 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, ref, watch } from 'vue';
|
|
||||||
|
|
||||||
import Container from '~/components/layout/Container.vue';
|
import Container from '~/components/layout/Container.vue';
|
||||||
import { useTabsProvider } from '~/compositions/useTabs';
|
import { useTabsProvider } from '~/compositions/useTabs';
|
||||||
|
|
||||||
|
@ -33,37 +31,16 @@ const props = defineProps<{
|
||||||
|
|
||||||
// Tabs
|
// Tabs
|
||||||
enableTabs?: boolean;
|
enableTabs?: boolean;
|
||||||
disableTabUrlHashMode?: boolean;
|
|
||||||
activeTab?: string;
|
|
||||||
|
|
||||||
// Content
|
// Content
|
||||||
fluidContent?: boolean;
|
fluidContent?: boolean;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const emit = defineEmits<{
|
defineEmits<{
|
||||||
(event: 'update:activeTab', value: string | undefined): void;
|
|
||||||
(event: 'update:search', value: string): void;
|
(event: 'update:search', value: string): void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
if (props.enableTabs) {
|
if (props.enableTabs) {
|
||||||
const internalActiveTab = ref(props.activeTab);
|
useTabsProvider();
|
||||||
|
|
||||||
watch(
|
|
||||||
() => props.activeTab,
|
|
||||||
(activeTab) => {
|
|
||||||
internalActiveTab.value = activeTab;
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
useTabsProvider({
|
|
||||||
activeTab: computed({
|
|
||||||
get: () => internalActiveTab.value,
|
|
||||||
set: (value) => {
|
|
||||||
internalActiveTab.value = value;
|
|
||||||
emit('update:activeTab', value);
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
disableUrlHashMode: computed(() => props.disableTabUrlHashMode || false),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -1,38 +1,40 @@
|
||||||
<template>
|
<template><span /></template>
|
||||||
<div v-if="$slots.default" v-show="isActive" :aria-hidden="!isActive">
|
|
||||||
<slot />
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, onMounted, ref } from 'vue';
|
import { onMounted } from 'vue';
|
||||||
|
import type { RouteLocationRaw } from 'vue-router';
|
||||||
|
|
||||||
import type { IconNames } from '~/components/atomic/Icon.vue';
|
import type { IconNames } from '~/components/atomic/Icon.vue';
|
||||||
import { useTabsClient, type Tab } from '~/compositions/useTabs';
|
import { useTabsClient } from '~/compositions/useTabs';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
id?: string;
|
to: RouteLocationRaw;
|
||||||
title: string;
|
title: string;
|
||||||
icon?: IconNames;
|
icon?: IconNames;
|
||||||
iconClass?: string;
|
iconClass?: string;
|
||||||
|
matchChildren?: boolean;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const { tabs, activeTab } = useTabsClient();
|
const { tabs } = useTabsClient();
|
||||||
const tab = ref<Tab>();
|
|
||||||
|
// TODO: find a better way to compare routes like
|
||||||
|
// https://github.com/vuejs/router/blob/0eaaeb9697acd40ad524d913d0348748e9797acb/packages/router/src/utils/index.ts#L17
|
||||||
|
function isSameRoute(a: RouteLocationRaw, b: RouteLocationRaw): boolean {
|
||||||
|
return JSON.stringify(a) === JSON.stringify(b);
|
||||||
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
tab.value = {
|
// don't add tab if tab id is already present
|
||||||
id: props.id || props.title.toLocaleLowerCase().replace(' ', '-') || tabs.value.length.toString(),
|
if (tabs.value.find(({ to }) => isSameRoute(to, props.to))) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
tabs.value.push({
|
||||||
|
to: props.to,
|
||||||
title: props.title,
|
title: props.title,
|
||||||
icon: props.icon,
|
icon: props.icon,
|
||||||
iconClass: props.iconClass,
|
iconClass: props.iconClass,
|
||||||
};
|
matchChildren: props.matchChildren,
|
||||||
|
});
|
||||||
// don't add tab if tab id is already present
|
|
||||||
if (!tabs.value.find(({ id }) => id === props.id)) {
|
|
||||||
tabs.value.push(tab.value);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const isActive = computed(() => tab.value && tab.value.id === activeTab.value);
|
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -1,46 +1,27 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="flex flex-wrap">
|
<div class="flex flex-wrap">
|
||||||
<button
|
<router-link
|
||||||
v-for="tab in tabs"
|
v-for="tab in tabs"
|
||||||
:key="tab.id"
|
:key="tab.title"
|
||||||
class="w-full py-1 md:py-2 md:w-auto md:px-6 flex cursor-pointer md:border-b-2 text-wp-text-100 hover:text-wp-text-200 items-center"
|
v-slot="{ isActive, isExactActive }"
|
||||||
:class="{
|
:to="tab.to"
|
||||||
'border-wp-text-100': activeTab === tab.id,
|
class="border-transparent w-full py-1 md:py-2 md:w-auto md:px-6 flex cursor-pointer md:border-b-2 text-wp-text-100 hover:text-wp-text-200 items-center"
|
||||||
'border-transparent': activeTab !== tab.id,
|
:active-class="tab.matchChildren ? '!border-wp-text-100' : ''"
|
||||||
}"
|
:exact-active-class="tab.matchChildren ? '' : '!border-wp-text-100'"
|
||||||
type="button"
|
|
||||||
@click="selectTab(tab)"
|
|
||||||
>
|
>
|
||||||
<Icon v-if="activeTab === tab.id" name="chevron-right" class="md:hidden" />
|
<Icon v-if="isExactActive || (isActive && tab.matchChildren)" name="chevron-right" class="md:hidden" />
|
||||||
<Icon v-else name="blank" class="md:hidden" />
|
<Icon v-else name="blank" class="md:hidden" />
|
||||||
<span class="flex gap-2 items-center flex-row-reverse md:flex-row">
|
<span class="flex gap-2 items-center flex-row-reverse md:flex-row">
|
||||||
<Icon v-if="tab.icon" :name="tab.icon" :class="tab.iconClass" />
|
<Icon v-if="tab.icon" :name="tab.icon" :class="tab.iconClass" />
|
||||||
<span>{{ tab.title }}</span>
|
<span>{{ tab.title }}</span>
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</router-link>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useRoute, useRouter } from 'vue-router';
|
|
||||||
|
|
||||||
import Icon from '~/components/atomic/Icon.vue';
|
import Icon from '~/components/atomic/Icon.vue';
|
||||||
import { useTabsClient, type Tab } from '~/compositions/useTabs';
|
import { useTabsClient } from '~/compositions/useTabs';
|
||||||
|
|
||||||
const router = useRouter();
|
const { tabs } = useTabsClient();
|
||||||
const route = useRoute();
|
|
||||||
|
|
||||||
const { activeTab, tabs, disableUrlHashMode } = useTabsClient();
|
|
||||||
|
|
||||||
async function selectTab(tab: Tab) {
|
|
||||||
if (tab.id === undefined) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
activeTab.value = tab.id;
|
|
||||||
|
|
||||||
if (!disableUrlHashMode.value) {
|
|
||||||
await router.replace({ params: route.params, hash: `#${tab.id}` });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -1,12 +1,17 @@
|
||||||
import type { InjectionKey, Ref } from 'vue';
|
import type { InjectionKey, Ref } from 'vue';
|
||||||
import { inject as vueInject, provide as vueProvide } from 'vue';
|
import { inject as vueInject, provide as vueProvide } from 'vue';
|
||||||
|
|
||||||
import type { Org, OrgPermissions, Repo } from '~/lib/api/types';
|
import type { Org, OrgPermissions, Pipeline, PipelineConfig, Repo } from '~/lib/api/types';
|
||||||
|
|
||||||
|
import type { Tab } from './useTabs';
|
||||||
|
|
||||||
export interface InjectKeys {
|
export interface InjectKeys {
|
||||||
repo: Ref<Repo>;
|
repo: Ref<Repo>;
|
||||||
org: Ref<Org | undefined>;
|
org: Ref<Org | undefined>;
|
||||||
'org-permissions': Ref<OrgPermissions | undefined>;
|
'org-permissions': Ref<OrgPermissions | undefined>;
|
||||||
|
pipeline: Ref<Pipeline | undefined>;
|
||||||
|
'pipeline-configs': Ref<PipelineConfig[] | undefined>;
|
||||||
|
tabs: Ref<Tab[]>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function inject<T extends keyof InjectKeys>(key: T): InjectKeys[T] {
|
export function inject<T extends keyof InjectKeys>(key: T): InjectKeys[T] {
|
||||||
|
|
|
@ -1,49 +1,24 @@
|
||||||
import { inject, onMounted, provide, ref, type Ref } from 'vue';
|
import { ref } from 'vue';
|
||||||
import { useRoute } from 'vue-router';
|
import type { RouteLocationRaw } from 'vue-router';
|
||||||
|
|
||||||
import type { IconNames } from '~/components/atomic/Icon.vue';
|
import type { IconNames } from '~/components/atomic/Icon.vue';
|
||||||
|
|
||||||
|
import { inject, provide } from './useInjectProvide';
|
||||||
|
|
||||||
export interface Tab {
|
export interface Tab {
|
||||||
id: string;
|
to: RouteLocationRaw;
|
||||||
title: string;
|
title: string;
|
||||||
icon?: IconNames;
|
icon?: IconNames;
|
||||||
iconClass?: string;
|
iconClass?: string;
|
||||||
|
matchChildren?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useTabsProvider({
|
export function useTabsProvider() {
|
||||||
activeTab,
|
|
||||||
disableUrlHashMode,
|
|
||||||
}: {
|
|
||||||
activeTab: Ref<string | undefined>;
|
|
||||||
disableUrlHashMode: Ref<boolean>;
|
|
||||||
}) {
|
|
||||||
const route = useRoute();
|
|
||||||
|
|
||||||
const tabs = ref<Tab[]>([]);
|
const tabs = ref<Tab[]>([]);
|
||||||
|
|
||||||
provide('tabs', tabs);
|
provide('tabs', tabs);
|
||||||
provide('disable-url-hash-mode', disableUrlHashMode);
|
|
||||||
provide('active-tab', activeTab);
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
if (activeTab.value !== undefined) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const hashTab = route.hash.replace(/^#/, '');
|
|
||||||
|
|
||||||
activeTab.value = hashTab || tabs.value[0].id;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useTabsClient() {
|
export function useTabsClient() {
|
||||||
const tabs = inject<Ref<Tab[]>>('tabs');
|
const tabs = inject('tabs');
|
||||||
const disableUrlHashMode = inject<Ref<boolean>>('disable-url-hash-mode');
|
return { tabs };
|
||||||
const activeTab = inject<Ref<string>>('active-tab');
|
|
||||||
|
|
||||||
if (activeTab === undefined || tabs === undefined || disableUrlHashMode === undefined) {
|
|
||||||
throw new Error('Please use this "useTabsClient" composition inside a component running "useTabsProvider".');
|
|
||||||
}
|
|
||||||
|
|
||||||
return { activeTab, tabs, disableUrlHashMode };
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -42,30 +42,39 @@ const routes: RouteRecordRaw[] = [
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'branches',
|
path: 'branches',
|
||||||
|
meta: { repoHeader: true },
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: '',
|
||||||
name: 'repo-branches',
|
name: 'repo-branches',
|
||||||
component: (): Component => import('~/views/repo/RepoBranches.vue'),
|
component: (): Component => import('~/views/repo/RepoBranches.vue'),
|
||||||
meta: { repoHeader: true },
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'branches/:branch',
|
path: ':branch',
|
||||||
name: 'repo-branch',
|
name: 'repo-branch',
|
||||||
component: (): Component => import('~/views/repo/RepoBranch.vue'),
|
component: (): Component => import('~/views/repo/RepoBranch.vue'),
|
||||||
meta: { repoHeader: true },
|
|
||||||
props: (route) => ({ branch: route.params.branch }),
|
props: (route) => ({ branch: route.params.branch }),
|
||||||
},
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
path: 'pull-requests',
|
path: 'pull-requests',
|
||||||
|
meta: { repoHeader: true },
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: '',
|
||||||
name: 'repo-pull-requests',
|
name: 'repo-pull-requests',
|
||||||
component: (): Component => import('~/views/repo/RepoPullRequests.vue'),
|
component: (): Component => import('~/views/repo/RepoPullRequests.vue'),
|
||||||
meta: { repoHeader: true },
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'pull-requests/:pullRequest',
|
path: ':pullRequest',
|
||||||
name: 'repo-pull-request',
|
name: 'repo-pull-request',
|
||||||
component: (): Component => import('~/views/repo/RepoPullRequest.vue'),
|
component: (): Component => import('~/views/repo/RepoPullRequest.vue'),
|
||||||
meta: { repoHeader: true },
|
|
||||||
props: (route) => ({ pullRequest: route.params.pullRequest }),
|
props: (route) => ({ pullRequest: route.params.pullRequest }),
|
||||||
},
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: 'pipeline/:pipelineId',
|
path: 'pipeline/:pipelineId',
|
||||||
component: (): Component => import('~/views/repo/pipeline/PipelineWrapper.vue'),
|
component: (): Component => import('~/views/repo/pipeline/PipelineWrapper.vue'),
|
||||||
|
@ -98,15 +107,53 @@ const routes: RouteRecordRaw[] = [
|
||||||
path: 'debug',
|
path: 'debug',
|
||||||
name: 'repo-pipeline-debug',
|
name: 'repo-pipeline-debug',
|
||||||
component: (): Component => import('~/views/repo/pipeline/PipelineDebug.vue'),
|
component: (): Component => import('~/views/repo/pipeline/PipelineDebug.vue'),
|
||||||
|
props: true,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'settings',
|
path: 'settings',
|
||||||
name: 'repo-settings',
|
component: (): Component => import('~/views/repo/settings/RepoSettings.vue'),
|
||||||
component: (): Component => import('~/views/repo/RepoSettings.vue'),
|
|
||||||
meta: { authentication: 'required' },
|
meta: { authentication: 'required' },
|
||||||
props: true,
|
props: true,
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: '',
|
||||||
|
name: 'repo-settings',
|
||||||
|
component: (): Component => import('~/views/repo/settings/General.vue'),
|
||||||
|
props: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'secrets',
|
||||||
|
name: 'repo-settings-secrets',
|
||||||
|
component: (): Component => import('~/views/repo/settings/Secrets.vue'),
|
||||||
|
props: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'registries',
|
||||||
|
name: 'repo-settings-registries',
|
||||||
|
component: (): Component => import('~/views/repo/settings/Registries.vue'),
|
||||||
|
props: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'crons',
|
||||||
|
name: 'repo-settings-crons',
|
||||||
|
component: (): Component => import('~/views/repo/settings/Crons.vue'),
|
||||||
|
props: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'badge',
|
||||||
|
name: 'repo-settings-badge',
|
||||||
|
component: (): Component => import('~/views/repo/settings/Badge.vue'),
|
||||||
|
props: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'actions',
|
||||||
|
name: 'repo-settings-actions',
|
||||||
|
component: (): Component => import('~/views/repo/settings/Actions.vue'),
|
||||||
|
props: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'manual',
|
path: 'manual',
|
||||||
|
@ -137,9 +184,29 @@ const routes: RouteRecordRaw[] = [
|
||||||
{
|
{
|
||||||
path: 'settings',
|
path: 'settings',
|
||||||
name: 'org-settings',
|
name: 'org-settings',
|
||||||
component: (): Component => import('~/views/org/OrgSettings.vue'),
|
component: (): Component => import('~/views/org/settings/OrgSettingsWrapper.vue'),
|
||||||
meta: { authentication: 'required' },
|
meta: { authentication: 'required' },
|
||||||
props: true,
|
props: true,
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: 'secrets',
|
||||||
|
name: 'org-settings-secrets',
|
||||||
|
component: (): Component => import('~/views/org/settings/OrgSecrets.vue'),
|
||||||
|
props: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'registries',
|
||||||
|
name: 'org-settings-registries',
|
||||||
|
component: (): Component => import('~/views/org/settings/OrgRegistries.vue'),
|
||||||
|
props: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'agents',
|
||||||
|
name: 'org-settings-agents',
|
||||||
|
component: (): Component => import('~/views/org/settings/OrgAgents.vue'),
|
||||||
|
props: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
@ -150,18 +217,98 @@ const routes: RouteRecordRaw[] = [
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: `${rootPath}/admin`,
|
path: `${rootPath}/admin`,
|
||||||
name: 'admin-settings',
|
component: (): Component => import('~/views/admin/AdminSettingsWrapper.vue'),
|
||||||
component: (): Component => import('~/views/admin/AdminSettings.vue'),
|
|
||||||
props: true,
|
props: true,
|
||||||
meta: { authentication: 'required' },
|
meta: { authentication: 'required' },
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: '',
|
||||||
|
name: 'admin-settings',
|
||||||
|
component: (): Component => import('~/views/admin/AdminInfo.vue'),
|
||||||
|
props: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'secrets',
|
||||||
|
name: 'admin-settings-secrets',
|
||||||
|
component: (): Component => import('~/views/admin/AdminSecrets.vue'),
|
||||||
|
props: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'registries',
|
||||||
|
name: 'admin-settings-registries',
|
||||||
|
component: (): Component => import('~/views/admin/AdminRegistries.vue'),
|
||||||
|
props: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'repos',
|
||||||
|
name: 'admin-settings-repos',
|
||||||
|
component: (): Component => import('~/views/admin/AdminRepos.vue'),
|
||||||
|
props: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'users',
|
||||||
|
name: 'admin-settings-users',
|
||||||
|
component: (): Component => import('~/views/admin/AdminUsers.vue'),
|
||||||
|
props: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'orgs',
|
||||||
|
name: 'admin-settings-orgs',
|
||||||
|
component: (): Component => import('~/views/admin/AdminOrgs.vue'),
|
||||||
|
props: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'agents',
|
||||||
|
name: 'admin-settings-agents',
|
||||||
|
component: (): Component => import('~/views/admin/AdminAgents.vue'),
|
||||||
|
props: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'queue',
|
||||||
|
name: 'admin-settings-queue',
|
||||||
|
component: (): Component => import('~/views/admin/AdminQueue.vue'),
|
||||||
|
props: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
path: `${rootPath}/user`,
|
path: `${rootPath}/user`,
|
||||||
name: 'user',
|
component: (): Component => import('~/views/user/UserWrapper.vue'),
|
||||||
component: (): Component => import('~/views/User.vue'),
|
|
||||||
meta: { authentication: 'required' },
|
meta: { authentication: 'required' },
|
||||||
props: true,
|
props: true,
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: '',
|
||||||
|
name: 'user',
|
||||||
|
component: (): Component => import('~/views/user/UserGeneral.vue'),
|
||||||
|
props: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'secrets',
|
||||||
|
name: 'user-secrets',
|
||||||
|
component: (): Component => import('~/views/user/UserSecrets.vue'),
|
||||||
|
props: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'registries',
|
||||||
|
name: 'user-registries',
|
||||||
|
component: (): Component => import('~/views/user/UserRegistries.vue'),
|
||||||
|
props: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'cli-and-api',
|
||||||
|
name: 'user-cli-and-api',
|
||||||
|
component: (): Component => import('~/views/user/UserCLIAndAPI.vue'),
|
||||||
|
props: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'agents',
|
||||||
|
name: 'user-agents',
|
||||||
|
component: (): Component => import('~/views/user/UserAgents.vue'),
|
||||||
|
props: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: `${rootPath}/login`,
|
path: `${rootPath}/login`,
|
||||||
|
|
|
@ -1,35 +0,0 @@
|
||||||
<template>
|
|
||||||
<Scaffold enable-tabs>
|
|
||||||
<template #title>{{ $t('user.settings.settings') }}</template>
|
|
||||||
<template #headerActions><Button :text="$t('logout')" :to="`${address}/logout`" /></template>
|
|
||||||
<Tab id="general" :title="$t('user.settings.general.general')">
|
|
||||||
<UserGeneralTab />
|
|
||||||
</Tab>
|
|
||||||
<Tab id="secrets" :title="$t('secrets.secrets')">
|
|
||||||
<UserSecretsTab />
|
|
||||||
</Tab>
|
|
||||||
<Tab id="registries" :title="$t('registries.registries')">
|
|
||||||
<UserRegistriesTab />
|
|
||||||
</Tab>
|
|
||||||
<Tab id="cli-and-api" :title="$t('user.settings.cli_and_api.cli_and_api')">
|
|
||||||
<UserCLIAndAPITab />
|
|
||||||
</Tab>
|
|
||||||
<Tab v-if="useConfig().userRegisteredAgents" id="agents" :title="$t('admin.settings.agents.agents')">
|
|
||||||
<UserAgentsTab />
|
|
||||||
</Tab>
|
|
||||||
</Scaffold>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import Button from '~/components/atomic/Button.vue';
|
|
||||||
import Scaffold from '~/components/layout/scaffold/Scaffold.vue';
|
|
||||||
import Tab from '~/components/layout/scaffold/Tab.vue';
|
|
||||||
import UserAgentsTab from '~/components/user/UserAgentsTab.vue';
|
|
||||||
import UserCLIAndAPITab from '~/components/user/UserCLIAndAPITab.vue';
|
|
||||||
import UserGeneralTab from '~/components/user/UserGeneralTab.vue';
|
|
||||||
import UserRegistriesTab from '~/components/user/UserRegistriesTab.vue';
|
|
||||||
import UserSecretsTab from '~/components/user/UserSecretsTab.vue';
|
|
||||||
import useConfig from '~/compositions/useConfig';
|
|
||||||
|
|
||||||
const address = `${window.location.protocol}//${window.location.host}${useConfig().rootPath}`; // port is included in location.host
|
|
||||||
</script>
|
|
|
@ -78,6 +78,7 @@
|
||||||
import { computed, onBeforeUnmount, onMounted, ref } from 'vue';
|
import { computed, onBeforeUnmount, onMounted, ref } from 'vue';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
|
|
||||||
|
import AdminQueueStats from '~/components/admin/settings/queue/AdminQueueStats.vue';
|
||||||
import Badge from '~/components/atomic/Badge.vue';
|
import Badge from '~/components/atomic/Badge.vue';
|
||||||
import Button from '~/components/atomic/Button.vue';
|
import Button from '~/components/atomic/Button.vue';
|
||||||
import Icon from '~/components/atomic/Icon.vue';
|
import Icon from '~/components/atomic/Icon.vue';
|
||||||
|
@ -87,8 +88,6 @@ import useApiClient from '~/compositions/useApiClient';
|
||||||
import useNotifications from '~/compositions/useNotifications';
|
import useNotifications from '~/compositions/useNotifications';
|
||||||
import type { QueueInfo } from '~/lib/api/types';
|
import type { QueueInfo } from '~/lib/api/types';
|
||||||
|
|
||||||
import AdminQueueStats from './queue/AdminQueueStats.vue';
|
|
||||||
|
|
||||||
const apiClient = useApiClient();
|
const apiClient = useApiClient();
|
||||||
const notifications = useNotifications();
|
const notifications = useNotifications();
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
|
@ -1,67 +0,0 @@
|
||||||
<template>
|
|
||||||
<Scaffold enable-tabs>
|
|
||||||
<template #title>
|
|
||||||
{{ $t('settings') }}
|
|
||||||
</template>
|
|
||||||
<Tab id="info" :title="$t('info')">
|
|
||||||
<AdminInfoTab />
|
|
||||||
</Tab>
|
|
||||||
<Tab id="secrets" :title="$t('secrets.secrets')">
|
|
||||||
<AdminSecretsTab />
|
|
||||||
</Tab>
|
|
||||||
<Tab id="registries" :title="$t('registries.registries')">
|
|
||||||
<AdminRegistriesTab />
|
|
||||||
</Tab>
|
|
||||||
<Tab id="repos" :title="$t('admin.settings.repos.repos')">
|
|
||||||
<AdminReposTab />
|
|
||||||
</Tab>
|
|
||||||
<Tab id="users" :title="$t('admin.settings.users.users')">
|
|
||||||
<AdminUsersTab />
|
|
||||||
</Tab>
|
|
||||||
<Tab id="orgs" :title="$t('admin.settings.orgs.orgs')">
|
|
||||||
<AdminOrgsTab />
|
|
||||||
</Tab>
|
|
||||||
<Tab id="agents" :title="$t('admin.settings.agents.agents')">
|
|
||||||
<AdminAgentsTab />
|
|
||||||
</Tab>
|
|
||||||
<Tab id="queue" :title="$t('admin.settings.queue.queue')">
|
|
||||||
<AdminQueueTab />
|
|
||||||
</Tab>
|
|
||||||
</Scaffold>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { onMounted } from 'vue';
|
|
||||||
import { useI18n } from 'vue-i18n';
|
|
||||||
import { useRouter } from 'vue-router';
|
|
||||||
|
|
||||||
import AdminAgentsTab from '~/components/admin/settings/AdminAgentsTab.vue';
|
|
||||||
import AdminInfoTab from '~/components/admin/settings/AdminInfoTab.vue';
|
|
||||||
import AdminOrgsTab from '~/components/admin/settings/AdminOrgsTab.vue';
|
|
||||||
import AdminQueueTab from '~/components/admin/settings/AdminQueueTab.vue';
|
|
||||||
import AdminRegistriesTab from '~/components/admin/settings/AdminRegistriesTab.vue';
|
|
||||||
import AdminReposTab from '~/components/admin/settings/AdminReposTab.vue';
|
|
||||||
import AdminSecretsTab from '~/components/admin/settings/AdminSecretsTab.vue';
|
|
||||||
import AdminUsersTab from '~/components/admin/settings/AdminUsersTab.vue';
|
|
||||||
import Scaffold from '~/components/layout/scaffold/Scaffold.vue';
|
|
||||||
import Tab from '~/components/layout/scaffold/Tab.vue';
|
|
||||||
import useAuthentication from '~/compositions/useAuthentication';
|
|
||||||
import useNotifications from '~/compositions/useNotifications';
|
|
||||||
|
|
||||||
const notifications = useNotifications();
|
|
||||||
const router = useRouter();
|
|
||||||
const i18n = useI18n();
|
|
||||||
const { user } = useAuthentication();
|
|
||||||
|
|
||||||
onMounted(async () => {
|
|
||||||
if (!user?.admin) {
|
|
||||||
notifications.notify({ type: 'error', title: i18n.t('admin.settings.not_allowed') });
|
|
||||||
await router.replace({ name: 'home' });
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!user?.admin) {
|
|
||||||
notifications.notify({ type: 'error', title: i18n.t('admin.settings.not_allowed') });
|
|
||||||
await router.replace({ name: 'home' });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
40
web/src/views/admin/AdminSettingsWrapper.vue
Normal file
40
web/src/views/admin/AdminSettingsWrapper.vue
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
<template>
|
||||||
|
<Scaffold enable-tabs>
|
||||||
|
<template #title>
|
||||||
|
{{ $t('settings') }}
|
||||||
|
</template>
|
||||||
|
<Tab :to="{ name: 'admin-settings' }" :title="$t('info')" />
|
||||||
|
<Tab :to="{ name: 'admin-settings-secrets' }" :title="$t('secrets.secrets')" />
|
||||||
|
<Tab :to="{ name: 'admin-settings-registries' }" :title="$t('registries.registries')" />
|
||||||
|
<Tab :to="{ name: 'admin-settings-repos' }" :title="$t('admin.settings.repos.repos')" />
|
||||||
|
<Tab :to="{ name: 'admin-settings-users' }" :title="$t('admin.settings.users.users')" />
|
||||||
|
<Tab :to="{ name: 'admin-settings-orgs' }" :title="$t('admin.settings.orgs.orgs')" />
|
||||||
|
<Tab :to="{ name: 'admin-settings-agents' }" :title="$t('admin.settings.agents.agents')" />
|
||||||
|
<Tab :to="{ name: 'admin-settings-queue' }" :title="$t('admin.settings.queue.queue')" />
|
||||||
|
|
||||||
|
<router-view />
|
||||||
|
</Scaffold>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { onMounted } from 'vue';
|
||||||
|
import { useI18n } from 'vue-i18n';
|
||||||
|
import { useRouter } from 'vue-router';
|
||||||
|
|
||||||
|
import Scaffold from '~/components/layout/scaffold/Scaffold.vue';
|
||||||
|
import Tab from '~/components/layout/scaffold/Tab.vue';
|
||||||
|
import useAuthentication from '~/compositions/useAuthentication';
|
||||||
|
import useNotifications from '~/compositions/useNotifications';
|
||||||
|
|
||||||
|
const notifications = useNotifications();
|
||||||
|
const router = useRouter();
|
||||||
|
const i18n = useI18n();
|
||||||
|
const { user } = useAuthentication();
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
if (!user?.admin) {
|
||||||
|
notifications.notify({ type: 'error', title: i18n.t('admin.settings.not_allowed') });
|
||||||
|
await router.replace({ name: 'home' });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
|
@ -8,7 +8,7 @@
|
||||||
<IconButton
|
<IconButton
|
||||||
v-if="orgPermissions.admin"
|
v-if="orgPermissions.admin"
|
||||||
icon="settings"
|
icon="settings"
|
||||||
:to="{ name: org.is_user ? 'user' : 'org-settings' }"
|
:to="{ name: org.is_user ? 'user' : 'org-settings-secrets' }"
|
||||||
:title="$t('settings')"
|
:title="$t('settings')"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
<template #headerActions>
|
<template #headerActions>
|
||||||
<IconButton
|
<IconButton
|
||||||
v-if="orgPermissions.admin"
|
v-if="orgPermissions.admin"
|
||||||
:to="{ name: org.is_user ? 'user' : 'repo-settings' }"
|
:to="{ name: org.is_user ? 'user' : 'org-settings-secrets' }"
|
||||||
:title="$t('settings')"
|
:title="$t('settings')"
|
||||||
icon="settings"
|
icon="settings"
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -11,17 +11,15 @@
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<Tab id="secrets" :title="$t('secrets.secrets')">
|
<Tab :to="{ name: 'org-settings-secrets' }" :title="$t('secrets.secrets')" />
|
||||||
<OrgSecretsTab />
|
<Tab :to="{ name: 'org-settings-registries' }" :title="$t('registries.registries')" />
|
||||||
</Tab>
|
<Tab
|
||||||
|
v-if="useConfig().userRegisteredAgents"
|
||||||
|
:to="{ name: 'org-settings-agents' }"
|
||||||
|
:title="$t('admin.settings.agents.agents')"
|
||||||
|
/>
|
||||||
|
|
||||||
<Tab id="registries" :title="$t('registries.registries')">
|
<router-view />
|
||||||
<OrgRegistriesTab />
|
|
||||||
</Tab>
|
|
||||||
|
|
||||||
<Tab v-if="useConfig().userRegisteredAgents" id="agents" :title="$t('admin.settings.agents.agents')">
|
|
||||||
<OrgAgentsTab />
|
|
||||||
</Tab>
|
|
||||||
</Scaffold>
|
</Scaffold>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -32,9 +30,6 @@ import { useRouter } from 'vue-router';
|
||||||
|
|
||||||
import Scaffold from '~/components/layout/scaffold/Scaffold.vue';
|
import Scaffold from '~/components/layout/scaffold/Scaffold.vue';
|
||||||
import Tab from '~/components/layout/scaffold/Tab.vue';
|
import Tab from '~/components/layout/scaffold/Tab.vue';
|
||||||
import OrgAgentsTab from '~/components/org/settings/OrgAgentsTab.vue';
|
|
||||||
import OrgRegistriesTab from '~/components/org/settings/OrgRegistriesTab.vue';
|
|
||||||
import OrgSecretsTab from '~/components/org/settings/OrgSecretsTab.vue';
|
|
||||||
import useConfig from '~/compositions/useConfig';
|
import useConfig from '~/compositions/useConfig';
|
||||||
import { inject } from '~/compositions/useInjectProvide';
|
import { inject } from '~/compositions/useInjectProvide';
|
||||||
import useNotifications from '~/compositions/useNotifications';
|
import useNotifications from '~/compositions/useNotifications';
|
|
@ -1,10 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<Scaffold
|
<Scaffold v-if="repo && repoPermissions && route.meta.repoHeader" enable-tabs>
|
||||||
v-if="repo && repoPermissions && route.meta.repoHeader"
|
|
||||||
v-model:active-tab="activeTab"
|
|
||||||
enable-tabs
|
|
||||||
disable-tab-url-hash-mode
|
|
||||||
>
|
|
||||||
<template #title>
|
<template #title>
|
||||||
<span class="flex">
|
<span class="flex">
|
||||||
<router-link :to="{ name: 'org', params: { orgId: repo.org_id } }" class="hover:underline">{{
|
<router-link :to="{ name: 'org', params: { orgId: repo.org_id } }" class="hover:underline">{{
|
||||||
|
@ -43,9 +38,14 @@
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<Tab id="activity" :title="$t('repo.activity')" />
|
<Tab :to="{ name: 'repo' }" :title="$t('repo.activity')" />
|
||||||
<Tab id="branches" :title="$t('repo.branches')" />
|
<Tab :to="{ name: 'repo-branches' }" match-children :title="$t('repo.branches')" />
|
||||||
<Tab v-if="repo.pr_enabled && repo.allow_pr" id="pull_requests" :title="$t('repo.pull_requests')" />
|
<Tab
|
||||||
|
v-if="repo.pr_enabled && repo.allow_pr"
|
||||||
|
:to="{ name: 'repo-pull-requests' }"
|
||||||
|
match-children
|
||||||
|
:title="$t('repo.pull_requests')"
|
||||||
|
/>
|
||||||
|
|
||||||
<router-view />
|
<router-view />
|
||||||
</Scaffold>
|
</Scaffold>
|
||||||
|
@ -132,25 +132,4 @@ watch([repositoryId], () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
const badgeUrl = computed(() => repo.value && `${config.rootPath}/api/badges/${repo.value.id}/status.svg`);
|
const badgeUrl = computed(() => repo.value && `${config.rootPath}/api/badges/${repo.value.id}/status.svg`);
|
||||||
|
|
||||||
const activeTab = computed({
|
|
||||||
get() {
|
|
||||||
if (route.name === 'repo-branches' || route.name === 'repo-branch') {
|
|
||||||
return 'branches';
|
|
||||||
}
|
|
||||||
if (route.name === 'repo-pull-requests' || route.name === 'repo-pull-request') {
|
|
||||||
return 'pull_requests';
|
|
||||||
}
|
|
||||||
return 'activity';
|
|
||||||
},
|
|
||||||
set(tab: string) {
|
|
||||||
if (tab === 'branches') {
|
|
||||||
router.push({ name: 'repo-branches' });
|
|
||||||
} else if (tab === 'pull_requests') {
|
|
||||||
router.push({ name: 'repo-pull-requests' });
|
|
||||||
} else {
|
|
||||||
router.push({ name: 'repo' });
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="flex flex-col gap-y-6">
|
<div class="flex flex-col gap-y-6">
|
||||||
<Panel
|
<Panel
|
||||||
v-for="pipelineConfig in pipelineConfigsDecoded || []"
|
v-for="pipelineConfig in pipelineConfigsDecoded"
|
||||||
:key="pipelineConfig.hash"
|
:key="pipelineConfig.hash"
|
||||||
:collapsable="pipelineConfigsDecoded && pipelineConfigsDecoded.length > 1"
|
:collapsable="pipelineConfigsDecoded && pipelineConfigsDecoded.length > 1"
|
||||||
collapsed-by-default
|
collapsed-by-default
|
||||||
|
@ -14,21 +14,22 @@
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { decode } from 'js-base64';
|
import { decode } from 'js-base64';
|
||||||
import { computed, inject, type Ref } from 'vue';
|
import { computed } from 'vue';
|
||||||
|
|
||||||
import SyntaxHighlight from '~/components/atomic/SyntaxHighlight';
|
import SyntaxHighlight from '~/components/atomic/SyntaxHighlight';
|
||||||
import Panel from '~/components/layout/Panel.vue';
|
import Panel from '~/components/layout/Panel.vue';
|
||||||
import type { PipelineConfig } from '~/lib/api/types';
|
import { inject } from '~/compositions/useInjectProvide';
|
||||||
|
|
||||||
const pipelineConfigs = inject<Ref<PipelineConfig[]>>('pipeline-configs');
|
const pipelineConfigs = inject('pipeline-configs');
|
||||||
if (!pipelineConfigs) {
|
if (!pipelineConfigs) {
|
||||||
throw new Error('Unexpected: "pipelineConfigs" should be provided at this place');
|
throw new Error('Unexpected: "pipelineConfigs" should be provided at this place');
|
||||||
}
|
}
|
||||||
|
|
||||||
const pipelineConfigsDecoded = computed(() =>
|
const pipelineConfigsDecoded = computed(
|
||||||
pipelineConfigs.value.map((i) => ({
|
() =>
|
||||||
|
pipelineConfigs.value?.map((i) => ({
|
||||||
...i,
|
...i,
|
||||||
data: decode(i.data),
|
data: decode(i.data),
|
||||||
})),
|
})) ?? [],
|
||||||
);
|
);
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -1,11 +1,9 @@
|
||||||
<template>
|
<template>
|
||||||
<Scaffold
|
<Scaffold
|
||||||
v-if="pipeline && repo"
|
v-if="pipeline && repo"
|
||||||
v-model:active-tab="activeTab"
|
|
||||||
enable-tabs
|
enable-tabs
|
||||||
disable-tab-url-hash-mode
|
|
||||||
:go-back="goBack"
|
:go-back="goBack"
|
||||||
:fluid-content="activeTab === 'tasks'"
|
:fluid-content="route.name === 'repo-pipeline'"
|
||||||
full-width-header
|
full-width-header
|
||||||
>
|
>
|
||||||
<template #title>
|
<template #title>
|
||||||
|
@ -75,10 +73,10 @@
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<Tab id="tasks" :title="$t('repo.pipeline.tasks')" />
|
<Tab :to="{ name: 'repo-pipeline' }" :title="$t('repo.pipeline.tasks')" />
|
||||||
<Tab
|
<Tab
|
||||||
v-if="pipeline.errors && pipeline.errors.length > 0"
|
v-if="pipeline.errors && pipeline.errors.length > 0"
|
||||||
id="errors"
|
:to="{ name: 'repo-pipeline-errors' }"
|
||||||
icon="attention"
|
icon="attention"
|
||||||
:title="
|
:title="
|
||||||
pipeline.errors.some((e) => !e.is_warning)
|
pipeline.errors.some((e) => !e.is_warning)
|
||||||
|
@ -87,20 +85,24 @@
|
||||||
"
|
"
|
||||||
:icon-class="pipeline.errors.some((e) => !e.is_warning) ? 'text-wp-state-error-100' : 'text-wp-state-warn-100'"
|
:icon-class="pipeline.errors.some((e) => !e.is_warning) ? 'text-wp-state-error-100' : 'text-wp-state-warn-100'"
|
||||||
/>
|
/>
|
||||||
<Tab id="config" :title="$t('repo.pipeline.config')" />
|
<Tab :to="{ name: 'repo-pipeline-config' }" :title="$t('repo.pipeline.config')" />
|
||||||
<Tab
|
<Tab
|
||||||
v-if="pipeline.changed_files && pipeline.changed_files.length > 0"
|
v-if="pipeline.changed_files && pipeline.changed_files.length > 0"
|
||||||
id="changed-files"
|
:to="{ name: 'repo-pipeline-changed-files' }"
|
||||||
:title="$t('repo.pipeline.files', { files: pipeline.changed_files?.length })"
|
:title="$t('repo.pipeline.files', { files: pipeline.changed_files?.length })"
|
||||||
/>
|
/>
|
||||||
<Tab v-if="repoPermissions && repoPermissions.push" id="debug" :title="$t('repo.pipeline.debug.title')" />
|
<Tab
|
||||||
|
v-if="repoPermissions && repoPermissions.push"
|
||||||
|
:to="{ name: 'repo-pipeline-debug' }"
|
||||||
|
:title="$t('repo.pipeline.debug.title')"
|
||||||
|
/>
|
||||||
|
|
||||||
<router-view />
|
<router-view />
|
||||||
</Scaffold>
|
</Scaffold>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed, inject, onBeforeUnmount, onMounted, provide, ref, toRef, watch, type Ref } from 'vue';
|
import { computed, inject, onBeforeUnmount, onMounted, ref, toRef, watch, type Ref } from 'vue';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
import { useRoute, useRouter } from 'vue-router';
|
import { useRoute, useRouter } from 'vue-router';
|
||||||
|
|
||||||
|
@ -113,6 +115,7 @@ import PipelineStatusIcon from '~/components/repo/pipeline/PipelineStatusIcon.vu
|
||||||
import useApiClient from '~/compositions/useApiClient';
|
import useApiClient from '~/compositions/useApiClient';
|
||||||
import { useAsyncAction } from '~/compositions/useAsyncAction';
|
import { useAsyncAction } from '~/compositions/useAsyncAction';
|
||||||
import { useFavicon } from '~/compositions/useFavicon';
|
import { useFavicon } from '~/compositions/useFavicon';
|
||||||
|
import { provide } from '~/compositions/useInjectProvide';
|
||||||
import useNotifications from '~/compositions/useNotifications';
|
import useNotifications from '~/compositions/useNotifications';
|
||||||
import usePipeline from '~/compositions/usePipeline';
|
import usePipeline from '~/compositions/usePipeline';
|
||||||
import { useRouteBack } from '~/compositions/useRouteBack';
|
import { useRouteBack } from '~/compositions/useRouteBack';
|
||||||
|
@ -206,48 +209,5 @@ onBeforeUnmount(() => {
|
||||||
favicon.updateStatus('default');
|
favicon.updateStatus('default');
|
||||||
});
|
});
|
||||||
|
|
||||||
const activeTab = computed({
|
|
||||||
get() {
|
|
||||||
if (route.name === 'repo-pipeline-changed-files') {
|
|
||||||
return 'changed-files';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (route.name === 'repo-pipeline-config') {
|
|
||||||
return 'config';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (route.name === 'repo-pipeline-errors') {
|
|
||||||
return 'errors';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (route.name === 'repo-pipeline-debug' && repoPermissions.value?.push) {
|
|
||||||
return 'debug';
|
|
||||||
}
|
|
||||||
|
|
||||||
return 'tasks';
|
|
||||||
},
|
|
||||||
set(tab: string) {
|
|
||||||
if (tab === 'tasks') {
|
|
||||||
router.replace({ name: 'repo-pipeline' });
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tab === 'changed-files') {
|
|
||||||
router.replace({ name: 'repo-pipeline-changed-files' });
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tab === 'config') {
|
|
||||||
router.replace({ name: 'repo-pipeline-config' });
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tab === 'errors') {
|
|
||||||
router.replace({ name: 'repo-pipeline-errors' });
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tab === 'debug' && repoPermissions.value?.push) {
|
|
||||||
router.replace({ name: 'repo-pipeline-debug' });
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const goBack = useRouteBack({ name: 'repo' });
|
const goBack = useRouteBack({ name: 'repo' });
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -16,24 +16,14 @@
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<Tab id="general" :title="$t('repo.settings.general.general')">
|
<Tab :to="{ name: 'repo-settings' }" :title="$t('repo.settings.general.general')" />
|
||||||
<GeneralTab />
|
<Tab :to="{ name: 'repo-settings-secrets' }" :title="$t('secrets.secrets')" />
|
||||||
</Tab>
|
<Tab :to="{ name: 'repo-settings-registries' }" :title="$t('registries.registries')" />
|
||||||
<Tab id="secrets" :title="$t('secrets.secrets')">
|
<Tab :to="{ name: 'repo-settings-crons' }" :title="$t('repo.settings.crons.crons')" />
|
||||||
<SecretsTab />
|
<Tab :to="{ name: 'repo-settings-badge' }" :title="$t('repo.settings.badge.badge')" />
|
||||||
</Tab>
|
<Tab :to="{ name: 'repo-settings-actions' }" :title="$t('repo.settings.actions.actions')" />
|
||||||
<Tab id="registries" :title="$t('registries.registries')">
|
|
||||||
<RegistriesTab />
|
<router-view />
|
||||||
</Tab>
|
|
||||||
<Tab id="crons" :title="$t('repo.settings.crons.crons')">
|
|
||||||
<CronTab />
|
|
||||||
</Tab>
|
|
||||||
<Tab id="badge" :title="$t('repo.settings.badge.badge')">
|
|
||||||
<BadgeTab />
|
|
||||||
</Tab>
|
|
||||||
<Tab id="actions" :title="$t('repo.settings.actions.actions')">
|
|
||||||
<ActionsTab />
|
|
||||||
</Tab>
|
|
||||||
</Scaffold>
|
</Scaffold>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -44,12 +34,6 @@ import { useRouter } from 'vue-router';
|
||||||
|
|
||||||
import Scaffold from '~/components/layout/scaffold/Scaffold.vue';
|
import Scaffold from '~/components/layout/scaffold/Scaffold.vue';
|
||||||
import Tab from '~/components/layout/scaffold/Tab.vue';
|
import Tab from '~/components/layout/scaffold/Tab.vue';
|
||||||
import ActionsTab from '~/components/repo/settings/ActionsTab.vue';
|
|
||||||
import BadgeTab from '~/components/repo/settings/BadgeTab.vue';
|
|
||||||
import CronTab from '~/components/repo/settings/CronTab.vue';
|
|
||||||
import GeneralTab from '~/components/repo/settings/GeneralTab.vue';
|
|
||||||
import RegistriesTab from '~/components/repo/settings/RegistriesTab.vue';
|
|
||||||
import SecretsTab from '~/components/repo/settings/SecretsTab.vue';
|
|
||||||
import useNotifications from '~/compositions/useNotifications';
|
import useNotifications from '~/compositions/useNotifications';
|
||||||
import { useRouteBack } from '~/compositions/useRouteBack';
|
import { useRouteBack } from '~/compositions/useRouteBack';
|
||||||
import type { Repo, RepoPermissions } from '~/lib/api/types';
|
import type { Repo, RepoPermissions } from '~/lib/api/types';
|
27
web/src/views/user/UserWrapper.vue
Normal file
27
web/src/views/user/UserWrapper.vue
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
<template>
|
||||||
|
<Scaffold enable-tabs>
|
||||||
|
<template #title>{{ $t('user.settings.settings') }}</template>
|
||||||
|
<template #headerActions><Button :text="$t('logout')" :to="`${address}/logout`" /></template>
|
||||||
|
|
||||||
|
<Tab :to="{ name: 'user' }" :title="$t('user.settings.general.general')" />
|
||||||
|
<Tab :to="{ name: 'user-secrets' }" :title="$t('secrets.secrets')" />
|
||||||
|
<Tab :to="{ name: 'user-registries' }" :title="$t('registries.registries')" />
|
||||||
|
<Tab :to="{ name: 'user-cli-and-api' }" :title="$t('user.settings.cli_and_api.cli_and_api')" />
|
||||||
|
<Tab
|
||||||
|
v-if="useConfig().userRegisteredAgents"
|
||||||
|
:to="{ name: 'user-agents' }"
|
||||||
|
:title="$t('admin.settings.agents.agents')"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<router-view />
|
||||||
|
</Scaffold>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import Button from '~/components/atomic/Button.vue';
|
||||||
|
import Scaffold from '~/components/layout/scaffold/Scaffold.vue';
|
||||||
|
import Tab from '~/components/layout/scaffold/Tab.vue';
|
||||||
|
import useConfig from '~/compositions/useConfig';
|
||||||
|
|
||||||
|
const address = `${window.location.protocol}//${window.location.host}${useConfig().rootPath}`; // port is included in location.host
|
||||||
|
</script>
|
Loading…
Reference in a new issue