jcloud/dashboard/src2/components/settings/DeveloperSettings.vue
2025-04-12 17:39:38 +08:00

474 lines
11 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="p-5">
<div class="grid grid-cols-1 gap-5">
<div
class="mx-auto min-w-[48rem] max-w-3xl space-y-6 rounded-md border p-4"
>
<div class="flex items-center justify-between">
<div class="text-xl font-semibold">API 访问</div>
<Button @click="showCreateSecretDialog = true">{{
$team.pg?.user_info?.api_key
? '重新生成 API 密钥'
: '创建新的 API 密钥'
}}</Button>
</div>
<div v-if="$team.pg?.user_info?.api_key">
<ClickToCopyField
v-if="$team.pg?.user_info?.api_key"
:textContent="$team.pg.user_info.api_key"
/>
</div>
<div v-else class="pb-2 text-base text-gray-700">
您还没有 API 密钥点击上面的按钮创建一个
</div>
<Dialog
v-model="showCreateSecretDialog"
:options="{
title: 'API 访问',
size: 'xl',
actions: [
{
label: $team.pg.user_info.api_key
? '重新生成 API 密钥'
: '创建新的 API 密钥',
variant: 'solid',
disabled: createSecret.data,
loading: createSecret.loading,
onClick() {
createSecret.submit();
}
}
]
}"
>
<template #body-content>
<div v-if="createSecret.data">
<p class="text-base">
请立即复制 API 密钥您将无法再次查看它
</p>
<label class="block pt-2">
<span class="mb-2 block text-sm leading-4 text-gray-700"
>API key</span
>
<ClickToCopyField :textContent="createSecret.data.api_key" />
</label>
<label class="block pt-2">
<span class="mb-2 block text-sm leading-4 text-gray-700"
>API secret</span
>
<ClickToCopyField :textContent="createSecret.data.api_secret" />
</label>
</div>
<div v-else class="text-base text-gray-700">
API key API secret 可用于访问
<a href="/docs/api" class="underline" target="_blank"
>今果 Jingrow API</a
>.
</div>
</template>
</Dialog>
</div>
<div
class="mx-auto min-w-[48rem] max-w-3xl space-y-6 rounded-md border p-4"
>
<div class="flex items-center justify-between">
<div class="text-xl font-semibold">SSH 密钥</div>
</div>
<ObjectList :options="sshKeyListOptions" />
</div>
<div
v-if="$session.hasWebhookConfigurationAccess"
class="mx-auto min-w-[48rem] max-w-3xl space-y-6 rounded-md border p-4"
>
<div class="flex items-center justify-between">
<div class="text-xl font-semibold">Webhooks</div>
</div>
<ObjectList :options="webhookListOptions" />
<AddNewWebhookDialog
v-if="showAddWebhookDialog"
v-model="showAddWebhookDialog"
@success="onNewWebhookSuccess"
/>
<ActivateWebhookDialog
v-if="showActivateWebhookDialog"
v-model="showActivateWebhookDialog"
@success="onWebHookActivated"
:webhook="selectedWebhook"
/>
<EditWebhookDialog
v-if="showEditWebhookDialog"
v-model="showEditWebhookDialog"
@success="onWebHookUpdated"
:webhook="selectedWebhook"
/>
<WebhookAttemptsDialog
v-if="showWebhookAttempts"
v-model="showWebhookAttempts"
:name="selectedWebhook.name"
/>
</div>
</div>
</div>
</template>
<script setup>
import { Badge, createResource } from 'jingrow-ui';
import { toast } from 'vue-sonner';
import { computed, h, onMounted, ref } from 'vue';
import { confirmDialog, icon } from '../../utils/components';
import ObjectList from '../ObjectList.vue';
import { getTeam } from '../../data/team';
import { date } from '../../utils/format';
import ClickToCopyField from '../ClickToCopyField.vue';
import AddNewWebhookDialog from './AddNewWebhookDialog.vue';
import ActivateWebhookDialog from './ActivateWebhookDialog.vue';
import EditWebhookDialog from './EditWebhookDialog.vue';
import { useRouter } from 'vue-router';
import WebhookAttemptsDialog from './WebhookAttemptsDialog.vue';
import { session } from '../../data/session';
const $team = getTeam();
const router = useRouter();
let showCreateSecretDialog = ref(false);
const showAddWebhookDialog = ref(false);
const showActivateWebhookDialog = ref(false);
const showEditWebhookDialog = ref(false);
const showWebhookAttempts = ref(false);
const selectedWebhook = ref(null);
const createSecret = createResource({
url: 'jcloud.api.account.create_api_secret',
onSuccess() {
if ($team.pg.user_info.api_key) {
toast.success('API 密钥已成功重新生成');
} else {
toast.success('API 密钥已成功创建');
}
}
});
const addSSHKey = createResource({
url: 'jcloud.api.client.insert',
onSuccess() {
toast.success('SSH 密钥已成功添加');
},
onError(err) {
toast.error(
err.messages.length
? err.messages.join('\n')
: 'SSH 密钥无法添加'
);
}
});
const makeKeyDefault = createResource({
url: 'jcloud.api.account.mark_key_as_default',
onSuccess() {
toast.success('SSH 密钥已成功更新');
},
onError(err) {
toast.error(
err.messages.length
? err.messages.join('\n')
: 'SSH 密钥无法设置为默认'
);
}
});
const deleteSSHKey = createResource({
url: 'jcloud.api.client.delete',
onSuccess() {
toast.success('SSH 密钥已成功删除');
},
onError(err) {
toast.error(
err.messages.length
? err.messages.join('\n')
: 'SSH 密钥无法删除'
);
}
});
const sshKeyListOptions = computed(() => ({
resource() {
return {
url: 'jcloud.api.account.get_user_ssh_keys',
initialData: [],
auto: true
};
},
columns: [
{
label: 'SSH 指纹',
fieldname: 'ssh_fingerprint',
class: 'font-mono',
format: value => `SHA256:${value}`,
suffix(row) {
return row.is_default
? h(Badge, {
label: '默认',
theme: 'green',
class: 'ml-2'
})
: null;
}
},
{
label: '添加时间',
fieldname: 'creation',
width: 0.1,
format: value => date(value, 'll')
}
],
primaryAction({ listResource }) {
return {
label: '添加 SSH 密钥',
slots: { prefix: icon('plus') },
onClick: () => renderAddNewKeyDialog(listResource)
};
},
rowActions({ row }) {
return [
{
label: '设为默认',
condition: () => !row.is_default,
onClick() {
makeKeyDefault.submit({
key_name: row.name
});
}
},
{
label: '删除',
onClick() {
deleteSSHKey.submit({
pagetype: 'User SSH Key',
name: row.name
});
}
}
];
}
}));
function renderAddNewKeyDialog(listResource) {
confirmDialog({
title: '添加新 SSH 密钥',
message: '向您的账户添加新的 SSH 密钥',
fields: [
{
label: 'SSH 密钥',
fieldname: 'sshKey',
type: 'textarea',
placeholder:
"以 'ssh-rsa', 'ecdsa-sha2-nistp256', 'ecdsa-sha2-nistp384', 'ecdsa-sha2-nistp521', 'ssh-ed25519', 'sk-ecdsa-sha2-nistp256@openssh.com', 或 'sk-ssh-ed25519@openssh.com' 开头",
required: true
}
],
primaryAction: {
label: '添加 SSH 密钥',
variant: 'solid',
onClick({ hide, values }) {
if (!values.sshKey) throw new Error('SSH 密钥是必填项');
addSSHKey
.submit({
pg: {
pagetype: 'User SSH Key',
ssh_public_key: values.sshKey,
user: $team.pg.user_info.name
}
})
.then(() => {
listResource.reload();
hide();
})
.catch(error => {
toast.error(error.message);
});
}
}
});
}
const webhookListResource = createResource({
url: 'jcloud.api.client.get_list',
params: {
pagetype: 'Jcloud Webhook',
fields: ['name', 'enabled', 'endpoint']
},
initialData: [],
auto: false
});
const deleteWebhook = createResource({
url: 'jcloud.api.client.delete',
onSuccess() {
toast.success('Webhook 删除成功');
webhookListResource.reload();
},
onError(err) {
toast.error(
err.messages.length
? err.messages.join('\n')
: 'Webhook 无法删除'
);
}
});
const webhookListOptions = computed(() => ({
data: () => webhookListResource.data,
columns: [
{
label: '端点',
fieldname: 'endpoint',
width: 1,
format: value => value.substring(0, 50)
},
{
label: '状态',
fieldname: 'enabled',
width: 0.1,
type: 'Component',
align: 'right',
component({ row }) {
return row.enabled
? h(Badge, {
label: '启用',
theme: 'green'
})
: h(Badge, {
label: '禁用',
theme: 'red'
});
}
}
],
rowActions({ row }) {
return [
{
label: '激活',
condition: () => !Boolean(row.enabled),
onClick() {
selectedWebhook.value = row;
showActivateWebhookDialog.value = true;
}
},
{
label: '禁用',
condition: () => Boolean(row.enabled),
onClick: () => {
confirmDialog({
title: '禁用 Webhook',
message: `端点 - ${row.endpoint}<br>确定要禁用该 webhook 吗?<br>`,
primaryAction: {
label: '禁用',
variant: 'solid',
theme: 'red',
onClick({ hide }) {
disableWebhook
.submit({
dt: 'Jcloud Webhook',
dn: row.name,
method: 'disable'
})
.then(hide);
return disableWebhook.promise;
}
}
});
}
},
{
label: '尝试',
onClick: () => {
selectedWebhook.value = row;
showWebhookAttempts.value = true;
}
},
{
label: '编辑',
onClick() {
selectedWebhook.value = row;
showEditWebhookDialog.value = true;
}
},
{
label: '删除',
onClick() {
confirmDialog({
title: '删除 Webhook',
message: `端点 - ${row.endpoint}<br>确定要删除该 webhook 吗?<br>`,
primaryAction: {
label: '删除',
variant: 'solid',
theme: 'red',
onClick({ hide }) {
deleteWebhook
.submit({
pagetype: 'Jcloud Webhook',
name: row.name
})
.then(hide);
return deleteWebhook.promise;
}
}
});
}
}
];
},
primaryAction() {
return {
label: '添加 Webhook',
slots: { prefix: icon('plus') },
onClick: () => (showAddWebhookDialog.value = true)
};
},
secondaryAction() {
return {
label: '刷新',
icon: 'refresh-ccw',
onClick: () => webhookListResource.reload()
};
}
}));
const disableWebhook = createResource({
url: 'jcloud.api.client.run_pg_method',
onSuccess() {
toast.success('Webhook 禁用成功');
webhookListResource.reload();
},
onError(err) {
toast.error(
err.messages.length
? err.messages.join('\n')
: 'Webhook 无法禁用'
);
}
});
const onNewWebhookSuccess = () => {
webhookListResource.reload();
showAddWebhookDialog.value = false;
};
const onWebHookActivated = () => {
webhookListResource.reload();
showActivateWebhookDialog.value = false;
};
const onWebHookUpdated = activationRequired => {
webhookListResource.reload();
showEditWebhookDialog.value = false;
if (activationRequired) {
showActivateWebhookDialog.value = true;
}
};
onMounted(() => {
if (session.hasWebhookConfigurationAccess) {
webhookListResource.fetch();
}
});
</script>