Use centrally typed inject provide in Vue (#5113)

Co-authored-by: Robert Kaussow <mail@thegeeklab.de>
This commit is contained in:
Lukas 2025-04-23 09:40:33 +02:00 committed by GitHub
parent cc4b362410
commit 208ac771d4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
31 changed files with 123 additions and 320 deletions

View file

@ -39,7 +39,7 @@ import TextField from '~/components/form/TextField.vue';
import Panel from '~/components/layout/Panel.vue'; import Panel from '~/components/layout/Panel.vue';
import Popup from '~/components/layout/Popup.vue'; import Popup from '~/components/layout/Popup.vue';
import useApiClient from '~/compositions/useApiClient'; import useApiClient from '~/compositions/useApiClient';
import { inject } from '~/compositions/useInjectProvide'; import { requiredInject } from '~/compositions/useInjectProvide';
const props = defineProps<{ const props = defineProps<{
open: boolean; open: boolean;
@ -51,7 +51,7 @@ const emit = defineEmits<{
}>(); }>();
const apiClient = useApiClient(); const apiClient = useApiClient();
const repo = inject('repo'); const repo = requiredInject('repo');
const router = useRouter(); const router = useRouter();
const payload = ref<{ const payload = ref<{

View file

@ -111,16 +111,16 @@ import { useStorage } from '@vueuse/core';
import { AnsiUp } from 'ansi_up'; import { AnsiUp } from 'ansi_up';
import { decode } from 'js-base64'; import { decode } from 'js-base64';
import { debounce } from 'lodash'; import { debounce } from 'lodash';
import { computed, inject, nextTick, onBeforeUnmount, onMounted, ref, toRef, watch } from 'vue'; import { computed, nextTick, onBeforeUnmount, onMounted, ref, toRef, watch } from 'vue';
import type { Ref } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import IconButton from '~/components/atomic/IconButton.vue'; import IconButton from '~/components/atomic/IconButton.vue';
import PipelineStatusIcon from '~/components/repo/pipeline/PipelineStatusIcon.vue'; import PipelineStatusIcon from '~/components/repo/pipeline/PipelineStatusIcon.vue';
import useApiClient from '~/compositions/useApiClient'; import useApiClient from '~/compositions/useApiClient';
import { requiredInject } from '~/compositions/useInjectProvide';
import useNotifications from '~/compositions/useNotifications'; import useNotifications from '~/compositions/useNotifications';
import type { Pipeline, PipelineStep, PipelineWorkflow, Repo, RepoPermissions } from '~/lib/api/types'; import type { Pipeline, PipelineStep, PipelineWorkflow } from '~/lib/api/types';
interface LogLine { interface LogLine {
index: number; index: number;
@ -143,8 +143,8 @@ const notifications = useNotifications();
const i18n = useI18n(); const i18n = useI18n();
const pipeline = toRef(props, 'pipeline'); const pipeline = toRef(props, 'pipeline');
const stepId = toRef(props, 'stepId'); const stepId = toRef(props, 'stepId');
const repo = inject<Ref<Repo>>('repo'); const repo = requiredInject('repo');
const repoPermissions = inject<Ref<RepoPermissions>>('repo-permissions'); const repoPermissions = requiredInject('repo-permissions');
const apiClient = useApiClient(); const apiClient = useApiClient();
const route = useRoute(); const route = useRoute();
@ -286,10 +286,6 @@ async function loadLogs() {
return; return;
} }
if (!repo) {
throw new Error('Unexpected: "repo" should be provided at this place');
}
log.value = undefined; log.value = undefined;
logBuffer.value = []; logBuffer.value = [];
ansiUp.value = new AnsiUp(); ansiUp.value = new AnsiUp();

View file

@ -118,17 +118,17 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { computed, inject, nextTick, ref, toRef, useTemplateRef, watch } from 'vue'; import { computed, nextTick, ref, toRef, useTemplateRef, watch } from 'vue';
import type { Ref } from 'vue';
import Badge from '~/components/atomic/Badge.vue'; import Badge from '~/components/atomic/Badge.vue';
import Icon from '~/components/atomic/Icon.vue'; import Icon from '~/components/atomic/Icon.vue';
import Panel from '~/components/layout/Panel.vue'; import Panel from '~/components/layout/Panel.vue';
import PipelineStatusIcon from '~/components/repo/pipeline/PipelineStatusIcon.vue'; import PipelineStatusIcon from '~/components/repo/pipeline/PipelineStatusIcon.vue';
import PipelineStepDuration from '~/components/repo/pipeline/PipelineStepDuration.vue'; import PipelineStepDuration from '~/components/repo/pipeline/PipelineStepDuration.vue';
import { requiredInject } from '~/compositions/useInjectProvide';
import usePipeline from '~/compositions/usePipeline'; import usePipeline from '~/compositions/usePipeline';
import { StepType } from '~/lib/api/types'; import { StepType } from '~/lib/api/types';
import type { Pipeline, PipelineConfig, PipelineStep } from '~/lib/api/types'; import type { Pipeline, PipelineStep } from '~/lib/api/types';
const props = defineProps<{ const props = defineProps<{
pipeline: Pipeline; pipeline: Pipeline;
@ -142,7 +142,7 @@ defineEmits<{
const pipeline = toRef(props, 'pipeline'); const pipeline = toRef(props, 'pipeline');
const selectedStepId = toRef(props, 'selectedStepId'); const selectedStepId = toRef(props, 'selectedStepId');
const { prettyRef } = usePipeline(pipeline); const { prettyRef } = usePipeline(pipeline);
const pipelineConfigs = inject<Ref<PipelineConfig[]>>('pipeline-configs'); const pipelineConfigs = requiredInject('pipeline-configs');
const workflowsCollapsed = ref<Record<PipelineStep['id'], boolean>>( const workflowsCollapsed = ref<Record<PipelineStep['id'], boolean>>(
pipeline.value.workflows && pipeline.value.workflows.length > 1 pipeline.value.workflows && pipeline.value.workflows.length > 1

View file

@ -1,23 +1,25 @@
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, Pipeline, PipelineConfig, Repo } from '~/lib/api/types'; import type { Org, OrgPermissions, Pipeline, PipelineConfig, Repo, RepoPermissions } from '~/lib/api/types';
import type { Tab } from './useTabs'; import type { Tab } from './useTabs';
export interface InjectKeys { export interface InjectKeys {
repo: Ref<Repo>; repo: Ref<Repo>;
org: Ref<Org | undefined>; 'repo-permissions': Ref<RepoPermissions>;
'org-permissions': Ref<OrgPermissions | undefined>; org: Ref<Org>;
pipeline: Ref<Pipeline | undefined>; 'org-permissions': Ref<OrgPermissions>;
pipeline: Ref<Pipeline>;
'pipeline-configs': Ref<PipelineConfig[] | undefined>; 'pipeline-configs': Ref<PipelineConfig[] | undefined>;
tabs: Ref<Tab[]>; tabs: Ref<Tab[]>;
pipelines: Ref<Pipeline[]>;
} }
export function inject<T extends keyof InjectKeys>(key: T): InjectKeys[T] { export function requiredInject<T extends keyof InjectKeys>(key: T): InjectKeys[T] {
const value = vueInject<InjectKeys[T]>(key); const value = vueInject<InjectKeys[T]>(key);
if (value === undefined) { if (value === undefined) {
throw new Error(`Please provide a value for ${key}`); throw new Error(`Unexpected: ${key} should be provided at this place`);
} }
return value; return value;
} }

View file

@ -3,7 +3,7 @@ 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'; import { provide, requiredInject } from './useInjectProvide';
export interface Tab { export interface Tab {
to: RouteLocationRaw; to: RouteLocationRaw;
@ -20,6 +20,6 @@ export function useTabsProvider() {
} }
export function useTabsClient() { export function useTabsClient() {
const tabs = inject('tabs'); const tabs = requiredInject('tabs');
return { tabs }; return { tabs };
} }

View file

@ -28,7 +28,7 @@ import { computed, onMounted, ref } from 'vue';
import IconButton from '~/components/atomic/IconButton.vue'; import IconButton from '~/components/atomic/IconButton.vue';
import Scaffold from '~/components/layout/scaffold/Scaffold.vue'; import Scaffold from '~/components/layout/scaffold/Scaffold.vue';
import RepoItem from '~/components/repo/RepoItem.vue'; import RepoItem from '~/components/repo/RepoItem.vue';
import { inject } from '~/compositions/useInjectProvide'; import { requiredInject } from '~/compositions/useInjectProvide';
import useRepos from '~/compositions/useRepos'; import useRepos from '~/compositions/useRepos';
import { useRepoSearch } from '~/compositions/useRepoSearch'; import { useRepoSearch } from '~/compositions/useRepoSearch';
import { useRepoStore } from '~/store/repos'; import { useRepoStore } from '~/store/repos';
@ -36,8 +36,8 @@ import { useRepoStore } from '~/store/repos';
const repoStore = useRepoStore(); const repoStore = useRepoStore();
const { repoWithLastPipeline, sortReposByLastActivity } = useRepos(); const { repoWithLastPipeline, sortReposByLastActivity } = useRepos();
const org = inject('org'); const org = requiredInject('org');
const orgPermissions = inject('org-permissions'); const orgPermissions = requiredInject('org-permissions');
const search = ref(''); const search = ref('');
const repos = computed(() => const repos = computed(() =>

View file

@ -19,6 +19,7 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import type { Ref } from 'vue';
import { computed, onMounted, ref, watch } from 'vue'; import { computed, onMounted, ref, watch } from 'vue';
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
@ -38,8 +39,8 @@ const route = useRoute();
const org = ref<Org>(); const org = ref<Org>();
const orgPermissions = ref<OrgPermissions>(); const orgPermissions = ref<OrgPermissions>();
provide('org', org); provide('org', org as Ref<Org>); // can't be undefined because of v-if in template
provide('org-permissions', orgPermissions); provide('org-permissions', orgPermissions as Ref<OrgPermissions>); // can't be undefined because of v-if in template
async function load() { async function load() {
org.value = await apiClient.getOrg(orgId.value); org.value = await apiClient.getOrg(orgId.value);

View file

@ -9,18 +9,13 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { inject } from 'vue';
import type { Ref } from 'vue';
import AgentManager from '~/components/agent/AgentManager.vue'; import AgentManager from '~/components/agent/AgentManager.vue';
import useApiClient from '~/compositions/useApiClient'; import useApiClient from '~/compositions/useApiClient';
import type { Agent, Org } from '~/lib/api/types'; import { requiredInject } from '~/compositions/useInjectProvide';
import type { Agent } from '~/lib/api/types';
const apiClient = useApiClient(); const apiClient = useApiClient();
const org = inject<Ref<Org>>('org'); const org = requiredInject('org');
if (org === undefined) {
throw new Error('Unexpected: "org" should be provided at this place');
}
const loadAgents = (page: number) => apiClient.getOrgAgents(org.value.id, { page }); const loadAgents = (page: number) => apiClient.getOrgAgents(org.value.id, { page });
const createAgent = (agent: Partial<Agent>) => apiClient.createOrgAgent(org.value.id, agent); const createAgent = (agent: Partial<Agent>) => apiClient.createOrgAgent(org.value.id, agent);

View file

@ -34,8 +34,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import { cloneDeep } from 'lodash'; import { cloneDeep } from 'lodash';
import { computed, inject, ref } from 'vue'; import { computed, ref } from 'vue';
import type { Ref } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import Button from '~/components/atomic/Button.vue'; import Button from '~/components/atomic/Button.vue';
@ -44,9 +43,10 @@ import RegistryEdit from '~/components/registry/RegistryEdit.vue';
import RegistryList from '~/components/registry/RegistryList.vue'; import RegistryList from '~/components/registry/RegistryList.vue';
import useApiClient from '~/compositions/useApiClient'; import useApiClient from '~/compositions/useApiClient';
import { useAsyncAction } from '~/compositions/useAsyncAction'; import { useAsyncAction } from '~/compositions/useAsyncAction';
import { requiredInject } from '~/compositions/useInjectProvide';
import useNotifications from '~/compositions/useNotifications'; import useNotifications from '~/compositions/useNotifications';
import { usePagination } from '~/compositions/usePaginate'; import { usePagination } from '~/compositions/usePaginate';
import type { Org, Registry } from '~/lib/api/types'; import type { Registry } from '~/lib/api/types';
const emptyRegistry: Partial<Registry> = { const emptyRegistry: Partial<Registry> = {
address: '', address: '',
@ -58,25 +58,17 @@ const apiClient = useApiClient();
const notifications = useNotifications(); const notifications = useNotifications();
const i18n = useI18n(); const i18n = useI18n();
const org = inject<Ref<Org>>('org'); const org = requiredInject('org');
const selectedRegistry = ref<Partial<Registry>>(); const selectedRegistry = ref<Partial<Registry>>();
const isEditing = computed(() => !!selectedRegistry.value?.id); const isEditing = computed(() => !!selectedRegistry.value?.id);
async function loadRegistries(page: number): Promise<Registry[] | null> { async function loadRegistries(page: number): Promise<Registry[] | null> {
if (!org?.value) {
throw new Error("Unexpected: Can't load org");
}
return apiClient.getOrgRegistryList(org.value.id, { page }); return apiClient.getOrgRegistryList(org.value.id, { page });
} }
const { resetPage, data: registries } = usePagination(loadRegistries, () => !selectedRegistry.value); const { resetPage, data: registries } = usePagination(loadRegistries, () => !selectedRegistry.value);
const { doSubmit: createRegistry, isLoading: isSaving } = useAsyncAction(async () => { const { doSubmit: createRegistry, isLoading: isSaving } = useAsyncAction(async () => {
if (!org?.value) {
throw new Error("Unexpected: Can't load org");
}
if (!selectedRegistry.value) { if (!selectedRegistry.value) {
throw new Error("Unexpected: Can't get registry"); throw new Error("Unexpected: Can't get registry");
} }
@ -95,10 +87,6 @@ const { doSubmit: createRegistry, isLoading: isSaving } = useAsyncAction(async (
}); });
const { doSubmit: deleteRegistry, isLoading: isDeleting } = useAsyncAction(async (_registry: Registry) => { const { doSubmit: deleteRegistry, isLoading: isDeleting } = useAsyncAction(async (_registry: Registry) => {
if (!org?.value) {
throw new Error("Unexpected: Can't load org");
}
await apiClient.deleteOrgRegistry(org.value.id, _registry.address); await apiClient.deleteOrgRegistry(org.value.id, _registry.address);
notifications.notify({ title: i18n.t('registries.deleted'), type: 'success' }); notifications.notify({ title: i18n.t('registries.deleted'), type: 'success' });
resetPage(); resetPage();

View file

@ -25,8 +25,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import { cloneDeep } from 'lodash'; import { cloneDeep } from 'lodash';
import { computed, inject, ref } from 'vue'; import { computed, ref } from 'vue';
import type { Ref } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import Button from '~/components/atomic/Button.vue'; import Button from '~/components/atomic/Button.vue';
@ -35,10 +34,11 @@ import SecretEdit from '~/components/secrets/SecretEdit.vue';
import SecretList from '~/components/secrets/SecretList.vue'; import SecretList from '~/components/secrets/SecretList.vue';
import useApiClient from '~/compositions/useApiClient'; import useApiClient from '~/compositions/useApiClient';
import { useAsyncAction } from '~/compositions/useAsyncAction'; import { useAsyncAction } from '~/compositions/useAsyncAction';
import { requiredInject } from '~/compositions/useInjectProvide';
import useNotifications from '~/compositions/useNotifications'; import useNotifications from '~/compositions/useNotifications';
import { usePagination } from '~/compositions/usePaginate'; import { usePagination } from '~/compositions/usePaginate';
import { WebhookEvents } from '~/lib/api/types'; import { WebhookEvents } from '~/lib/api/types';
import type { Org, Secret } from '~/lib/api/types'; import type { Secret } from '~/lib/api/types';
const emptySecret: Partial<Secret> = { const emptySecret: Partial<Secret> = {
name: '', name: '',
@ -51,25 +51,17 @@ const apiClient = useApiClient();
const notifications = useNotifications(); const notifications = useNotifications();
const i18n = useI18n(); const i18n = useI18n();
const org = inject<Ref<Org>>('org'); const org = requiredInject('org');
const selectedSecret = ref<Partial<Secret>>(); const selectedSecret = ref<Partial<Secret>>();
const isEditingSecret = computed(() => !!selectedSecret.value?.id); const isEditingSecret = computed(() => !!selectedSecret.value?.id);
async function loadSecrets(page: number): Promise<Secret[] | null> { async function loadSecrets(page: number): Promise<Secret[] | null> {
if (!org?.value) {
throw new Error("Unexpected: Can't load org");
}
return apiClient.getOrgSecretList(org.value.id, { page }); return apiClient.getOrgSecretList(org.value.id, { page });
} }
const { resetPage, data: secrets } = usePagination(loadSecrets, () => !selectedSecret.value); const { resetPage, data: secrets } = usePagination(loadSecrets, () => !selectedSecret.value);
const { doSubmit: createSecret, isLoading: isSaving } = useAsyncAction(async () => { const { doSubmit: createSecret, isLoading: isSaving } = useAsyncAction(async () => {
if (!org?.value) {
throw new Error("Unexpected: Can't load org");
}
if (!selectedSecret.value) { if (!selectedSecret.value) {
throw new Error("Unexpected: Can't get secret"); throw new Error("Unexpected: Can't get secret");
} }
@ -88,10 +80,6 @@ const { doSubmit: createSecret, isLoading: isSaving } = useAsyncAction(async ()
}); });
const { doSubmit: deleteSecret, isLoading: isDeleting } = useAsyncAction(async (_secret: Secret) => { const { doSubmit: deleteSecret, isLoading: isDeleting } = useAsyncAction(async (_secret: Secret) => {
if (!org?.value) {
throw new Error("Unexpected: Can't load org");
}
await apiClient.deleteOrgSecret(org.value.id, _secret.name); await apiClient.deleteOrgSecret(org.value.id, _secret.name);
notifications.notify({ title: i18n.t('secrets.deleted'), type: 'success' }); notifications.notify({ title: i18n.t('secrets.deleted'), type: 'success' });
resetPage(); resetPage();

View file

@ -32,7 +32,7 @@ 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 useConfig from '~/compositions/useConfig'; import useConfig from '~/compositions/useConfig';
import { inject } from '~/compositions/useInjectProvide'; import { requiredInject } from '~/compositions/useInjectProvide';
import useNotifications from '~/compositions/useNotifications'; import useNotifications from '~/compositions/useNotifications';
import { useRouteBack } from '~/compositions/useRouteBack'; import { useRouteBack } from '~/compositions/useRouteBack';
@ -40,8 +40,8 @@ const notifications = useNotifications();
const router = useRouter(); const router = useRouter();
const i18n = useI18n(); const i18n = useI18n();
const org = inject('org'); const org = requiredInject('org');
const orgPermissions = inject('org-permissions'); const orgPermissions = requiredInject('org-permissions');
onMounted(async () => { onMounted(async () => {
if (!orgPermissions.value?.admin) { if (!orgPermissions.value?.admin) {

View file

@ -6,26 +6,21 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { computed, inject, toRef } from 'vue'; import { computed, toRef } from 'vue';
import type { Ref } from 'vue';
import PipelineList from '~/components/repo/pipeline/PipelineList.vue'; import PipelineList from '~/components/repo/pipeline/PipelineList.vue';
import type { Pipeline, Repo, RepoPermissions } from '~/lib/api/types'; import { requiredInject } from '~/compositions/useInjectProvide';
const props = defineProps<{ const props = defineProps<{
branch: string; branch: string;
}>(); }>();
const branch = toRef(props, 'branch'); const branch = toRef(props, 'branch');
const repo = inject<Ref<Repo>>('repo'); const repo = requiredInject('repo');
const repoPermissions = inject<Ref<RepoPermissions>>('repo-permissions');
if (!repo || !repoPermissions) {
throw new Error('Unexpected: "repo" & "repoPermissions" should be provided at this place');
}
const allPipelines = inject<Ref<Pipeline[]>>('pipelines'); const allPipelines = requiredInject('pipelines');
const pipelines = computed(() => const pipelines = computed(() =>
allPipelines?.value.filter( allPipelines.value.filter(
(b) => b.branch === branch.value && b.event !== 'pull_request' && b.event !== 'pull_request_closed', (b) => b.branch === branch.value && b.event !== 'pull_request' && b.event !== 'pull_request_closed',
), ),
); );

View file

@ -21,29 +21,21 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { computed, inject, watch } from 'vue'; import { computed, watch } from 'vue';
import type { Ref } from 'vue';
import Badge from '~/components/atomic/Badge.vue'; import Badge from '~/components/atomic/Badge.vue';
import Icon from '~/components/atomic/Icon.vue'; import Icon from '~/components/atomic/Icon.vue';
import ListItem from '~/components/atomic/ListItem.vue'; import ListItem from '~/components/atomic/ListItem.vue';
import Panel from '~/components/layout/Panel.vue'; import Panel from '~/components/layout/Panel.vue';
import useApiClient from '~/compositions/useApiClient'; import useApiClient from '~/compositions/useApiClient';
import { requiredInject } from '~/compositions/useInjectProvide';
import { usePagination } from '~/compositions/usePaginate'; import { usePagination } from '~/compositions/usePaginate';
import type { Repo } from '~/lib/api/types';
const apiClient = useApiClient(); const apiClient = useApiClient();
const repo = inject<Ref<Repo>>('repo'); const repo = requiredInject('repo');
if (!repo) {
throw new Error('Unexpected: "repo" should be provided at this place');
}
async function loadBranches(page: number): Promise<string[]> { async function loadBranches(page: number): Promise<string[]> {
if (!repo) {
throw new Error('Unexpected: "repo" should be provided at this place');
}
return apiClient.getRepoBranches(repo.value.id, { page }); return apiClient.getRepoBranches(repo.value.id, { page });
} }

View file

@ -26,8 +26,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import { useNotification } from '@kyvg/vue3-notification'; import { useNotification } from '@kyvg/vue3-notification';
import type { Ref } from 'vue'; import { computed, onMounted, ref } from 'vue';
import { computed, onMounted, ref, inject as vueInject } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useRouter } from 'vue-router'; import { useRouter } from 'vue-router';
@ -38,9 +37,8 @@ import KeyValueEditor from '~/components/form/KeyValueEditor.vue';
import SelectField from '~/components/form/SelectField.vue'; import SelectField from '~/components/form/SelectField.vue';
import Panel from '~/components/layout/Panel.vue'; import Panel from '~/components/layout/Panel.vue';
import useApiClient from '~/compositions/useApiClient'; import useApiClient from '~/compositions/useApiClient';
import { inject } from '~/compositions/useInjectProvide'; import { requiredInject } from '~/compositions/useInjectProvide';
import { usePaginate } from '~/compositions/usePaginate'; import { usePaginate } from '~/compositions/usePaginate';
import type { RepoPermissions } from '~/lib/api/types';
defineProps<{ defineProps<{
open: boolean; open: boolean;
@ -54,11 +52,8 @@ const apiClient = useApiClient();
const notifications = useNotification(); const notifications = useNotification();
const i18n = useI18n(); const i18n = useI18n();
const repo = inject('repo'); const repo = requiredInject('repo');
const repoPermissions = vueInject<Ref<RepoPermissions>>('repo-permissions'); const repoPermissions = requiredInject('repo-permissions');
if (!repoPermissions) {
throw new Error('Unexpected: "repo" and "repoPermissions" should be provided at this place');
}
const router = useRouter(); const router = useRouter();
const branches = ref<{ text: string; value: string }[]>([]); const branches = ref<{ text: string; value: string }[]>([]);

View file

@ -3,17 +3,9 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { inject } from 'vue';
import type { Ref } from 'vue';
import PipelineList from '~/components/repo/pipeline/PipelineList.vue'; import PipelineList from '~/components/repo/pipeline/PipelineList.vue';
import type { Pipeline, Repo, RepoPermissions } from '~/lib/api/types'; import { requiredInject } from '~/compositions/useInjectProvide';
const repo = inject<Ref<Repo>>('repo'); const repo = requiredInject('repo');
const repoPermissions = inject<Ref<RepoPermissions>>('repo-permissions'); const pipelines = requiredInject('pipelines');
if (!repo || !repoPermissions) {
throw new Error('Unexpected: "repo" & "repoPermissions" should be provided at this place');
}
const pipelines = inject<Ref<Pipeline[]>>('pipelines');
</script> </script>

View file

@ -6,28 +6,23 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { computed, inject, toRef } from 'vue'; import { computed, toRef } from 'vue';
import type { Ref } from 'vue';
import PipelineList from '~/components/repo/pipeline/PipelineList.vue'; import PipelineList from '~/components/repo/pipeline/PipelineList.vue';
import type { Pipeline, Repo, RepoPermissions } from '~/lib/api/types'; import { requiredInject } from '~/compositions/useInjectProvide';
const props = defineProps<{ const props = defineProps<{
pullRequest: string; pullRequest: string;
}>(); }>();
const pullRequest = toRef(props, 'pullRequest'); const pullRequest = toRef(props, 'pullRequest');
const repo = inject<Ref<Repo>>('repo'); const repo = requiredInject('repo');
const repoPermissions = inject<Ref<RepoPermissions>>('repo-permissions');
if (!repo || !repoPermissions) {
throw new Error('Unexpected: "repo" and "repoPermissions" should be provided at this place');
}
if (!repo.value.pr_enabled || !repo.value.allow_pr) { if (!repo.value.pr_enabled || !repo.value.allow_pr) {
throw new Error('Unexpected: pull requests are disabled for repo'); throw new Error('Unexpected: pull requests are disabled for repo');
} }
const allPipelines = inject<Ref<Pipeline[]>>('pipelines'); const allPipelines = requiredInject('pipelines');
const pipelines = computed(() => const pipelines = computed(() =>
allPipelines?.value.filter( allPipelines.value.filter(
(b) => (b) =>
(b.event === 'pull_request' || b.event === 'pull_request_closed') && (b.event === 'pull_request' || b.event === 'pull_request_closed') &&
b.ref b.ref

View file

@ -26,31 +26,24 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { inject, watch } from 'vue'; import { watch } from 'vue';
import type { Ref } from 'vue';
import Icon from '~/components/atomic/Icon.vue'; import Icon from '~/components/atomic/Icon.vue';
import ListItem from '~/components/atomic/ListItem.vue'; import ListItem from '~/components/atomic/ListItem.vue';
import Panel from '~/components/layout/Panel.vue'; import Panel from '~/components/layout/Panel.vue';
import useApiClient from '~/compositions/useApiClient'; import useApiClient from '~/compositions/useApiClient';
import { requiredInject } from '~/compositions/useInjectProvide';
import { usePagination } from '~/compositions/usePaginate'; import { usePagination } from '~/compositions/usePaginate';
import type { PullRequest, Repo } from '~/lib/api/types'; import type { PullRequest } from '~/lib/api/types';
const apiClient = useApiClient(); const apiClient = useApiClient();
const repo = inject<Ref<Repo>>('repo'); const repo = requiredInject('repo');
if (!repo) {
throw new Error('Unexpected: "repo" should be provided at this place');
}
if (!repo.value.pr_enabled || !repo.value.allow_pr) { if (!repo.value.pr_enabled || !repo.value.allow_pr) {
throw new Error('Unexpected: pull requests are disabled for repo'); throw new Error('Unexpected: pull requests are disabled for repo');
} }
async function loadPullRequests(page: number): Promise<PullRequest[]> { async function loadPullRequests(page: number): Promise<PullRequest[]> {
if (!repo) {
throw new Error('Unexpected: "repo" should be provided at this place');
}
return apiClient.getRepoPullRequests(repo.value.id, { page }); return apiClient.getRepoPullRequests(repo.value.id, { page });
} }

View file

@ -54,7 +54,8 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { computed, onMounted, provide, ref, toRef, watch } from 'vue'; import type { Ref } from 'vue';
import { computed, onMounted, ref, toRef, watch } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useRoute, useRouter } from 'vue-router'; import { useRoute, useRouter } from 'vue-router';
@ -67,9 +68,10 @@ import useApiClient from '~/compositions/useApiClient';
import useAuthentication from '~/compositions/useAuthentication'; import useAuthentication from '~/compositions/useAuthentication';
import useConfig from '~/compositions/useConfig'; import useConfig from '~/compositions/useConfig';
import { useForgeStore } from '~/compositions/useForgeStore'; import { useForgeStore } from '~/compositions/useForgeStore';
import { provide } from '~/compositions/useInjectProvide';
import useNotifications from '~/compositions/useNotifications'; import useNotifications from '~/compositions/useNotifications';
import useRepos from '~/compositions/useRepos'; import useRepos from '~/compositions/useRepos';
import type { Forge, RepoPermissions } from '~/lib/api/types'; import type { Forge, Repo, RepoPermissions } from '~/lib/api/types';
import { usePipelineStore } from '~/store/pipelines'; import { usePipelineStore } from '~/store/pipelines';
import { useRepoStore } from '~/store/repos'; import { useRepoStore } from '~/store/repos';
@ -94,8 +96,8 @@ const { updateLastAccess } = useRepos();
const repo = repoStore.getRepo(repositoryId); const repo = repoStore.getRepo(repositoryId);
const repoPermissions = ref<RepoPermissions>(); const repoPermissions = ref<RepoPermissions>();
const pipelines = pipelineStore.getRepoPipelines(repositoryId); const pipelines = pipelineStore.getRepoPipelines(repositoryId);
provide('repo', repo); provide('repo', repo as Ref<Repo>); // can't be undefined because of v-if in template
provide('repo-permissions', repoPermissions); provide('repo-permissions', repoPermissions as Ref<RepoPermissions>); // can't be undefined because of v-if in template
provide('pipelines', pipelines); provide('pipelines', pipelines);
const forge = ref<Forge>(); const forge = ref<Forge>();
const forgeIcon = computed<IconNames>(() => { const forgeIcon = computed<IconNames>(() => {

View file

@ -62,8 +62,7 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { computed, inject, toRef } from 'vue'; import { computed, toRef } from 'vue';
import 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';
@ -75,8 +74,9 @@ import PipelineLog from '~/components/repo/pipeline/PipelineLog.vue';
import PipelineStepList from '~/components/repo/pipeline/PipelineStepList.vue'; import PipelineStepList from '~/components/repo/pipeline/PipelineStepList.vue';
import useApiClient from '~/compositions/useApiClient'; import useApiClient from '~/compositions/useApiClient';
import { useAsyncAction } from '~/compositions/useAsyncAction'; import { useAsyncAction } from '~/compositions/useAsyncAction';
import { requiredInject } from '~/compositions/useInjectProvide';
import useNotifications from '~/compositions/useNotifications'; import useNotifications from '~/compositions/useNotifications';
import type { Pipeline, PipelineStep, Repo, RepoPermissions } from '~/lib/api/types'; import type { PipelineStep } from '~/lib/api/types';
const props = defineProps<{ const props = defineProps<{
stepId?: string | null; stepId?: string | null;
@ -88,12 +88,9 @@ const route = useRoute();
const notifications = useNotifications(); const notifications = useNotifications();
const i18n = useI18n(); const i18n = useI18n();
const pipeline = inject<Ref<Pipeline>>('pipeline'); const pipeline = requiredInject('pipeline');
const repo = inject<Ref<Repo>>('repo'); const repo = requiredInject('repo');
const repoPermissions = inject<Ref<RepoPermissions>>('repo-permissions'); const repoPermissions = requiredInject('repo-permissions');
if (!repo || !repoPermissions || !pipeline) {
throw new Error('Unexpected: "repo", "repoPermissions" & "pipeline" should be provided at this place');
}
const stepId = toRef(props, 'stepId'); const stepId = toRef(props, 'stepId');
@ -139,19 +136,11 @@ const selectedStepId = computed({
}); });
const { doSubmit: approvePipeline, isLoading: isApprovingPipeline } = useAsyncAction(async () => { const { doSubmit: approvePipeline, isLoading: isApprovingPipeline } = useAsyncAction(async () => {
if (!repo) {
throw new Error('Unexpected: Repo is undefined');
}
await apiClient.approvePipeline(repo.value.id, `${pipeline.value.number}`); await apiClient.approvePipeline(repo.value.id, `${pipeline.value.number}`);
notifications.notify({ title: i18n.t('repo.pipeline.protected.approve_success'), type: 'success' }); notifications.notify({ title: i18n.t('repo.pipeline.protected.approve_success'), type: 'success' });
}); });
const { doSubmit: declinePipeline, isLoading: isDecliningPipeline } = useAsyncAction(async () => { const { doSubmit: declinePipeline, isLoading: isDecliningPipeline } = useAsyncAction(async () => {
if (!repo) {
throw new Error('Unexpected: Repo is undefined');
}
await apiClient.declinePipeline(repo.value.id, `${pipeline.value.number}`); await apiClient.declinePipeline(repo.value.id, `${pipeline.value.number}`);
notifications.notify({ title: i18n.t('repo.pipeline.protected.decline_success'), type: 'success' }); notifications.notify({ title: i18n.t('repo.pipeline.protected.decline_success'), type: 'success' });
}); });

View file

@ -1,20 +1,14 @@
<template> <template>
<Panel> <Panel>
<ul class="w-full list-inside list-disc"> <ul class="w-full list-inside list-disc">
<li v-for="file in pipeline!.changed_files" :key="file">{{ file }}</li> <li v-for="file in pipeline.changed_files" :key="file">{{ file }}</li>
</ul> </ul>
</Panel> </Panel>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { inject } from 'vue';
import type { Ref } from 'vue';
import Panel from '~/components/layout/Panel.vue'; import Panel from '~/components/layout/Panel.vue';
import type { Pipeline } from '~/lib/api/types'; import { requiredInject } from '~/compositions/useInjectProvide';
const pipeline = inject<Ref<Pipeline>>('pipeline'); const pipeline = requiredInject('pipeline');
if (!pipeline) {
throw new Error('Unexpected: "pipeline" should be provided at this place');
}
</script> </script>

View file

@ -18,12 +18,9 @@ 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 { inject } from '~/compositions/useInjectProvide'; import { requiredInject } from '~/compositions/useInjectProvide';
const pipelineConfigs = inject('pipeline-configs'); const pipelineConfigs = requiredInject('pipeline-configs');
if (!pipelineConfigs) {
throw new Error('Unexpected: "pipelineConfigs" should be provided at this place');
}
const pipelineConfigsDecoded = computed( const pipelineConfigsDecoded = computed(
() => () =>

View file

@ -18,24 +18,23 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { computed, inject, ref } from 'vue'; import { computed, ref } from 'vue';
import type { Ref } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import Button from '~/components/atomic/Button.vue'; import Button from '~/components/atomic/Button.vue';
import InputField from '~/components/form/InputField.vue'; import InputField from '~/components/form/InputField.vue';
import Panel from '~/components/layout/Panel.vue'; import Panel from '~/components/layout/Panel.vue';
import useApiClient from '~/compositions/useApiClient'; import useApiClient from '~/compositions/useApiClient';
import { requiredInject } from '~/compositions/useInjectProvide';
import useNotifications from '~/compositions/useNotifications'; import useNotifications from '~/compositions/useNotifications';
import type { Pipeline, Repo, RepoPermissions } from '~/lib/api/types';
const { t } = useI18n(); const { t } = useI18n();
const apiClient = useApiClient(); const apiClient = useApiClient();
const notifications = useNotifications(); const notifications = useNotifications();
const repo = inject<Ref<Repo>>('repo'); const repo = requiredInject('repo');
const pipeline = inject<Ref<Pipeline>>('pipeline'); const pipeline = requiredInject('pipeline');
const repoPermissions = inject<Ref<RepoPermissions>>('repo-permissions'); const repoPermissions = requiredInject('repo-permissions');
const isLoading = ref(false); const isLoading = ref(false);

View file

@ -48,19 +48,14 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { inject } from 'vue';
import type { Ref } from 'vue';
import DocsLink from '~/components/atomic/DocsLink.vue'; import DocsLink from '~/components/atomic/DocsLink.vue';
import Icon from '~/components/atomic/Icon.vue'; import Icon from '~/components/atomic/Icon.vue';
import RenderMarkdown from '~/components/atomic/RenderMarkdown.vue'; import RenderMarkdown from '~/components/atomic/RenderMarkdown.vue';
import Panel from '~/components/layout/Panel.vue'; import Panel from '~/components/layout/Panel.vue';
import type { Pipeline, PipelineError } from '~/lib/api/types'; import { requiredInject } from '~/compositions/useInjectProvide';
import type { PipelineError } from '~/lib/api/types';
const pipeline = inject<Ref<Pipeline>>('pipeline'); const pipeline = requiredInject('pipeline');
if (!pipeline) {
throw new Error('Unexpected: "pipeline" should be provided at this place');
}
function isLinterError(error: PipelineError): error is PipelineError<{ file?: string; field: string }> { function isLinterError(error: PipelineError): error is PipelineError<{ file?: string; field: string }> {
return error.type === 'linter'; return error.type === 'linter';

View file

@ -102,7 +102,7 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { computed, inject, onBeforeUnmount, onMounted, ref, toRef, watch } from 'vue'; import { computed, onBeforeUnmount, onMounted, ref, toRef, watch } from 'vue';
import type { Ref } from 'vue'; import 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';
@ -116,11 +116,11 @@ 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 { provide, requiredInject } 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';
import type { PipelineConfig, Repo, RepoPermissions } from '~/lib/api/types'; import type { Pipeline, PipelineConfig } from '~/lib/api/types';
import { usePipelineStore } from '~/store/pipelines'; import { usePipelineStore } from '~/store/pipelines';
const props = defineProps<{ const props = defineProps<{
@ -139,15 +139,12 @@ const pipelineStore = usePipelineStore();
const pipelineId = toRef(props, 'pipelineId'); const pipelineId = toRef(props, 'pipelineId');
const _repoId = toRef(props, 'repoId'); const _repoId = toRef(props, 'repoId');
const repositoryId = computed(() => Number.parseInt(_repoId.value, 10)); const repositoryId = computed(() => Number.parseInt(_repoId.value, 10));
const repo = inject<Ref<Repo>>('repo'); const repo = requiredInject('repo');
const repoPermissions = inject<Ref<RepoPermissions>>('repo-permissions'); const repoPermissions = requiredInject('repo-permissions');
if (!repo || !repoPermissions) {
throw new Error('Unexpected: "repo" & "repoPermissions" should be provided at this place');
}
const pipeline = pipelineStore.getPipeline(repositoryId, pipelineId); const pipeline = pipelineStore.getPipeline(repositoryId, pipelineId);
const { since, duration, created, message, shortMessage } = usePipeline(pipeline); const { since, duration, created, message, shortMessage } = usePipeline(pipeline);
provide('pipeline', pipeline); provide('pipeline', pipeline as Ref<Pipeline>); // can't be undefined because of v-if in template
const pipelineConfigs = ref<PipelineConfig[]>(); const pipelineConfigs = ref<PipelineConfig[]>();
provide('pipeline-configs', pipelineConfigs); provide('pipeline-configs', pipelineConfigs);
@ -163,10 +160,6 @@ watch(
const showDeployPipelinePopup = ref(false); const showDeployPipelinePopup = ref(false);
async function loadPipeline(): Promise<void> { async function loadPipeline(): Promise<void> {
if (!repo) {
throw new Error('Unexpected: Repo is undefined');
}
await pipelineStore.loadPipeline(repo.value.id, Number.parseInt(pipelineId.value, 10)); await pipelineStore.loadPipeline(repo.value.id, Number.parseInt(pipelineId.value, 10));
if (!pipeline.value?.number) { if (!pipeline.value?.number) {
@ -177,10 +170,6 @@ async function loadPipeline(): Promise<void> {
} }
const { doSubmit: cancelPipeline, isLoading: isCancelingPipeline } = useAsyncAction(async () => { const { doSubmit: cancelPipeline, isLoading: isCancelingPipeline } = useAsyncAction(async () => {
if (!repo) {
throw new Error('Unexpected: Repo is undefined');
}
if (!pipeline.value?.number) { if (!pipeline.value?.number) {
throw new Error('Unexpected: Pipeline number not found'); throw new Error('Unexpected: Pipeline number not found');
} }
@ -190,10 +179,6 @@ const { doSubmit: cancelPipeline, isLoading: isCancelingPipeline } = useAsyncAct
}); });
const { doSubmit: restartPipeline, isLoading: isRestartingPipeline } = useAsyncAction(async () => { const { doSubmit: restartPipeline, isLoading: isRestartingPipeline } = useAsyncAction(async () => {
if (!repo) {
throw new Error('Unexpected: Repo is undefined');
}
const newPipeline = await apiClient.restartPipeline(repo.value.id, pipelineId.value, { const newPipeline = await apiClient.restartPipeline(repo.value.id, pipelineId.value, {
fork: true, fork: true,
}); });

View file

@ -42,8 +42,7 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { computed, inject } from 'vue'; import { computed } from 'vue';
import type { Ref } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useRouter } from 'vue-router'; import { useRouter } from 'vue-router';
@ -51,30 +50,22 @@ import Button from '~/components/atomic/Button.vue';
import Settings from '~/components/layout/Settings.vue'; import Settings from '~/components/layout/Settings.vue';
import useApiClient from '~/compositions/useApiClient'; import useApiClient from '~/compositions/useApiClient';
import { useAsyncAction } from '~/compositions/useAsyncAction'; import { useAsyncAction } from '~/compositions/useAsyncAction';
import { requiredInject } from '~/compositions/useInjectProvide';
import useNotifications from '~/compositions/useNotifications'; import useNotifications from '~/compositions/useNotifications';
import type { Repo } from '~/lib/api/types';
const apiClient = useApiClient(); const apiClient = useApiClient();
const router = useRouter(); const router = useRouter();
const notifications = useNotifications(); const notifications = useNotifications();
const i18n = useI18n(); const i18n = useI18n();
const repo = inject<Ref<Repo>>('repo'); const repo = requiredInject('repo');
const { doSubmit: repairRepo, isLoading: isRepairingRepo } = useAsyncAction(async () => { const { doSubmit: repairRepo, isLoading: isRepairingRepo } = useAsyncAction(async () => {
if (!repo) {
throw new Error('Unexpected: Repo should be set');
}
await apiClient.repairRepo(repo.value.id); await apiClient.repairRepo(repo.value.id);
notifications.notify({ title: i18n.t('repo.settings.actions.repair.success'), 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 () => {
if (!repo) {
throw new Error('Unexpected: Repo should be set');
}
// TODO: use proper dialog // TODO: use proper dialog
// eslint-disable-next-line no-alert // eslint-disable-next-line no-alert
if (!confirm(i18n.t('repo.settings.actions.delete.confirm'))) { if (!confirm(i18n.t('repo.settings.actions.delete.confirm'))) {
@ -87,19 +78,11 @@ const { doSubmit: deleteRepo, isLoading: isDeletingRepo } = useAsyncAction(async
}); });
const { doSubmit: activateRepo, isLoading: isActivatingRepo } = useAsyncAction(async () => { const { doSubmit: activateRepo, isLoading: isActivatingRepo } = useAsyncAction(async () => {
if (!repo) {
throw new Error('Unexpected: Repo should be set');
}
await apiClient.activateRepo(repo.value.forge_remote_id); await apiClient.activateRepo(repo.value.forge_remote_id);
notifications.notify({ title: i18n.t('repo.settings.actions.enable.success'), type: 'success' }); notifications.notify({ title: i18n.t('repo.settings.actions.enable.success'), type: 'success' });
}); });
const { doSubmit: deactivateRepo, isLoading: isDeactivatingRepo } = useAsyncAction(async () => { const { doSubmit: deactivateRepo, isLoading: isDeactivatingRepo } = useAsyncAction(async () => {
if (!repo) {
throw new Error('Unexpected: Repo should be set');
}
await apiClient.deleteRepo(repo.value.id, false); await apiClient.deleteRepo(repo.value.id, false);
notifications.notify({ title: i18n.t('repo.settings.actions.disable.success'), type: 'success' }); notifications.notify({ title: i18n.t('repo.settings.actions.disable.success'), type: 'success' });
await router.replace({ name: 'repos' }); await router.replace({ name: 'repos' });

View file

@ -41,8 +41,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import { useStorage } from '@vueuse/core'; import { useStorage } from '@vueuse/core';
import { computed, inject, onMounted, ref, watch } from 'vue'; import { computed, onMounted, ref, watch } from 'vue';
import type { Ref } from 'vue';
import type { SelectOption } from '~/components/form/form.types'; import type { SelectOption } from '~/components/form/form.types';
import InputField from '~/components/form/InputField.vue'; import InputField from '~/components/form/InputField.vue';
@ -50,27 +49,19 @@ import SelectField from '~/components/form/SelectField.vue';
import Settings from '~/components/layout/Settings.vue'; import Settings from '~/components/layout/Settings.vue';
import useApiClient from '~/compositions/useApiClient'; import useApiClient from '~/compositions/useApiClient';
import useConfig from '~/compositions/useConfig'; import useConfig from '~/compositions/useConfig';
import { requiredInject } from '~/compositions/useInjectProvide';
import { usePaginate } from '~/compositions/usePaginate'; import { usePaginate } from '~/compositions/usePaginate';
import type { Repo } from '~/lib/api/types';
const apiClient = useApiClient(); const apiClient = useApiClient();
const repo = inject<Ref<Repo>>('repo'); const repo = requiredInject('repo');
const badgeType = useStorage('woodpecker:last-badge-type', 'markdown'); const badgeType = useStorage('woodpecker:last-badge-type', 'markdown');
if (!repo) {
throw new Error('Unexpected: "repo" should be provided at this place');
}
const defaultBranch = computed(() => repo.value.default_branch); const defaultBranch = computed(() => repo.value.default_branch);
const branches = ref<SelectOption[]>([]); const branches = ref<SelectOption[]>([]);
const branch = ref<string>(''); const branch = ref<string>('');
async function loadBranches() { async function loadBranches() {
if (!repo) {
throw new Error('Unexpected: "repo" should be provided at this place');
}
branches.value = (await usePaginate((page) => apiClient.getRepoBranches(repo.value.id, { page }))) branches.value = (await usePaginate((page) => apiClient.getRepoBranches(repo.value.id, { page })))
.map((b) => ({ .map((b) => ({
value: b, value: b,
@ -96,10 +87,6 @@ const repoUrl = computed(
); );
const badgeContent = computed(() => { const badgeContent = computed(() => {
if (!repo) {
throw new Error('Unexpected: "repo" should be provided at this place');
}
if (badgeType.value === 'url') { if (badgeType.value === 'url') {
return `${baseUrl}${badgeUrl.value}`; return `${baseUrl}${badgeUrl.value}`;
} }

View file

@ -105,8 +105,7 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { computed, inject, ref } from 'vue'; import { computed, ref } from 'vue';
import type { Ref } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import Button from '~/components/atomic/Button.vue'; import Button from '~/components/atomic/Button.vue';
@ -118,35 +117,28 @@ import Settings from '~/components/layout/Settings.vue';
import useApiClient from '~/compositions/useApiClient'; import useApiClient from '~/compositions/useApiClient';
import { useAsyncAction } from '~/compositions/useAsyncAction'; import { useAsyncAction } from '~/compositions/useAsyncAction';
import { useDate } from '~/compositions/useDate'; import { useDate } from '~/compositions/useDate';
import { requiredInject } from '~/compositions/useInjectProvide';
import useNotifications from '~/compositions/useNotifications'; import useNotifications from '~/compositions/useNotifications';
import { usePagination } from '~/compositions/usePaginate'; import { usePagination } from '~/compositions/usePaginate';
import type { Cron, Repo } from '~/lib/api/types'; import type { Cron } from '~/lib/api/types';
import router from '~/router'; import router from '~/router';
const apiClient = useApiClient(); const apiClient = useApiClient();
const notifications = useNotifications(); const notifications = useNotifications();
const i18n = useI18n(); const i18n = useI18n();
const repo = inject<Ref<Repo>>('repo'); const repo = requiredInject('repo');
const selectedCron = ref<Partial<Cron>>(); const selectedCron = ref<Partial<Cron>>();
const isEditingCron = computed(() => !!selectedCron.value?.id); const isEditingCron = computed(() => !!selectedCron.value?.id);
const date = useDate(); const date = useDate();
async function loadCrons(page: number): Promise<Cron[] | null> { async function loadCrons(page: number): Promise<Cron[] | null> {
if (!repo?.value) {
throw new Error("Unexpected: Can't load repo");
}
return apiClient.getCronList(repo.value.id, { page }); return apiClient.getCronList(repo.value.id, { page });
} }
const { resetPage, data: crons } = usePagination(loadCrons, () => !selectedCron.value); const { resetPage, data: crons } = usePagination(loadCrons, () => !selectedCron.value);
const { doSubmit: createCron, isLoading: isSaving } = useAsyncAction(async () => { const { doSubmit: createCron, isLoading: isSaving } = useAsyncAction(async () => {
if (!repo?.value) {
throw new Error("Unexpected: Can't load repo");
}
if (!selectedCron.value) { if (!selectedCron.value) {
throw new Error("Unexpected: Can't get cron"); throw new Error("Unexpected: Can't get cron");
} }
@ -165,20 +157,12 @@ const { doSubmit: createCron, isLoading: isSaving } = useAsyncAction(async () =>
}); });
const { doSubmit: deleteCron, isLoading: isDeleting } = useAsyncAction(async (_cron: Cron) => { const { doSubmit: deleteCron, isLoading: isDeleting } = useAsyncAction(async (_cron: Cron) => {
if (!repo?.value) {
throw new Error("Unexpected: Can't load repo");
}
await apiClient.deleteCron(repo.value.id, _cron.id); await apiClient.deleteCron(repo.value.id, _cron.id);
notifications.notify({ title: i18n.t('repo.settings.crons.deleted'), type: 'success' }); notifications.notify({ title: i18n.t('repo.settings.crons.deleted'), type: 'success' });
resetPage(); resetPage();
}); });
const { doSubmit: runCron } = useAsyncAction(async (_cron: Cron) => { const { doSubmit: runCron } = useAsyncAction(async (_cron: Cron) => {
if (!repo?.value) {
throw new Error("Unexpected: Can't load repo");
}
const pipeline = await apiClient.runCron(repo.value.id, _cron.id); const pipeline = await apiClient.runCron(repo.value.id, _cron.id);
await router.push({ await router.push({
name: 'repo-pipeline', name: 'repo-pipeline',

View file

@ -171,8 +171,7 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { inject, onMounted, ref } from 'vue'; import { onMounted, ref } from 'vue';
import type { Ref } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import Button from '~/components/atomic/Button.vue'; import Button from '~/components/atomic/Button.vue';
@ -187,9 +186,10 @@ import Settings from '~/components/layout/Settings.vue';
import useApiClient from '~/compositions/useApiClient'; import useApiClient from '~/compositions/useApiClient';
import { useAsyncAction } from '~/compositions/useAsyncAction'; import { useAsyncAction } from '~/compositions/useAsyncAction';
import useAuthentication from '~/compositions/useAuthentication'; import useAuthentication from '~/compositions/useAuthentication';
import { requiredInject } from '~/compositions/useInjectProvide';
import useNotifications from '~/compositions/useNotifications'; import useNotifications from '~/compositions/useNotifications';
import { RepoRequireApproval, RepoVisibility, WebhookEvents } from '~/lib/api/types'; import { RepoRequireApproval, RepoVisibility, WebhookEvents } from '~/lib/api/types';
import type { Repo, RepoSettings } from '~/lib/api/types'; import type { RepoSettings } from '~/lib/api/types';
import { useRepoStore } from '~/store/repos'; import { useRepoStore } from '~/store/repos';
const apiClient = useApiClient(); const apiClient = useApiClient();
@ -198,14 +198,10 @@ const { user } = useAuthentication();
const repoStore = useRepoStore(); const repoStore = useRepoStore();
const i18n = useI18n(); const i18n = useI18n();
const repo = inject<Ref<Repo>>('repo'); const repo = requiredInject('repo');
const repoSettings = ref<RepoSettings>(); const repoSettings = ref<RepoSettings>();
function loadRepoSettings() { function loadRepoSettings() {
if (!repo) {
throw new Error('Unexpected: Repo should be set');
}
repoSettings.value = { repoSettings.value = {
config_file: repo.value.config_file, config_file: repo.value.config_file,
timeout: repo.value.timeout, timeout: repo.value.timeout,
@ -221,19 +217,11 @@ function loadRepoSettings() {
} }
async function loadRepo() { async function loadRepo() {
if (!repo) {
throw new Error('Unexpected: Repo should be set');
}
await repoStore.loadRepo(repo.value.id); await repoStore.loadRepo(repo.value.id);
loadRepoSettings(); loadRepoSettings();
} }
const { doSubmit: saveRepoSettings, isLoading: isSaving } = useAsyncAction(async () => { const { doSubmit: saveRepoSettings, isLoading: isSaving } = useAsyncAction(async () => {
if (!repo) {
throw new Error('Unexpected: Repo should be set');
}
if (!repoSettings.value) { if (!repoSettings.value) {
throw new Error('Unexpected: Repo-Settings should be set'); throw new Error('Unexpected: Repo-Settings should be set');
} }

View file

@ -30,8 +30,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import { cloneDeep } from 'lodash'; import { cloneDeep } from 'lodash';
import { computed, inject, ref } from 'vue'; import { computed, ref } from 'vue';
import type { Ref } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import Button from '~/components/atomic/Button.vue'; import Button from '~/components/atomic/Button.vue';
@ -40,9 +39,10 @@ import RegistryEdit from '~/components/registry/RegistryEdit.vue';
import RegistryList from '~/components/registry/RegistryList.vue'; import RegistryList from '~/components/registry/RegistryList.vue';
import useApiClient from '~/compositions/useApiClient'; import useApiClient from '~/compositions/useApiClient';
import { useAsyncAction } from '~/compositions/useAsyncAction'; import { useAsyncAction } from '~/compositions/useAsyncAction';
import { requiredInject } from '~/compositions/useInjectProvide';
import useNotifications from '~/compositions/useNotifications'; import useNotifications from '~/compositions/useNotifications';
import { usePagination } from '~/compositions/usePaginate'; import { usePagination } from '~/compositions/usePaginate';
import type { Registry, Repo } from '~/lib/api/types'; import type { Registry } from '~/lib/api/types';
const emptyRegistry: Partial<Registry> = { const emptyRegistry: Partial<Registry> = {
address: '', address: '',
@ -54,25 +54,17 @@ const apiClient = useApiClient();
const notifications = useNotifications(); const notifications = useNotifications();
const i18n = useI18n(); const i18n = useI18n();
const repo = inject<Ref<Repo>>('repo'); const repo = requiredInject('repo');
const selectedRegistry = ref<Partial<Registry>>(); const selectedRegistry = ref<Partial<Registry>>();
const isEditingRegistry = computed(() => !!selectedRegistry.value?.id); const isEditingRegistry = computed(() => !!selectedRegistry.value?.id);
async function loadRegistries(page: number): Promise<Registry[] | null> { async function loadRegistries(page: number): Promise<Registry[] | null> {
if (!repo?.value) {
throw new Error("Unexpected: Can't load repo");
}
return apiClient.getRegistryList(repo.value.id, { page }); return apiClient.getRegistryList(repo.value.id, { page });
} }
const { resetPage, data: registries } = usePagination(loadRegistries, () => !selectedRegistry.value); const { resetPage, data: registries } = usePagination(loadRegistries, () => !selectedRegistry.value);
const { doSubmit: createRegistry, isLoading: isSaving } = useAsyncAction(async () => { const { doSubmit: createRegistry, isLoading: isSaving } = useAsyncAction(async () => {
if (!repo?.value) {
throw new Error("Unexpected: Can't load repo");
}
if (!selectedRegistry.value) { if (!selectedRegistry.value) {
throw new Error("Unexpected: Can't get registry"); throw new Error("Unexpected: Can't get registry");
} }
@ -91,10 +83,6 @@ const { doSubmit: createRegistry, isLoading: isSaving } = useAsyncAction(async (
}); });
const { doSubmit: deleteRegistry, isLoading: isDeleting } = useAsyncAction(async (_registry: Registry) => { const { doSubmit: deleteRegistry, isLoading: isDeleting } = useAsyncAction(async (_registry: Registry) => {
if (!repo?.value) {
throw new Error("Unexpected: Can't load repo");
}
const registryAddress = encodeURIComponent(_registry.address); const registryAddress = encodeURIComponent(_registry.address);
await apiClient.deleteRegistry(repo.value.id, registryAddress); await apiClient.deleteRegistry(repo.value.id, registryAddress);
notifications.notify({ title: i18n.t('registries.deleted'), type: 'success' }); notifications.notify({ title: i18n.t('registries.deleted'), type: 'success' });

View file

@ -2,7 +2,7 @@
<Scaffold enable-tabs :go-back="goBack"> <Scaffold enable-tabs :go-back="goBack">
<template #title> <template #title>
<span> <span>
<router-link :to="{ name: 'org', params: { orgId: repo!.org_id } }" class="hover:underline">{{ <router-link :to="{ name: 'org', params: { orgId: repo.org_id } }" class="hover:underline">{{
repo!.owner repo!.owner
/* eslint-disable-next-line @intlify/vue-i18n/no-raw-text */ /* eslint-disable-next-line @intlify/vue-i18n/no-raw-text */
}}</router-link> }}</router-link>
@ -28,30 +28,22 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { inject, onMounted } from 'vue'; import { onMounted } from 'vue';
import type { Ref } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useRouter } from 'vue-router'; 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 { requiredInject } from '~/compositions/useInjectProvide';
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';
const notifications = useNotifications(); const notifications = useNotifications();
const router = useRouter(); const router = useRouter();
const i18n = useI18n(); const i18n = useI18n();
const repoPermissions = inject<Ref<RepoPermissions>>('repo-permissions'); const repoPermissions = requiredInject('repo-permissions');
if (!repoPermissions) { const repo = requiredInject('repo');
throw new Error('Unexpected: "repoPermissions" should be provided at this place');
}
const repo = inject<Ref<Repo>>('repo');
if (!repo) {
throw new Error('Unexpected: "repo" should be provided at this place');
}
onMounted(async () => { onMounted(async () => {
if (!repoPermissions.value.admin) { if (!repoPermissions.value.admin) {

View file

@ -25,8 +25,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import { cloneDeep } from 'lodash'; import { cloneDeep } from 'lodash';
import { computed, inject, ref } from 'vue'; import { computed, ref } from 'vue';
import type { Ref } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import Button from '~/components/atomic/Button.vue'; import Button from '~/components/atomic/Button.vue';
@ -35,10 +34,11 @@ import SecretEdit from '~/components/secrets/SecretEdit.vue';
import SecretList from '~/components/secrets/SecretList.vue'; import SecretList from '~/components/secrets/SecretList.vue';
import useApiClient from '~/compositions/useApiClient'; import useApiClient from '~/compositions/useApiClient';
import { useAsyncAction } from '~/compositions/useAsyncAction'; import { useAsyncAction } from '~/compositions/useAsyncAction';
import { requiredInject } from '~/compositions/useInjectProvide';
import useNotifications from '~/compositions/useNotifications'; import useNotifications from '~/compositions/useNotifications';
import { usePagination } from '~/compositions/usePaginate'; import { usePagination } from '~/compositions/usePaginate';
import { WebhookEvents } from '~/lib/api/types'; import { WebhookEvents } from '~/lib/api/types';
import type { Repo, Secret } from '~/lib/api/types'; import type { Secret } from '~/lib/api/types';
const emptySecret: Partial<Secret> = { const emptySecret: Partial<Secret> = {
name: '', name: '',
@ -51,15 +51,11 @@ const apiClient = useApiClient();
const notifications = useNotifications(); const notifications = useNotifications();
const i18n = useI18n(); const i18n = useI18n();
const repo = inject<Ref<Repo>>('repo'); const repo = requiredInject('repo');
const selectedSecret = ref<Partial<Secret>>(); const selectedSecret = ref<Partial<Secret>>();
const isEditingSecret = computed(() => !!selectedSecret.value?.id); const isEditingSecret = computed(() => !!selectedSecret.value?.id);
async function loadSecrets(page: number, level: 'repo' | 'org' | 'global'): Promise<Secret[] | null> { async function loadSecrets(page: number, level: 'repo' | 'org' | 'global'): Promise<Secret[] | null> {
if (!repo?.value) {
throw new Error("Unexpected: Can't load repo");
}
switch (level) { switch (level) {
case 'repo': case 'repo':
return apiClient.getSecretList(repo.value.id, { page }); return apiClient.getSecretList(repo.value.id, { page });
@ -103,10 +99,6 @@ const secrets = computed(() => {
}); });
const { doSubmit: createSecret, isLoading: isSaving } = useAsyncAction(async () => { const { doSubmit: createSecret, isLoading: isSaving } = useAsyncAction(async () => {
if (!repo?.value) {
throw new Error("Unexpected: Can't load repo");
}
if (!selectedSecret.value) { if (!selectedSecret.value) {
throw new Error("Unexpected: Can't get secret"); throw new Error("Unexpected: Can't get secret");
} }
@ -125,10 +117,6 @@ const { doSubmit: createSecret, isLoading: isSaving } = useAsyncAction(async ()
}); });
const { doSubmit: deleteSecret, isLoading: isDeleting } = useAsyncAction(async (_secret: Secret) => { const { doSubmit: deleteSecret, isLoading: isDeleting } = useAsyncAction(async (_secret: Secret) => {
if (!repo?.value) {
throw new Error("Unexpected: Can't load repo");
}
await apiClient.deleteSecret(repo.value.id, _secret.name); await apiClient.deleteSecret(repo.value.id, _secret.name);
notifications.notify({ title: i18n.t('secrets.deleted'), type: 'success' }); notifications.notify({ title: i18n.t('secrets.deleted'), type: 'success' });
await resetPage(); await resetPage();