This commit is contained in:
2026-09-06 04:04:59 +03:00
commit 83a975ba3c
44 changed files with 6558 additions and 0 deletions
+28
View File
@@ -0,0 +1,28 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
# Auto-generated type declarations
auto-imports.d.ts
components.d.ts
+4
View File
@@ -0,0 +1,4 @@
semi: false
tabWidth: 2
trailingComma: all
singleQuote: true
+3
View File
@@ -0,0 +1,3 @@
{
"recommendations": ["Vue.volar"]
}
+5
View File
@@ -0,0 +1,5 @@
# Vue 3 + Vite
This template should help get you started developing with Vue 3 in Vite. The template uses Vue 3 `<script setup>` SFCs, check out the [script setup docs](https://v3.vuejs.org/api/sfc-script-setup.html#sfc-script-setup) to learn more.
Learn more about IDE Support for Vue in the [Vue Docs Scaling up Guide](https://vuejs.org/guide/scaling-up/tooling.html#ide-support).
+1713
View File
File diff suppressed because it is too large Load Diff
+92
View File
@@ -0,0 +1,92 @@
/**
* Copyright 2018 Google Inc. All Rights Reserved.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// If the loader is already loaded, just stop.
if (!self.define) {
let registry = {};
// Used for `eval` and `importScripts` where we can't get script URL by other means.
// In both cases, it's safe to use a global var because those functions are synchronous.
let nextDefineUri;
const singleRequire = (uri, parentUri) => {
uri = new URL(uri + ".js", parentUri).href;
return registry[uri] || (
new Promise(resolve => {
if ("document" in self) {
const script = document.createElement("script");
script.src = uri;
script.onload = resolve;
document.head.appendChild(script);
} else {
nextDefineUri = uri;
importScripts(uri);
resolve();
}
})
.then(() => {
let promise = registry[uri];
if (!promise) {
throw new Error(`Module ${uri} didn’t register its module`);
}
return promise;
})
);
};
self.define = (depsNames, factory) => {
const uri = nextDefineUri || ("document" in self ? document.currentScript.src : "") || location.href;
if (registry[uri]) {
// Module is already loading or loaded.
return;
}
let exports = {};
const require = depUri => singleRequire(depUri, uri);
const specialDeps = {
module: { uri },
exports,
require
};
registry[uri] = Promise.all(depsNames.map(
depName => specialDeps[depName] || require(depName)
)).then(deps => {
factory(...deps);
return exports;
});
};
}
define(['./workbox-f0c192c2'], (function (workbox) { 'use strict';
self.addEventListener('message', event => {
if (event.data && event.data.type === 'SKIP_WAITING') {
self.skipWaiting();
}
});
/**
* The precacheAndRoute() method efficiently caches and responds to
* requests for URLs in the manifest.
* See https://goo.gl/S9QRab
*/
workbox.precacheAndRoute([{
"url": "index.html",
"revision": "0.kapsgh4ngf8"
}], {});
workbox.cleanupOutdatedCaches();
workbox.registerRoute(new workbox.NavigationRoute(workbox.createHandlerBoundToURL("index.html"), {
allowlist: [/^\/$/]
}));
}));
File diff suppressed because it is too large Load Diff
+31
View File
@@ -0,0 +1,31 @@
import pluginVue from 'eslint-plugin-vue'
import prettierConfig from 'eslint-config-prettier'
import pluginPrettier from 'eslint-plugin-prettier'
export default [
// Рекомендованные правила для Vue 3 (плоский конфиг)
...pluginVue.configs['flat/recommended'],
// Интеграция Prettier: отключает правила ESLint, конфликтующие с Prettier,
// и добавляет правило проверки форматирования через eslint-plugin-prettier
{
plugins: {
prettier: pluginPrettier,
},
rules: {
...prettierConfig.rules,
'prettier/prettier': 'warn', // Выводить предупреждения при нарушении форматирования Prettier
// Ваши кастомные правила (при необходимости)
'vue/multi-word-component-names': 'off', // Пример: отключить обязательное многословное имя для Vue компонентов
},
},
// Глобальные настройки окружения и игнорирования файлов
{
languageOptions: {
ecmaVersion: 'latest',
sourceType: 'module',
},
},
]
+16
View File
@@ -0,0 +1,16 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="x-ua-compatible" content="ie=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
<meta name="keywords" content="pgtune, postgresql, postgres, tuning, config, configuration, free, open source" />
<meta name="description" content="PgTune - Tuning PostgreSQL config by your hardware" />
<link type="text/plain" rel="author" href="/humans.txt" />
<title>PGTune - calculate configuration for PostgreSQL based on the maximum performance for a given hardware configuration</title>
</head>
<body>
<div id="app" class="isolate"></div>
<script async="async" defer="defer" type="module" src="/src/main.js"></script>
</body>
</html>
+36
View File
@@ -0,0 +1,36 @@
{
"name": "pgtune",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
},
"dependencies": {
"vue": "^3.5.41"
},
"devDependencies": {
"@iconify-json/lucide": "^1.2.129",
"@iconify-json/simple-icons": "^1.2.94",
"@nuxt/ui": "^4.11.0",
"@tailwindcss/typography": "^0.5.20",
"@tailwindcss/vite": "^4.3.3",
"@unhead/vue": "^3.0.0-beta.9",
"@vitejs/plugin-vue": "^6.0.8",
"@vueuse/core": "^14.4.0",
"eslint": "^10.9.1",
"eslint-config-prettier": "^10.1.8",
"eslint-config-vue": "^2.0.2",
"eslint-plugin-prettier": "^5.5.6",
"eslint-plugin-vue": "^10.10.0",
"pinia": "^4.0.3",
"prettier": "^3.9.6",
"tailwindcss": "^4.3.3",
"vite": "^8.2.2",
"vite-plugin-pwa": "^1.3.0",
"vue-router": "^5.3.1",
"yup": "^1.7.1"
}
}
BIN
View File
Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

