Design
This commit is contained in:
+29
@@ -0,0 +1,29 @@
|
||||
<script setup>
|
||||
import { RouterView, useRoute } from 'vue-router'
|
||||
import Header from './components/header.vue'
|
||||
import { useThemeColor } from './composables/useThemeColor.js'
|
||||
import { onMounted } from 'vue'
|
||||
import { useUpdateApp } from './composables/useUpdateApp.js'
|
||||
|
||||
/*
|
||||
const register = registerSW()
|
||||
*/
|
||||
|
||||
const route = useRoute()
|
||||
|
||||
onMounted(() => {
|
||||
useThemeColor()
|
||||
useUpdateApp()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UApp>
|
||||
<UContainer>
|
||||
<Header />
|
||||
<UMain class="p-4">
|
||||
<RouterView :key="route.fullPath" />
|
||||
</UMain>
|
||||
</UContainer>
|
||||
</UApp>
|
||||
</template>
|
||||
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 46 KiB |
@@ -0,0 +1 @@
|
||||
<svg width="112" height="98" xmlns="http://www.w3.org/2000/svg"><g id="Page-1" fill="none" fill-rule="evenodd"><g id="pgtune" fill-rule="nonzero"><path d="M25.3 96.8h-7.1c-1.4 0-2.8-.7-4.4-2.2-2.4-2.2-3.9-5-4.9-7.1-1-2.2-3.3-7.8-4-10-2.2-6.7-3.2-11.4-3.2-14 0-.3-.1-3.6.4-6.4.4-2.6 1-6.6 2.8-11.5 1.9-5 3.6-7.9 8.7-14.2 2.8-3.5 6.1-7 9.5-10.2 3.9-3.7 8.2-6.8 12.2-9.6C48.8 2.2 60 1.5 63.1 1.5c1.2 0 2.7.1 4.4.2 5.1.4 7.4 1.2 8.9 1.7l.5.2c7.6 2.7 11.5 6.3 13.6 8.3l.3.3c2.8 2.6 7.1 6.9 11.1 13.6 2.5 4.1 5.3 11 7.7 19.2.7 2.5 1.2 11.2.9 14.7-.2 2.6-1 7.8-3.6 13.3-2.1 4.4-4.5 9.4-8.3 13-1.9 1.8-4.4 3.5-6.9 4.7-2.9 1.4-8.2 2.1-8.4 2.1-4.8 0-6.3-.8-7.4-2.4-.7-.9-.4-1.6-.2-1.9l.5-.8.9-2.1c.5-1.2.9-2.4 1.7-3.6l.1-.2c.7-1 1.4-1.9 2.3-1.9l.1-.2h.9l.3.3s.1.1.2.1c.3.3.7.6 1.1.6h.2c.8-.2 1.7-.6 3.8-3.3 2.8-3.6 3.6-5.2 4.5-7 .2-.4.3-.7.5-1.1 1.4-2.7 2.5-7.6 1.8-12.4-.7-4.9-1.6-7.5-4.8-10.3-1.3-1.1-2.7-1.4-3.5-1.4-.4 0-.6 0-.7.1h-.1v.8c0 .1.6 9.3.4 11.8-.3 5-.8 8.8-1.6 11.3-.9 2.9-2.6 6.1-4.7 9.9-.2.3-.4.7-.6 1.1-.3.5-.6 1.1-.7 1.3-.1.2-2.4 5.1-2.5 5.2v.1c-1.3 2.2-2.1 3.5-2.6 4.1-.7 1-2 2.5-2.8 3.2-1.3 1.4-3 2.2-5 2.2h-9.1c-2.1 0-3.6-1-4.2-2.8l-.3-.9c-.4-1.3-1-3.3-2.1-4.7-1.3-1.7-2.8-2.6-6-2.7h-.2c-2.3 0-5.1.1-5.8.4-1.5.5-1.5.5-1.7 1.6-.4 1.5-.3 1.9-.1 3.5v.2c.4 3.2-.8 4.5-1.9 5.1-.7.4-1.6.4-3 .4h-.7c-1.3.4-3.6.4-5 .4zm17.9-69.7c-.4 1.7-.5 3.5-.4 5.2.1 2.5.8 6.4 3.6 10.9 2.8 4.6 7.1 6.9 8.8 7.7 1 .5 2.3.8 3.7 1-.5-.2-1-.3-1.5-.5-4.7-2.1-6.1-3.7-7.5-5.4l-.1-.1c-2-2.3-3.4-4.7-4.8-8.2-1.7-4.3-2-7.2-1.8-10.3v-.3zm27.6-4.4c-.2.3-.6 1.8-.1 4.1.5 2.2 1.4 3.4 1.8 3.6.3-.4.6-1.8.1-4.1-.5-2.2-1.4-3.4-1.8-3.6z" id="Shape" fill="#859900"/><path d="M63.2 2.5c1.2 0 2.6.1 4.3.2 5 .3 7.2 1.2 8.7 1.7.2.1.4.1.5.2 7.8 2.8 11.6 6.5 13.5 8.4 2.8 2.6 7 6.8 11 13.4 2.5 4.1 5.2 10.8 7.6 19v.1c.7 2.3 1.1 10.9.8 14.2-.2 2.6-1 7.6-3.5 13-2.1 4.3-4.4 9.2-8.1 12.7-1.9 1.7-4.2 3.3-6.7 4.5-2.8 1.3-7.9 2-7.9 2-4.7 0-5.8-.9-6.6-2-.4-.6-.2-.7-.2-.7l.5-.9c.9-2 1.4-3.8 2.5-5.6.3-.4 1.1-1.6 1.6-1.6h.3c.4 0 .3-.2.3-.2l.2.2c.5.4 1.1.8 1.7.8h.4c1.2-.3 2.3-1 4.4-3.6 2.9-3.7 3.7-5.3 4.6-7.2.2-.3.3-.7.5-1.1 1.4-2.9 2.6-8 1.9-13-.7-5-1.7-8-5.1-10.9-1.6-1.3-3.1-1.6-4.2-1.6-.6 0-1 .1-1.1.1-.1 0-.1.1-.2.1-.2.1-.3.2-.3.3-.1.2-.2.5-.2 1.5.1 1.8.5 9.6.4 11.7-.3 4.9-.8 8.6-1.5 11.1-.8 2.7-2.3 5.6-4.6 9.7-.3.5-1 1.9-1.3 2.4l-2.5 5.3c-1.2 1.9-2.1 3.4-2.5 4.1-.8 1.2-2.6 3.1-2.7 3.2-.4.4-1.7 1.8-4.3 1.8h-9.2c-1.6 0-2.7-.8-3.2-2.1l-.3-.9c-.4-1.4-1-3.4-2.3-5-1.5-1.8-3.2-3-6.8-3h-.2c-1.2 0-5 0-6.1.4-1.8.6-2 .9-2.4 2.3-.4 1.6-.3 2.2-.1 3.8v.2c.3 2.6-.6 3.6-1.4 4.1-.5.3-1.3.3-2.6.3H18c-1.1 0-2.3-.6-3.7-1.9-2.3-2.1-3.7-4.7-4.7-6.8-1-2.1-3.3-7.7-4-9.9-2.1-6.6-3.2-11.2-3.1-13.7 0-.3-.1-3.5.4-6.3.4-2.6.9-6.5 2.8-11.3 1.8-4.9 3.6-7.7 8.6-13.9 2.8-3.4 6-6.9 9.4-10 3.9-3.6 8.1-6.7 12.1-9.5C49.2 3.2 60.3 2.5 63.2 2.5zm-9 10s-2.4 1-5.4 3.2c-1.8 1.4-3.4 3.3-4.7 5.9-.6 1.1-2.6 5.2-2.3 10.8.2 3.1 1.1 7.1 3.7 11.4 2.4 4 6.2 6.7 9.3 8.1 2 .9 4.9 1.4 7.7 1.4 1.5 0 2.9-.1 4.2-.4 3.5-.8 6.1-2.3 7.3-3.1 1.2-.8 3-2.5 3-2.5s-1.9 1.4-3.3 2c-1.4.7-3.6 1.4-5.2 1.7-.9.2-2.9.6-5.2.6-1.7 0-3.6-.2-5.4-1-4.6-2.1-5.9-3.6-7.3-5.2-1.7-2-3.1-4.2-4.6-7.9-1.6-4-1.9-6.8-1.8-9.9.1-2.9.6-4.3 1.3-5.8.6-1.5 1.9-3.6 3.5-5.2 1.7-2 5.1-4 5.2-4.1zm18.4 19h.2c1.1-.2 1.4-2.6.8-5.3-.6-2.5-1.8-4.5-2.8-4.5h-.2c-1.1.2-1.4 2.6-.8 5.3.5 2.6 1.8 4.5 2.8 4.5zM63.2.5C50.6.5 38.3 8.4 34.8 10.8c-4 2.8-8.3 5.9-12.3 9.6-3.4 3.2-6.7 6.7-9.6 10.3-5.2 6.4-7 9.4-8.9 14.5-1.9 5.1-2.5 9.1-2.9 11.7-.4 2.9-.4 6.1-.4 6.6 0 2.7 1 7.4 3.2 14.3.7 2.3 3 7.9 4.1 10.2 1.1 2.2 2.6 5.1 5.1 7.4 1.8 1.7 3.4 2.4 5 2.4h12.6c1.3 0 2.5 0 3.5-.6 1.1-.6 2.9-2.2 2.4-6.1v-.2c-.2-1.6-.2-1.8.1-3.1.1-.2.1-.4.1-.5.2-.1.5-.2 1-.4s3-.3 5.4-.3h.2c2.9 0 4 .8 5.3 2.3 1 1.3 1.6 3.1 1.9 4.3.1.4.2.7.3 1 .7 2.2 2.6 3.5 5.1 3.5h9.2c2.3 0 4.2-.9 5.7-2.5.9-1 2.2-2.4 2.9-3.4.2-.3.5-.7.8-1.3.1.1.2.3.3.4 1.3 1.8 3.1 2.8 8.3 2.8.4 0 5.8-.8 8.8-2.2 2.6-1.3 5.2-3 7.2-4.9 4-3.7 6.4-8.8 8.5-13.3 2.7-5.6 3.5-11 3.7-13.7.3-3.3-.1-12.2-.9-15v-.1c-2.5-8.3-5.3-15.2-7.8-19.4-4.2-6.8-8.5-11.2-11.3-13.8l-.3-.3C89 9 85 5.3 77.2 2.5l-.2-.1-.3-.1c-1.6-.6-4-1.4-9.2-1.8-1.6.1-3.1 0-4.3 0zm18 78.4c1.9-3.4 3.4-6.4 4.2-9.1.8-2.6 1.3-6.5 1.6-11.5.1-2.4-.3-10.6-.4-11.8.7 0 1.6.3 2.6 1.1 3 2.6 3.8 4.9 4.5 9.7.7 4.6-.4 9.3-1.7 11.8-.2.4-.4.7-.5 1.1-.9 1.8-1.6 3.3-4.4 6.8-1.8 2.3-2.6 2.7-3.2 2.9-.1 0-.2-.1-.4-.3l-.1-.1-.6-.5h-1.6v-.1z" id="Shape" fill="#032C36"/></g></g></svg>
|
||||
|
After Width: | Height: | Size: 4.3 KiB |
@@ -0,0 +1,4 @@
|
||||
@import "tailwindcss";
|
||||
@import "@nuxt/ui";
|
||||
|
||||
@plugin "@tailwindcss/typography";
|
||||
@@ -0,0 +1,104 @@
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
|
||||
const dbVersions = ref(['18', '17', '16', '15', '14', '13', '12', '11', '10'])
|
||||
const dbVersion = ref('18')
|
||||
|
||||
const osTypes = ref(['Linux', 'Windows', 'OS X'])
|
||||
const osType = ref('Linux')
|
||||
|
||||
const dbTypes = ref([
|
||||
'Web application',
|
||||
'Online transaction processing system',
|
||||
'Data warehouse',
|
||||
'Desktop application',
|
||||
'Mixed type of application',
|
||||
])
|
||||
const dbType = ref('Web application')
|
||||
|
||||
const totalRam = ref()
|
||||
const ramUnits = ref(['TB', 'GB', 'MB'])
|
||||
const ramUnit = ref('GB')
|
||||
|
||||
const totalCPUs = ref(1)
|
||||
|
||||
const totalConnections = ref(100)
|
||||
|
||||
const dataStorages = ref([
|
||||
'NVME storage',
|
||||
'SSD storage',
|
||||
'Network (SAN) storage',
|
||||
'HDD storage',
|
||||
])
|
||||
const dataStorage = ref('SSD storage')
|
||||
|
||||
const dataSizes = ref(['Less than RAM', 'RAM - 3xRAM', 'More than 3xRAM'])
|
||||
const dataSize = ref('Less than RAM')
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UForm class="space-y-4">
|
||||
<UFormField
|
||||
label="DB version"
|
||||
help="PostgreSQL version (find out via 'SELECT version();')"
|
||||
>
|
||||
<USelect v-model="dbVersion" :items="dbVersions" class="w-full" />
|
||||
</UFormField>
|
||||
<UFormField
|
||||
label="OS Type"
|
||||
help="Operation system of the PostgreSQL server host"
|
||||
>
|
||||
<USelect v-model="osType" :items="osTypes" class="w-full" />
|
||||
</UFormField>
|
||||
<UFormField
|
||||
label="DB type"
|
||||
help="What type of application is PostgreSQL used for"
|
||||
>
|
||||
<USelect v-model="dbType" :items="dbTypes" class="w-full" />
|
||||
</UFormField>
|
||||
<UFormField
|
||||
label="Total memory (RAM)"
|
||||
help="How much memory can PostgreSQL use"
|
||||
required
|
||||
>
|
||||
<div class="flex items-center gap-4">
|
||||
<UInputNumber
|
||||
v-model="totalRam"
|
||||
class="grow"
|
||||
placeholder="Total memory (RAM) (* required)"
|
||||
/>
|
||||
<USelect v-model="ramUnit" :items="ramUnits" />
|
||||
</div>
|
||||
</UFormField>
|
||||
<UFormField
|
||||
label="Number of CPUs"
|
||||
help="Number of CPUs, which PostgreSQL can use. CPUs = N threads per core * N cores per socket * N sockets"
|
||||
>
|
||||
<UInputNumber
|
||||
v-model="totalCPUs"
|
||||
class="w-full"
|
||||
placeholder="Number of CPUs"
|
||||
/>
|
||||
</UFormField>
|
||||
<UFormField
|
||||
label="Number of connections"
|
||||
help="Maximum number of PostgreSQL client connections"
|
||||
>
|
||||
<UInputNumber
|
||||
v-model="totalConnections"
|
||||
class="w-full"
|
||||
placeholder="Number of connections"
|
||||
/>
|
||||
</UFormField>
|
||||
<UFormField label="Data storage" help="Type of data storage device">
|
||||
<USelect v-model="dataStorage" :items="dataStorages" class="w-full" />
|
||||
</UFormField>
|
||||
<UFormField
|
||||
label="Total data size"
|
||||
help="Estimated total size of your database compared to total server RAM"
|
||||
>
|
||||
<USelect v-model="dataSize" :items="dataSizes" class="w-full" />
|
||||
</UFormField>
|
||||
<UButton size="xl" label="GENERATE" block type="submit" class="h-16" />
|
||||
</UForm>
|
||||
</template>
|
||||
@@ -0,0 +1,54 @@
|
||||
<template>
|
||||
<article class="prose dark:prose-invert max-w-full">
|
||||
<p>
|
||||
Please provide basic information about the configuration of your hardware
|
||||
on which the PostgreSQL database is running. Results will be calculated
|
||||
after clicking the "Generate" button
|
||||
</p>
|
||||
|
||||
<p>More information about "DB Type" setting:</p>
|
||||
|
||||
<ul>
|
||||
<li>
|
||||
<p>Web Application (web)</p>
|
||||
<ul>
|
||||
<li>Typically CPU-bound</li>
|
||||
<li>DB much smaller than RAM</li>
|
||||
<li>90% or more simple queries</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<p>Online Transaction Processing (oltp)</p>
|
||||
<ul>
|
||||
<li>Typically CPU- or I/O-bound</li>
|
||||
<li>DB slightly larger than RAM to 1TB</li>
|
||||
<li>20-40% small data write queries</li>
|
||||
<li>Some long transactions and complex read queries</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<p>Data Warehouse (dw)</p>
|
||||
<ul>
|
||||
<li>Typically I/O- or RAM-bound</li>
|
||||
<li>Large bulk loads of data</li>
|
||||
<li>Large complex reporting queries</li>
|
||||
<li>Also called "Decision Support" or "Business Intelligence"</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<p>Desktop application</p>
|
||||
<ul>
|
||||
<li>Not a dedicated database</li>
|
||||
<li>A general workstation, perhaps for a developer</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<p>Mixed type of application</p>
|
||||
<ul>
|
||||
<li>Mixed DW and OLTP characteristics</li>
|
||||
<li>wide mixture of queries</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</article>
|
||||
</template>
|
||||
@@ -0,0 +1,49 @@
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import Logo from '/favicon.svg?url'
|
||||
|
||||
const items = ref([
|
||||
{
|
||||
label: 'Home',
|
||||
to: '/',
|
||||
},
|
||||
{
|
||||
label: 'How it works',
|
||||
to: '/about',
|
||||
},
|
||||
])
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UHeader>
|
||||
<template #title>
|
||||
<img
|
||||
:src="Logo"
|
||||
alt="PGTune"
|
||||
class="h-(--ui-header-height) w-auto py-2"
|
||||
/>
|
||||
<h1 class="text-xl font-bold self-center ml-2">PGTune</h1>
|
||||
</template>
|
||||
|
||||
<UNavigationMenu :items />
|
||||
|
||||
<template #right>
|
||||
<UTooltip text="Open on Gitea" :kbds="['meta', 'G']">
|
||||
<UButton
|
||||
color="neutral"
|
||||
variant="ghost"
|
||||
to="https://gitea.minis.mudrov.tech"
|
||||
target="_blank"
|
||||
icon="simple-icons:gitea"
|
||||
aria-label="Gitea"
|
||||
/>
|
||||
</UTooltip>
|
||||
|
||||
<UColorModeButton class="cursor-pointer" />
|
||||
</template>
|
||||
|
||||
<template #body>
|
||||
<UNavigationMenu :items orientation="vertical" />
|
||||
</template>
|
||||
</UHeader>
|
||||
</template>
|
||||
@@ -0,0 +1,3 @@
|
||||
<template>
|
||||
<div>To be refactored</div>
|
||||
</template>
|
||||
@@ -0,0 +1,38 @@
|
||||
import { useHead } from '@unhead/vue'
|
||||
import { ref, watch } from 'vue'
|
||||
import { useDark } from '@vueuse/core'
|
||||
import { convertOklchToHex } from '../utils/oklch2hex'
|
||||
|
||||
export function useThemeColor() {
|
||||
const isDark = useDark()
|
||||
const currentBgColor = ref()
|
||||
|
||||
const getBGColor = () => {
|
||||
const rootStyles = window.getComputedStyle(document.documentElement)
|
||||
// Получаем текущее значение переменной --ui-bg
|
||||
const c = rootStyles.getPropertyValue('--ui-bg').trim()
|
||||
currentBgColor.value = c.startsWith('oklch') ? convertOklchToHex(c) : c
|
||||
}
|
||||
|
||||
const head = useHead()
|
||||
|
||||
watch(
|
||||
isDark,
|
||||
(n, o) => {
|
||||
if (n !== o) {
|
||||
setTimeout(() => {
|
||||
getBGColor()
|
||||
head.patch({
|
||||
meta: [
|
||||
{
|
||||
name: 'theme-color',
|
||||
content: currentBgColor.value,
|
||||
},
|
||||
],
|
||||
})
|
||||
}, 50)
|
||||
}
|
||||
},
|
||||
{ immediate: true },
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
import { useRegisterSW } from 'virtual:pwa-register/vue'
|
||||
import { watch } from 'vue'
|
||||
|
||||
const intervalMS = import.meta.env.DEV ? 5 * 60 * 1000 : 60 * 60 * 1000
|
||||
|
||||
export function useUpdateApp() {
|
||||
const { needRefresh, updateServiceWorker } = useRegisterSW({
|
||||
onRegistered(r) {
|
||||
r &&
|
||||
setInterval(() => {
|
||||
r.update()
|
||||
}, intervalMS)
|
||||
},
|
||||
})
|
||||
const toast = useToast()
|
||||
|
||||
if (import.meta.env.DEV) console.log({ SWNeedRefresh: needRefresh.value })
|
||||
|
||||
async function close(e) {
|
||||
needRefresh.value = false
|
||||
e?.stopPropagation()
|
||||
}
|
||||
|
||||
// New version
|
||||
|
||||
watch(
|
||||
() => needRefresh,
|
||||
(n) => {
|
||||
if (n) {
|
||||
toast.add({
|
||||
title: 'Update',
|
||||
description:
|
||||
'A newer version of the app is available. Click the “Update” button to install.',
|
||||
duration: 0,
|
||||
icon: 'lucide:cloud-download',
|
||||
close: false,
|
||||
actions: [
|
||||
{
|
||||
icon: 'lucide:arrow-big-up-dash',
|
||||
label: 'Update',
|
||||
onClick: updateServiceWorker,
|
||||
color: 'error',
|
||||
},
|
||||
|
||||
{
|
||||
icon: 'lucide:x',
|
||||
label: 'Dismiss',
|
||||
onClick: close,
|
||||
variant: 'outline',
|
||||
},
|
||||
],
|
||||
onEscapeKeyDown: close,
|
||||
})
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
import { createApp } from 'vue'
|
||||
import { createPinia } from 'pinia'
|
||||
import '/src/assets/styles/app.css'
|
||||
import App from './App.vue'
|
||||
import { router } from '/router.js'
|
||||
import ui from '@nuxt/ui/vue-plugin'
|
||||
import { createHead } from '@unhead/vue/client'
|
||||
|
||||
createApp(App)
|
||||
.use(router)
|
||||
.use(createPinia())
|
||||
.use(ui)
|
||||
.use(createHead())
|
||||
.mount('#app')
|
||||
@@ -0,0 +1,24 @@
|
||||
<script setup>
|
||||
import HeroImage from '/src/assets/images/logo.svg'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<article class="prose dark:prose-invert max-w-full">
|
||||
<img :src="HeroImage" class="w-full h-auto rounded-2xl" alt="PGTune" />
|
||||
<p>
|
||||
<strong>PGTune</strong> calculate configuration for PostgreSQL based on
|
||||
the maximum performance for a given hardware configuration. It isn't a
|
||||
<a
|
||||
href="https://en.wikipedia.org/wiki/No_Silver_Bullet"
|
||||
target="_blank"
|
||||
class="font-medium underline"
|
||||
>silver bullet</a
|
||||
>
|
||||
for the optimization settings of PostgreSQL. Many settings depend not only
|
||||
on the hardware configuration, but also on the size of the database, the
|
||||
number of clients and the complexity of queries. An optimal configuration
|
||||
of the database can only be made given all these parameters are taken into
|
||||
account.
|
||||
</p>
|
||||
</article>
|
||||
</template>
|
||||
@@ -0,0 +1,14 @@
|
||||
<script setup>
|
||||
import ConfigurationForm from '../components/configurationForm.vue'
|
||||
import DefaultDescription from '../components/defaultDescription.vue'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="grid lg:grid-cols-2 gap-16">
|
||||
<div class="space-y-4">
|
||||
<h3 class="text-xl font-bold">Parameters of your system</h3>
|
||||
<ConfigurationForm />
|
||||
</div>
|
||||
<DefaultDescription />
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,479 @@
|
||||
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,
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,42 @@
|
||||
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,
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,8 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { ref } from 'vue'
|
||||
|
||||
export const useConfigurationStore = defineStore('sw', () => {
|
||||
const state = ref()
|
||||
|
||||
return { state }
|
||||
})
|
||||
@@ -0,0 +1,28 @@
|
||||
// postgresql versions
|
||||
export const DEFAULT_DB_VERSION = 18
|
||||
export const DB_VERSIONS = [DEFAULT_DB_VERSION, 17, 16, 15, 14, 13, 12, 11, 10]
|
||||
// os types
|
||||
export const OS_LINUX = 'linux'
|
||||
export const OS_WINDOWS = 'windows'
|
||||
export const OS_MAC = 'mac'
|
||||
// db types
|
||||
export const DB_TYPE_WEB = 'web'
|
||||
export const DB_TYPE_OLTP = 'oltp'
|
||||
export const DB_TYPE_DW = 'dw'
|
||||
export const DB_TYPE_DESKTOP = 'desktop'
|
||||
export const DB_TYPE_MIXED = 'mixed'
|
||||
// size units
|
||||
export const SIZE_UNIT_MB = 'MB'
|
||||
export const SIZE_UNIT_GB = 'GB'
|
||||
export const SIZE_UNIT_TB = 'TB'
|
||||
// harddrive types
|
||||
export const HARD_DRIVE_SSD = 'ssd'
|
||||
export const HARD_DRIVE_SAN = 'san'
|
||||
export const HARD_DRIVE_HDD = 'hdd'
|
||||
export const HARD_DRIVE_NVME = 'nvme'
|
||||
// database size vs ram
|
||||
export const DB_SIZE_LESS_RAM = 'less_ram'
|
||||
export const DB_SIZE_MID_RAM = 'mid_ram'
|
||||
export const DB_SIZE_GREATER_RAM = 'greater_ram'
|
||||
// maximum value for integer fields
|
||||
export const MAX_NUMERIC_VALUE = 999999
|
||||
@@ -0,0 +1,5 @@
|
||||
export const APP_THEMES_LIGHT = 'light'
|
||||
export const APP_THEMES_DARK = 'dark'
|
||||
// tabs
|
||||
export const TAB_CONFIG = 'config_tab'
|
||||
export const TAB_ALTER_SYSTEM = 'alter_tab'
|
||||
@@ -0,0 +1,24 @@
|
||||
export function convertOklchToHex(oklchString) {
|
||||
// 1. Создаем виртуальный пиксель
|
||||
const canvas = document.createElement('canvas')
|
||||
canvas.width = 1
|
||||
canvas.height = 1
|
||||
const ctx = canvas.getContext('2d', { willReadFrequently: true })
|
||||
if (!ctx) return ''
|
||||
|
||||
// 2. Закрашиваем пиксель цветом oklch
|
||||
ctx.fillStyle = oklchString
|
||||
ctx.fillRect(0, 0, 1, 1)
|
||||
|
||||
// 3. Получаем массив [r, g, b, a] со значениями от 0 до 255
|
||||
const [r, g, b, a] = ctx.getImageData(0, 0, 1, 1).data
|
||||
|
||||
// Вспомогательная функция для перевода числа в 16-ричный формат (например, 255 -> "ff")
|
||||
const toHex = (num) => num.toString(16).padStart(2, '0')
|
||||
|
||||
// 4. Собираем HEX-строку
|
||||
const hexRGB = `#${toHex(r)}${toHex(g)}${toHex(b)}`
|
||||
|
||||
// Если есть прозрачность (альфа-канал меньше 255), добавляем её в конец HEX
|
||||
return a === 255 ? hexRGB : `${hexRGB}${toHex(a)}`
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
import * as Yup from 'yup'
|
||||
import {
|
||||
DB_VERSIONS,
|
||||
OS_LINUX,
|
||||
OS_WINDOWS,
|
||||
OS_MAC,
|
||||
DB_TYPE_WEB,
|
||||
DB_TYPE_OLTP,
|
||||
DB_TYPE_DW,
|
||||
DB_TYPE_DESKTOP,
|
||||
DB_TYPE_MIXED,
|
||||
SIZE_UNIT_MB,
|
||||
SIZE_UNIT_GB,
|
||||
SIZE_UNIT_TB,
|
||||
HARD_DRIVE_HDD,
|
||||
HARD_DRIVE_SSD,
|
||||
HARD_DRIVE_SAN,
|
||||
HARD_DRIVE_NVME,
|
||||
MAX_NUMERIC_VALUE,
|
||||
DB_SIZE_LESS_RAM,
|
||||
DB_SIZE_MID_RAM,
|
||||
DB_SIZE_GREATER_RAM,
|
||||
} from './constants/configuration'
|
||||
|
||||
const MIN_MB_MEMORY = 512
|
||||
|
||||
const DB_TYPES = [
|
||||
DB_TYPE_WEB,
|
||||
DB_TYPE_OLTP,
|
||||
DB_TYPE_DW,
|
||||
DB_TYPE_DESKTOP,
|
||||
DB_TYPE_MIXED,
|
||||
]
|
||||
|
||||
const HARD_DRIVE_TYPES = [
|
||||
HARD_DRIVE_HDD,
|
||||
HARD_DRIVE_SSD,
|
||||
HARD_DRIVE_SAN,
|
||||
HARD_DRIVE_NVME,
|
||||
]
|
||||
|
||||
const DB_SIZES = [DB_SIZE_LESS_RAM, DB_SIZE_MID_RAM, DB_SIZE_GREATER_RAM]
|
||||
|
||||
export const validationSchema = Yup.object().shape({
|
||||
dbVersion: Yup.number()
|
||||
.required('Required')
|
||||
.oneOf(DB_VERSIONS, 'Unsupported database version'),
|
||||
osType: Yup.string()
|
||||
.required('Required')
|
||||
.oneOf([OS_LINUX, OS_WINDOWS, OS_MAC], 'Unsupported OS'),
|
||||
dbType: Yup.string()
|
||||
.required('Required')
|
||||
.oneOf(DB_TYPES, 'Unsupported database type'),
|
||||
totalMemoryUnit: Yup.string()
|
||||
.required('Required')
|
||||
.oneOf([SIZE_UNIT_MB, SIZE_UNIT_GB, SIZE_UNIT_TB], 'Unsupported unit'),
|
||||
totalMemory: Yup.number()
|
||||
.required('Required')
|
||||
.integer('Must be an integer')
|
||||
.when('totalMemoryUnit', (totalMemoryUnit, schema) => {
|
||||
if (totalMemoryUnit === SIZE_UNIT_MB) {
|
||||
return schema.min(
|
||||
MIN_MB_MEMORY,
|
||||
`Must be greater than or equal to ${MIN_MB_MEMORY} MB`,
|
||||
)
|
||||
}
|
||||
return schema.min(1, 'Must be greater than zero')
|
||||
})
|
||||
.max(
|
||||
MAX_NUMERIC_VALUE,
|
||||
`Must be less than or equal to ${MAX_NUMERIC_VALUE}`,
|
||||
),
|
||||
hdType: Yup.string()
|
||||
.required('Required')
|
||||
.oneOf(HARD_DRIVE_TYPES, 'Unsupported hard drive'),
|
||||
dbSize: Yup.string()
|
||||
.required('Required')
|
||||
.oneOf(DB_SIZES, 'Unsupported db size'),
|
||||
cpuNum: Yup.number()
|
||||
.transform((value) => (isNaN(value) ? null : value))
|
||||
.nullable()
|
||||
.notRequired()
|
||||
.integer('Must be an integer')
|
||||
.min(1, 'Must be greater than zero')
|
||||
.max(
|
||||
MAX_NUMERIC_VALUE,
|
||||
`Must be less than or equal to ${MAX_NUMERIC_VALUE}`,
|
||||
),
|
||||
connectionNum: Yup.number()
|
||||
.transform((value) => (isNaN(value) ? null : value))
|
||||
.nullable()
|
||||
.notRequired()
|
||||
.integer('Must be an integer')
|
||||
.min(20, 'Must be greater than or equal to 20')
|
||||
.max(
|
||||
MAX_NUMERIC_VALUE,
|
||||
`Must be less than or equal to ${MAX_NUMERIC_VALUE}`,
|
||||
),
|
||||
})
|
||||
Reference in New Issue
Block a user