Cli setup command (#3384)

Co-authored-by: Robert Kaussow <xoxys@rknet.org>
This commit is contained in:
Anbraten
2024-03-13 11:08:22 +01:00
committed by GitHub
parent 1026f95f7e
commit 03c891eb93
17 changed files with 665 additions and 43 deletions

1
web/components.d.ts vendored
View File

@@ -110,6 +110,7 @@ declare module 'vue' {
Tabs: typeof import('./src/components/layout/scaffold/Tabs.vue')['default']
TextField: typeof import('./src/components/form/TextField.vue')['default']
UserAPITab: typeof import('./src/components/user/UserAPITab.vue')['default']
UserCLIAndAPITab: typeof import('./src/components/user/UserCLIAndAPITab.vue')['default']
UserGeneralTab: typeof import('./src/components/user/UserGeneralTab.vue')['default']
UserSecretsTab: typeof import('./src/components/user/UserSecretsTab.vue')['default']
Warning: typeof import('./src/components/atomic/Warning.vue')['default']

View File

@@ -486,15 +486,13 @@
"pr_warning": "Please be careful with this option as a bad actor can submit a malicious pull request that exposes your secrets."
}
},
"api": {
"api": "API",
"desc": "Personal Access Token and API usage",
"cli_and_api": {
"cli_and_api": "CLI & API",
"desc": "Personal Access Token, CLI and API usage",
"token": "Personal Access Token",
"shell_setup": "Shell setup",
"api_usage": "Example API Usage",
"cli_usage": "Example CLI Usage",
"dl_cli": "Download CLI",
"shell_setup_before": "do shell setup steps before",
"download_cli": "Download CLI",
"reset_token": "Reset token",
"swagger_ui": "Swagger UI"
}
@@ -508,5 +506,12 @@
"running_version": "You are running Woodpecker {0}",
"update_woodpecker": "Please update your Woodpecker instance to {0}",
"global_level_secret": "global secret",
"org_level_secret": "organization secret"
"org_level_secret": "organization secret",
"login_to_cli": "Login to CLI",
"login_to_cli_description": "By continuing you will be logged in to the CLI.",
"abort": "Abort",
"cli_login_success": "Login to CLI successful",
"cli_login_failed": "Login to CLI failed",
"cli_login_denied": "Login to CLI denied",
"return_to_cli": "You can now close this tab and return to the CLI."
}

View File