+1
View File
@@ -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="#cdd9ea"/><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="#656b74"/></g></g></svg>

After

Width:  |  Height:  |  Size: 4.3 KiB

+15
View File
@@ -0,0 +1,15 @@
# humanstxt.org/
# The humans responsible & technology colophon
/* TEAM */
Developer: Oleksii Vasyliev
Twitter: @leopard_me
Web designer: Anna Rotar
Twitter: @Ste4any
/* SITE */
Language: English
Standards: HTML5, CSS3
Doctype: HTML5
Source: https://github.com/le0pard/pgtune
Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

+24
View File
@@ -0,0 +1,24 @@
<svg xmlns="http://www.w3.org/2000/svg">
<symbol id="bluesky-icon" viewBox="0 0 16 17">
<g clip-path="url(#bluesky-clip)"><path fill="#08060d" d="M7.75 7.735c-.693-1.348-2.58-3.86-4.334-5.097-1.68-1.187-2.32-.981-2.74-.79C.188 2.065.1 2.812.1 3.251s.241 3.602.398 4.13c.52 1.744 2.367 2.333 4.07 2.145-2.495.37-4.71 1.278-1.805 4.512 3.196 3.309 4.38-.71 4.987-2.746.608 2.036 1.307 5.91 4.93 2.746 2.72-2.746.747-4.143-1.747-4.512 1.702.189 3.55-.4 4.07-2.145.156-.528.397-3.691.397-4.13s-.088-1.186-.575-1.406c-.42-.19-1.06-.395-2.741.79-1.755 1.24-3.64 3.752-4.334 5.099"/></g>
<defs><clipPath id="bluesky-clip"><path fill="#fff" d="M.1.85h15.3v15.3H.1z"/></clipPath></defs>
</symbol>
<symbol id="discord-icon" viewBox="0 0 20 19">
<path fill="#08060d" d="M16.224 3.768a14.5 14.5 0 0 0-3.67-1.153c-.158.286-.343.67-.47.976a13.5 13.5 0 0 0-4.067 0c-.128-.306-.317-.69-.476-.976A14.4 14.4 0 0 0 3.868 3.77C1.546 7.28.916 10.703 1.231 14.077a14.7 14.7 0 0 0 4.5 2.306q.545-.748.965-1.587a9.5 9.5 0 0 1-1.518-.74q.191-.14.372-.293c2.927 1.369 6.107 1.369 8.999 0q.183.152.372.294-.723.437-1.52.74.418.838.963 1.588a14.6 14.6 0 0 0 4.504-2.308c.37-3.911-.63-7.302-2.644-10.309m-9.13 8.234c-.878 0-1.599-.82-1.599-1.82 0-.998.705-1.82 1.6-1.82.894 0 1.614.82 1.599 1.82.001 1-.705 1.82-1.6 1.82m5.91 0c-.878 0-1.599-.82-1.599-1.82 0-.998.705-1.82 1.6-1.82.893 0 1.614.82 1.599 1.82 0 1-.706 1.82-1.6 1.82"/>
</symbol>
<symbol id="documentation-icon" viewBox="0 0 21 20">
<path fill="none" stroke="#aa3bff" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.35" d="m15.5 13.333 1.533 1.322c.645.555.967.833.967 1.178s-.322.623-.967 1.179L15.5 18.333m-3.333-5-1.534 1.322c-.644.555-.966.833-.966 1.178s.322.623.966 1.179l1.534 1.321"/>
<path fill="none" stroke="#aa3bff" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.35" d="M17.167 10.836v-4.32c0-1.41 0-2.117-.224-2.68-.359-.906-1.118-1.621-2.08-1.96-.599-.21-1.349-.21-2.848-.21-2.623 0-3.935 0-4.983.369-1.684.591-3.013 1.842-3.641 3.428C3 6.449 3 7.684 3 10.154v2.122c0 2.558 0 3.838.706 4.726q.306.383.713.671c.76.536 1.79.64 3.581.66"/>
<path fill="none" stroke="#aa3bff" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.35" d="M3 10a2.78 2.78 0 0 1 2.778-2.778c.555 0 1.209.097 1.748-.047.48-.129.854-.503.982-.982.145-.54.048-1.194.048-1.749a2.78 2.78 0 0 1 2.777-2.777"/>
</symbol>
<symbol id="github-icon" viewBox="0 0 19 19">
<path fill="#08060d" fill-rule="evenodd" d="M9.356 1.85C5.05 1.85 1.57 5.356 1.57 9.694a7.84 7.84 0 0 0 5.324 7.44c.387.079.528-.168.528-.376 0-.182-.013-.805-.013-1.454-2.165.467-2.616-.935-2.616-.935-.349-.91-.864-1.143-.864-1.143-.71-.48.051-.48.051-.48.787.051 1.2.805 1.2.805.695 1.194 1.817.857 2.268.649.064-.507.27-.857.49-1.052-1.728-.182-3.545-.857-3.545-3.87 0-.857.31-1.558.8-2.104-.078-.195-.349-1 .077-2.078 0 0 .657-.208 2.14.805a7.5 7.5 0 0 1 1.946-.26c.657 0 1.328.092 1.946.26 1.483-1.013 2.14-.805 2.14-.805.426 1.078.155 1.883.078 2.078.502.546.799 1.247.799 2.104 0 3.013-1.818 3.675-3.558 3.87.284.247.528.714.528 1.454 0 1.052-.012 1.896-.012 2.156 0 .208.142.455.528.377a7.84 7.84 0 0 0 5.324-7.441c.013-4.338-3.48-7.844-7.773-7.844" clip-rule="evenodd"/>
</symbol>
<symbol id="social-icon" viewBox="0 0 20 20">
<path fill="none" stroke="#aa3bff" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.35" d="M12.5 6.667a4.167 4.167 0 1 0-8.334 0 4.167 4.167 0 0 0 8.334 0"/>
<path fill="none" stroke="#aa3bff" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.35" d="M2.5 16.667a5.833 5.833 0 0 1 8.75-5.053m3.837.474.513 1.035c.07.144.257.282.414.309l.93.155c.596.1.736.536.307.965l-.723.73a.64.64 0 0 0-.152.531l.207.903c.164.715-.213.991-.84.618l-.872-.52a.63.63 0 0 0-.577 0l-.872.52c-.624.373-1.003.094-.84-.618l.207-.903a.64.64 0 0 0-.152-.532l-.723-.729c-.426-.43-.289-.864.306-.964l.93-.156a.64.64 0 0 0 .412-.31l.513-1.034c.28-.562.735-.562 1.012 0"/>
</symbol>
<symbol id="x-icon" viewBox="0 0 19 19">
<path fill="#08060d" fill-rule="evenodd" d="M1.893 1.98c.052.072 1.245 1.769 2.653 3.77l2.892 4.114c.183.261.333.48.333.486s-.068.089-.152.183l-.522.593-.765.867-3.597 4.087c-.375.426-.734.834-.798.905a1 1 0 0 0-.118.148c0 .01.236.017.664.017h.663l.729-.83c.4-.457.796-.906.879-.999a692 692 0 0 0 1.794-2.038c.034-.037.301-.34.594-.675l.551-.624.345-.392a7 7 0 0 1 .34-.374c.006 0 .93 1.306 2.052 2.903l2.084 2.965.045.063h2.275c1.87 0 2.273-.003 2.266-.021-.008-.02-1.098-1.572-3.894-5.547-2.013-2.862-2.28-3.246-2.273-3.266.008-.019.282-.332 2.085-2.38l2-2.274 1.567-1.782c.022-.028-.016-.03-.65-.03h-.674l-.3.342a871 871 0 0 1-1.782 2.025c-.067.075-.405.458-.75.852a100 100 0 0 1-.803.91c-.148.172-.299.344-.99 1.127-.304.343-.32.358-.345.327-.015-.019-.904-1.282-1.976-2.808L6.365 1.85H1.8zm1.782.91 8.078 11.294c.772 1.08 1.413 1.973 1.425 1.984.016.017.241.02 1.05.017l1.03-.004-2.694-3.766L7.796 5.75 5.722 2.852l-1.039-.004-1.039-.004z" clip-rule="evenodd"/>
</symbol>
</svg>

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

