mirror of
https://github.com/InvoiceShelf/InvoiceShelf.git
synced 2026-04-07 13:41:23 +00:00
Support S3 compatible storage services (#56)
* add s3compat filesystem driver * add s3compat ui modal * fix code style
This commit is contained in:
@@ -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',
|
||||
|
||||
@@ -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'),
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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>
|
||||
@@ -51,6 +51,17 @@ export const useDiskStore = (useWindow = false) => {
|
||||
bucket: '',
|
||||
root: '',
|
||||
},
|
||||
|
||||
s3CompatDiskConfigData: {
|
||||
name: '',
|
||||
selected_driver: 's3compat',
|
||||
key: '',
|
||||
secret: '',
|
||||
region: '',
|
||||
bucket: '',
|
||||
root: '',
|
||||
endpoint: '',
|
||||
},
|
||||
}),
|
||||
|
||||
getters: {
|
||||
|
||||
@@ -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',
|
||||
|
||||
Reference in New Issue
Block a user