@@ -1,43 +1,38 @@
<template>
<Settings :title="$t('user.settings.api.api')" :desc="$t('user.settings.api.desc')">
<InputField :label="$t('user.settings.api.token')">
<Settings :title="$t('user.settings.cli_and_api.cli_and_api')" :desc="$t('user.settings.cli_and_api.desc')">
<InputField :label="$t('user.settings.cli_and_api.cli_usage')">
<template #titleActions>
<Button class="ml-auto" :text="$t('user.settings.api.reset_token')" @click="resetToken" />
<a :href="cliDownload" target="_blank" class="ml-4 text-wp-link-100 hover:text-wp-link-200">{{
$t('user.settings.cli_and_api.download_cli')
}}</a>
</template>
<pre class="code-box">{{ usageWithCli }}</pre>
</InputField>
<InputField :label="$t('user.settings.cli_and_api.token')">
<template #titleActions>
<Button class="ml-auto" :text="$t('user.settings.cli_and_api.reset_token')" @click="resetToken" />
</template>
<pre class="code-box">{{ token }}</pre>
</InputField>
<InputField :label="$t('user.settings.api.shell_setup')">
<pre class="code-box">{{ usageWithShell }}</pre>
</InputField>
<InputField :label="$t('user.settings.api.api_usage')">
<InputField :label="$t('user.settings.cli_and_api.api_usage')">
<template #titleActions>
<a
v-if="enableSwagger"
:href="`${address}/swagger/index.html`"
target="_blank"
class="ml-4 text-wp-link-100 hover:text-wp-link-200"
>{{ $t('user.settings.api.swagger_ui') }}</a
>{{ $t('user.settings.cli_and_api.swagger_ui') }}</a
>
</template>
<pre class="code-box">{{ usageWithCurl }}</pre>
</InputField>
<InputField :label="$t('user.settings.api.cli_usage')">
<template #titleActions>
<a :href="cliDownload" target="_blank" class="ml-4 text-wp-link-100 hover:text-wp-link-200">{{
$t('user.settings.api.dl_cli')
}}</a>
</template>
<pre class="code-box">{{ usageWithCli }}</pre>
</InputField>
</Settings>
</template>
<script lang="ts" setup>
import { computed, onMounted, ref } from 'vue';
import { useI18n } from 'vue-i18n';
import Button from '~/components/atomic/Button.vue';
import InputField from '~/components/form/InputField.vue';
@@ -45,7 +40,6 @@ import Settings from '~/components/layout/Settings.vue';
import useApiClient from '~/compositions/useApiClient';
import useConfig from '~/compositions/useConfig';
const { t } = useI18n();
const { rootPath, enableSwagger } = useConfig();
const apiClient = useApiClient();
@@ -57,17 +51,15 @@ onMounted(async () => {
const address = `${window.location.protocol}//${window.location.host}${rootPath}`; // port is included in location.host
const usageWithShell = computed(() => {
const usageWithCurl = computed(() => {
let usage = `export WOODPECKER_SERVER="${address}"\n`;
usage += `export WOODPECKER_TOKEN="${token.value}"\n`;
usage += `\n`;
usage += `# curl -i \${WOODPECKER_SERVER}/api/user -H "Authorization: Bearer \${WOODPECKER_TOKEN}"`;
return usage;
});
const usageWithCurl = `# ${t(
'user.settings.api.shell_setup_before',
)}\ncurl -i \${WOODPECKER_SERVER}/api/user -H "Authorization: Bearer \${WOODPECKER_TOKEN}"`;
const usageWithCli = `# ${t('user.settings.api.shell_setup_before')}\nwoodpecker info`;
const usageWithCli = `# woodpecker setup --server-url ${address}`;
const cliDownload = 'https://github.com/woodpecker-ci/woodpecker/releases';

View File

@@ -166,6 +166,11 @@ const routes: RouteRecordRaw[] = [
meta: { blank: true },
props: true,
},
{
path: `${rootPath}/cli/auth`,
component: () => import('~/views/cli/Auth.vue'),
meta: { authentication: 'required' },
},
// TODO: deprecated routes => remove after some time
{

View File

@@ -8,8 +8,8 @@
<Tab id="secrets" :title="$t('user.settings.secrets.secrets')">
<UserSecretsTab />
</Tab>
<Tab id="api" :title="$t('user.settings.api.api')">
<UserAPITab />
<Tab id="cli-and-api" :title="$t('user.settings.cli_and_api.cli_and_api')">
<UserCLIAndAPITab />
</Tab>
</Scaffold>
</template>
@@ -17,7 +17,7 @@
<script lang="ts" setup>
import Scaffold from '~/components/layout/scaffold/Scaffold.vue';
import Tab from '~/components/layout/scaffold/Tab.vue';
import UserAPITab from '~/components/user/UserAPITab.vue';
import UserCLIAndAPITab from '~/components/user/UserCLIAndAPITab.vue';
import UserGeneralTab from '~/components/user/UserGeneralTab.vue';
import UserSecretsTab from '~/components/user/UserSecretsTab.vue';
import useConfig from '~/compositions/useConfig';

View File

@@ -0,0 +1,80 @@
<template>
<div class="flex flex-col gap-4 m-auto">
<div class="text-center text-wp-text-100">
<img src="../../assets/logo.svg" alt="CLI" class="w-32 m-auto mb-8" />
<template v-if="state === 'confirm'">
<h1 class="text-4xl font-bold">{{ $t('login_to_cli') }}</h1>
<p class="text-2xl">{{ $t('login_to_cli_description') }}</p>
</template>
<template v-else-if="state === 'success'">
<h1 class="text-4xl font-bold">{{ $t('cli_login_success') }}</h1>
<p class="text-2xl">{{ $t('return_to_cli') }}</p>
</template>
<template v-else-if="state === 'failed'">
<h1 class="text-4xl font-bold mt-4">{{ $t('cli_login_failed') }}</h1>
<p class="text-2xl">{{ $t('return_to_cli') }}</p>
</template>
<template v-else-if="state === 'denied'">
<h1 class="text-4xl font-bold mt-4">{{ $t('cli_login_denied') }}</h1>
<p class="text-2xl">{{ $t('return_to_cli') }}</p>
</template>
</div>
<div v-if="state === 'confirm'" class="flex gap-4 justify-center">
<Button :text="$t('login_to_cli')" color="green" @click="sendToken(false)" />
<Button :text="$t('abort')" color="red" @click="abortLogin" />
</div>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { useI18n } from 'vue-i18n';
import { useRoute } from 'vue-router';
import Button from '~/components/atomic/Button.vue';
import useApiClient from '~/compositions/useApiClient';
const apiClient = useApiClient();
const route = useRoute();
const { t } = useI18n();
const state = ref<'confirm' | 'success' | 'failed' | 'denied'>('confirm');
async function sendToken(abort = false) {
const port = route.query.port as string;
if (!port) {
throw new Error('Unexpected: port not found');
}
const address = `http://localhost:${port}`;
const token = abort ? '' : await apiClient.getToken();
const resp = await fetch(`${address}/token`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ token }),
});
if (abort) {
state.value = 'denied';
window.close();
return;
}
const data = (await resp.json()) as { ok: string };
if (data.ok === 'true') {
state.value = 'success';
} else {
state.value = 'failed';
// eslint-disable-next-line no-alert
alert(t('cli_login_failed'));
}
}
async function abortLogin() {
await sendToken(true);
}
</script>