+1
View File
@@ -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="#cdd9ea"/><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="#656b74"/></g></g></svg>

After

Width:  |  Height:  |  Size: 4.3 KiB

+1
View File
@@ -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="#cdd9ea"/><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="#333333"/></g></g></svg>

After

Width:  |  Height:  |  Size: 4.3 KiB

+2
View File
@@ -0,0 +1,2 @@
User-agent: *
Allow: /
+19
View File
@@ -0,0 +1,19 @@
import { createRouter, createWebHistory } from 'vue-router'
const routes = [
{
path: '/',
component: () => import('/src/pages/index.vue'),
name: 'Index',
},
{
path: '/about',
component: () => import('/src/pages/about.vue'),
name: 'About',
},
]
export const router = createRouter({
history: createWebHistory(),
routes,
})
+29
View File
@@ -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

+1
View File
@@ -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

+4
View File
@@ -0,0 +1,4 @@
@import "tailwindcss";
@import "@nuxt/ui";
@plugin "@tailwindcss/typography";
+104
View File
@@ -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>
+54
View File
@@ -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>
+49
View File
@@ -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>
+3
View File
@@ -0,0 +1,3 @@
<template>
<div>To be refactored</div>
</template>
+38
View File
@@ -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 },
)
}
+57
View File
@@ -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
View File
@@ -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')
+24
View File
@@ -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>
+14
View File
@@ -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>
+479
View File
@@ -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,
}
})
+42
View File
@@ -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,
}
})
+8
View File
@@ -0,0 +1,8 @@
import { defineStore } from 'pinia'
import { ref } from 'vue'
export const useConfigurationStore = defineStore('sw', () => {
const state = ref()
return { state }
})
+28
View File
@@ -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
+5
View File
@@ -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'
+24
View File
@@ -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)}`
}
+99
View File
@@ -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}`,
),
})
+113
View File
@@ -0,0 +1,113 @@
import vue from '@vitejs/plugin-vue'
import ui from '@nuxt/ui/vite'
import { defineConfig } from 'vite'
import { VitePWA } from 'vite-plugin-pwa'
// https://vite.dev/config/
export default defineConfig({
plugins: [
vue(),
ui(),
VitePWA({
injectRegister: null,
registerType: 'prompt',
devOptions: {
enabled: true,
type: 'module',
},
workbox: {
globPatterns: ['**/*.{js,css,html,ico,png,svg}'],
},
includeAssets: [
'favicon.svg',
'favicon.ico',
'icon-192x192.png',
'icon-512x512.png',
'maskable_icon.png',
],
manifest: {
name: 'PGTune',
short_name: 'PGTune',
description: 'PgTune - Tuning PostgreSQL config by your hardware',
display: 'standalone',
start_url: '/',
theme_color: '#0f172b',
background_color: '#fff',
icons: [
{
src: '/icon-192x192.png',
sizes: '192x192',
type: 'image/png',
purpose: 'any',
},
{
src: '/icon-512x512.png',
sizes: '512x512',
type: 'image/png',
purpose: 'any',
},
{
src: '/maskable_icon.png',
sizes: '1024x1024',
type: 'image/png',
purpose: 'maskable',
},
],
},
}),
/*
VitePWA({
injectRegister: null,
strategies: 'injectManifest',
registerType: 'prompt',
srcDir: 'src',
// filename: 'sw.js',
base: '/',
scope: '/',
includeAssets: [
'favicon.svg',
'favicon.ico',
'icon-192x192.png',
'icon-512x512.png',
'maskable_icon.png',
'about.html',
],
injectManifest: {
globPatterns: ['\*\*\/*.{css,js,html,png,svg,ico}'],
},
devOptions: {
enabled: true,
type: 'module',
},
manifest: {
name: 'PGTune',
short_name: 'PGTune',
description: 'PgTune - Tuning PostgreSQL config by your hardware',
display: 'standalone',
start_url: '/',
theme_color: '#fdf6e3',
background_color: '#fdf6e3',
icons: [
{
src: '/icon-192x192.png',
sizes: '192x192',
type: 'image/png',
purpose: 'any',
},
{
src: '/icon-512x512.png',
sizes: '512x512',
type: 'image/png',
purpose: 'any',
},
{
src: '/maskable_icon.png',
sizes: '1024x1024',
type: 'image/png',
purpose: 'maskable',
},
],
},
}), */
],
})