Support S3 compatible storage services (#56)

* add s3compat filesystem driver

* add s3compat ui modal

* fix code style
This commit is contained in:
Timo
2024-04-16 17:24:56 +02:00
committed by GitHub
parent 093b2acc24
commit dc8a85538f
8 changed files with 398 additions and 22 deletions

View File

@@ -94,6 +94,16 @@ class DiskController extends Controller
break;
case 's3compat':
$diskData = [
'endpoint' => '',
'key' => '',
'secret' => '',
'region' => '',
'bucket' => '',
'root' => '',
];
case 'doSpaces':
$diskData = [
'key' => '',
@@ -160,6 +170,10 @@ class DiskController extends Controller
'name' => 'Amazon S3',
'value' => 's3',
],
[
'name' => 'S3 Compatible Storage',
'value' => 's3compat',
],
[
'name' => 'Digital Ocean Spaces',
'value' => 'doSpaces',

View File

@@ -64,6 +64,16 @@ return [
'root' => env('AWS_ROOT'),
],
's3compat' => [
'driver' => 's3',
'endpoint' => env('S3_COMPAT_ENDPOINT'),
'use_path_style_endpoint' => true,
'key' => env('S3_COMPAT_KEY'),
'secret' => env('S3_COMPAT_SECRET'),
'region' => env('S3_COMPAT_REGION'),
'bucket' => env('S3_COMPAT_BUCKET'),
],
'media' => [
'driver' => 'local',
'root' => public_path('media'),

View File

@@ -1267,6 +1267,12 @@
"aws_region": "AWS-Region",
"aws_bucket": "AWS Bucket",
"aws_root": "AWS-Pfad",
"s3_endpoint": "S3 Endpunkt",
"s3_key": "S3 Schlüssel",
"s3_secret": "S3 Geheimnis",
"s3_region": "S3 Region",
"s3_bucket": "S3 Bucket",
"s3_root": "S3 Pfad",
"do_spaces_type": "Do Spaces-Typ",
"do_spaces_key": "Do Spaces Key",
"do_spaces_secret": "Do Spaces Secret",

View File

@@ -1267,6 +1267,12 @@
"aws_region": "AWS Region",
"aws_bucket": "AWS Bucket",
"aws_root": "AWS Root",
"s3_endpoint": "S3 Endpoint",
"s3_key": "S3 Key",
"s3_secret": "S3 Secret",
"s3_region": "S3 Region",
"s3_bucket": "S3 Bucket",
"s3_root": "S3 Root",
"do_spaces_type": "Do Spaces type",
"do_spaces_key": "Do Spaces key",
"do_spaces_secret": "Do Spaces Secret",

View File

@@ -21,13 +21,7 @@
>
<template #default="slotProps">
<div
class="
z-0
flex
justify-end
p-4
border-t border-solid border-gray-light
"
class="z-0 flex justify-end p-4 border-t border-solid border-gray-light"
>
<BaseButton
class="mr-3 text-sm"
@@ -66,12 +60,14 @@ import { computed, ref, watchEffect } from 'vue'
import Dropbox from '@/scripts/admin/components/modal-components/disks/DropboxDisk.vue'
import Local from '@/scripts/admin/components/modal-components/disks/LocalDisk.vue'
import S3 from '@/scripts/admin/components/modal-components/disks/S3Disk.vue'
import S3compat from '@/scripts/admin/components/modal-components/disks/S3CompatDisk.vue'
import DoSpaces from '@/scripts/admin/components/modal-components/disks/DoSpacesDisk.vue'
export default {
components: {
Dropbox,
Local,
S3,
S3compat,
DoSpaces,
},
setup() {
@@ -109,20 +105,28 @@ export default {
}
async function createNewDisk(data) {
Object.assign(diskStore.diskConfigData, data)
isLoading.value = true
try {
Object.assign(diskStore.diskConfigData, data)
isLoading.value = true
let formData = {
id: modalStore.id,
...data,
let formData = {
id: modalStore.id,
...data,
}
let response = null
const action = isEdit.value
? diskStore.updateDisk
: diskStore.createDisk
response = await action(formData)
modalStore.refreshData()
closeDiskModal()
} catch (e) {
// error is handled by the disk store
} finally {
isLoading.value = false
}
let response = null
const action = isEdit.value ? diskStore.updateDisk : diskStore.createDisk
response = await action(formData)
isLoading.value = false
modalStore.refreshData()
closeDiskModal()
}
function closeDiskModal() {

View File

@@ -0,0 +1,325 @@
<template>
<form @submit.prevent="submitData">
<div class="px-8 py-6">
<BaseInputGrid>
<BaseInputGroup
:label="$t('settings.disk.name')"
:error="
v$.s3CompatDiskConfigData.name.$error &&
v$.s3CompatDiskConfigData.name.$errors[0].$message
"
required
>
<BaseInput
v-model="diskStore.s3CompatDiskConfigData.name"
type="text"
name="name"
:invalid="v$.s3CompatDiskConfigData.name.$error"
@input="v$.s3CompatDiskConfigData.name.$touch()"
/>
</BaseInputGroup>
<BaseInputGroup
:label="$t('settings.disk.driver')"
:error="
v$.s3CompatDiskConfigData.selected_driver.$error &&
v$.s3CompatDiskConfigData.selected_driver.$errors[0].$message
"
required
>
<BaseMultiselect
v-model="selected_driver"
:invalid="v$.s3CompatDiskConfigData.selected_driver.$error"
value-prop="value"
:options="disks"
searchable
label="name"
:can-deselect="false"
@update:modelValue="onChangeDriver(data)"
/>
</BaseInputGroup>
<BaseInputGroup
:label="$t('settings.disk.s3_endpoint')"
:error="
v$.s3CompatDiskConfigData.root.$error &&
v$.s3CompatDiskConfigData.root.$errors[0].$message
"
required
>
<BaseInput
v-model.trim="diskStore.s3CompatDiskConfigData.endpoint"
type="url"
name="endpoint"
placeholder="http://127.0.0.1:9005"
:invalid="v$.s3CompatDiskConfigData.endpoint.$error"
@input="v$.s3CompatDiskConfigData.endpoint.$touch()"
/>
</BaseInputGroup>
<BaseInputGroup
:label="$t('settings.disk.s3_root')"
:error="
v$.s3CompatDiskConfigData.root.$error &&
v$.s3CompatDiskConfigData.root.$errors[0].$message
"
required
>
<BaseInput
v-model.trim="diskStore.s3CompatDiskConfigData.root"
type="text"
name="root"
placeholder="Ex. /user/root/"
:invalid="v$.s3CompatDiskConfigData.root.$error"
@input="v$.s3CompatDiskConfigData.root.$touch()"
/>
</BaseInputGroup>
<BaseInputGroup
:label="$t('settings.disk.s3_key')"
:error="
v$.s3CompatDiskConfigData.key.$error &&
v$.s3CompatDiskConfigData.key.$errors[0].$message
"
required
>
<BaseInput
v-model.trim="diskStore.s3CompatDiskConfigData.key"
type="text"
name="key"
placeholder="Ex. KEIS4S39SERSDS"
:invalid="v$.s3CompatDiskConfigData.key.$error"
@input="v$.s3CompatDiskConfigData.key.$touch()"
/>
</BaseInputGroup>
<BaseInputGroup
:label="$t('settings.disk.s3_secret')"
:error="
v$.s3CompatDiskConfigData.secret.$error &&
v$.s3CompatDiskConfigData.secret.$errors[0].$message
"
required
>
<BaseInput
v-model.trim="diskStore.s3CompatDiskConfigData.secret"
type="text"
name="secret"
placeholder="Ex. ********"
:invalid="v$.s3CompatDiskConfigData.secret.$error"
@input="v$.s3CompatDiskConfigData.secret.$touch()"
/>
</BaseInputGroup>
<BaseInputGroup
:label="$t('settings.disk.s3_region')"
:error="
v$.s3CompatDiskConfigData.region.$error &&
v$.s3CompatDiskConfigData.region.$errors[0].$message
"
required
>
<BaseInput
v-model.trim="diskStore.s3CompatDiskConfigData.region"
type="text"
name="region"
placeholder="Ex. us-west"
:invalid="v$.s3CompatDiskConfigData.region.$error"
@input="v$.s3CompatDiskConfigData.region.$touch()"
/>
</BaseInputGroup>
<BaseInputGroup
:label="$t('settings.disk.s3_bucket')"
:error="
v$.s3CompatDiskConfigData.bucket.$error &&
v$.s3CompatDiskConfigData.bucket.$errors[0].$message
"
required
>
<BaseInput
v-model.trim="diskStore.s3CompatDiskConfigData.bucket"
type="text"
name="bucket"
placeholder="Ex. AppName"
:invalid="v$.s3CompatDiskConfigData.bucket.$error"
@input="v$.s3CompatDiskConfigData.bucket.$touch()"
/>
</BaseInputGroup>
</BaseInputGrid>
<div v-if="!isDisabled" class="flex items-center mt-6">
<div class="relative flex items-center w-12">
<BaseSwitch v-model="set_as_default" class="flex" />
</div>
<div class="ml-4 right">
<p class="p-0 mb-1 text-base leading-snug text-black box-title">
{{ $t('settings.disk.is_default') }}
</p>
</div>
</div>
</div>
<slot :disk-data="{ isLoading, submitData }" />
</form>
</template>
<script>
import { useDiskStore } from '@/scripts/admin/stores/disk'
import { useModalStore } from '@/scripts/stores/modal'
import { computed, onBeforeUnmount, reactive, ref } from 'vue'
import { useI18n } from 'vue-i18n'
import useVuelidate from '@vuelidate/core'
import { required, helpers } from '@vuelidate/validators'
export default {
props: {
isEdit: {
type: Boolean,
require: true,
default: false,
},
loading: {
type: Boolean,
require: true,
default: false,
},
disks: {
type: Array,
require: true,
default: Array,
},
},
emits: ['submit', 'onChangeDisk'],
setup(props, { emit }) {
const diskStore = useDiskStore()
const modalStore = useModalStore()
const { t } = useI18n()
let set_as_default = ref(false)
let isLoading = ref(false)
let selected_disk = ref(null)
let is_current_disk = ref(null)
const selected_driver = computed({
get: () => diskStore.selected_driver,
set: (value) => {
diskStore.selected_driver = value
diskStore.s3CompatDiskConfigData.selected_driver = value
},
})
const rules = computed(() => {
return {
s3CompatDiskConfigData: {
name: {
required: helpers.withMessage(t('validation.required'), required),
},
endpoint: {
required: helpers.withMessage(t('validation.required'), required),
},
root: {
required: helpers.withMessage(t('validation.required'), required),
},
key: {
required: helpers.withMessage(t('validation.required'), required),
},
secret: {
required: helpers.withMessage(t('validation.required'), required),
},
region: {
required: helpers.withMessage(t('validation.required'), required),
},
bucket: {
required: helpers.withMessage(t('validation.required'), required),
},
selected_driver: {
required: helpers.withMessage(t('validation.required'), required),
},
},
}
})
const v$ = useVuelidate(
rules,
computed(() => diskStore),
)
onBeforeUnmount(() => {
diskStore.s3CompatDiskConfigData = {
name: null,
selected_driver: 's3compat',
key: null,
secret: null,
region: null,
bucket: null,
root: null,
}
})
loadData()
async function loadData() {
isLoading.value = true
let data = reactive({
disk: 's3compat',
})
if (props.isEdit) {
Object.assign(diskStore.s3CompatDiskConfigData, modalStore.data)
set_as_default.value = modalStore.data.set_as_default
if (set_as_default.value) {
is_current_disk.value = true
}
} else {
let diskData = await diskStore.fetchDiskEnv(data)
Object.assign(diskStore.s3CompatDiskConfigData, diskData.data)
}
selected_disk.value = props.disks.find((v) => v.value == 's3compat')
isLoading.value = false
}
const isDisabled = computed(() => {
return props.isEdit && set_as_default.value && is_current_disk.value
? true
: false
})
async function submitData() {
v$.value.s3CompatDiskConfigData.$touch()
if (v$.value.s3CompatDiskConfigData.$invalid) {
return true
}
let data = {
credentials: diskStore.s3CompatDiskConfigData,
name: diskStore.s3CompatDiskConfigData.name,
driver: selected_disk.value.value,
set_as_default: set_as_default.value,
}
emit('submit', data)
return false
}
function onChangeDriver() {
emit('onChangeDisk', diskStore.s3CompatDiskConfigData.selected_driver)
}
return {
v$,
diskStore,
modalStore,
set_as_default,
isLoading,
selected_disk,
selected_driver,
is_current_disk,
loadData,
submitData,
onChangeDriver,
isDisabled,
}
},
}
</script>

View File

@@ -51,6 +51,17 @@ export const useDiskStore = (useWindow = false) => {
bucket: '',
root: '',
},
s3CompatDiskConfigData: {
name: '',
selected_driver: 's3compat',
key: '',
secret: '',
region: '',
bucket: '',
root: '',
endpoint: '',
},
}),
getters: {

View File

@@ -93,7 +93,7 @@ import FileDiskModal from '@/scripts/admin/components/modal-components/FileDiskM
const utils = inject('utils')
const modelStore = useModalStore()
const modalStore = useModalStore()
const diskStore = useDiskStore()
const companyStore = useCompanyStore()
const dialogStore = useDialogStore()
@@ -189,7 +189,7 @@ function isNotSystemDisk(disk) {
}
function openCreateDiskModal() {
modelStore.openModal({
modalStore.openModal({
title: t('settings.disk.new_disk'),
componentName: 'FileDiskModal',
variant: 'lg',
@@ -198,7 +198,7 @@ function openCreateDiskModal() {
}
function openEditDiskModal(data) {
modelStore.openModal({
modalStore.openModal({
title: t('settings.disk.edit_file_disk'),
componentName: 'FileDiskModal',
variant: 'lg',