mirror of
https://github.com/rustfs/console.git
synced 2026-08-31 01:37:52 +08:00
Merge branch 'refactor/shadcn-vue'
This commit is contained in:
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="70" height="30" fill="none" viewBox="0 0 70 30" style="width:204px;height:30px"><g clip-path="url(#a)"><path fill="#FBAD41" d="M52.688 13.028c-.22 0-.437.008-.654.015a.297.297 0 0 0-.102.024.365.365 0 0 0-.236.255l-.93 3.249c-.401 1.397-.252 2.687.422 3.634.618.876 1.646 1.39 2.894 1.45l5.045.306c.15.008.28.08.359.199a.492.492 0 0 1 .051.434.64.64 0 0 1-.547.426l-5.242.306c-2.848.132-5.912 2.456-6.987 5.29l-.378 1a.28.28 0 0 0 .248.382h18.054a.48.48 0 0 0 .464-.35 13.12 13.12 0 0 0 .48-3.54c0-7.22-5.789-13.072-12.933-13.072"></path><path fill="#F6821F" d="m44.808 29.578.334-1.175c.402-1.397.253-2.687-.42-3.634-.62-.876-1.647-1.39-2.896-1.45l-23.665-.306a.467.467 0 0 1-.374-.199.492.492 0 0 1-.052-.434.64.64 0 0 1 .552-.426l23.886-.306c2.836-.131 5.9-2.456 6.975-5.29l1.362-3.6a.914.914 0 0 0 .04-.477C48.998 5.259 42.79 0 35.368 0c-6.842 0-12.647 4.462-14.73 10.665a6.92 6.92 0 0 0-4.911-1.374c-3.28.33-5.92 3.002-6.246 6.318a7.148 7.148 0 0 0 .18 2.472c-5.36.16-9.66 4.598-9.66 10.052 0 .493.035.979.106 1.453a.46.46 0 0 0 .457.402h43.704a.57.57 0 0 0 .54-.418"></path></g><defs><clipPath id="a"><path fill="#FFF" d="M0 0h204v30H0z"></path></clipPath></defs></svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
@@ -34,9 +34,22 @@ const visibleColumnCount = computed(() => props.table.getVisibleLeafColumns().le
|
||||
const hasRows = computed(() => props.table.getRowModel().rows.length > 0)
|
||||
|
||||
const getColumnStyles = (column: Column<TData, unknown>) => {
|
||||
const maxWidth = column.columnDef.meta?.maxWidth
|
||||
if (maxWidth == null) return undefined
|
||||
return { maxWidth: typeof maxWidth === 'number' ? `${maxWidth}px` : maxWidth }
|
||||
const meta = column.columnDef.meta
|
||||
if (!meta) return undefined
|
||||
|
||||
const styles: Record<string, string> = {}
|
||||
|
||||
if (meta.width != null) {
|
||||
styles.width = typeof meta.width === 'number' ? `${meta.width}px` : meta.width
|
||||
}
|
||||
if (meta.minWidth != null) {
|
||||
styles.minWidth = typeof meta.minWidth === 'number' ? `${meta.minWidth}px` : meta.minWidth
|
||||
}
|
||||
if (meta.maxWidth != null) {
|
||||
styles.maxWidth = typeof meta.maxWidth === 'number' ? `${meta.maxWidth}px` : meta.maxWidth
|
||||
}
|
||||
|
||||
return Object.keys(styles).length > 0 ? styles : undefined
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@@ -74,11 +74,11 @@ const handleEscape = (event: Event) => {
|
||||
</slot>
|
||||
</DialogHeader>
|
||||
|
||||
<div class="max-h-[80vh]">
|
||||
<div class="max-h-[80vh] overflow-scroll">
|
||||
<slot />
|
||||
</div>
|
||||
|
||||
<DialogFooter v-if="showFooter && $slots.footer">
|
||||
<DialogFooter v-if="showFooter && $slots.footer" class="border-t pt-4">
|
||||
<slot name="footer" />
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="sticky top-0 z-10 flex flex-col justify-between gap-2 md:flex-row">
|
||||
<div class="sticky bg-background top-0 z-10 flex flex-col justify-between gap-2 md:flex-row">
|
||||
<div class="space-y-2">
|
||||
<slot />
|
||||
<slot name="description"></slot>
|
||||
|
||||
@@ -52,24 +52,40 @@
|
||||
</FieldContent>
|
||||
</Field>
|
||||
|
||||
<Field>
|
||||
<FieldLabel>{{ t('Access Key') }}</FieldLabel>
|
||||
<FieldContent>
|
||||
<Input v-model="formData.accesskey" :placeholder="t('Please enter Access Key')" autocomplete="off" />
|
||||
</FieldContent>
|
||||
</Field>
|
||||
<template v-if="isGCS">
|
||||
<Field>
|
||||
<FieldLabel>{{ t('Credentials') }} (JSON)</FieldLabel>
|
||||
<FieldContent>
|
||||
<Textarea
|
||||
v-model="formData.creds"
|
||||
:placeholder="t('Please enter GCS credentials JSON')"
|
||||
autocomplete="off"
|
||||
:rows="6"
|
||||
/>
|
||||
</FieldContent>
|
||||
</Field>
|
||||
</template>
|
||||
|
||||
<Field>
|
||||
<FieldLabel>{{ t('Secret Key') }}</FieldLabel>
|
||||
<FieldContent>
|
||||
<Input
|
||||
v-model="formData.secretkey"
|
||||
type="password"
|
||||
autocomplete="off"
|
||||
:placeholder="t('Please enter Secret Key')"
|
||||
/>
|
||||
</FieldContent>
|
||||
</Field>
|
||||
<template v-else>
|
||||
<Field>
|
||||
<FieldLabel>{{ t('Access Key') }}</FieldLabel>
|
||||
<FieldContent>
|
||||
<Input v-model="formData.accesskey" :placeholder="t('Please enter Access Key')" autocomplete="off" />
|
||||
</FieldContent>
|
||||
</Field>
|
||||
|
||||
<Field>
|
||||
<FieldLabel>{{ t('Secret Key') }}</FieldLabel>
|
||||
<FieldContent>
|
||||
<Input
|
||||
v-model="formData.secretkey"
|
||||
type="password"
|
||||
autocomplete="off"
|
||||
:placeholder="t('Please enter Secret Key')"
|
||||
/>
|
||||
</FieldContent>
|
||||
</Field>
|
||||
</template>
|
||||
|
||||
<Field>
|
||||
<FieldLabel>{{ t('Bucket') }}</FieldLabel>
|
||||
@@ -116,13 +132,20 @@
|
||||
<script setup lang="ts">
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { Textarea } from '@/components/ui/textarea'
|
||||
|
||||
import { Field, FieldContent, FieldDescription, FieldLabel } from '@/components/ui/field'
|
||||
import { reactive, ref } from 'vue'
|
||||
import { reactive, ref, computed } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import RustfsIcon from '~/assets/logo.svg'
|
||||
import AWSIcon from '~/assets/svg/aws.svg'
|
||||
import MinioIcon from '~/assets/svg/minio.svg'
|
||||
import AliyunIcon from '~/assets/svg/aliyun.svg'
|
||||
import TencentIcon from '~/assets/svg/tenxunyun.svg'
|
||||
import HuaweiIcon from '~/assets/svg/huaweiyun.svg'
|
||||
import AzureIcon from '~/assets/svg/azure.svg'
|
||||
import GoogleIcon from '~/assets/svg/google.svg'
|
||||
import CloudflareIcon from '~/assets/svg/cloudflare.svg'
|
||||
import Modal from '~/components/modal.vue'
|
||||
|
||||
const { t } = useI18n()
|
||||
@@ -133,6 +156,22 @@ const typeOptions = [
|
||||
{ label: t('RustFS'), value: 'rustfs', iconUrl: RustfsIcon, description: t('RustFS built-in cold storage') },
|
||||
{ label: t('Minio'), value: 'minio', iconUrl: MinioIcon, description: t('External MinIO tier') },
|
||||
{ label: t('AWS S3'), value: 's3', iconUrl: AWSIcon, description: t('Standard AWS S3 tier') },
|
||||
{
|
||||
label: t('Aliyun OSS'),
|
||||
value: 'aliyun',
|
||||
iconUrl: AliyunIcon,
|
||||
description: t('Alibaba Cloud Object Storage Service'),
|
||||
},
|
||||
{ label: t('Tencent COS'), value: 'tencent', iconUrl: TencentIcon, description: t('Tencent Cloud Object Storage') },
|
||||
{
|
||||
label: t('Huawei OBS'),
|
||||
value: 'huaweicloud',
|
||||
iconUrl: HuaweiIcon,
|
||||
description: t('Huawei Cloud Object Storage Service'),
|
||||
},
|
||||
{ label: t('Azure Blob'), value: 'azure', iconUrl: AzureIcon, description: t('Microsoft Azure Blob Storage') },
|
||||
{ label: t('Google Cloud Storage'), value: 'gcs', iconUrl: GoogleIcon, description: t('Google Cloud Storage') },
|
||||
{ label: t('Cloudflare R2'), value: 'r2', iconUrl: CloudflareIcon, description: t('Cloudflare R2 Storage') },
|
||||
]
|
||||
|
||||
const visible = ref(false)
|
||||
@@ -145,12 +184,15 @@ const formData = reactive({
|
||||
endpoint: '',
|
||||
accesskey: '',
|
||||
secretkey: '',
|
||||
creds: '',
|
||||
bucket: '',
|
||||
prefix: '',
|
||||
region: '',
|
||||
storageclass: 'STANDARD',
|
||||
})
|
||||
|
||||
const isGCS = computed(() => formData.type === 'gcs')
|
||||
|
||||
const errors = reactive({
|
||||
name: '',
|
||||
})
|
||||
@@ -175,6 +217,7 @@ const resetForm = () => {
|
||||
formData.endpoint = ''
|
||||
formData.accesskey = ''
|
||||
formData.secretkey = ''
|
||||
formData.creds = ''
|
||||
formData.bucket = ''
|
||||
formData.prefix = ''
|
||||
formData.region = ''
|
||||
@@ -203,18 +246,25 @@ const handleSave = async () => {
|
||||
if (!validate()) return
|
||||
submitting.value = true
|
||||
try {
|
||||
const config: any = {
|
||||
name: formData.name,
|
||||
endpoint: formData.endpoint,
|
||||
bucket: formData.bucket,
|
||||
prefix: formData.prefix,
|
||||
region: formData.region,
|
||||
storageClass: formData.storageclass,
|
||||
}
|
||||
|
||||
if (isGCS.value) {
|
||||
config.creds = formData.creds
|
||||
} else {
|
||||
config.accessKey = formData.accesskey
|
||||
config.secretKey = formData.secretkey
|
||||
}
|
||||
|
||||
const payload = {
|
||||
type: formData.type,
|
||||
[formData.type]: {
|
||||
name: formData.name,
|
||||
endpoint: formData.endpoint,
|
||||
bucket: formData.bucket,
|
||||
prefix: formData.prefix,
|
||||
region: formData.region,
|
||||
accessKey: formData.accesskey,
|
||||
secretKey: formData.secretkey,
|
||||
storageClass: formData.storageclass,
|
||||
},
|
||||
[formData.type]: config,
|
||||
}
|
||||
|
||||
await usetier.addTiers(payload)
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
<script setup lang="ts">
|
||||
import type { HTMLAttributes } from 'vue'
|
||||
import type { HTMLAttributes, CSSProperties } from 'vue'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const props = defineProps<{
|
||||
class?: HTMLAttributes['class']
|
||||
style?: CSSProperties | string
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<td :class="cn('p-4 align-middle [&:has([role=checkbox])]:pr-0', props.class)">
|
||||
<td :class="cn('p-4 align-middle [&:has([role=checkbox])]:pr-0', props.class)" :style="props.style">
|
||||
<slot />
|
||||
</td>
|
||||
</template>
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
<script setup lang="ts">
|
||||
import type { HTMLAttributes } from 'vue'
|
||||
import type { HTMLAttributes, CSSProperties } from 'vue'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const props = defineProps<{
|
||||
class?: HTMLAttributes['class']
|
||||
style?: CSSProperties | string
|
||||
}>()
|
||||
</script>
|
||||
|
||||
@@ -15,6 +16,7 @@ const props = defineProps<{
|
||||
props.class
|
||||
)
|
||||
"
|
||||
:style="props.style"
|
||||
>
|
||||
<slot />
|
||||
</th>
|
||||
|
||||
@@ -13,10 +13,10 @@
|
||||
</TabsList>
|
||||
|
||||
<TabsContent value="users" class="mt-0">
|
||||
<users-group-members :group="group" @search="getGroupData(group.name)" />
|
||||
<user-group-members :group="group" @search="getGroupData(group.name)" />
|
||||
</TabsContent>
|
||||
<TabsContent value="policy" class="mt-0">
|
||||
<users-group-policies :group="group" @search="getGroupData(group.name)" />
|
||||
<user-group-policies :group="group" @search="getGroupData(group.name)" />
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
</div>
|
||||
|
||||
@@ -19,12 +19,17 @@
|
||||
:placeholder="t('Select user group members')"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex items-center gap-2 sm:self-start">
|
||||
<div class="flex items-center gap-2 sm:self-end">
|
||||
<Button type="button" variant="outline" @click="changeMembers">{{ t('Submit') }}</Button>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<div v-if="editStatus && members.length" class="flex flex-wrap gap-2">
|
||||
<Badge v-for="value in members" :key="value" variant="secondary">
|
||||
{{ value }}
|
||||
</Badge>
|
||||
</div>
|
||||
|
||||
<DataTable :table="table" />
|
||||
</div>
|
||||
@@ -124,11 +129,11 @@ const changeMembers = async () => {
|
||||
groupStatus: 'enabled',
|
||||
})
|
||||
|
||||
message.success('修改成功')
|
||||
message.success(t('Edit Success'))
|
||||
editStatus.value = false
|
||||
emit('search')
|
||||
} catch (error) {
|
||||
message.error('修改失败')
|
||||
message.error(t('Edit Failed'))
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -52,12 +52,9 @@
|
||||
</Command>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
<div v-if="selectedPolicies.length" class="flex flex-wrap gap-2">
|
||||
<Badge v-for="value in selectedPolicies" :key="value" variant="secondary">{{ value }}</Badge>
|
||||
</div>
|
||||
</FieldContent>
|
||||
</Field>
|
||||
<div class="flex items-center gap-2 sm:self-start">
|
||||
<div class="flex items-center gap-2 sm:self-end">
|
||||
<Button variant="ghost" @click="cancelEditing">
|
||||
{{ t('Cancel') }}
|
||||
</Button>
|
||||
@@ -66,7 +63,9 @@
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="editStatus && selectedPolicies.length" class="flex flex-wrap gap-2">
|
||||
<Badge v-for="value in selectedPolicies" :key="value" variant="secondary">{{ value }}</Badge>
|
||||
</div>
|
||||
<DataTable :table="table" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -139,11 +139,11 @@ defineExpose({
|
||||
</Command>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
<div v-if="showBadges && modelValue.length" class="flex flex-wrap gap-2">
|
||||
<Badge v-for="value in modelValue" :key="value" variant="secondary">
|
||||
{{ userLabelMap[value] ?? value }}
|
||||
</Badge>
|
||||
</div>
|
||||
</FieldContent>
|
||||
</Field>
|
||||
<!-- <div v-if="showBadges && modelValue.length" class="flex flex-wrap gap-2">
|
||||
<Badge v-for="value in modelValue" :key="value" variant="secondary">
|
||||
{{ userLabelMap[value] ?? value }}
|
||||
</Badge>
|
||||
</div> -->
|
||||
</template>
|
||||
|
||||
@@ -75,7 +75,7 @@ const refreshUser = async () => {
|
||||
user.value = {
|
||||
accessKey: user.value.accessKey,
|
||||
memberOf: latest.memberOf ?? [],
|
||||
policy: latest.policy ?? [],
|
||||
policy: latest.policyName?.split(',') ?? [],
|
||||
status: latest.status ?? 'enabled',
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,12 +52,9 @@
|
||||
</Command>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
<div v-if="selectedGroups.length" class="flex flex-wrap gap-2">
|
||||
<Badge v-for="value in selectedGroups" :key="value" variant="secondary">{{ value }}</Badge>
|
||||
</div>
|
||||
</FieldContent>
|
||||
</Field>
|
||||
<div class="flex items-center gap-2 sm:self-start">
|
||||
<div class="flex items-center gap-2 sm:self-end">
|
||||
<Button variant="ghost" @click="cancelEditing">
|
||||
{{ t('Cancel') }}
|
||||
</Button>
|
||||
@@ -66,6 +63,9 @@
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="selectedGroups.length" class="flex flex-wrap gap-2">
|
||||
<Badge v-for="value in selectedGroups" :key="value" variant="secondary">{{ value }}</Badge>
|
||||
</div>
|
||||
|
||||
<DataTable :table="table" />
|
||||
</div>
|
||||
|
||||
@@ -52,12 +52,9 @@
|
||||
</Command>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
<div v-if="selectedPolicies.length" class="flex flex-wrap gap-2">
|
||||
<Badge v-for="value in selectedPolicies" :key="value" variant="secondary">{{ value }}</Badge>
|
||||
</div>
|
||||
</FieldContent>
|
||||
</Field>
|
||||
<div class="flex items-center gap-2 sm:self-start">
|
||||
<div class="flex items-center gap-2 sm:self-end">
|
||||
<Button variant="ghost" @click="cancelEditing">
|
||||
{{ t('Cancel') }}
|
||||
</Button>
|
||||
@@ -66,6 +63,9 @@
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="selectedPolicies.length" class="flex flex-wrap gap-2">
|
||||
<Badge v-for="value in selectedPolicies" :key="value" variant="secondary">{{ value }}</Badge>
|
||||
</div>
|
||||
|
||||
<DataTable :table="table" />
|
||||
</div>
|
||||
|
||||
@@ -848,5 +848,7 @@
|
||||
"username length cannot be less than 8 characters and greater than 16 characters": "username length cannot be less than 8 characters and greater than 16 characters",
|
||||
"Validation failed": "Validation failed",
|
||||
"API request failed": "API request failed",
|
||||
"Operation failed": "Operation failed"
|
||||
"Operation failed": "Operation failed",
|
||||
"Create a user to get started": "Create a user to get started",
|
||||
"Get Notification Config Failed": "Get Notification Config Failed"
|
||||
}
|
||||
|
||||
@@ -848,5 +848,7 @@
|
||||
"username length cannot be less than 8 characters and greater than 16 characters": "username length cannot be less than 8 characters and greater than 16 characters",
|
||||
"Validation failed": "Doğrulama başarısız",
|
||||
"API request failed": "API isteği başarısız",
|
||||
"Operation failed": "İşlem başarısız"
|
||||
"Operation failed": "İşlem başarısız",
|
||||
"Create a user to get started": "Başlamak için bir kullanıcı oluşturun",
|
||||
"Get Notification Config Failed": "Bildirim Yapılandırması Alınamadı"
|
||||
}
|
||||
|
||||
@@ -848,5 +848,7 @@
|
||||
"username length cannot be less than 8 characters and greater than 16 characters": "用户名长度不能少于8个字符且不能大于16个字符",
|
||||
"Validation failed": "验证失败",
|
||||
"API request failed": "API 请求失败",
|
||||
"Operation failed": "操作失败"
|
||||
"Operation failed": "操作失败",
|
||||
"Create a user to get started": "开始创建一个用户",
|
||||
"Get Notification Config Failed": "获取通知配置失败"
|
||||
}
|
||||
|
||||
@@ -118,6 +118,9 @@ const columns: ColumnDef<RowData>[] = [
|
||||
header: () => t('Actions'),
|
||||
enableSorting: false,
|
||||
enableHiding: false,
|
||||
meta: {
|
||||
width: 200,
|
||||
},
|
||||
cell: ({ row }) =>
|
||||
h('div', { class: 'flex justify-center gap-2' }, [
|
||||
h(
|
||||
|
||||
@@ -29,14 +29,14 @@ const handleLogin = async () => {
|
||||
const credentials = method.value === 'accessKeyAndSecretKey' ? accessKeyAndSecretKey.value : sts.value
|
||||
|
||||
try {
|
||||
// 使用配置管理器获取配置(优先使用localStorage中的配置)
|
||||
// use configManager to get config .use localStorage first, then use the config to login
|
||||
const { configManager } = await import('~/utils/config')
|
||||
const currentConfig = await configManager.loadConfig()
|
||||
|
||||
await auth.login(credentials, currentConfig)
|
||||
|
||||
message.success(t('Login Success'))
|
||||
// 重新加载页面以确保新的配置生效
|
||||
// reload the page to ensure the new config is applied
|
||||
window.location.reload()
|
||||
} catch (error) {
|
||||
message.error(t('Login Failed'))
|
||||
@@ -56,7 +56,7 @@ const handleLogin = async () => {
|
||||
<div
|
||||
class="w-full lg:w-1/2 flex flex-col justify-center items-center bg-white dark:bg-neutral-900 dark:border-neutral-700 relative"
|
||||
>
|
||||
<!-- 表单容器右上角的配置按钮 -->
|
||||
<!-- config button in the top right corner of the form container -->
|
||||
<div class="absolute top-4 right-4 z-10">
|
||||
<NuxtLink
|
||||
to="/config"
|
||||
|
||||
@@ -130,6 +130,9 @@ const columns: ColumnDef<BucketRow>[] = [
|
||||
id: 'actions',
|
||||
header: () => t('Actions'),
|
||||
enableSorting: false,
|
||||
meta: {
|
||||
width: 200,
|
||||
},
|
||||
cell: ({ row }) =>
|
||||
h('div', { class: 'flex items-center gap-2' }, [
|
||||
h(
|
||||
|
||||
+75
-73
@@ -95,82 +95,84 @@ onMounted(() => {
|
||||
|
||||
<template>
|
||||
<page>
|
||||
<img src="~/assets/backgrounds/scillate.svg" class="absolute inset-0 z-0 opacity-45" alt="" />
|
||||
<div
|
||||
class="flex-1 flex w-full z-10 max-w-7xl lg:max-h-[85vh] shadow-lg rounded-lg overflow-hidden mx-auto dark:bg-neutral-800 dark:border-neutral-700"
|
||||
>
|
||||
<div class="hidden lg:block w-1/2">
|
||||
<auth-heros-static></auth-heros-static>
|
||||
</div>
|
||||
<div class="lg:p-20 flex flex-col items-center justify-center min-h-screen bg-gray-100 dark:bg-neutral-800">
|
||||
<img src="~/assets/backgrounds/scillate.svg" class="absolute inset-0 z-0 opacity-45" alt="" />
|
||||
<div
|
||||
class="w-full lg:w-1/2 flex flex-col justify-center items-center bg-white dark:bg-neutral-900 dark:border-neutral-700 relative"
|
||||
class="flex-1 flex w-full z-10 max-w-7xl lg:max-h-[85vh] shadow-lg rounded-lg overflow-hidden mx-auto dark:bg-neutral-800 dark:border-neutral-700"
|
||||
>
|
||||
<div class="max-w-sm w-full p-4 sm:p-7">
|
||||
<img src="~/assets/logo.svg" class="max-w-28" alt="" />
|
||||
<div class="py-6">
|
||||
<h1 class="block text-2xl font-bold text-gray-800 dark:text-white">
|
||||
{{ t('Server Configuration') }}
|
||||
</h1>
|
||||
<p class="mt-2 text-sm text-gray-600 dark:text-neutral-400">
|
||||
{{ t('Please configure your RustFS server address') }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="mt-5 space-y-4">
|
||||
<!-- Form -->
|
||||
<form @submit.prevent="validateAndSave" autocomplete="off">
|
||||
<div class="grid gap-y-6">
|
||||
<Field>
|
||||
<FieldLabel for="serverHost">{{ t('Server Address') }}</FieldLabel>
|
||||
<FieldDescription>
|
||||
{{ t('Leave empty to use current host as default') }}
|
||||
</FieldDescription>
|
||||
<FieldContent>
|
||||
<Input
|
||||
id="serverHost"
|
||||
v-model="serverHost"
|
||||
type="text"
|
||||
:placeholder="t('Please enter server address (e.g., http://localhost:9000)')"
|
||||
/>
|
||||
</FieldContent>
|
||||
<FieldDescription>
|
||||
{{ t('Example: http://localhost:9000 or https://your-domain.com') }}
|
||||
</FieldDescription>
|
||||
</Field>
|
||||
|
||||
<div class="flex gap-3">
|
||||
<Button type="submit" class="flex-1">
|
||||
{{ t('Save Configuration') }}
|
||||
</Button>
|
||||
|
||||
<Button type="button" variant="outline" @click="resetToCurrentHost">
|
||||
{{ t('Reset') }}
|
||||
</Button>
|
||||
|
||||
<Button type="button" variant="outline" @click="skipConfig">
|
||||
{{ t('Skip') }}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<!-- End Form -->
|
||||
</div>
|
||||
|
||||
<div class="my-8">
|
||||
<p class="text-sm text-gray-600 dark:text-neutral-400">
|
||||
{{ t('Need help?') }}
|
||||
<NuxtLink to="https://docs.rustfs.com" class="text-blue-600 hover:underline">{{
|
||||
t('View Documentation')
|
||||
}}</NuxtLink>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-around gap-4 w-1/2 mx-auto">
|
||||
<div class="inline-flex">
|
||||
<theme-switcher />
|
||||
<div class="hidden lg:block w-1/2">
|
||||
<auth-heros-static></auth-heros-static>
|
||||
</div>
|
||||
<div
|
||||
class="w-full lg:w-1/2 flex flex-col justify-center items-center bg-white dark:bg-neutral-900 dark:border-neutral-700 relative"
|
||||
>
|
||||
<div class="max-w-sm w-full p-4 sm:p-7">
|
||||
<img src="~/assets/logo.svg" class="max-w-28" alt="" />
|
||||
<div class="py-6">
|
||||
<h1 class="block text-2xl font-bold text-gray-800 dark:text-white">
|
||||
{{ t('Server Configuration') }}
|
||||
</h1>
|
||||
<p class="mt-2 text-sm text-gray-600 dark:text-neutral-400">
|
||||
{{ t('Please configure your RustFS server address') }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="inline-flex">
|
||||
<language-switcher />
|
||||
|
||||
<div class="mt-5 space-y-4">
|
||||
<!-- Form -->
|
||||
<form @submit.prevent="validateAndSave" autocomplete="off">
|
||||
<div class="grid gap-y-6">
|
||||
<Field>
|
||||
<FieldLabel for="serverHost">{{ t('Server Address') }}</FieldLabel>
|
||||
<FieldDescription>
|
||||
{{ t('Leave empty to use current host as default') }}
|
||||
</FieldDescription>
|
||||
<FieldContent>
|
||||
<Input
|
||||
id="serverHost"
|
||||
v-model="serverHost"
|
||||
type="text"
|
||||
:placeholder="t('Please enter server address (e.g., http://localhost:9000)')"
|
||||
/>
|
||||
</FieldContent>
|
||||
<FieldDescription>
|
||||
{{ t('Example: http://localhost:9000 or https://your-domain.com') }}
|
||||
</FieldDescription>
|
||||
</Field>
|
||||
|
||||
<div class="flex gap-3">
|
||||
<Button type="submit" class="flex-1">
|
||||
{{ t('Save Configuration') }}
|
||||
</Button>
|
||||
|
||||
<Button type="button" variant="outline" @click="resetToCurrentHost">
|
||||
{{ t('Reset') }}
|
||||
</Button>
|
||||
|
||||
<Button type="button" variant="outline" @click="skipConfig">
|
||||
{{ t('Skip') }}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<!-- End Form -->
|
||||
</div>
|
||||
|
||||
<div class="my-8">
|
||||
<p class="text-sm text-gray-600 dark:text-neutral-400">
|
||||
{{ t('Need help?') }}
|
||||
<NuxtLink to="https://docs.rustfs.com" class="text-blue-600 hover:underline">{{
|
||||
t('View Documentation')
|
||||
}}</NuxtLink>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-around gap-4 w-1/2 mx-auto">
|
||||
<div class="inline-flex">
|
||||
<theme-switcher />
|
||||
</div>
|
||||
<div class="inline-flex">
|
||||
<language-switcher />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -81,6 +81,9 @@ const columns: ColumnDef<RowData>[] = [
|
||||
header: () => t('Actions'),
|
||||
enableSorting: false,
|
||||
enableHiding: false,
|
||||
meta: {
|
||||
width: 90,
|
||||
},
|
||||
cell: ({ row }) =>
|
||||
h('div', { class: 'flex items-center gap-2' }, [
|
||||
h(
|
||||
|
||||
@@ -212,7 +212,7 @@ const handleRowDelete = async (row: NotificationItem, event?: Event) => {
|
||||
message.success(t('Delete Success'))
|
||||
await refresh()
|
||||
} catch (error: any) {
|
||||
console.error('删除通知配置失败:', error)
|
||||
console.error(t('Delete Failed'), error)
|
||||
message.error(`${t('Delete Failed')}: ${error.message || error}`)
|
||||
} finally {
|
||||
loading.value = false
|
||||
@@ -294,7 +294,7 @@ const refresh = async () => {
|
||||
|
||||
pageData.value = notifications
|
||||
} catch (error) {
|
||||
console.error('获取通知配置失败:', error)
|
||||
console.error(t('Get Notification Config Failed'), error)
|
||||
pageData.value = []
|
||||
} finally {
|
||||
loading.value = false
|
||||
|
||||
@@ -72,6 +72,9 @@ const columns: ColumnDef<PolicyRow>[] = [
|
||||
header: () => t('Actions'),
|
||||
enableSorting: false,
|
||||
enableHiding: false,
|
||||
meta: {
|
||||
width: 200,
|
||||
},
|
||||
cell: ({ row }) =>
|
||||
h('div', { class: 'flex items-center gap-2' }, [
|
||||
h(
|
||||
|
||||
@@ -62,6 +62,18 @@ const getConfig = (row: TierRow) => {
|
||||
return row.minio
|
||||
case 's3':
|
||||
return row.s3
|
||||
case 'aliyun':
|
||||
return row.aliyun
|
||||
case 'tencent':
|
||||
return row.tencent
|
||||
case 'huaweicloud':
|
||||
return row.huaweicloud
|
||||
case 'azure':
|
||||
return row.azure
|
||||
case 'gcs':
|
||||
return row.gcs
|
||||
case 'r2':
|
||||
return row.r2
|
||||
default:
|
||||
return undefined
|
||||
}
|
||||
|
||||
@@ -80,6 +80,9 @@ const columns: ColumnDef<GroupRow>[] = [
|
||||
header: () => t('Actions'),
|
||||
enableSorting: false,
|
||||
enableHiding: false,
|
||||
meta: {
|
||||
width: 200,
|
||||
},
|
||||
cell: ({ row }) =>
|
||||
h('div', { class: 'flex items-center gap-2' }, [
|
||||
h(
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
:table="table"
|
||||
:is-loading="loading"
|
||||
:empty-title="t('No Data')"
|
||||
:empty-description="t('Create a user to get started.')"
|
||||
:empty-description="t('Create a user to get started')"
|
||||
table-class="min-w-full"
|
||||
/>
|
||||
<DataTablePagination :table="table" />
|
||||
@@ -122,6 +122,9 @@ const columns: ColumnDef<UserRow>[] = [
|
||||
header: () => t('Actions'),
|
||||
enableSorting: false,
|
||||
enableHiding: false,
|
||||
meta: {
|
||||
width: 200,
|
||||
},
|
||||
cell: ({ row }) =>
|
||||
h('div', { class: 'flex items-center gap-2' }, [
|
||||
h(
|
||||
|
||||
Vendored
+2
@@ -2,6 +2,8 @@ import type { RowData } from '@tanstack/vue-table'
|
||||
|
||||
declare module '@tanstack/vue-table' {
|
||||
interface ColumnMeta<TData extends RowData, TValue> {
|
||||
width?: string | number
|
||||
minWidth?: string | number
|
||||
maxWidth?: string | number
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user