mirror of
https://github.com/woodpecker-ci/woodpecker.git
synced 2024-11-22 01:41:02 +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>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, ref, watch } from 'vue';
|
||||
|
||||
import Container from '~/components/layout/Container.vue';
|
||||
import { useTabsProvider } from '~/compositions/useTabs';
|
||||
|
||||
|
@ -33,37 +31,16 @@ const props = defineProps<{
|
|||
|
||||
// Tabs
|
||||
enableTabs?: boolean;
|
||||
disableTabUrlHashMode?: boolean;
|
||||
activeTab?: string;
|
||||
|
||||
// Content
|
||||
fluidContent?: boolean;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(event: 'update:activeTab', value: string | undefined): void;
|
||||
defineEmits<{
|
||||
(event: 'update:search', value: string): void;
|
||||
}>();
|
||||
|
||||
if (props.enableTabs) {
|
||||
const internalActiveTab = ref(props.activeTab);
|
||||
|
||||
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),
|
||||
});
|
||||
useTabsProvider();
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -1,38 +1,40 @@
|
|||
<template>
|
||||
<div v-if="$slots.default" v-show="isActive" :aria-hidden="!isActive">
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
<template><span /></template>
|
||||
|
||||
<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 { useTabsClient, type Tab } from '~/compositions/useTabs';
|
||||
import { useTabsClient } from '~/compositions/useTabs';
|
||||
|
||||
const props = defineProps<{
|
||||
id?: string;
|
||||
to: RouteLocationRaw;
|
||||
title: string;
|
||||
icon?: IconNames;
|
||||
iconClass?: string;
|
||||
matchChildren?: boolean;
|
||||
}>();
|
||||
|
||||
const { tabs, activeTab } = useTabsClient();
|
||||
const tab = ref<Tab>();
|
||||
const { tabs } = useTabsClient();
|
||||
|
||||
// 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(() => {
|
||||
tab.value = {
|
||||
id: props.id || props.title.toLocaleLowerCase().replace(' ', '-') || tabs.value.length.toString(),
|
||||
// don't add tab if tab id is already present
|
||||
if (tabs.value.find(({ to }) => isSameRoute(to, props.to))) {
|
||||
return;
|
||||
}
|
||||
|
||||
tabs.value.push({
|
||||
to: props.to,
|
||||
title: props.title,
|
||||
icon: props.icon,
|
||||
iconClass: props.iconClass,
|
||||
};
|
||||
|
||||
// don't add tab if tab id is already present
|
||||
if (!tabs.value.find(({ id }) => id === props.id)) {
|
||||
tabs.value.push(tab.value);
|
||||
}
|
||||
matchChildren: props.matchChildren,
|
||||
});
|
||||
});
|
||||
|
||||
const isActive = computed(() => tab.value && tab.value.id === activeTab.value);
|
||||
</script>
|
||||
|
|
|
@ -1,46 +1,27 @@
|
|||
<template>
|
||||
<div class="flex flex-wrap">
|
||||
<button
|
||||
<router-link
|
||||
v-for="tab in tabs"
|
||||
:key="tab.id"
|
||||
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"
|
||||
:class="{
|
||||
'border-wp-text-100': activeTab === tab.id,
|
||||
'border-transparent': activeTab !== tab.id,
|
||||
}"
|
||||
type="button"
|
||||
@click="selectTab(tab)"
|
||||
:key="tab.title"
|
||||
v-slot="{ isActive, isExactActive }"
|
||||
:to="tab.to"
|
||||
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"
|
||||
:active-class="tab.matchChildren ? '!border-wp-text-100' : ''"
|
||||
:exact-active-class="tab.matchChildren ? '' : '!border-wp-text-100'"
|
||||
>
|
||||
<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" />
|
||||
<span class="flex gap-2 items-center flex-row-reverse md:flex-row">
|
||||
<Icon v-if="tab.icon" :name="tab.icon" :class="tab.iconClass" />
|
||||
<span>{{ tab.title }}</span>
|
||||
</span>
|
||||
</button>
|
||||
</router-link>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
|
||||
import Icon from '~/components/atomic/Icon.vue';
|
||||
import { useTabsClient, type Tab } from '~/compositions/useTabs';
|
||||
import { useTabsClient } from '~/compositions/useTabs';
|
||||
|
||||
const router = useRouter();
|
||||
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}` });
|
||||
}
|
||||
}
|
||||
const { tabs } = useTabsClient();
|
||||
</script>
|
||||
|
|
|
@ -1,12 +1,17 @@
|
|||
import type { InjectionKey, Ref } 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 {
|
||||
repo: Ref<Repo>;
|
||||
org: Ref<Org | 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] {
|
||||
|
|
|
@ -1,49 +1,24 @@
|
|||
import { inject, onMounted, provide, ref, type Ref } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { ref } from 'vue';
|
||||
import type { RouteLocationRaw } from 'vue-router';
|
||||
|
||||
import type { IconNames } from '~/components/atomic/Icon.vue';
|
||||
|
||||
import { inject, provide } from './useInjectProvide';
|
||||
|
||||
export interface Tab {
|
||||
id: string;
|
||||
to: RouteLocationRaw;
|
||||
title: string;
|
||||
icon?: IconNames;
|
||||
iconClass?: string;
|
||||
matchChildren?: boolean;
|
||||
}
|
||||
|
||||
export function useTabsProvider({
|
||||
activeTab,
|
||||
disableUrlHashMode,
|
||||
}: {
|
||||
activeTab: Ref<string | undefined>;
|
||||
disableUrlHashMode: Ref<boolean>;
|
||||
}) {
|
||||
const route = useRoute();
|
||||
|
||||
export function useTabsProvider() {
|
||||
const tabs = ref<Tab[]>([]);
|
||||
|
||||
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() {
|
||||
const tabs = inject<Ref<Tab[]>>('tabs');
|
||||
const disableUrlHashMode = inject<Ref<boolean>>('disable-url-hash-mode');
|
||||
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 };
|
||||
const tabs = inject('tabs');
|
||||
return { tabs };
|
||||
}
|
||||
|
|
|
@ -42,29 +42,38 @@ const routes: RouteRecordRaw[] = [
|
|||
},
|
||||
{
|
||||
path: 'branches',
|
||||
name: 'repo-branches',
|
||||
component: (): Component => import('~/views/repo/RepoBranches.vue'),
|
||||
meta: { repoHeader: true },
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
name: 'repo-branches',
|
||||
component: (): Component => import('~/views/repo/RepoBranches.vue'),
|
||||
},
|
||||
{
|
||||
path: ':branch',
|
||||
name: 'repo-branch',
|
||||
component: (): Component => import('~/views/repo/RepoBranch.vue'),
|
||||
props: (route) => ({ branch: route.params.branch }),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: 'branches/:branch',
|
||||
name: 'repo-branch',
|
||||
component: (): Component => import('~/views/repo/RepoBranch.vue'),
|
||||
meta: { repoHeader: true },
|
||||
props: (route) => ({ branch: route.params.branch }),
|
||||
},
|
||||
|
||||
{
|
||||
path: 'pull-requests',
|
||||
name: 'repo-pull-requests',
|
||||
component: (): Component => import('~/views/repo/RepoPullRequests.vue'),
|
||||
meta: { repoHeader: true },
|
||||
},
|
||||
{
|
||||
path: 'pull-requests/:pullRequest',
|
||||
name: 'repo-pull-request',
|
||||
component: (): Component => import('~/views/repo/RepoPullRequest.vue'),
|
||||
meta: { repoHeader: true },
|
||||
props: (route) => ({ pullRequest: route.params.pullRequest }),
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
name: 'repo-pull-requests',
|
||||
component: (): Component => import('~/views/repo/RepoPullRequests.vue'),
|
||||
},
|
||||
{
|
||||
path: ':pullRequest',
|
||||
name: 'repo-pull-request',
|
||||
component: (): Component => import('~/views/repo/RepoPullRequest.vue'),
|
||||
props: (route) => ({ pullRequest: route.params.pullRequest }),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: 'pipeline/:pipelineId',
|
||||
|
@ -98,15 +107,53 @@ const routes: RouteRecordRaw[] = [
|
|||
path: 'debug',
|
||||
name: 'repo-pipeline-debug',
|
||||
component: (): Component => import('~/views/repo/pipeline/PipelineDebug.vue'),
|
||||
props: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: 'settings',
|
||||
name: 'repo-settings',
|
||||
component: (): Component => import('~/views/repo/RepoSettings.vue'),
|
||||
component: (): Component => import('~/views/repo/settings/RepoSettings.vue'),
|
||||
meta: { authentication: 'required' },
|
||||
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',
|
||||
|
@ -137,9 +184,29 @@ const routes: RouteRecordRaw[] = [
|
|||
{
|
||||
path: 'settings',
|
||||
name: 'org-settings',
|
||||
component: (): Component => import('~/views/org/OrgSettings.vue'),
|
||||
component: (): Component => import('~/views/org/settings/OrgSettingsWrapper.vue'),
|
||||
meta: { authentication: 'required' },
|
||||
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`,
|
||||
name: 'admin-settings',
|
||||
component: (): Component => import('~/views/admin/AdminSettings.vue'),
|
||||
component: (): Component => import('~/views/admin/AdminSettingsWrapper.vue'),
|
||||
props: true,
|
||||
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`,
|
||||
name: 'user',
|
||||
component: (): Component => import('~/views/User.vue'),
|
||||
component: (): Component => import('~/views/user/UserWrapper.vue'),
|
||||
meta: { authentication: 'required' },
|
||||
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`,
|
||||
|
|
|
@ -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 { useI18n } from 'vue-i18n';
|
||||
|
||||
import AdminQueueStats from '~/components/admin/settings/queue/AdminQueueStats.vue';
|
||||
import Badge from '~/components/atomic/Badge.vue';
|
||||
import Button from '~/components/atomic/Button.vue';
|
||||
import Icon from '~/components/atomic/Icon.vue';
|
||||
|
@ -87,8 +88,6 @@ import useApiClient from '~/compositions/useApiClient';
|
|||
import useNotifications from '~/compositions/useNotifications';
|
||||
import type { QueueInfo } from '~/lib/api/types';
|
||||
|
||||
import AdminQueueStats from './queue/AdminQueueStats.vue';
|
||||
|
||||
const apiClient = useApiClient();
|
||||
const notifications = useNotifications();
|
||||
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
|
||||
v-if="orgPermissions.admin"
|
||||
icon="settings"
|
||||
:to="{ name: org.is_user ? 'user' : 'org-settings' }"
|
||||
:to="{ name: org.is_user ? 'user' : 'org-settings-secrets' }"
|
||||
:title="$t('settings')"
|
||||
/>
|
||||
</template>
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
<template #headerActions>
|
||||
<IconButton
|
||||
v-if="orgPermissions.admin"
|
||||
:to="{ name: org.is_user ? 'user' : 'repo-settings' }"
|
||||
:to="{ name: org.is_user ? 'user' : 'org-settings-secrets' }"
|
||||
:title="$t('settings')"
|
||||
icon="settings"
|
||||
/>
|
||||
|
|
|
@ -11,17 +11,15 @@
|
|||
</span>
|
||||
</template>
|
||||
|
||||
<Tab id="secrets" :title="$t('secrets.secrets')">
|
||||
<OrgSecretsTab />
|
||||
</Tab>
|
||||
<Tab :to="{ name: 'org-settings-secrets' }" :title="$t('secrets.secrets')" />
|
||||
<Tab :to="{ name: 'org-settings-registries' }" :title="$t('registries.registries')" />
|
||||
<Tab
|
||||
v-if="useConfig().userRegisteredAgents"
|
||||
:to="{ name: 'org-settings-agents' }"
|
||||
:title="$t('admin.settings.agents.agents')"
|
||||
/>
|
||||
|
||||
<Tab id="registries" :title="$t('registries.registries')">
|
||||
<OrgRegistriesTab />
|
||||
</Tab>
|
||||
|
||||
<Tab v-if="useConfig().userRegisteredAgents" id="agents" :title="$t('admin.settings.agents.agents')">
|
||||
<OrgAgentsTab />
|
||||
</Tab>
|
||||
<router-view />
|
||||
</Scaffold>
|
||||
</template>
|
||||
|
||||
|
@ -32,9 +30,6 @@ import { useRouter } from 'vue-router';
|
|||
|
||||
import Scaffold from '~/components/layout/scaffold/Scaffold.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 { inject } from '~/compositions/useInjectProvide';
|
||||
import useNotifications from '~/compositions/useNotifications';
|
|
@ -1,10 +1,5 @@
|
|||
<template>
|
||||
<Scaffold
|
||||
v-if="repo && repoPermissions && route.meta.repoHeader"
|
||||
v-model:active-tab="activeTab"
|
||||
enable-tabs
|
||||
disable-tab-url-hash-mode
|
||||
>
|
||||
<Scaffold v-if="repo && repoPermissions && route.meta.repoHeader" enable-tabs>
|
||||
<template #title>
|
||||
<span class="flex">
|
||||
<router-link :to="{ name: 'org', params: { orgId: repo.org_id } }" class="hover:underline">{{
|
||||
|
@ -43,9 +38,14 @@
|
|||
/>
|
||||
</template>
|
||||
|
||||
<Tab id="activity" :title="$t('repo.activity')" />
|
||||
<Tab id="branches" :title="$t('repo.branches')" />
|
||||
<Tab v-if="repo.pr_enabled && repo.allow_pr" id="pull_requests" :title="$t('repo.pull_requests')" />
|
||||
<Tab :to="{ name: 'repo' }" :title="$t('repo.activity')" />
|
||||
<Tab :to="{ name: 'repo-branches' }" match-children :title="$t('repo.branches')" />
|
||||
<Tab
|
||||
v-if="repo.pr_enabled && repo.allow_pr"
|
||||
:to="{ name: 'repo-pull-requests' }"
|
||||
match-children
|
||||
:title="$t('repo.pull_requests')"
|
||||
/>
|
||||
|
||||
<router-view />
|
||||
</Scaffold>
|
||||
|
@ -132,25 +132,4 @@ watch([repositoryId], () => {
|
|||
});
|
||||
|
||||
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>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<template>
|
||||
<div class="flex flex-col gap-y-6">
|
||||
<Panel
|
||||
v-for="pipelineConfig in pipelineConfigsDecoded || []"
|
||||
v-for="pipelineConfig in pipelineConfigsDecoded"
|
||||
:key="pipelineConfig.hash"
|
||||
:collapsable="pipelineConfigsDecoded && pipelineConfigsDecoded.length > 1"
|
||||
collapsed-by-default
|
||||
|
@ -14,21 +14,22 @@
|
|||
|
||||
<script lang="ts" setup>
|
||||
import { decode } from 'js-base64';
|
||||
import { computed, inject, type Ref } from 'vue';
|
||||
import { computed } from 'vue';
|
||||
|
||||
import SyntaxHighlight from '~/components/atomic/SyntaxHighlight';
|
||||
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) {
|
||||
throw new Error('Unexpected: "pipelineConfigs" should be provided at this place');
|
||||
}
|
||||
|
||||
const pipelineConfigsDecoded = computed(() =>
|
||||
pipelineConfigs.value.map((i) => ({
|
||||
...i,
|
||||
data: decode(i.data),
|
||||
})),
|
||||
const pipelineConfigsDecoded = computed(
|
||||
() =>
|
||||
pipelineConfigs.value?.map((i) => ({
|
||||
...i,
|
||||
data: decode(i.data),
|
||||
})) ?? [],
|
||||
);
|
||||
</script>
|
||||
|
|
|
@ -1,11 +1,9 @@
|
|||
<template>
|
||||
<Scaffold
|
||||
v-if="pipeline && repo"
|
||||
v-model:active-tab="activeTab"
|
||||
enable-tabs
|
||||
disable-tab-url-hash-mode
|
||||
:go-back="goBack"
|
||||
:fluid-content="activeTab === 'tasks'"
|
||||
:fluid-content="route.name === 'repo-pipeline'"
|
||||
full-width-header
|
||||
>
|
||||
<template #title>
|
||||
|
@ -75,10 +73,10 @@
|
|||
</div>
|
||||
</template>
|
||||
|
||||
<Tab id="tasks" :title="$t('repo.pipeline.tasks')" />
|
||||
<Tab :to="{ name: 'repo-pipeline' }" :title="$t('repo.pipeline.tasks')" />
|
||||
<Tab
|
||||
v-if="pipeline.errors && pipeline.errors.length > 0"
|
||||
id="errors"
|
||||
:to="{ name: 'repo-pipeline-errors' }"
|
||||
icon="attention"
|
||||
:title="
|
||||
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'"
|
||||
/>
|
||||
<Tab id="config" :title="$t('repo.pipeline.config')" />
|
||||
<Tab :to="{ name: 'repo-pipeline-config' }" :title="$t('repo.pipeline.config')" />
|
||||
<Tab
|
||||
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 })"
|
||||
/>
|
||||
<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 />
|
||||
</Scaffold>
|
||||
</template>
|
||||
|
||||
<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 { useRoute, useRouter } from 'vue-router';
|
||||
|
||||
|
@ -113,6 +115,7 @@ import PipelineStatusIcon from '~/components/repo/pipeline/PipelineStatusIcon.vu
|
|||
import useApiClient from '~/compositions/useApiClient';
|
||||
import { useAsyncAction } from '~/compositions/useAsyncAction';
|
||||
import { useFavicon } from '~/compositions/useFavicon';
|
||||
import { provide } from '~/compositions/useInjectProvide';
|
||||
import useNotifications from '~/compositions/useNotifications';
|
||||
import usePipeline from '~/compositions/usePipeline';
|
||||
import { useRouteBack } from '~/compositions/useRouteBack';
|
||||
|
@ -206,48 +209,5 @@ onBeforeUnmount(() => {
|
|||
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' });
|
||||
</script>
|
||||
|
|
|
@ -16,24 +16,14 @@
|
|||
</span>
|
||||
</template>
|
||||
|
||||
<Tab id="general" :title="$t('repo.settings.general.general')">
|
||||
<GeneralTab />
|
||||
</Tab>
|
||||
<Tab id="secrets" :title="$t('secrets.secrets')">
|
||||
<SecretsTab />
|
||||
</Tab>
|
||||
<Tab id="registries" :title="$t('registries.registries')">
|
||||
<RegistriesTab />
|
||||
</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>
|
||||
<Tab :to="{ name: 'repo-settings' }" :title="$t('repo.settings.general.general')" />
|
||||
<Tab :to="{ name: 'repo-settings-secrets' }" :title="$t('secrets.secrets')" />
|
||||
<Tab :to="{ name: 'repo-settings-registries' }" :title="$t('registries.registries')" />
|
||||
<Tab :to="{ name: 'repo-settings-crons' }" :title="$t('repo.settings.crons.crons')" />
|
||||
<Tab :to="{ name: 'repo-settings-badge' }" :title="$t('repo.settings.badge.badge')" />
|
||||
<Tab :to="{ name: 'repo-settings-actions' }" :title="$t('repo.settings.actions.actions')" />
|
||||
|
||||
<router-view />
|
||||
</Scaffold>
|
||||
</template>
|
||||
|
||||
|
@ -44,12 +34,6 @@ import { useRouter } from 'vue-router';
|
|||
|
||||
import Scaffold from '~/components/layout/scaffold/Scaffold.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 { useRouteBack } from '~/compositions/useRouteBack';
|
||||
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