FEAT: перевод формы рассчёта
continuous-integration/drone/push Build is passing

This commit is contained in:
2026-09-08 17:03:16 +03:00
parent 672e92d621
commit 4760b1e046
3 changed files with 31 additions and 568 deletions
+31 -47
View File
@@ -53,23 +53,23 @@ const osTypes = ref([
const dbTypes = ref([ const dbTypes = ref([
{ {
label: 'Web application', label: 'Web-приложение',
value: DB_TYPE_WEB, value: DB_TYPE_WEB,
}, },
{ {
label: 'Online transaction processing system', label: 'Система обработки онлайн-транзакций',
value: DB_TYPE_OLTP, value: DB_TYPE_OLTP,
}, },
{ {
label: 'Data warehouse', label: 'Хранилище данных',
value: DB_TYPE_DW, value: DB_TYPE_DW,
}, },
{ {
label: 'Desktop application', label: 'Десктопное приложение',
value: DB_TYPE_DESKTOP, value: DB_TYPE_DESKTOP,
}, },
{ {
label: 'Mixed type of application', label: 'Приложение смешанного типа',
value: DB_TYPE_MIXED, value: DB_TYPE_MIXED,
}, },
]) ])
@@ -78,26 +78,26 @@ const ramUnits = ref([SIZE_UNIT_TB, SIZE_UNIT_GB, SIZE_UNIT_MB])
const dataStorages = ref([ const dataStorages = ref([
{ {
label: 'NVMe storage', label: 'Хранилище NVMe',
value: HARD_DRIVE_NVME, value: HARD_DRIVE_NVME,
}, },
{ {
label: 'SSD storage', label: 'Хранилище SSD',
value: HARD_DRIVE_SSD, value: HARD_DRIVE_SSD,
}, },
{ {
label: 'Network (SAN) storage', label: 'Сетевое хранилище (SAN)',
value: HARD_DRIVE_SAN, value: HARD_DRIVE_SAN,
}, },
{ {
label: 'HDD storage', label: 'Хранилище HDD',
value: HARD_DRIVE_HDD, value: HARD_DRIVE_HDD,
}, },
]) ])
const dataSizes = ref([ const dataSizes = ref([
{ {
label: 'Less than RAM', label: 'Меньше RAM',
value: DB_SIZE_LESS_RAM, value: DB_SIZE_LESS_RAM,
}, },
{ {
@@ -105,7 +105,7 @@ const dataSizes = ref([
value: DB_SIZE_MID_RAM, value: DB_SIZE_MID_RAM,
}, },
{ {
label: 'More than 3x RAM', label: 'Более 3xRAM',
value: DB_SIZE_GREATER_RAM, value: DB_SIZE_GREATER_RAM,
}, },
]) ])
@@ -129,81 +129,65 @@ const onSubmit = async () => {
<template> <template>
<UForm class="space-y-4" :state :schema @submit="onSubmit"> <UForm class="space-y-4" :state :schema @submit="onSubmit">
<UFormField <UFormField label="Версия БД" help="Версия PostgreSQL" name="dbVersion">
label="DB version"
help="PostgreSQL version (find out via 'SELECT version();')"
name="dbVersion"
>
<USelect v-model="state.dbVersion" :items="dbVersions" class="w-full" /> <USelect v-model="state.dbVersion" :items="dbVersions" class="w-full" />
</UFormField> </UFormField>
<UFormField <UFormField
label="OS Type" label="Тип ОС"
help="Operation system of the PostgreSQL server host" help="Операционная система, запускающая PostgreSQL"
name="osType" name="osType"
> >
<USelect v-model="state.osType" :items="osTypes" class="w-full" /> <USelect v-model="state.osType" :items="osTypes" class="w-full" />
</UFormField> </UFormField>
<UFormField <UFormField
label="DB type" label="Тип БД"
help="What type of application is PostgreSQL used for" help="Тип приложения, которое обслуживает PostgreSQL"
name="dbType" name="dbType"
> >
<USelect v-model="state.dbType" :items="dbTypes" class="w-full" /> <USelect v-model="state.dbType" :items="dbTypes" class="w-full" />
</UFormField> </UFormField>
<div class="flex items-start gap-4"> <div class="flex items-start gap-4">
<UFormField <UFormField
label="Total memory (RAM)" label="Оперативная память (RAM)"
help="How much memory can PostgreSQL use" help="Объём оперативной памяти, которую PostgreSQL может использовать"
required required
name="totalMemory" name="totalMemory"
class="grow" class="grow"
> >
<UInputNumber <UInputNumber v-model="state.totalMemory" class="w-full" />
v-model="state.totalMemory"
class="w-full"
placeholder="Total memory (RAM) (* required)"
/>
</UFormField> </UFormField>
<UFormField required name="totalMemoryUnit" label="Unit"> <UFormField required name="totalMemoryUnit" label="Е.И.">
<USelect v-model="state.totalMemoryUnit" :items="ramUnits" /> <USelect v-model="state.totalMemoryUnit" :items="ramUnits" />
</UFormField> </UFormField>
</div> </div>
<UFormField <UFormField
label="Number of CPUs" label="Количество CPU"
help="Number of CPUs, which PostgreSQL can use. CPUs = N threads per core * N cores per socket * N sockets" help="Количество потоков CPU, которые PostgreSQL может использовать. CPU = № потоков на ядро * № ядер на сокет * № сокетов"
name="cpuNum" name="cpuNum"
> >
<UInputNumber <UInputNumber v-model="state.cpuNum" class="w-full" />
v-model="state.cpuNum"
class="w-full"
placeholder="Number of CPUs"
/>
</UFormField> </UFormField>
<UFormField <UFormField
label="Number of connections" label="Количество соединений"
help="Maximum number of PostgreSQL client connections" help="Максимальное количество клиентских соединений"
name="connectionNum" name="connectionNum"
> >
<UInputNumber <UInputNumber v-model="state.connectionNum" class="w-full" />
v-model="state.connectionNum"
class="w-full"
placeholder="Number of connections"
/>
</UFormField> </UFormField>
<UFormField <UFormField
label="Data storage" label="Хранилище данных"
help="Type of data storage device" help="Тип устройства хранения данных"
name="hdType" name="hdType"
> >
<USelect v-model="state.hdType" :items="dataStorages" class="w-full" /> <USelect v-model="state.hdType" :items="dataStorages" class="w-full" />
</UFormField> </UFormField>
<UFormField <UFormField
label="Total data size" label="Объём базы данных"
help="Estimated total size of your database compared to total server RAM" help="Приблизительное отношение объёма хранимых данных к объёму оперативной памяти сервера"
name="dbSize" name="dbSize"
> >
<USelect v-model="state.dbSize" :items="dataSizes" class="w-full" /> <USelect v-model="state.dbSize" :items="dataSizes" class="w-full" />
</UFormField> </UFormField>
<UButton size="xl" label="GENERATE" block type="submit" class="h-16" /> <UButton size="xl" label="Рассчитать" block type="submit" class="h-16" />
</UForm> </UForm>
</template> </template>
-479
View File
@@ -1,479 +0,0 @@
import { defineStore } from 'pinia'
import { reactive, computed } from 'vue'
import { useSettingsStore } from './useSettingsStore'
import {
DEFAULT_DB_VERSION,
OS_LINUX,
OS_WINDOWS,
OS_MAC,
DB_TYPE_WEB,
DB_TYPE_OLTP,
DB_TYPE_DW,
DB_TYPE_DESKTOP,
DB_TYPE_MIXED,
SIZE_UNIT_GB,
HARD_DRIVE_SSD,
HARD_DRIVE_HDD,
HARD_DRIVE_SAN,
HARD_DRIVE_NVME,
DB_SIZE_LESS_RAM,
DB_SIZE_MID_RAM,
DB_SIZE_GREATER_RAM,
} from '/src/utils/constants/configuration.js'
const SIZE_UNIT_MAP = {
KB: 1024,
MB: 1048576,
GB: 1073741824,
TB: 1099511627776,
PB: 1125899906842624,
}
const DEFAULT_DB_SETTINGS = {
default: {
['max_worker_processes']: 8,
['max_parallel_workers_per_gather']: 2,
['max_parallel_workers']: 8,
},
}
const initialState = {
dbVersion: DEFAULT_DB_VERSION,
osType: OS_LINUX,
dbType: DB_TYPE_WEB,
totalMemory: null,
totalMemoryUnit: SIZE_UNIT_GB,
cpuNum: null,
connectionNum: null,
hdType: HARD_DRIVE_SSD,
dbSize: DB_SIZE_MID_RAM,
}
const selectCheckpointCompletionTarget = 0.9
export const useConfigurationStore = defineStore('configuration', () => {
const state = reactive({ ...initialState })
const getConfiguration = computed(() => ({ ...state }))
const getDBVersion = computed(() => getConfiguration.value.dbVersion)
const getOSType = computed(() => getConfiguration.value.osType)
const getDBType = computed(() => getConfiguration.value.dbType)
const getTotalMemory = computed(() => getConfiguration.value.totalMemory)
const getTotalMemoryUnit = computed(
() => getConfiguration.value.totalMemoryUnit,
)
const getCPUNum = computed(() => getConfiguration.value.cpuNum || null)
const getConnectionNum = computed(
() => getConfiguration.value.connectionNum || null,
)
const getHDType = computed(() => getConfiguration.value.hdType)
const getDBSize = computed(() => getConfiguration.value.dbSize)
const getTotalMemoryInBytes = computed(
() => getTotalMemory.value * SIZE_UNIT_MAP[getTotalMemoryUnit.value],
)
const getTotalMemoryInKb = computed(
() => getTotalMemoryInBytes.value / SIZE_UNIT_MAP['KB'],
)
const getDBDefaultValues = computed(
() => DEFAULT_DB_SETTINGS[getDBVersion] || DEFAULT_DB_VERSION.default,
)
const getIsConfigured = computed(() => !!getTotalMemory.value)
const getMaxConnections = computed(
() =>
getConnectionNum.value ??
{
[DB_TYPE_WEB]: 200,
[DB_TYPE_OLTP]: 300,
[DB_TYPE_DW]: 40,
[DB_TYPE_DESKTOP]: 20,
[DB_TYPE_MIXED]: 100,
}[getDBType.value],
)
const getSharedBuffers = computed(() => {
const totalMemoryKb = getTotalMemoryInKb.value
const val = {
[DB_TYPE_WEB]: Math.floor(totalMemoryKb / 4),
[DB_TYPE_OLTP]: Math.floor(totalMemoryKb / 4),
[DB_TYPE_DW]: Math.floor(totalMemoryKb / 4),
[DB_TYPE_DESKTOP]: Math.floor(totalMemoryKb / 16),
[DB_TYPE_MIXED]: Math.floor(totalMemoryKb / 4),
}[getDBType.value]
return getDBVersion.value < 10 && OS_WINDOWS === getOSType.value
? Math.min(val, (512 * SIZE_UNIT_MAP['MB']) / SIZE_UNIT_MAP['KB'])
: val
})
const getHugePages = computed(() =>
getOSType.value !== OS_MAC &&
getSharedBuffers.value >= (2 * SIZE_UNIT_MAP['GB']) / SIZE_UNIT_MAP['KB']
? 'try'
: 'off',
)
const getEffectiveCacheSize = computed(() => {
const totalMemoryKb = getTotalMemoryInKb.value
return {
[DB_TYPE_WEB]: Math.floor((totalMemoryKb * 3) / 4),
[DB_TYPE_OLTP]: Math.floor((totalMemoryKb * 3) / 4),
[DB_TYPE_DW]: Math.floor((totalMemoryKb * 3) / 4),
[DB_TYPE_DESKTOP]: Math.floor(totalMemoryKb / 4),
[DB_TYPE_MIXED]: Math.floor((totalMemoryKb * 3) / 4),
}[getDBType.value]
})
const getMaintenanceWorkMem = computed(() => {
const totalMemoryKb = getTotalMemoryInKb.value
// 1. Вычисляем базовое значение по типу БД
const baseMemValue = {
[DB_TYPE_WEB]: Math.floor(totalMemoryKb / 16),
[DB_TYPE_OLTP]: Math.floor(totalMemoryKb / 16),
[DB_TYPE_DW]: Math.floor(totalMemoryKb / 8),
[DB_TYPE_DESKTOP]: Math.floor(totalMemoryKb / 16),
[DB_TYPE_MIXED]: Math.floor(totalMemoryKb / 16),
}[getDBType.value]
// 2. Определяем, действует ли строгое ограничение для Windows
const isLegacyWindows =
OS_WINDOWS === getOSType.value && getDBVersion.value <= 17
// 3. Задаем лимит памяти (2GB для старых Windows, иначе 8GB)
const maxLimitGb = isLegacyWindows ? 2 : 8
const memoryLimit = (maxLimitGb * SIZE_UNIT_MAP['GB']) / SIZE_UNIT_MAP['KB']
// 4. Если превышен лимит на Windows, вычитаем 1MB, иначе просто возвращаем минимум
return baseMemValue >= memoryLimit && isLegacyWindows
? memoryLimit - (1 * SIZE_UNIT_MAP['MB']) / SIZE_UNIT_MAP['KB']
: Math.min(baseMemValue, memoryLimit)
})
const getCheckpointSegment = computed(() => [
{
key: 'min_wal_size',
value: {
[DB_TYPE_WEB]: (1024 * SIZE_UNIT_MAP['MB']) / SIZE_UNIT_MAP['KB'],
[DB_TYPE_OLTP]: (2048 * SIZE_UNIT_MAP['MB']) / SIZE_UNIT_MAP['KB'],
[DB_TYPE_DW]: (4096 * SIZE_UNIT_MAP['MB']) / SIZE_UNIT_MAP['KB'],
[DB_TYPE_DESKTOP]: (100 * SIZE_UNIT_MAP['MB']) / SIZE_UNIT_MAP['KB'],
[DB_TYPE_MIXED]: (1024 * SIZE_UNIT_MAP['MB']) / SIZE_UNIT_MAP['KB'],
}[getDBType.value],
},
{
key: 'max_wal_size',
value: {
[DB_TYPE_WEB]: (4096 * SIZE_UNIT_MAP['MB']) / SIZE_UNIT_MAP['KB'],
[DB_TYPE_OLTP]: (8192 * SIZE_UNIT_MAP['MB']) / SIZE_UNIT_MAP['KB'],
[DB_TYPE_DW]: (16384 * SIZE_UNIT_MAP['MB']) / SIZE_UNIT_MAP['KB'],
[DB_TYPE_DESKTOP]: (2048 * SIZE_UNIT_MAP['MB']) / SIZE_UNIT_MAP['KB'],
[DB_TYPE_MIXED]: (4096 * SIZE_UNIT_MAP['MB']) / SIZE_UNIT_MAP['KB'],
}[getDBType.value],
},
])
const getWalBuffers = computed(() => {
const KB_IN_MB = SIZE_UNIT_MAP['MB'] / SIZE_UNIT_MAP['KB']
const baseWal = Math.floor((3 * sharedBuffersValue) / 100)
return Math.max(
32,
baseWal > 14 * KB_IN_MB
? 16 * KB_IN_MB
: Math.min(baseWal, 16 * KB_IN_MB),
)
})
const getDefaultStatisticsTarget = computed(
() =>
({
[DB_TYPE_WEB]: 100,
[DB_TYPE_OLTP]: 100,
[DB_TYPE_DW]: 500,
[DB_TYPE_DESKTOP]: 100,
[DB_TYPE_MIXED]: 100,
})[getDBType.value],
)
const getRandomPageCost = computed(() => {
// Если база помещается в RAM, поиск на диске не важен
if (getDBSize.value === DB_SIZE_LESS_RAM) return 1.1
// HDD или аналитические БД (DW), не помещающиеся в RAM, требуют дефолтной стоимости (4.0)
if (getHDType.value === HARD_DRIVE_HDD || getDBType.value === DB_TYPE_DW)
return 4
return 1.1
})
const getEffectiveIOConcurrency = computed(() =>
getOSType.value === OS_LINUX
? {
[HARD_DRIVE_HDD]: 2,
[HARD_DRIVE_SSD]: 200,
[HARD_DRIVE_SAN]: 300,
[HARD_DRIVE_NVME]: 1000,
}[getHDType.value]
: null,
)
const getParallelSettings = computed(() => {
const cpuNum = getCPUNum.value
const dbVersion = getDBVersion.value
const dbType = getDBType.value
if (!cpuNum || cpuNum < 4) return []
const halfCpu = Math.ceil(cpuNum / 2)
const cappedWorkers = Math.min(4, halfCpu) // Ограничение в 4 воркера
const config = [
{ key: 'max_worker_processes', value: cpuNum },
{
key: 'max_parallel_workers_per_gather',
value: dbType !== DB_TYPE_DW ? cappedWorkers : halfCpu,
},
...(dbVersion >= 10
? [{ key: 'max_parallel_workers', value: cpuNum }]
: []),
...(dbVersion >= 11
? [{ key: 'max_parallel_maintenance_workers', value: cappedWorkers }]
: []),
]
return config
})
const getWorkMem = computed(() => {
const dbDefaultValues = getDBDefaultValues.value
const maxWorkerProcesses = getParallelSettings.value.find(
(param) => param['key'] === 'max_worker_processes',
)
const parallelForWorkMem =
maxWorkerProcesses?.value > 0
? maxWorkerProcesses.value
: dbDefaultValues['max_worker_processes'] > 0
? dbDefaultValues['max_worker_processes']
: 1
// Базовая формула расчёта work_mem
const baseWorkMem =
(getTotalMemoryInKb.value - sharedBuffersValue) /
((maxConnectionsValue + parallelForWorkMem) * 3)
// Коэффициенты для разных типов БД
const dbTypeCoefficients = {
[DB_TYPE_WEB]: 1,
[DB_TYPE_OLTP]: 1,
[DB_TYPE_DW]: 0.5,
[DB_TYPE_DESKTOP]: 1 / 6,
[DB_TYPE_MIXED]: 0.5,
}
// Базовая формула с коэффициентом типа БД
const baseWorkMemResult = Math.floor(
baseWorkMem * dbTypeCoefficients[dbType],
)
// Корректировка в зависимости от размера БД относительно RAM
const sizeAdjustedWorkMem =
dbSize === DB_SIZE_LESS_RAM
? Math.floor(baseWorkMemResult * 1.3)
: dbSize === DB_SIZE_GREATER_RAM
? Math.floor(baseWorkMemResult * 0.9)
: baseWorkMemResult
// Применяем минимальное значение 4 MB
const minWorkMem = 4 * 1024 // 4 MB в KB
const workMemWithMin = Math.max(sizeAdjustedWorkMem, minWorkMem)
// Применяем ограничение для Windows 64-bit до PostgreSQL 17
const winMemoryLimit = 2 * 1024 * 1024 - 1024 // ~2GB - 1MB в KB
const workMemResult =
osType === OS_WINDOWS && dbVersion <= 17
? Math.min(workMemWithMin, winMemoryLimit)
: workMemWithMin
return workMemResult
})
const getWalLevel = computed(() =>
getDBType.value === DB_TYPE_DESKTOP
? [
{
key: 'wal_level',
value: 'minimal',
},
// max_wal_senders must be 0 when wal_level=minimal
{
key: 'max_wal_senders',
value: '0',
},
]
: [],
)
const getJit = computed(() =>
getDBVersion.value >= 12 &&
[DB_TYPE_WEB, DB_TYPE_OLTP, DB_TYPE_MIXED].includes(getDBType.value)
? 'off'
: null,
)
const getWalCompression = computed(() => {
const ver = getDBVersion.value
return ver >= 15 ? 'lz4' : ver >= 10 ? 'on' : null
})
const getAutovacuumMaxWorkers = computed(() => {
const cpuNum = getCPUNum.value
if (!cpuNum) return null
if (cpuNum >= 32) return 5
if (cpuNum >= 16) return 4
return null
})
const getAutovacuumWorkMem = computed(() => {
const threshold = (2 * SIZE_UNIT_MAP['GB']) / SIZE_UNIT_MAP['KB']
// Windows 64-bit has a strict 2GB limit up to PostgreSQL 17
const winMemoryLimit =
(2 * SIZE_UNIT_MAP['GB']) / SIZE_UNIT_MAP['KB'] -
(1 * SIZE_UNIT_MAP['MB']) / SIZE_UNIT_MAP['KB']
const isWindowsWithLowLimit =
getOSType.value === OS_WINDOWS && getDBVersion.value <= 17
if (getMaintenanceWorkMem.value < threshold) {
return null
}
if (isWindowsWithLowLimit) {
return Math.min(threshold, winMemoryLimit)
}
return threshold
})
const getIOMethod = computed(() =>
getDBVersion.value < 18
? null
: getOSType.value === OS_LINUX
? 'io_uring'
: 'worker',
)
const getIOWorkers = computed(() => {
if (dbVersion < 18 || !cpuNum || ioMethod === 'io_uring') return null
return Math.max(4, Math.min(32, Math.floor(cpuNum / 4))) || null
})
const getWarningInfoMessages = computed(() => {
const warnings = []
const totalMemory = getTotalMemoryInBytes.value
// Memory warnings
if (totalMemory < 256 * SIZE_UNIT_MAP['MB']) {
warnings.push('this tool not being optimal', 'for low memory systems')
} else if (totalMemory > 100 * SIZE_UNIT_MAP['GB']) {
warnings.push(
'this tool not being optimal',
'for very high memory systems',
)
}
// Advanced features compilation warnings
if (getWalCompression.value === 'lz4') {
if (warnings.length > 0) warnings.push('')
warnings.push(
'wal_compression = lz4 requires PostgreSQL',
'to be compiled with --with-lz4',
)
}
if (getIOMethod.value === 'io_uring') {
if (warnings.length > 0) warnings.push('')
warnings.push(
'io_method = io_uring requires PostgreSQL',
'to be compiled with --with-liburing',
)
}
// I/O Cost Warning for Analytical DBs
if (
getDBType.value === DB_TYPE_DW &&
getHDType.value !== HARD_DRIVE_HDD &&
getDBSize.value !== DB_SIZE_LESS_RAM
) {
const driveName = {
[HARD_DRIVE_SSD]: 'SSDs',
[HARD_DRIVE_NVME]: 'NVMe drives',
[HARD_DRIVE_SAN]: 'SAN storage',
}[hdType]
if (warnings.length > 0) warnings.push('')
warnings.push(
`Cost parameters for Data Warehouses on ${driveName} are left at defaults`,
'to avoid catastrophic index scan selections',
'Monitor query planner behavior and adjust random_page_cost if necessary',
)
}
return warnings.length > 0 ? ['WARNING', ...warnings] : []
})
function setState(payload) {
const settingsStore = useSettingsStore()
state.value = {
dbVersion: parseFloat(payload.dbVersion),
osType: payload.osType,
dbType: payload.dbType,
totalMemory: parseInt(payload.totalMemory, 10),
totalMemoryUnit: payload.totalMemoryUnit,
cpuNum: payload.cpuNum ? parseInt(payload.cpuNum, 10) : null,
connectionNum: payload.connectionNum
? parseInt(payload.connectionNum, 10)
: null,
hdType: payload.hdType,
dbSize: payload.dbSize,
}
settingsStore.onSubmitConfiguration()
}
const $reset = () => ({ ...initialState })
return {
state,
// Getters
getDBVersion,
getOSType,
getDBType,
getTotalMemory,
getTotalMemoryUnit,
getCPUNum,
getConnectionNum,
getHDType,
getDBSize,
getTotalMemoryInBytes,
getTotalMemoryInKb,
getDBDefaultValues,
getIsConfigured,
getMaxConnections,
getSharedBuffers,
getHugePages,
getEffectiveCacheSize,
getMaintenanceWorkMem,
getCheckpointSegment,
getWalBuffers,
getDefaultStatisticsTarget,
getRandomPageCost,
getEffectiveIOConcurrency,
getParallelSettings,
getWorkMem,
getWalLevel,
getJit,
getWalCompression,
getAutovacuumMaxWorkers,
getAutovacuumWorkMem,
getIOMethod,
getIOWorkers,
getWarningInfoMessages,
// Actions
setState,
$reset,
}
})
-42
View File
@@ -1,42 +0,0 @@
import { defineStore } from 'pinia'
import { reactive, computed } from 'vue'
import { TAB_CONFIG, APP_THEMES_LIGHT } from '/src/utils/constants/settings.js'
const initialState = {
tabState: TAB_CONFIG,
theme: APP_THEMES_LIGHT,
}
export const useSettingsStore = defineStore('settings', () => {
const state = reactive({ ...initialState })
const getSettings = computed(() => state)
const getThemeSettings = computed(() => state.theme)
const getTabSettings = computed(() => state.tabState)
function toggleTheme() {
state.theme =
APP_THEMES_LIGHT === state.theme ? APP_THEMES_DARK : APP_THEMES_LIGHT
}
function onSubmitConfiguration() {
state.tabState = TAB_CONFIG
}
function $reset() {
state = { ...initialState }
}
return {
// STATE
state,
// GETTERS
getSettings,
getThemeSettings,
getTabSettings,
toggleTheme,
// ACTIONS
onSubmitConfiguration,
$reset,
}
})