mirror of
https://github.com/gotson/komga.git
synced 2026-01-03 22:36:07 +01:00
next-ui wip
This commit is contained in:
parent
ced89c5c54
commit
a4ea8eeab3
91 changed files with 18126 additions and 1 deletions
2
.nvmrc
2
.nvmrc
|
|
@ -1 +1 @@
|
|||
18
|
||||
22
|
||||
|
|
|
|||
4
next-ui/.browserslistrc
Normal file
4
next-ui/.browserslistrc
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
> 1%
|
||||
last 2 versions
|
||||
not dead
|
||||
not ie 11
|
||||
6
next-ui/.editorconfig
Normal file
6
next-ui/.editorconfig
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
[*.{js,jsx,mjs,cjs,ts,tsx,mts,cts,vue}]
|
||||
charset = utf-8
|
||||
indent_size = 2
|
||||
indent_style = space
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
78
next-ui/.eslintrc-auto-import.json
Normal file
78
next-ui/.eslintrc-auto-import.json
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
{
|
||||
"globals": {
|
||||
"Component": true,
|
||||
"ComponentPublicInstance": true,
|
||||
"ComputedRef": true,
|
||||
"EffectScope": true,
|
||||
"ExtractDefaultPropTypes": true,
|
||||
"ExtractPropTypes": true,
|
||||
"ExtractPublicPropTypes": true,
|
||||
"InjectionKey": true,
|
||||
"PropType": true,
|
||||
"Ref": true,
|
||||
"VNode": true,
|
||||
"WritableComputedRef": true,
|
||||
"computed": true,
|
||||
"createApp": true,
|
||||
"customRef": true,
|
||||
"defineAsyncComponent": true,
|
||||
"defineComponent": true,
|
||||
"effectScope": true,
|
||||
"getCurrentInstance": true,
|
||||
"getCurrentScope": true,
|
||||
"h": true,
|
||||
"inject": true,
|
||||
"isProxy": true,
|
||||
"isReactive": true,
|
||||
"isReadonly": true,
|
||||
"isRef": true,
|
||||
"markRaw": true,
|
||||
"nextTick": true,
|
||||
"onActivated": true,
|
||||
"onBeforeMount": true,
|
||||
"onBeforeUnmount": true,
|
||||
"onBeforeUpdate": true,
|
||||
"onDeactivated": true,
|
||||
"onErrorCaptured": true,
|
||||
"onMounted": true,
|
||||
"onRenderTracked": true,
|
||||
"onRenderTriggered": true,
|
||||
"onScopeDispose": true,
|
||||
"onServerPrefetch": true,
|
||||
"onUnmounted": true,
|
||||
"onUpdated": true,
|
||||
"provide": true,
|
||||
"reactive": true,
|
||||
"readonly": true,
|
||||
"ref": true,
|
||||
"resolveComponent": true,
|
||||
"shallowReactive": true,
|
||||
"shallowReadonly": true,
|
||||
"shallowRef": true,
|
||||
"toRaw": true,
|
||||
"toRef": true,
|
||||
"toRefs": true,
|
||||
"toValue": true,
|
||||
"triggerRef": true,
|
||||
"unref": true,
|
||||
"useAttrs": true,
|
||||
"useCssModule": true,
|
||||
"useCssVars": true,
|
||||
"useRoute": true,
|
||||
"useRouter": true,
|
||||
"useSlots": true,
|
||||
"watch": true,
|
||||
"watchEffect": true,
|
||||
"watchPostEffect": true,
|
||||
"watchSyncEffect": true,
|
||||
"DirectiveBinding": true,
|
||||
"MaybeRef": true,
|
||||
"MaybeRefOrGetter": true,
|
||||
"onWatcherCleanup": true,
|
||||
"useId": true,
|
||||
"useModel": true,
|
||||
"useTemplateRef": true,
|
||||
"Slot": true,
|
||||
"Slots": true
|
||||
}
|
||||
}
|
||||
22
next-ui/.gitignore
vendored
Normal file
22
next-ui/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
.DS_Store
|
||||
node_modules
|
||||
/dist
|
||||
|
||||
# local env files
|
||||
.env.local
|
||||
.env.*.local
|
||||
|
||||
# Log files
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
|
||||
# Editor directories and files
|
||||
.idea
|
||||
.vscode
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
3
next-ui/env.d.ts
vendored
Normal file
3
next-ui/env.d.ts
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
/// <reference types="vite/client" />
|
||||
/// <reference types="unplugin-vue-router/client" />
|
||||
/// <reference types="vite-plugin-vue-layouts/client" />
|
||||
36
next-ui/eslint.config.js
Normal file
36
next-ui/eslint.config.js
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
/**
|
||||
* .eslint.js
|
||||
*
|
||||
* ESLint configuration file.
|
||||
*/
|
||||
|
||||
import pluginVue from 'eslint-plugin-vue'
|
||||
import vueTsEslintConfig from '@vue/eslint-config-typescript'
|
||||
|
||||
export default [
|
||||
{
|
||||
name: 'app/files-to-lint',
|
||||
files: ['**/*.{ts,mts,tsx,vue}'],
|
||||
},
|
||||
|
||||
{
|
||||
name: 'app/files-to-ignore',
|
||||
ignores: ['**/dist/**', '**/dist-ssr/**', '**/coverage/**'],
|
||||
},
|
||||
|
||||
...pluginVue.configs['flat/recommended'],
|
||||
...vueTsEslintConfig(),
|
||||
|
||||
{
|
||||
rules: {
|
||||
'@typescript-eslint/no-unused-expressions': [
|
||||
'error',
|
||||
{
|
||||
allowShortCircuit: true,
|
||||
allowTernary: true,
|
||||
},
|
||||
],
|
||||
'vue/multi-word-component-names': 'off',
|
||||
}
|
||||
}
|
||||
]
|
||||
13
next-ui/index.html
Normal file
13
next-ui/index.html
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<link rel="icon" href="/favicon.ico">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Welcome to Vuetify 3</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
</body>
|
||||
</html>
|
||||
26
next-ui/openapi-generator.ts
Normal file
26
next-ui/openapi-generator.ts
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
import fs from "node:fs";
|
||||
import openapiTS, { astToString } from "openapi-typescript";
|
||||
import ts from "typescript";
|
||||
|
||||
// From https://openapi-ts.dev/node
|
||||
// We use the Node.js API as the CLI does not support Date types
|
||||
|
||||
const mySchema = new URL("../komga/docs/openapi.json", import.meta.url)
|
||||
|
||||
const DATE = ts.factory.createTypeReferenceNode(ts.factory.createIdentifier("Date")); // `Date`
|
||||
const NULL = ts.factory.createLiteralTypeNode(ts.factory.createNull()); // `null`
|
||||
|
||||
const ast = await openapiTS(mySchema, {
|
||||
transform(schemaObject, metadata) {
|
||||
if (schemaObject.format === "date-time") {
|
||||
return schemaObject.nullable
|
||||
? ts.factory.createUnionTypeNode([DATE, NULL])
|
||||
: DATE;
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
const contents = astToString(ast);
|
||||
|
||||
// (optional) write to file
|
||||
fs.writeFileSync("./src/generated/openapi/komga.d.ts", contents);
|
||||
6446
next-ui/package-lock.json
generated
Normal file
6446
next-ui/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
52
next-ui/package.json
Normal file
52
next-ui/package.json
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
{
|
||||
"name": "next-ui",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"version": "0.0.0",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "run-p type-check \"build-only {@}\" --",
|
||||
"preview": "vite preview",
|
||||
"build-only": "vite build",
|
||||
"type-check": "vue-tsc --build --force",
|
||||
"lint": "eslint . --fix",
|
||||
"openapi-generate": "npx tsx ./openapi-generator.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"@pinia/colada": "^0.15.3",
|
||||
"@pinia/colada-plugin-auto-refetch": "^0.0.6",
|
||||
"@vueuse/core": "^13.1.0",
|
||||
"core-js": "^3.37.1",
|
||||
"marked": "^15.0.11",
|
||||
"openapi-fetch": "^0.14.0",
|
||||
"pinia-plugin-persistedstate": "^4.3.0",
|
||||
"vue": "^3.4.31",
|
||||
"vuetify": "^3.6.14"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.14.0",
|
||||
"@mdi/font": "7.4.47",
|
||||
"@tsconfig/node22": "^22.0.0",
|
||||
"@types/node": "^22.9.0",
|
||||
"@vitejs/plugin-vue": "^5.1.4",
|
||||
"@vue/eslint-config-typescript": "^14.1.3",
|
||||
"@vue/tsconfig": "^0.7.0",
|
||||
"eslint": "^9.14.0",
|
||||
"eslint-plugin-vue": "^10.1.0",
|
||||
"npm-run-all2": "^8.0.1",
|
||||
"openapi-typescript": "^7.8.0",
|
||||
"pinia": "^3.0.2",
|
||||
"sass": "1.77.8",
|
||||
"sass-embedded": "^1.77.8",
|
||||
"typescript": "^5.8.3",
|
||||
"unplugin-auto-import": "^19.2.0",
|
||||
"unplugin-fonts": "^1.1.1",
|
||||
"unplugin-vue-components": "^28.5.0",
|
||||
"unplugin-vue-router": "^0.12.0",
|
||||
"vite": "^6.3.5",
|
||||
"vite-plugin-vue-layouts-next": "^0.1.1",
|
||||
"vite-plugin-vuetify": "^2.0.3",
|
||||
"vue-router": "^4.4.0",
|
||||
"vue-tsc": "^2.1.10"
|
||||
}
|
||||
}
|
||||
BIN
next-ui/public/favicon-16x16.png
Normal file
BIN
next-ui/public/favicon-16x16.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.1 KiB |
BIN
next-ui/public/favicon-32x32.png
Normal file
BIN
next-ui/public/favicon-32x32.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.2 KiB |
BIN
next-ui/public/favicon.ico
Normal file
BIN
next-ui/public/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
32
next-ui/src/App.vue
Normal file
32
next-ui/src/App.vue
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
<template>
|
||||
<v-app>
|
||||
<router-view />
|
||||
</v-app>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useTheme } from 'vuetify'
|
||||
import {useAppStore} from '@/stores/app'
|
||||
import { usePreferredDark } from '@vueuse/core'
|
||||
|
||||
const appStore = useAppStore()
|
||||
const theme = useTheme()
|
||||
const prefersDark = usePreferredDark()
|
||||
|
||||
function updateTheme(selectedTheme: string, prefersDark: boolean) {
|
||||
if(selectedTheme === 'system') {
|
||||
theme.global.name.value = prefersDark ? 'dark' : 'light'
|
||||
} else {
|
||||
theme.global.name.value = selectedTheme
|
||||
}
|
||||
}
|
||||
|
||||
watch([() => appStore.theme, prefersDark], ([selectedTheme, prefersDark]) => updateTheme(selectedTheme, prefersDark))
|
||||
|
||||
// trigger an update on startup to get the proper theme loaded
|
||||
updateTheme(appStore.theme, prefersDark.value)
|
||||
</script>
|
||||
|
||||
<style>
|
||||
@import "styles/global.scss";
|
||||
</style>
|
||||
24
next-ui/src/api/komga-client.ts
Normal file
24
next-ui/src/api/komga-client.ts
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
import type {Middleware} from 'openapi-fetch'
|
||||
import createClient from 'openapi-fetch'
|
||||
import type {paths} from '@/generated/openapi/komga'
|
||||
|
||||
// Middleware that throws on error, so it works with Pinia Colada
|
||||
const coladaMiddleware: Middleware = {
|
||||
async onResponse({response}: {response: Response}) {
|
||||
if (!response.ok)
|
||||
throw new Error(`${response.url}: ${response.status} ${response.statusText}`)
|
||||
// return response untouched
|
||||
return undefined
|
||||
},
|
||||
}
|
||||
|
||||
const client = createClient<paths>({
|
||||
baseUrl: 'http://localhost:8080',
|
||||
// required to pass the session cookie on all requests
|
||||
credentials: 'include',
|
||||
// required to avoid browser basic-auth popups
|
||||
headers: {'X-Requested-With': 'XMLHttpRequest'},
|
||||
})
|
||||
client.use(coladaMiddleware)
|
||||
|
||||
export const komgaClient = client
|
||||
113
next-ui/src/assets/logo.svg
Normal file
113
next-ui/src/assets/logo.svg
Normal file
|
|
@ -0,0 +1,113 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:osb="http://www.openswatchbook.org/uri/2009/osb"
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
height="512pt"
|
||||
viewBox="0 0 512 512"
|
||||
width="512pt"
|
||||
version="1.1"
|
||||
id="svg4586"
|
||||
sodipodi:docname="komga - Copy.svg"
|
||||
inkscape:version="0.92.4 (5da689c313, 2019-01-14)">
|
||||
<metadata
|
||||
id="metadata4592">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage"/>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<defs
|
||||
id="defs4590">
|
||||
<linearGradient
|
||||
id="linearGradient6082"
|
||||
osb:paint="solid">
|
||||
<stop
|
||||
style="stop-color:#000000;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop6080"/>
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="linearGradient6076"
|
||||
osb:paint="solid">
|
||||
<stop
|
||||
style="stop-color:#000000;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop6074"/>
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient6082"
|
||||
id="linearGradient6084"
|
||||
x1="77.866812"
|
||||
y1="386.00679"
|
||||
x2="217.20259"
|
||||
y2="386.00679"
|
||||
gradientUnits="userSpaceOnUse"/>
|
||||
</defs>
|
||||
<sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1656"
|
||||
inkscape:window-height="1368"
|
||||
id="namedview4588"
|
||||
showgrid="false"
|
||||
inkscape:zoom="1.2512475"
|
||||
inkscape:cx="264.73114"
|
||||
inkscape:cy="305.20589"
|
||||
inkscape:window-x="-7"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:current-layer="svg4586"/>
|
||||
<path
|
||||
d="m512 256c0 141.386719-114.613281 256-256 256s-256-114.613281-256-256 114.613281-256 256-256 256 114.613281 256 256zm0 0"
|
||||
fill="#005ed3"
|
||||
id="path4556"/>
|
||||
<path
|
||||
d="m 512,256 c 0,-11.71094 -0.80469,-23.23047 -2.32422,-34.52344 L 382.48047,94.28125 320.52344,121.85938 256,56.933594 212.69531,131.30469 129.51953,94.28125 141.86719,178.42187 49.949219,193.81641 114.32031,256 l -64.371091,62.18359 82.121091,82.16016 -2.55078,17.375 91.95703,91.95703 C 232.76953,511.19531 244.28906,512 256,512 397.38672,512 512,397.38672 512,256 Z"
|
||||
id="path4558"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#00459f"
|
||||
sodipodi:nodetypes="scccccccccccccss"/>
|
||||
<path
|
||||
d="m256 86.742188 37.109375 63.738281 70.574219-31.414063-10.527344 71.71875 77.078125 12.910156-54.144531 52.304688 54.144531 52.304688-77.078125 12.910156 10.527344 71.71875-70.574219-31.414063-37.109375 63.738281-37.109375-63.738281-70.574219 31.414063 10.527344-71.71875-77.078125-12.910156 54.144531-52.304688-54.144531-52.304688 77.078125-12.910156-10.527344-71.71875 70.574219 31.414063zm0 0"
|
||||
fill="#ff0335"
|
||||
id="path4560"/>
|
||||
<path
|
||||
d="m430.230469 308.300781-77.070313 12.910157 10.519532 71.71875-70.570313-31.410157-37.109375 63.742188v-338.523438l37.109375 63.742188 70.570313-31.410157-6.757813 46.101563-3.761719 25.617187 58.800782 9.851563 18.269531 3.058594-13.390625 12.929687-40.75 39.371094 11.378906 10.988281zm0 0"
|
||||
fill="#c2001b"
|
||||
id="path4562"/>
|
||||
<path
|
||||
d="m256 455.066406-43.304688-74.371094-83.175781 37.023438 12.347657-84.140625-91.917969-15.394531 64.371093-62.183594-64.371093-62.183594 91.917969-15.394531-12.347657-84.140625 83.179688 37.023438 43.300781-74.371094 43.304688 74.371094 83.175781-37.023438-12.347657 84.140625 91.917969 15.394531-64.371093 62.183594 64.371093 62.183594-91.917969 15.398437 12.347657 84.136719-83.175781-37.023438zm-30.917969-112.722656 30.917969 53.101562 30.917969-53.101562 57.964843 25.800781-8.703124-59.292969 62.238281-10.425781-43.917969-42.425781 43.917969-42.425781-62.238281-10.425781 8.703124-59.292969-57.964843 25.800781-30.917969-53.101562-30.917969 53.101562-57.964843-25.800781 8.703124 59.292969-62.238281 10.425781 43.917969 42.425781-43.917969 42.425781 62.238281 10.425781-8.703124 59.292969zm0 0"
|
||||
fill="#ffdf47"
|
||||
id="path4564"/>
|
||||
<path
|
||||
d="m403.308594 261.441406-5.628906-5.441406 25.160156-24.300781 39.210937-37.878907-55.75-9.339843-36.171875-6.058594 2.800782-19.09375 9.550781-65.046875-83.179688 37.019531-43.300781-74.371093v59.621093l30.921875 53.109375 57.957031-25.808594-3.910156 26.667969-2.546875 17.378907-2.242187 15.25 2.480468.421874 59.761719 10.007813-43.921875 42.421875 16.96875 16.390625 26.953125 26.03125-62.242187 10.429687 8.699218 59.296876-57.957031-25.808594-30.921875 53.109375v59.621093l43.300781-74.371093 83.179688 37.019531-12.351563-84.140625 91.921875-15.398437zm0 0"
|
||||
fill="#fec000"
|
||||
id="path4566"/>
|
||||
<g
|
||||
aria-label="K"
|
||||
transform="matrix(1.1590846,-0.34467221,0.22789693,0.794981,0,0)"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:296.55969238px;line-height:125%;font-family:Impact;-inkscape-font-specification:Impact;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:1.54528999;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="text4596">
|
||||
<path
|
||||
d="m 220.91497,266.9035 -34.89789,105.85211 38.2284,128.58643 H 161.2555 L 136.63873,400.84769 V 501.34204 H 75.676021 V 266.9035 h 60.962709 v 91.08205 l 27.07845,-91.08205 z"
|
||||
style="font-size:296.55969238px;fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:1.54528999;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="path824"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 6.4 KiB |
142
next-ui/src/auto-imports.d.ts
vendored
Normal file
142
next-ui/src/auto-imports.d.ts
vendored
Normal file
|
|
@ -0,0 +1,142 @@
|
|||
/* eslint-disable */
|
||||
/* prettier-ignore */
|
||||
// @ts-nocheck
|
||||
// noinspection JSUnusedGlobalSymbols
|
||||
// Generated by unplugin-auto-import
|
||||
// biome-ignore lint: disable
|
||||
export {}
|
||||
declare global {
|
||||
const EffectScope: typeof import('vue')['EffectScope']
|
||||
const computed: typeof import('vue')['computed']
|
||||
const createApp: typeof import('vue')['createApp']
|
||||
const customRef: typeof import('vue')['customRef']
|
||||
const defineAsyncComponent: typeof import('vue')['defineAsyncComponent']
|
||||
const defineComponent: typeof import('vue')['defineComponent']
|
||||
const effectScope: typeof import('vue')['effectScope']
|
||||
const getCurrentInstance: typeof import('vue')['getCurrentInstance']
|
||||
const getCurrentScope: typeof import('vue')['getCurrentScope']
|
||||
const h: typeof import('vue')['h']
|
||||
const inject: typeof import('vue')['inject']
|
||||
const isProxy: typeof import('vue')['isProxy']
|
||||
const isReactive: typeof import('vue')['isReactive']
|
||||
const isReadonly: typeof import('vue')['isReadonly']
|
||||
const isRef: typeof import('vue')['isRef']
|
||||
const markRaw: typeof import('vue')['markRaw']
|
||||
const nextTick: typeof import('vue')['nextTick']
|
||||
const onActivated: typeof import('vue')['onActivated']
|
||||
const onBeforeMount: typeof import('vue')['onBeforeMount']
|
||||
const onBeforeRouteLeave: typeof import('vue-router')['onBeforeRouteLeave']
|
||||
const onBeforeRouteUpdate: typeof import('vue-router')['onBeforeRouteUpdate']
|
||||
const onBeforeUnmount: typeof import('vue')['onBeforeUnmount']
|
||||
const onBeforeUpdate: typeof import('vue')['onBeforeUpdate']
|
||||
const onDeactivated: typeof import('vue')['onDeactivated']
|
||||
const onErrorCaptured: typeof import('vue')['onErrorCaptured']
|
||||
const onMounted: typeof import('vue')['onMounted']
|
||||
const onRenderTracked: typeof import('vue')['onRenderTracked']
|
||||
const onRenderTriggered: typeof import('vue')['onRenderTriggered']
|
||||
const onScopeDispose: typeof import('vue')['onScopeDispose']
|
||||
const onServerPrefetch: typeof import('vue')['onServerPrefetch']
|
||||
const onUnmounted: typeof import('vue')['onUnmounted']
|
||||
const onUpdated: typeof import('vue')['onUpdated']
|
||||
const onWatcherCleanup: typeof import('vue')['onWatcherCleanup']
|
||||
const provide: typeof import('vue')['provide']
|
||||
const reactive: typeof import('vue')['reactive']
|
||||
const readonly: typeof import('vue')['readonly']
|
||||
const ref: typeof import('vue')['ref']
|
||||
const resolveComponent: typeof import('vue')['resolveComponent']
|
||||
const shallowReactive: typeof import('vue')['shallowReactive']
|
||||
const shallowReadonly: typeof import('vue')['shallowReadonly']
|
||||
const shallowRef: typeof import('vue')['shallowRef']
|
||||
const toRaw: typeof import('vue')['toRaw']
|
||||
const toRef: typeof import('vue')['toRef']
|
||||
const toRefs: typeof import('vue')['toRefs']
|
||||
const toValue: typeof import('vue')['toValue']
|
||||
const triggerRef: typeof import('vue')['triggerRef']
|
||||
const unref: typeof import('vue')['unref']
|
||||
const useAttrs: typeof import('vue')['useAttrs']
|
||||
const useCssModule: typeof import('vue')['useCssModule']
|
||||
const useCssVars: typeof import('vue')['useCssVars']
|
||||
const useId: typeof import('vue')['useId']
|
||||
const useLink: typeof import('vue-router')['useLink']
|
||||
const useModel: typeof import('vue')['useModel']
|
||||
const useRoute: typeof import('vue-router/auto')['useRoute']
|
||||
const useRouter: typeof import('vue-router/auto')['useRouter']
|
||||
const useSlots: typeof import('vue')['useSlots']
|
||||
const useTemplateRef: typeof import('vue')['useTemplateRef']
|
||||
const watch: typeof import('vue')['watch']
|
||||
const watchEffect: typeof import('vue')['watchEffect']
|
||||
const watchPostEffect: typeof import('vue')['watchPostEffect']
|
||||
const watchSyncEffect: typeof import('vue')['watchSyncEffect']
|
||||
}
|
||||
// for type re-export
|
||||
declare global {
|
||||
// @ts-ignore
|
||||
export type { Component, Slot, Slots, ComponentPublicInstance, ComputedRef, DirectiveBinding, ExtractDefaultPropTypes, ExtractPropTypes, ExtractPublicPropTypes, InjectionKey, PropType, Ref, MaybeRef, MaybeRefOrGetter, VNode, WritableComputedRef } from 'vue'
|
||||
import('vue')
|
||||
}
|
||||
|
||||
// for vue template auto import
|
||||
import { UnwrapRef } from 'vue'
|
||||
declare module 'vue' {
|
||||
interface GlobalComponents {}
|
||||
interface ComponentCustomProperties {
|
||||
readonly EffectScope: UnwrapRef<typeof import('vue')['EffectScope']>
|
||||
readonly computed: UnwrapRef<typeof import('vue')['computed']>
|
||||
readonly createApp: UnwrapRef<typeof import('vue')['createApp']>
|
||||
readonly customRef: UnwrapRef<typeof import('vue')['customRef']>
|
||||
readonly defineAsyncComponent: UnwrapRef<typeof import('vue')['defineAsyncComponent']>
|
||||
readonly defineComponent: UnwrapRef<typeof import('vue')['defineComponent']>
|
||||
readonly effectScope: UnwrapRef<typeof import('vue')['effectScope']>
|
||||
readonly getCurrentInstance: UnwrapRef<typeof import('vue')['getCurrentInstance']>
|
||||
readonly getCurrentScope: UnwrapRef<typeof import('vue')['getCurrentScope']>
|
||||
readonly h: UnwrapRef<typeof import('vue')['h']>
|
||||
readonly inject: UnwrapRef<typeof import('vue')['inject']>
|
||||
readonly isProxy: UnwrapRef<typeof import('vue')['isProxy']>
|
||||
readonly isReactive: UnwrapRef<typeof import('vue')['isReactive']>
|
||||
readonly isReadonly: UnwrapRef<typeof import('vue')['isReadonly']>
|
||||
readonly isRef: UnwrapRef<typeof import('vue')['isRef']>
|
||||
readonly markRaw: UnwrapRef<typeof import('vue')['markRaw']>
|
||||
readonly nextTick: UnwrapRef<typeof import('vue')['nextTick']>
|
||||
readonly onActivated: UnwrapRef<typeof import('vue')['onActivated']>
|
||||
readonly onBeforeMount: UnwrapRef<typeof import('vue')['onBeforeMount']>
|
||||
readonly onBeforeUnmount: UnwrapRef<typeof import('vue')['onBeforeUnmount']>
|
||||
readonly onBeforeUpdate: UnwrapRef<typeof import('vue')['onBeforeUpdate']>
|
||||
readonly onDeactivated: UnwrapRef<typeof import('vue')['onDeactivated']>
|
||||
readonly onErrorCaptured: UnwrapRef<typeof import('vue')['onErrorCaptured']>
|
||||
readonly onMounted: UnwrapRef<typeof import('vue')['onMounted']>
|
||||
readonly onRenderTracked: UnwrapRef<typeof import('vue')['onRenderTracked']>
|
||||
readonly onRenderTriggered: UnwrapRef<typeof import('vue')['onRenderTriggered']>
|
||||
readonly onScopeDispose: UnwrapRef<typeof import('vue')['onScopeDispose']>
|
||||
readonly onServerPrefetch: UnwrapRef<typeof import('vue')['onServerPrefetch']>
|
||||
readonly onUnmounted: UnwrapRef<typeof import('vue')['onUnmounted']>
|
||||
readonly onUpdated: UnwrapRef<typeof import('vue')['onUpdated']>
|
||||
readonly onWatcherCleanup: UnwrapRef<typeof import('vue')['onWatcherCleanup']>
|
||||
readonly provide: UnwrapRef<typeof import('vue')['provide']>
|
||||
readonly reactive: UnwrapRef<typeof import('vue')['reactive']>
|
||||
readonly readonly: UnwrapRef<typeof import('vue')['readonly']>
|
||||
readonly ref: UnwrapRef<typeof import('vue')['ref']>
|
||||
readonly resolveComponent: UnwrapRef<typeof import('vue')['resolveComponent']>
|
||||
readonly shallowReactive: UnwrapRef<typeof import('vue')['shallowReactive']>
|
||||
readonly shallowReadonly: UnwrapRef<typeof import('vue')['shallowReadonly']>
|
||||
readonly shallowRef: UnwrapRef<typeof import('vue')['shallowRef']>
|
||||
readonly toRaw: UnwrapRef<typeof import('vue')['toRaw']>
|
||||
readonly toRef: UnwrapRef<typeof import('vue')['toRef']>
|
||||
readonly toRefs: UnwrapRef<typeof import('vue')['toRefs']>
|
||||
readonly toValue: UnwrapRef<typeof import('vue')['toValue']>
|
||||
readonly triggerRef: UnwrapRef<typeof import('vue')['triggerRef']>
|
||||
readonly unref: UnwrapRef<typeof import('vue')['unref']>
|
||||
readonly useAttrs: UnwrapRef<typeof import('vue')['useAttrs']>
|
||||
readonly useCssModule: UnwrapRef<typeof import('vue')['useCssModule']>
|
||||
readonly useCssVars: UnwrapRef<typeof import('vue')['useCssVars']>
|
||||
readonly useId: UnwrapRef<typeof import('vue')['useId']>
|
||||
readonly useModel: UnwrapRef<typeof import('vue')['useModel']>
|
||||
readonly useRoute: UnwrapRef<typeof import('vue-router/auto')['useRoute']>
|
||||
readonly useRouter: UnwrapRef<typeof import('vue-router/auto')['useRouter']>
|
||||
readonly useSlots: UnwrapRef<typeof import('vue')['useSlots']>
|
||||
readonly useTemplateRef: UnwrapRef<typeof import('vue')['useTemplateRef']>
|
||||
readonly watch: UnwrapRef<typeof import('vue')['watch']>
|
||||
readonly watchEffect: UnwrapRef<typeof import('vue')['watchEffect']>
|
||||
readonly watchPostEffect: UnwrapRef<typeof import('vue')['watchPostEffect']>
|
||||
readonly watchSyncEffect: UnwrapRef<typeof import('vue')['watchSyncEffect']>
|
||||
}
|
||||
}
|
||||
16
next-ui/src/colada/mutations/logout.ts
Normal file
16
next-ui/src/colada/mutations/logout.ts
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
import {defineMutation, useMutation, useQueryCache} from '@pinia/colada'
|
||||
import {komgaClient} from '@/api/komga-client'
|
||||
|
||||
export const useLogout = defineMutation(() => {
|
||||
const queryCache = useQueryCache()
|
||||
return useMutation({
|
||||
mutation: () =>
|
||||
komgaClient.POST('/api/logout'),
|
||||
onSuccess: () => {
|
||||
queryCache.invalidateQueries({key: ['current-user']})
|
||||
},
|
||||
onError: (error) => {
|
||||
console.log('logout error', error)
|
||||
},
|
||||
})
|
||||
})
|
||||
15
next-ui/src/colada/queries/actuator-info.ts
Normal file
15
next-ui/src/colada/queries/actuator-info.ts
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
import {useQuery} from '@pinia/colada'
|
||||
import {komgaClient} from '@/api/komga-client'
|
||||
import type {ActuatorInfo} from '@/types/Actuator'
|
||||
|
||||
export function useActuatorInfo() {
|
||||
return useQuery({
|
||||
key: () => ['actuator-info'],
|
||||
query: () => komgaClient.GET('/actuator/info')
|
||||
// unwrap the openapi-fetch structure on success
|
||||
.then((res) => res.data as ActuatorInfo),
|
||||
// 1 hour
|
||||
staleTime: 60 * 60 * 1000,
|
||||
gcTime: false,
|
||||
})
|
||||
}
|
||||
14
next-ui/src/colada/queries/app-releases.ts
Normal file
14
next-ui/src/colada/queries/app-releases.ts
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
import {useQuery} from '@pinia/colada'
|
||||
import {komgaClient} from '@/api/komga-client'
|
||||
|
||||
export function useAppReleases() {
|
||||
return useQuery({
|
||||
key: () => ['app-releases'],
|
||||
query: () => komgaClient.GET('/api/v1/releases')
|
||||
// unwrap the openapi-fetch structure on success
|
||||
.then((res) => res.data),
|
||||
// 1 hour
|
||||
staleTime: 60 * 60 * 1000,
|
||||
gcTime: false,
|
||||
})
|
||||
}
|
||||
15
next-ui/src/colada/queries/current-user.ts
Normal file
15
next-ui/src/colada/queries/current-user.ts
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
import {useQuery} from '@pinia/colada'
|
||||
import {komgaClient} from '@/api/komga-client'
|
||||
|
||||
export function useCurrentUser() {
|
||||
return useQuery({
|
||||
key: () => ['current-user'],
|
||||
query: () => komgaClient.GET('/api/v2/users/me')
|
||||
// unwrap the openapi-fetch structure on success
|
||||
.then((res) => res.data),
|
||||
// 10 minutes
|
||||
staleTime: 10 * 60 * 1000,
|
||||
gcTime: false,
|
||||
autoRefetch: true,
|
||||
})
|
||||
}
|
||||
30
next-ui/src/components.d.ts
vendored
Normal file
30
next-ui/src/components.d.ts
vendored
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
/* eslint-disable */
|
||||
// @ts-nocheck
|
||||
// Generated by unplugin-vue-components
|
||||
// Read more: https://github.com/vuejs/core/pull/3399
|
||||
// biome-ignore lint: disable
|
||||
export {}
|
||||
|
||||
/* prettier-ignore */
|
||||
declare module 'vue' {
|
||||
export interface GlobalComponents {
|
||||
AppBar: typeof import('./components/app/bar/AppBar.vue')['default']
|
||||
AppDrawer: typeof import('./components/app/drawer/AppDrawer.vue')['default']
|
||||
AppDrawerFooter: typeof import('./components/app/drawer/AppDrawerFooter.vue')['default']
|
||||
AppDrawerMenu: typeof import('./components/app/drawer/AppDrawerMenu.vue')['default']
|
||||
AppDrawerMenuAccount: typeof import('./components/app/drawer/AppDrawerMenuAccount.vue')['default']
|
||||
AppDrawerMenuHistory: typeof import('./components/app/drawer/AppDrawerMenuHistory.vue')['default']
|
||||
AppDrawerMenuImport: typeof import('./components/app/drawer/AppDrawerMenuImport.vue')['default']
|
||||
AppDrawerMenuLogout: typeof import('./components/app/drawer/AppDrawerMenuLogout.vue')['default']
|
||||
AppDrawerMenuMedia: typeof import('./components/app/drawer/AppDrawerMenuMedia.vue')['default']
|
||||
AppDrawerMenuServer: typeof import('./components/app/drawer/AppDrawerMenuServer.vue')['default']
|
||||
AppFooter: typeof import('./components/AppFooter.vue')['default']
|
||||
BuidVersion: typeof import('./components/BuidVersion.vue')['default']
|
||||
BuildCommit: typeof import('./components/BuildCommit.vue')['default']
|
||||
HelloWorld: typeof import('./components/HelloWorld.vue')['default']
|
||||
LoginForm: typeof import('./components/LoginForm.vue')['default']
|
||||
RouterLink: typeof import('vue-router')['RouterLink']
|
||||
RouterView: typeof import('vue-router')['RouterView']
|
||||
ThemeSelector: typeof import('./components/app/bar/ThemeSelector.vue')['default']
|
||||
}
|
||||
}
|
||||
82
next-ui/src/components/AppFooter.vue
Normal file
82
next-ui/src/components/AppFooter.vue
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
<template>
|
||||
<v-footer
|
||||
height="40"
|
||||
app
|
||||
>
|
||||
<a
|
||||
v-for="item in items"
|
||||
:key="item.title"
|
||||
:href="item.href"
|
||||
:title="item.title"
|
||||
class="d-inline-block mx-2 social-link"
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
<v-icon
|
||||
:icon="item.icon"
|
||||
:size="item.icon === '$vuetify' ? 24 : 16"
|
||||
/>
|
||||
</a>
|
||||
|
||||
<div
|
||||
class="text-caption text-disabled"
|
||||
style="position: absolute; right: 16px;"
|
||||
>
|
||||
© 2016-{{ (new Date()).getFullYear() }} <span class="d-none d-sm-inline-block">Vuetify, LLC</span>
|
||||
—
|
||||
<a
|
||||
class="text-decoration-none on-surface"
|
||||
href="https://vuetifyjs.com/about/licensing/"
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
MIT License
|
||||
</a>
|
||||
</div>
|
||||
</v-footer>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const items = [
|
||||
{
|
||||
title: 'Vuetify Documentation',
|
||||
icon: `$vuetify`,
|
||||
href: 'https://vuetifyjs.com/',
|
||||
},
|
||||
{
|
||||
title: 'Vuetify Support',
|
||||
icon: 'mdi-shield-star-outline',
|
||||
href: 'https://support.vuetifyjs.com/',
|
||||
},
|
||||
{
|
||||
title: 'Vuetify X',
|
||||
icon: ['M2.04875 3.00002L9.77052 13.3248L1.99998 21.7192H3.74882L10.5519 14.3697L16.0486 21.7192H22L13.8437 10.8137L21.0765 3.00002H19.3277L13.0624 9.76874L8.0001 3.00002H2.04875ZM4.62054 4.28821H7.35461L19.4278 20.4308H16.6937L4.62054 4.28821Z'],
|
||||
href: 'https://x.com/vuetifyjs',
|
||||
},
|
||||
{
|
||||
title: 'Vuetify GitHub',
|
||||
icon: `mdi-github`,
|
||||
href: 'https://github.com/vuetifyjs/vuetify',
|
||||
},
|
||||
{
|
||||
title: 'Vuetify Discord',
|
||||
icon: ['M22,24L16.75,19L17.38,21H4.5A2.5,2.5 0 0,1 2,18.5V3.5A2.5,2.5 0 0,1 4.5,1H19.5A2.5,2.5 0 0,1 22,3.5V24M12,6.8C9.32,6.8 7.44,7.95 7.44,7.95C8.47,7.03 10.27,6.5 10.27,6.5L10.1,6.33C8.41,6.36 6.88,7.53 6.88,7.53C5.16,11.12 5.27,14.22 5.27,14.22C6.67,16.03 8.75,15.9 8.75,15.9L9.46,15C8.21,14.73 7.42,13.62 7.42,13.62C7.42,13.62 9.3,14.9 12,14.9C14.7,14.9 16.58,13.62 16.58,13.62C16.58,13.62 15.79,14.73 14.54,15L15.25,15.9C15.25,15.9 17.33,16.03 18.73,14.22C18.73,14.22 18.84,11.12 17.12,7.53C17.12,7.53 15.59,6.36 13.9,6.33L13.73,6.5C13.73,6.5 15.53,7.03 16.56,7.95C16.56,7.95 14.68,6.8 12,6.8M9.93,10.59C10.58,10.59 11.11,11.16 11.1,11.86C11.1,12.55 10.58,13.13 9.93,13.13C9.29,13.13 8.77,12.55 8.77,11.86C8.77,11.16 9.28,10.59 9.93,10.59M14.1,10.59C14.75,10.59 15.27,11.16 15.27,11.86C15.27,12.55 14.75,13.13 14.1,13.13C13.46,13.13 12.94,12.55 12.94,11.86C12.94,11.16 13.45,10.59 14.1,10.59Z'],
|
||||
href: 'https://community.vuetifyjs.com/',
|
||||
},
|
||||
{
|
||||
title: 'Vuetify Reddit',
|
||||
icon: `mdi-reddit`,
|
||||
href: 'https://reddit.com/r/vuetifyjs',
|
||||
},
|
||||
]
|
||||
</script>
|
||||
|
||||
<style scoped lang="sass">
|
||||
.social-link :deep(.v-icon)
|
||||
color: rgba(var(--v-theme-on-background), var(--v-disabled-opacity))
|
||||
text-decoration: none
|
||||
transition: .2s ease-in-out
|
||||
|
||||
&:hover
|
||||
color: rgba(25, 118, 210, 1)
|
||||
</style>
|
||||
20
next-ui/src/components/BuidVersion.vue
Normal file
20
next-ui/src/components/BuidVersion.vue
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
<template>
|
||||
<template v-if="buildVersion">
|
||||
<v-btn
|
||||
prepend-icon="mdi-tag-outline"
|
||||
variant="text"
|
||||
color="grey"
|
||||
size="small"
|
||||
class="text-caption"
|
||||
to="/server/updates"
|
||||
>
|
||||
{{ buildVersion }}
|
||||
</v-btn>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {useBuildVersion} from '@/composables/buid-version.ts'
|
||||
|
||||
const {buildVersion} = useBuildVersion()
|
||||
</script>
|
||||
23
next-ui/src/components/BuildCommit.vue
Normal file
23
next-ui/src/components/BuildCommit.vue
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
<template>
|
||||
<template v-if="commit">
|
||||
<v-btn
|
||||
prepend-icon="mdi-source-commit"
|
||||
variant="text"
|
||||
color="grey"
|
||||
size="small"
|
||||
class="text-caption"
|
||||
:href="'https://github.com/gotson/komga/commits/' + commit"
|
||||
target="_blank"
|
||||
>
|
||||
{{ commit }}
|
||||
</v-btn>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {useActuatorInfo} from '@/colada/queries/actuator-info'
|
||||
|
||||
const {data} = useActuatorInfo()
|
||||
|
||||
const commit = computed(() => data.value?.git?.commit?.id)
|
||||
</script>
|
||||
189
next-ui/src/components/HelloWorld.vue
Normal file
189
next-ui/src/components/HelloWorld.vue
Normal file
|
|
@ -0,0 +1,189 @@
|
|||
<template>
|
||||
<v-container class="fill-height">
|
||||
<v-responsive
|
||||
class="align-centerfill-height mx-auto"
|
||||
max-width="900"
|
||||
>
|
||||
<v-img
|
||||
class="mb-4"
|
||||
height="150"
|
||||
src="@/assets/logo.svg"
|
||||
/>
|
||||
|
||||
<div class="text-center">
|
||||
<div class="text-body-2">
|
||||
Welcome "{{ currentUser?.email }}"
|
||||
</div>
|
||||
|
||||
<VBtn @click="performLogout">
|
||||
Logout
|
||||
</VBtn>
|
||||
</div>
|
||||
|
||||
<div class="py-4" />
|
||||
|
||||
<v-row>
|
||||
<v-col cols="12">
|
||||
<v-card
|
||||
class="py-4"
|
||||
color="surface-variant"
|
||||
image="https://cdn.vuetifyjs.com/docs/images/one/create/feature.png"
|
||||
prepend-icon="mdi-rocket-launch-outline"
|
||||
rounded="lg"
|
||||
variant="outlined"
|
||||
>
|
||||
<template #image>
|
||||
<v-img position="top right" />
|
||||
</template>
|
||||
|
||||
<template #title>
|
||||
<h2 class="text-h5 font-weight-bold">
|
||||
Get started
|
||||
</h2>
|
||||
</template>
|
||||
|
||||
<template #subtitle>
|
||||
<div class="text-subtitle-1">
|
||||
Replace this page by removing
|
||||
<v-kbd>
|
||||
{{
|
||||
`
|
||||
|
||||
|
||||
|
||||
|
||||
<HelloWorld/>
|
||||
` }}
|
||||
</v-kbd>
|
||||
in
|
||||
<v-kbd>pages/index.vue</v-kbd>
|
||||
.
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<v-overlay
|
||||
opacity=".12"
|
||||
scrim="primary"
|
||||
contained
|
||||
model-value
|
||||
persistent
|
||||
/>
|
||||
</v-card>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="6">
|
||||
<v-card
|
||||
append-icon="mdi-open-in-new"
|
||||
class="py-4"
|
||||
color="surface-variant"
|
||||
href="https://vuetifyjs.com/"
|
||||
prepend-icon="mdi-text-box-outline"
|
||||
rel="noopener noreferrer"
|
||||
rounded="lg"
|
||||
subtitle="Learn about all things Vuetify in our documentation."
|
||||
target="_blank"
|
||||
title="Documentation"
|
||||
variant="text"
|
||||
>
|
||||
<v-overlay
|
||||
opacity=".06"
|
||||
scrim="primary"
|
||||
contained
|
||||
model-value
|
||||
persistent
|
||||
/>
|
||||
</v-card>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="6">
|
||||
<v-card
|
||||
append-icon="mdi-open-in-new"
|
||||
class="py-4"
|
||||
color="surface-variant"
|
||||
href="https://vuetifyjs.com/introduction/why-vuetify/#feature-guides"
|
||||
prepend-icon="mdi-star-circle-outline"
|
||||
rel="noopener noreferrer"
|
||||
rounded="lg"
|
||||
subtitle="Explore available framework Features."
|
||||
target="_blank"
|
||||
title="Features"
|
||||
variant="text"
|
||||
>
|
||||
<v-overlay
|
||||
opacity=".06"
|
||||
scrim="primary"
|
||||
contained
|
||||
model-value
|
||||
persistent
|
||||
/>
|
||||
</v-card>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="6">
|
||||
<v-card
|
||||
append-icon="mdi-open-in-new"
|
||||
class="py-4"
|
||||
color="surface-variant"
|
||||
href="https://vuetifyjs.com/components/all"
|
||||
prepend-icon="mdi-widgets-outline"
|
||||
rel="noopener noreferrer"
|
||||
rounded="lg"
|
||||
subtitle="Discover components in the API Explorer."
|
||||
target="_blank"
|
||||
title="Components"
|
||||
variant="text"
|
||||
>
|
||||
<v-overlay
|
||||
opacity=".06"
|
||||
scrim="primary"
|
||||
contained
|
||||
model-value
|
||||
persistent
|
||||
/>
|
||||
</v-card>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="6">
|
||||
<v-card
|
||||
append-icon="mdi-open-in-new"
|
||||
class="py-4"
|
||||
color="surface-variant"
|
||||
href="https://discord.vuetifyjs.com"
|
||||
prepend-icon="mdi-account-group-outline"
|
||||
rel="noopener noreferrer"
|
||||
rounded="lg"
|
||||
subtitle="Connect with Vuetify developers."
|
||||
target="_blank"
|
||||
title="Community"
|
||||
variant="text"
|
||||
>
|
||||
<v-overlay
|
||||
opacity=".06"
|
||||
scrim="primary"
|
||||
contained
|
||||
model-value
|
||||
persistent
|
||||
/>
|
||||
</v-card>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-responsive>
|
||||
</v-container>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {useCurrentUser} from '@/colada/queries/current-user'
|
||||
import {useLogout} from '@/colada/mutations/logout'
|
||||
|
||||
const {data: currentUser} = useCurrentUser()
|
||||
|
||||
const router = useRouter()
|
||||
const {mutateAsync: logoutAsync} = useLogout()
|
||||
|
||||
async function performLogout() {
|
||||
logoutAsync().then(() => router.push('/login'))
|
||||
}
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
</script>
|
||||
97
next-ui/src/components/LoginForm.vue
Normal file
97
next-ui/src/components/LoginForm.vue
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
<template>
|
||||
<v-container max-width="550px">
|
||||
<v-row justify="center">
|
||||
<v-col>
|
||||
<v-img src="@/assets/logo.svg" />
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<v-row>
|
||||
<v-col>
|
||||
<v-text-field
|
||||
v-model="username"
|
||||
label="Email"
|
||||
autofocus
|
||||
/>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<v-row>
|
||||
<v-col>
|
||||
<v-text-field
|
||||
v-model="password"
|
||||
label="Password"
|
||||
type="password"
|
||||
/>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<v-row>
|
||||
<v-col>
|
||||
<v-checkbox
|
||||
v-model="rememberMe"
|
||||
label="Remember me"
|
||||
/>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<v-row>
|
||||
<v-col>
|
||||
<v-btn
|
||||
text="Sign in"
|
||||
@click="performLogin"
|
||||
/>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-container>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {komgaClient} from '@/api/komga-client'
|
||||
import {useMutation, useQueryCache} from '@pinia/colada'
|
||||
|
||||
const username = ref('')
|
||||
const password = ref('')
|
||||
const rememberMe = ref(false)
|
||||
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
|
||||
async function performLogin() {
|
||||
const queryCache = useQueryCache()
|
||||
const {mutate} = useMutation({
|
||||
mutation: () =>
|
||||
komgaClient.GET('/api/v2/users/me', {
|
||||
headers: {
|
||||
authorization: 'Basic ' + btoa(username.value + ':' + password.value),
|
||||
'X-Requested-With': 'XMLHttpRequest',
|
||||
},
|
||||
params: {
|
||||
query: {
|
||||
'remember-me': rememberMe.value,
|
||||
}
|
||||
}
|
||||
}),
|
||||
onSuccess: ({data}) => {
|
||||
queryCache.setQueryData(['current-user'], data)
|
||||
queryCache.cancelQueries({key: ['current-user']})
|
||||
if(route.query.redirect)
|
||||
router.push({path: route.query.redirect.toString()})
|
||||
else
|
||||
router.push('/')
|
||||
},
|
||||
onError: (error) => {
|
||||
//TODO: handle error
|
||||
},
|
||||
})
|
||||
|
||||
mutate()
|
||||
}
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
35
next-ui/src/components/README.md
Normal file
35
next-ui/src/components/README.md
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
# Components
|
||||
|
||||
Vue template files in this folder are automatically imported.
|
||||
|
||||
## 🚀 Usage
|
||||
|
||||
Importing is handled by [unplugin-vue-components](https://github.com/unplugin/unplugin-vue-components). This plugin automatically imports `.vue` files created in the `src/components` directory, and registers them as global components. This means that you can use any component in your application without having to manually import it.
|
||||
|
||||
The following example assumes a component located at `src/components/MyComponent.vue`:
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<div>
|
||||
<MyComponent />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
//
|
||||
</script>
|
||||
```
|
||||
|
||||
When your template is rendered, the component's import will automatically be inlined, which renders to this:
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<div>
|
||||
<MyComponent />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import MyComponent from '@/components/MyComponent.vue'
|
||||
</script>
|
||||
```
|
||||
29
next-ui/src/components/app/bar/AppBar.vue
Normal file
29
next-ui/src/components/app/bar/AppBar.vue
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
<template>
|
||||
<v-app-bar elevation="2">
|
||||
<template #prepend>
|
||||
<v-app-bar-nav-icon @click="appStore.drawer = !appStore.drawer" />
|
||||
</template>
|
||||
<v-app-bar-title>
|
||||
<RouterLink to="/">
|
||||
<v-avatar>
|
||||
<v-img src="@/assets/logo.svg" />
|
||||
</v-avatar>
|
||||
</RouterLink>
|
||||
Komga
|
||||
</v-app-bar-title>
|
||||
<ThemeSelector />
|
||||
</v-app-bar>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {useAppStore} from '@/stores/app'
|
||||
|
||||
const appStore = useAppStore()
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
55
next-ui/src/components/app/bar/ThemeSelector.vue
Normal file
55
next-ui/src/components/app/bar/ThemeSelector.vue
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
<template>
|
||||
<v-menu>
|
||||
<template #activator="{ props }">
|
||||
<v-btn
|
||||
v-bind="props"
|
||||
:icon="themeIcon"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<v-list>
|
||||
<v-list-item
|
||||
v-for="theme in themes"
|
||||
:key="theme.value"
|
||||
:prepend-icon="theme.icon"
|
||||
:title="theme.title"
|
||||
@click="appStore.theme = theme.value"
|
||||
/>
|
||||
</v-list>
|
||||
</v-menu>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {useAppStore} from '@/stores/app'
|
||||
|
||||
const appStore = useAppStore()
|
||||
|
||||
const themes= [
|
||||
{
|
||||
title: 'Light',
|
||||
value: 'light',
|
||||
icon: 'mdi-brightness-7'
|
||||
},
|
||||
{
|
||||
title: 'Dark',
|
||||
value: 'dark',
|
||||
icon: 'mdi-brightness-3'
|
||||
},
|
||||
{
|
||||
title: 'System',
|
||||
value: 'system',
|
||||
icon: 'mdi-brightness-auto'
|
||||
},
|
||||
]
|
||||
|
||||
const themeIcon = computed(
|
||||
() => themes.find(x => x.value === appStore.theme)?.icon || 'mdi-brightness-auto'
|
||||
)
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
17
next-ui/src/components/app/drawer/AppDrawer.vue
Normal file
17
next-ui/src/components/app/drawer/AppDrawer.vue
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
<template>
|
||||
<v-navigation-drawer
|
||||
v-model="appStore.drawer"
|
||||
>
|
||||
<AppDrawerMenu />
|
||||
|
||||
<template #append>
|
||||
<AppDrawerFooter />
|
||||
</template>
|
||||
</v-navigation-drawer>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {useAppStore} from '@/stores/app'
|
||||
|
||||
const appStore = useAppStore()
|
||||
</script>
|
||||
18
next-ui/src/components/app/drawer/AppDrawerFooter.vue
Normal file
18
next-ui/src/components/app/drawer/AppDrawerFooter.vue
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
<template>
|
||||
<template v-if="isAdmin">
|
||||
<v-divider />
|
||||
<div class="d-flex align-center text-caption text-medium-emphasis pa-2">
|
||||
<div class="d-flex ms-auto">
|
||||
<BuildCommit class="me-2" />
|
||||
<BuidVersion />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {useCurrentUserRole} from '@/composables/current-user-role.ts'
|
||||
import {UserRoles} from '@/types/UserRoles.ts'
|
||||
|
||||
const {hasRole: isAdmin} = useCurrentUserRole(UserRoles.ADMIN)
|
||||
</script>
|
||||
17
next-ui/src/components/app/drawer/AppDrawerMenu.vue
Normal file
17
next-ui/src/components/app/drawer/AppDrawerMenu.vue
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
<template>
|
||||
<v-list nav>
|
||||
<AppDrawerMenuImport v-if="isAdmin" />
|
||||
<AppDrawerMenuMedia v-if="isAdmin" />
|
||||
<AppDrawerMenuHistory v-if="isAdmin" />
|
||||
<AppDrawerMenuServer v-if="isAdmin" />
|
||||
<AppDrawerMenuAccount />
|
||||
<AppDrawerMenuLogout />
|
||||
</v-list>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {useCurrentUserRole} from '@/composables/current-user-role.ts'
|
||||
import {UserRoles} from '@/types/UserRoles.ts'
|
||||
|
||||
const {hasRole: isAdmin} = useCurrentUserRole(UserRoles.ADMIN)
|
||||
</script>
|
||||
38
next-ui/src/components/app/drawer/AppDrawerMenuAccount.vue
Normal file
38
next-ui/src/components/app/drawer/AppDrawerMenuAccount.vue
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
<template>
|
||||
<v-list-group value=" My Account">
|
||||
<template #activator="{ props }">
|
||||
<v-list-item
|
||||
v-bind="props"
|
||||
title="My Account"
|
||||
prepend-icon="mdi-account"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<v-list-item
|
||||
to="/account/details"
|
||||
title="Details"
|
||||
/>
|
||||
<v-list-item
|
||||
to="/account/api-keys"
|
||||
title="API Keys"
|
||||
/>
|
||||
<v-list-item
|
||||
to="/account/ui"
|
||||
title="User Interface"
|
||||
/>
|
||||
<v-list-item
|
||||
to="/account/activity"
|
||||
title="Activity"
|
||||
/>
|
||||
</v-list-group>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
<template>
|
||||
<v-list-item
|
||||
to="/history"
|
||||
title="History"
|
||||
prepend-icon="mdi-clock-time-four-outline"
|
||||
/>
|
||||
</template>
|
||||
21
next-ui/src/components/app/drawer/AppDrawerMenuImport.vue
Normal file
21
next-ui/src/components/app/drawer/AppDrawerMenuImport.vue
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
<template>
|
||||
<v-list-group value="Import">
|
||||
<template #activator="{ props }">
|
||||
<v-list-item
|
||||
v-bind="props"
|
||||
title="Import"
|
||||
prepend-icon="mdi-import"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<v-list-item
|
||||
to="/import/books"
|
||||
title="Books"
|
||||
/>
|
||||
|
||||
<v-list-item
|
||||
to="/import/readlist"
|
||||
title="Read List"
|
||||
/>
|
||||
</v-list-group>
|
||||
</template>
|
||||
18
next-ui/src/components/app/drawer/AppDrawerMenuLogout.vue
Normal file
18
next-ui/src/components/app/drawer/AppDrawerMenuLogout.vue
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
<template>
|
||||
<v-list-item
|
||||
title="Logout"
|
||||
prepend-icon="mdi-power"
|
||||
@click="performLogout"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {useLogout} from '@/colada/mutations/logout'
|
||||
|
||||
const router = useRouter()
|
||||
const {mutateAsync: logoutAsync} = useLogout()
|
||||
|
||||
async function performLogout() {
|
||||
logoutAsync().then(() => router.push('/login'))
|
||||
}
|
||||
</script>
|
||||
44
next-ui/src/components/app/drawer/AppDrawerMenuMedia.vue
Normal file
44
next-ui/src/components/app/drawer/AppDrawerMenuMedia.vue
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
<template>
|
||||
<v-list-group value="Media">
|
||||
<template #activator="{ props }">
|
||||
<v-list-item
|
||||
v-bind="props"
|
||||
title="Media"
|
||||
prepend-icon="mdi-book-cog"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<v-list-item
|
||||
to="/media/analysis"
|
||||
title="Media Analysis"
|
||||
/>
|
||||
<v-list-item
|
||||
to="/media/missing-posters"
|
||||
title="Missing Posters"
|
||||
/>
|
||||
<v-list-item
|
||||
to="/media/duplicate-files"
|
||||
title="Duplicate Files"
|
||||
/>
|
||||
|
||||
<v-list-group value="Duplicate Pages">
|
||||
<template #activator="{ props }">
|
||||
<v-list-item
|
||||
v-bind="props"
|
||||
title="Duplicate Pages"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<v-list-item
|
||||
to="/media/duplicate-pages/known"
|
||||
title="Known"
|
||||
/>
|
||||
<v-list-item
|
||||
to="/media/duplicate-pages/unknown"
|
||||
title="Unknown"
|
||||
/>
|
||||
</v-list-group>
|
||||
</v-list-group>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
</script>
|
||||
36
next-ui/src/components/app/drawer/AppDrawerMenuServer.vue
Normal file
36
next-ui/src/components/app/drawer/AppDrawerMenuServer.vue
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
<template>
|
||||
<v-list-group value="Server">
|
||||
<template #activator="{ props }">
|
||||
<v-list-item
|
||||
v-bind="props"
|
||||
title="Server"
|
||||
prepend-icon="mdi-cog"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<v-list-item
|
||||
to="/server/users"
|
||||
title="Users"
|
||||
/>
|
||||
<v-list-item
|
||||
to="/server/settings"
|
||||
title="Settings"
|
||||
/>
|
||||
<v-list-item
|
||||
to="/server/ui"
|
||||
title="User Interface"
|
||||
/>
|
||||
<v-list-item
|
||||
to="/server/metrics"
|
||||
title="Metrics"
|
||||
/>
|
||||
<v-list-item
|
||||
to="/server/announcements"
|
||||
title="Announcements"
|
||||
/>
|
||||
<v-list-item
|
||||
to="/server/updates"
|
||||
title="Updates"
|
||||
/>
|
||||
</v-list-group>
|
||||
</template>
|
||||
9
next-ui/src/composables/buid-version.ts
Normal file
9
next-ui/src/composables/buid-version.ts
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
import {useActuatorInfo} from '@/colada/queries/actuator-info'
|
||||
|
||||
export function useBuildVersion() {
|
||||
const {data} = useActuatorInfo()
|
||||
|
||||
const buildVersion = computed(() => data.value?.build?.version)
|
||||
|
||||
return {buildVersion}
|
||||
}
|
||||
10
next-ui/src/composables/current-user-role.ts
Normal file
10
next-ui/src/composables/current-user-role.ts
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
import {useCurrentUser} from '@/colada/queries/current-user.ts'
|
||||
import {UserRoles} from '@/types/UserRoles.ts'
|
||||
|
||||
export function useCurrentUserRole(role: UserRoles) {
|
||||
const {data} = useCurrentUser()
|
||||
|
||||
const hasRole = computed(() => data.value?.roles.includes(role))
|
||||
|
||||
return {hasRole}
|
||||
}
|
||||
17
next-ui/src/composables/latest-version.ts
Normal file
17
next-ui/src/composables/latest-version.ts
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
import {useAppReleases} from '@/colada/queries/app-releases.ts'
|
||||
import {useBuildVersion} from '@/composables/buid-version.ts'
|
||||
|
||||
export function useLatestVersion() {
|
||||
const {data, } = useAppReleases()
|
||||
|
||||
const {buildVersion} = useBuildVersion()
|
||||
const latestRelease = computed(() => data.value?.find(x => x.latest))
|
||||
|
||||
const isLatestVersion = computed(() => {
|
||||
if(buildVersion.value && latestRelease.value)
|
||||
return buildVersion.value == latestRelease.value?.version
|
||||
else return undefined
|
||||
})
|
||||
|
||||
return {isLatestVersion}
|
||||
}
|
||||
9401
next-ui/src/generated/openapi/komga.d.ts
vendored
Normal file
9401
next-ui/src/generated/openapi/komga.d.ts
vendored
Normal file
File diff suppressed because it is too large
Load diff
5
next-ui/src/layouts/README.md
Normal file
5
next-ui/src/layouts/README.md
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
# Layouts
|
||||
|
||||
Layouts are reusable components that wrap around pages. They are used to provide a consistent look and feel across multiple pages.
|
||||
|
||||
Full documentation for this feature can be found in the Official [vite-plugin-vue-layouts-next](https://github.com/loicduong/vite-plugin-vue-layouts-next) repository.
|
||||
20
next-ui/src/layouts/default.vue
Normal file
20
next-ui/src/layouts/default.vue
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
<template>
|
||||
<AppBar />
|
||||
|
||||
<AppDrawer />
|
||||
|
||||
<v-main scrollable>
|
||||
<v-container
|
||||
fluid
|
||||
class="pa-6"
|
||||
>
|
||||
<router-view />
|
||||
</v-container>
|
||||
</v-main>
|
||||
|
||||
<AppFooter />
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
|
||||
</script>
|
||||
9
next-ui/src/layouts/single.vue
Normal file
9
next-ui/src/layouts/single.vue
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
<template>
|
||||
<v-main>
|
||||
<router-view />
|
||||
</v-main>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
//
|
||||
</script>
|
||||
20
next-ui/src/main.ts
Normal file
20
next-ui/src/main.ts
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
/**
|
||||
* main.ts
|
||||
*
|
||||
* Bootstraps Vuetify and other plugins then mounts the App`
|
||||
*/
|
||||
|
||||
// Plugins
|
||||
import {registerPlugins} from '@/plugins'
|
||||
|
||||
// Components
|
||||
import App from './App.vue'
|
||||
|
||||
// Composables
|
||||
import {createApp} from 'vue'
|
||||
|
||||
const app = createApp(App)
|
||||
|
||||
registerPlugins(app)
|
||||
|
||||
app.mount('#app')
|
||||
5
next-ui/src/pages/README.md
Normal file
5
next-ui/src/pages/README.md
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
# Pages
|
||||
|
||||
Vue components created in this folder will automatically be converted to navigable routes.
|
||||
|
||||
Full documentation for this feature can be found in the Official [unplugin-vue-router](https://github.com/posva/unplugin-vue-router) repository.
|
||||
7
next-ui/src/pages/account/activity.vue
Normal file
7
next-ui/src/pages/account/activity.vue
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
<template>
|
||||
<h1>Authentication Activity</h1>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
//
|
||||
</script>
|
||||
7
next-ui/src/pages/account/api-keys.vue
Normal file
7
next-ui/src/pages/account/api-keys.vue
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
<template>
|
||||
<h1>API Keys</h1>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
//
|
||||
</script>
|
||||
7
next-ui/src/pages/account/details.vue
Normal file
7
next-ui/src/pages/account/details.vue
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
<template>
|
||||
<h1>Account Details</h1>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
//
|
||||
</script>
|
||||
7
next-ui/src/pages/account/ui.vue
Normal file
7
next-ui/src/pages/account/ui.vue
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
<template>
|
||||
<h1>UI</h1>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
//
|
||||
</script>
|
||||
12
next-ui/src/pages/history.vue
Normal file
12
next-ui/src/pages/history.vue
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
<template>
|
||||
<h1>History</h1>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
//
|
||||
</script>
|
||||
|
||||
<route lang="yaml">
|
||||
meta:
|
||||
requiresRole: ADMIN
|
||||
</route>
|
||||
12
next-ui/src/pages/import/books.vue
Normal file
12
next-ui/src/pages/import/books.vue
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
<template>
|
||||
<h1>Import Books</h1>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
//
|
||||
</script>
|
||||
|
||||
<route lang="yaml">
|
||||
meta:
|
||||
requiresRole: ADMIN
|
||||
</route>
|
||||
12
next-ui/src/pages/import/readlist.vue
Normal file
12
next-ui/src/pages/import/readlist.vue
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
<template>
|
||||
<h1>Import Read List</h1>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
//
|
||||
</script>
|
||||
|
||||
<route lang="yaml">
|
||||
meta:
|
||||
requiresRole: ADMIN
|
||||
</route>
|
||||
7
next-ui/src/pages/index.vue
Normal file
7
next-ui/src/pages/index.vue
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
<template>
|
||||
<HelloWorld />
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
//
|
||||
</script>
|
||||
13
next-ui/src/pages/login.vue
Normal file
13
next-ui/src/pages/login.vue
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
<template>
|
||||
<LoginForm />
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
//
|
||||
</script>
|
||||
|
||||
<route lang="yaml">
|
||||
meta:
|
||||
layout: single
|
||||
noAuth: true
|
||||
</route>
|
||||
12
next-ui/src/pages/media/analysis.vue
Normal file
12
next-ui/src/pages/media/analysis.vue
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
<template>
|
||||
<h1>Media Analysis</h1>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
//
|
||||
</script>
|
||||
|
||||
<route lang="yaml">
|
||||
meta:
|
||||
requiresRole: ADMIN
|
||||
</route>
|
||||
12
next-ui/src/pages/media/duplicate-files.vue
Normal file
12
next-ui/src/pages/media/duplicate-files.vue
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
<template>
|
||||
<h1>Duplicate Files</h1>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
//
|
||||
</script>
|
||||
|
||||
<route lang="yaml">
|
||||
meta:
|
||||
requiresRole: ADMIN
|
||||
</route>
|
||||
12
next-ui/src/pages/media/duplicate-pages/known.vue
Normal file
12
next-ui/src/pages/media/duplicate-pages/known.vue
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
<template>
|
||||
<h1>Known</h1>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
//
|
||||
</script>
|
||||
|
||||
<route lang="yaml">
|
||||
meta:
|
||||
requiresRole: ADMIN
|
||||
</route>
|
||||
12
next-ui/src/pages/media/duplicate-pages/unknown.vue
Normal file
12
next-ui/src/pages/media/duplicate-pages/unknown.vue
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
<template>
|
||||
<h1>Unknown</h1>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
//
|
||||
</script>
|
||||
|
||||
<route lang="yaml">
|
||||
meta:
|
||||
requiresRole: ADMIN
|
||||
</route>
|
||||
12
next-ui/src/pages/media/missing-posters.vue
Normal file
12
next-ui/src/pages/media/missing-posters.vue
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
<template>
|
||||
<h1>Missing Posters</h1>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
//
|
||||
</script>
|
||||
|
||||
<route lang="yaml">
|
||||
meta:
|
||||
requiresRole: ADMIN
|
||||
</route>
|
||||
12
next-ui/src/pages/server/announcements.vue
Normal file
12
next-ui/src/pages/server/announcements.vue
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
<template>
|
||||
<h1>Announcements</h1>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
//
|
||||
</script>
|
||||
|
||||
<route lang="yaml">
|
||||
meta:
|
||||
requiresRole: ADMIN
|
||||
</route>
|
||||
12
next-ui/src/pages/server/metrics.vue
Normal file
12
next-ui/src/pages/server/metrics.vue
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
<template>
|
||||
<h1>Metrics</h1>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
//
|
||||
</script>
|
||||
|
||||
<route lang="yaml">
|
||||
meta:
|
||||
requiresRole: ADMIN
|
||||
</route>
|
||||
12
next-ui/src/pages/server/settings.vue
Normal file
12
next-ui/src/pages/server/settings.vue
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
<template>
|
||||
<h1>Settings</h1>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
//
|
||||
</script>
|
||||
|
||||
<route lang="yaml">
|
||||
meta:
|
||||
requiresRole: ADMIN
|
||||
</route>
|
||||
12
next-ui/src/pages/server/ui.vue
Normal file
12
next-ui/src/pages/server/ui.vue
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
<template>
|
||||
<h1>UI</h1>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
//
|
||||
</script>
|
||||
|
||||
<route lang="yaml">
|
||||
meta:
|
||||
requiresRole: ADMIN
|
||||
</route>
|
||||
123
next-ui/src/pages/server/updates.vue
Normal file
123
next-ui/src/pages/server/updates.vue
Normal file
|
|
@ -0,0 +1,123 @@
|
|||
<template>
|
||||
<v-alert
|
||||
v-if="error"
|
||||
type="error"
|
||||
variant="tonal"
|
||||
>
|
||||
Error loading data
|
||||
</v-alert>
|
||||
|
||||
<template v-if="data">
|
||||
<v-row>
|
||||
<v-col>
|
||||
<div v-if="isLatestVersion == true">
|
||||
<v-alert
|
||||
type="success"
|
||||
variant="tonal"
|
||||
>
|
||||
The latest version of Komga is already installed
|
||||
</v-alert>
|
||||
</div>
|
||||
<div v-if="isLatestVersion == false">
|
||||
<v-alert
|
||||
type="warning"
|
||||
variant="tonal"
|
||||
>
|
||||
Updates are available
|
||||
</v-alert>
|
||||
</div>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<div
|
||||
v-for="(release, index) in data"
|
||||
:key="index"
|
||||
>
|
||||
<v-row
|
||||
justify="space-between"
|
||||
align="center"
|
||||
>
|
||||
<v-col cols="auto">
|
||||
<div>
|
||||
<a
|
||||
:href="release.url"
|
||||
target="_blank"
|
||||
class="text-h4 font-weight-medium link-underline me-2"
|
||||
>{{
|
||||
release.version
|
||||
}}</a>
|
||||
<v-chip
|
||||
v-if="release.version == currentVersion"
|
||||
class="mx-2 mt-n3"
|
||||
size="small"
|
||||
label
|
||||
color="info"
|
||||
>
|
||||
Currently installed
|
||||
</v-chip>
|
||||
<v-chip
|
||||
v-if="release.version == latest?.version"
|
||||
class="mx-2 mt-n3"
|
||||
size="small"
|
||||
label
|
||||
>
|
||||
Latest
|
||||
</v-chip>
|
||||
</div>
|
||||
<!-- TODO: i18n the date -->
|
||||
<div class="mt-2 subtitle-1">
|
||||
{{ release.releaseDate }}
|
||||
</div>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<v-row>
|
||||
<v-col cols="12">
|
||||
<!-- eslint-disable vue/no-v-html -->
|
||||
<div
|
||||
class="releases"
|
||||
v-html="marked(release.description)"
|
||||
/>
|
||||
<!-- eslint-enable vue/no-v-html -->
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<v-divider
|
||||
v-if="index != data.length - 1"
|
||||
class="my-8"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import {useAppReleases} from '@/colada/queries/app-releases.ts'
|
||||
import {marked} from 'marked'
|
||||
import {useBuildVersion} from '@/composables/buid-version.ts'
|
||||
import {useLatestVersion} from '@/composables/latest-version.ts'
|
||||
|
||||
const {data, error} = useAppReleases()
|
||||
|
||||
const latest = computed(() => data.value?.find(x => x.latest))
|
||||
const {buildVersion: currentVersion} = useBuildVersion()
|
||||
const {isLatestVersion} = useLatestVersion()
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.releases p {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.releases ul {
|
||||
padding-left: 24px;
|
||||
}
|
||||
|
||||
.releases a {
|
||||
color: var(--v-anchor-base);
|
||||
}
|
||||
</style>
|
||||
|
||||
<route lang="yaml">
|
||||
meta:
|
||||
requiresRole: ADMIN
|
||||
</route>
|
||||
12
next-ui/src/pages/server/users.vue
Normal file
12
next-ui/src/pages/server/users.vue
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
<template>
|
||||
<h1>Users</h1>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
//
|
||||
</script>
|
||||
|
||||
<route lang="yaml">
|
||||
meta:
|
||||
requiresRole: ADMIN
|
||||
</route>
|
||||
41
next-ui/src/pages/startup.vue
Normal file
41
next-ui/src/pages/startup.vue
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
<template>
|
||||
<v-container max-width="550px">
|
||||
<v-row justify="center">
|
||||
<v-col>
|
||||
<v-img src="@/assets/logo.svg" />
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-container>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import {useCurrentUser} from '@/colada/queries/current-user'
|
||||
import {onMounted} from 'vue'
|
||||
|
||||
async function checkAuthenticated() {
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
const {data, error, refresh} = useCurrentUser()
|
||||
|
||||
await refresh()
|
||||
if (data.value) {
|
||||
if(route.query.redirect)
|
||||
await router.push({path: route.query.redirect.toString()})
|
||||
else
|
||||
await router.push('/')
|
||||
}
|
||||
if (error.value) {
|
||||
await router.push({name: '/login', query: {redirect: route.query.redirect}})
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => checkAuthenticated())
|
||||
|
||||
// TODO: exchange header token for cookie
|
||||
</script>
|
||||
|
||||
<route lang="yaml">
|
||||
meta:
|
||||
layout: single
|
||||
noAuth: true
|
||||
</route>
|
||||
3
next-ui/src/plugins/README.md
Normal file
3
next-ui/src/plugins/README.md
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
# Plugins
|
||||
|
||||
Plugins are a way to extend the functionality of your Vue application. Use this folder for registering plugins that you want to use globally.
|
||||
36
next-ui/src/plugins/index.ts
Normal file
36
next-ui/src/plugins/index.ts
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
/**
|
||||
* plugins/index.ts
|
||||
*
|
||||
* Automatically included in `./src/main.ts`
|
||||
*/
|
||||
|
||||
// Plugins
|
||||
import vuetify from './vuetify'
|
||||
import pinia from '../stores'
|
||||
import router from '../router'
|
||||
import {PiniaColada} from '@pinia/colada'
|
||||
import { PiniaColadaAutoRefetch } from '@pinia/colada-plugin-auto-refetch'
|
||||
|
||||
// Types
|
||||
import type {App} from 'vue'
|
||||
|
||||
// Navigation guards
|
||||
import {useLoginGuard} from '@/router/login-guard'
|
||||
import {useRoleGuard} from '@/router/role-guard.ts'
|
||||
|
||||
export function registerPlugins(app: App) {
|
||||
app
|
||||
.use(vuetify)
|
||||
// .use(DataLoaderPlugin, {router})
|
||||
.use(router)
|
||||
.use(pinia)
|
||||
.use(PiniaColada, {
|
||||
plugins: [
|
||||
PiniaColadaAutoRefetch()
|
||||
]
|
||||
})
|
||||
|
||||
// register navigation guards
|
||||
useLoginGuard(router)
|
||||
useRoleGuard(router)
|
||||
}
|
||||
40
next-ui/src/plugins/vuetify.ts
Normal file
40
next-ui/src/plugins/vuetify.ts
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
/**
|
||||
* plugins/vuetify.ts
|
||||
*
|
||||
* Framework documentation: https://vuetifyjs.com`
|
||||
*/
|
||||
|
||||
// Styles
|
||||
import '@mdi/font/css/materialdesignicons.css'
|
||||
import 'vuetify/styles'
|
||||
|
||||
// Composables
|
||||
import {createVuetify} from 'vuetify'
|
||||
import {md3} from 'vuetify/blueprints'
|
||||
|
||||
|
||||
// https://vuetifyjs.com/en/introduction/why-vuetify/#feature-guides
|
||||
export default createVuetify({
|
||||
theme: {
|
||||
defaultTheme: 'light',
|
||||
themes: {
|
||||
light: {
|
||||
dark: false,
|
||||
colors: {
|
||||
primary: '#005ed3',
|
||||
secondary: '#fec000',
|
||||
accent: '#ff0335',
|
||||
},
|
||||
},
|
||||
dark: {
|
||||
dark: true,
|
||||
colors: {
|
||||
primary: '#78baec',
|
||||
secondary: '#fec000',
|
||||
accent: '#ff0335',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
blueprint: md3,
|
||||
})
|
||||
36
next-ui/src/router/index.ts
Normal file
36
next-ui/src/router/index.ts
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
/**
|
||||
* router/index.ts
|
||||
*
|
||||
* Automatic routes for `./src/pages/*.vue`
|
||||
*/
|
||||
|
||||
// Composables
|
||||
import {createRouter, createWebHistory} from 'vue-router/auto'
|
||||
import {setupLayouts} from 'virtual:generated-layouts'
|
||||
import {routes} from 'vue-router/auto-routes'
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHistory(import.meta.env.BASE_URL),
|
||||
routes: setupLayouts(routes),
|
||||
})
|
||||
|
||||
// Workaround for https://github.com/vitejs/vite/issues/11804
|
||||
router.onError((err, to) => {
|
||||
if (err?.message?.includes?.('Failed to fetch dynamically imported module')) {
|
||||
if (!localStorage.getItem('vuetify:dynamic-reload')) {
|
||||
console.log('Reloading page to fix dynamic import error')
|
||||
localStorage.setItem('vuetify:dynamic-reload', 'true')
|
||||
location.assign(to.fullPath)
|
||||
} else {
|
||||
console.error('Dynamic import error, reloading page did not fix it', err)
|
||||
}
|
||||
} else {
|
||||
console.error(err)
|
||||
}
|
||||
})
|
||||
|
||||
router.isReady().then(() => {
|
||||
localStorage.removeItem('vuetify:dynamic-reload')
|
||||
})
|
||||
|
||||
export default router
|
||||
18
next-ui/src/router/login-guard.ts
Normal file
18
next-ui/src/router/login-guard.ts
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
import type {Router} from 'vue-router'
|
||||
import {useCurrentUser} from '@/colada/queries/current-user'
|
||||
|
||||
// check if the user is authenticated before navigating to any page
|
||||
// the authentication is cached by Pinia Colada
|
||||
// redirect to the startup page if not authenticated
|
||||
export function useLoginGuard(router: Router) {
|
||||
router.beforeEach((to) => {
|
||||
if (!to.meta.noAuth) {
|
||||
const {data} = useCurrentUser()
|
||||
const authenticated = data.value
|
||||
if(!authenticated) {
|
||||
const query = Object.assign({}, to.query, {redirect: to.fullPath})
|
||||
return {name: '/startup', query: query}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
16
next-ui/src/router/role-guard.ts
Normal file
16
next-ui/src/router/role-guard.ts
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
import type {Router} from 'vue-router'
|
||||
import {useCurrentUser} from '@/colada/queries/current-user'
|
||||
|
||||
// check if the user has the necessary role before navigating to restricted pages
|
||||
// the authentication is cached by Pinia Colada
|
||||
// redirect to the home page in case of insufficient permissions
|
||||
export function useRoleGuard(router: Router) {
|
||||
router.beforeEach((to) => {
|
||||
if (to.meta.requiresRole) {
|
||||
const {data} = useCurrentUser()
|
||||
if(!data.value?.roles?.includes(to.meta.requiresRole)) {
|
||||
return {name: '/'}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
5
next-ui/src/stores/README.md
Normal file
5
next-ui/src/stores/README.md
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
# Store
|
||||
|
||||
Pinia stores are used to store reactive state and expose actions to mutate it.
|
||||
|
||||
Full documentation for this feature can be found in the Official [Pinia](https://pinia.esm.dev/) repository.
|
||||
11
next-ui/src/stores/app.ts
Normal file
11
next-ui/src/stores/app.ts
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
// Utilities
|
||||
import {defineStore} from 'pinia'
|
||||
import {useDisplay} from 'vuetify'
|
||||
|
||||
export const useAppStore = defineStore('app', {
|
||||
state: () => ({
|
||||
drawer: !useDisplay().mobile.value.valueOf(),
|
||||
theme: 'system',
|
||||
}),
|
||||
persist: true,
|
||||
})
|
||||
8
next-ui/src/stores/index.ts
Normal file
8
next-ui/src/stores/index.ts
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
// Utilities
|
||||
import { createPinia } from 'pinia'
|
||||
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
|
||||
|
||||
const pinia = createPinia()
|
||||
pinia.use(piniaPluginPersistedstate)
|
||||
|
||||
export default pinia
|
||||
7
next-ui/src/stores/user.ts
Normal file
7
next-ui/src/stores/user.ts
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
// Utilities
|
||||
import {defineStore} from 'pinia'
|
||||
|
||||
export const useUserStore = defineStore('user', {
|
||||
state: () => ({
|
||||
}),
|
||||
})
|
||||
3
next-ui/src/styles/README.md
Normal file
3
next-ui/src/styles/README.md
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
# Styles
|
||||
|
||||
This directory is for configuring the styles of the application.
|
||||
16
next-ui/src/styles/global.scss
Normal file
16
next-ui/src/styles/global.scss
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
html {
|
||||
overflow-y: auto !important;
|
||||
}
|
||||
|
||||
.link-none {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.link-underline {
|
||||
text-decoration: none;
|
||||
color: var(--v-anchor-base)
|
||||
}
|
||||
|
||||
.link-underline:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
11
next-ui/src/styles/settings.scss
Normal file
11
next-ui/src/styles/settings.scss
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
/**
|
||||
* src/styles/settings.scss
|
||||
*
|
||||
* Configures SASS variables and Vuetify overwrites
|
||||
*/
|
||||
|
||||
// https://vuetifyjs.com/features/sass-variables/`
|
||||
// @use 'vuetify/settings' with (
|
||||
// $reset: true
|
||||
// );
|
||||
|
||||
43
next-ui/src/typed-router.d.ts
vendored
Normal file
43
next-ui/src/typed-router.d.ts
vendored
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
/* eslint-disable */
|
||||
/* prettier-ignore */
|
||||
// @ts-nocheck
|
||||
// Generated by unplugin-vue-router. ‼️ DO NOT MODIFY THIS FILE ‼️
|
||||
// It's recommended to commit this file.
|
||||
// Make sure to add this file to your tsconfig.json file as an "includes" or "files" entry.
|
||||
|
||||
declare module 'vue-router/auto-routes' {
|
||||
import type {
|
||||
RouteRecordInfo,
|
||||
ParamValue,
|
||||
ParamValueOneOrMore,
|
||||
ParamValueZeroOrMore,
|
||||
ParamValueZeroOrOne,
|
||||
} from 'vue-router'
|
||||
|
||||
/**
|
||||
* Route name map generated by unplugin-vue-router
|
||||
*/
|
||||
export interface RouteNamedMap {
|
||||
'/': RouteRecordInfo<'/', '/', Record<never, never>, Record<never, never>>,
|
||||
'/account/activity': RouteRecordInfo<'/account/activity', '/account/activity', Record<never, never>, Record<never, never>>,
|
||||
'/account/api-keys': RouteRecordInfo<'/account/api-keys', '/account/api-keys', Record<never, never>, Record<never, never>>,
|
||||
'/account/details': RouteRecordInfo<'/account/details', '/account/details', Record<never, never>, Record<never, never>>,
|
||||
'/account/ui': RouteRecordInfo<'/account/ui', '/account/ui', Record<never, never>, Record<never, never>>,
|
||||
'/history': RouteRecordInfo<'/history', '/history', Record<never, never>, Record<never, never>>,
|
||||
'/import/books': RouteRecordInfo<'/import/books', '/import/books', Record<never, never>, Record<never, never>>,
|
||||
'/import/readlist': RouteRecordInfo<'/import/readlist', '/import/readlist', Record<never, never>, Record<never, never>>,
|
||||
'/login': RouteRecordInfo<'/login', '/login', Record<never, never>, Record<never, never>>,
|
||||
'/media/analysis': RouteRecordInfo<'/media/analysis', '/media/analysis', Record<never, never>, Record<never, never>>,
|
||||
'/media/duplicate-files': RouteRecordInfo<'/media/duplicate-files', '/media/duplicate-files', Record<never, never>, Record<never, never>>,
|
||||
'/media/duplicate-pages/known': RouteRecordInfo<'/media/duplicate-pages/known', '/media/duplicate-pages/known', Record<never, never>, Record<never, never>>,
|
||||
'/media/duplicate-pages/unknown': RouteRecordInfo<'/media/duplicate-pages/unknown', '/media/duplicate-pages/unknown', Record<never, never>, Record<never, never>>,
|
||||
'/media/missing-posters': RouteRecordInfo<'/media/missing-posters', '/media/missing-posters', Record<never, never>, Record<never, never>>,
|
||||
'/server/announcements': RouteRecordInfo<'/server/announcements', '/server/announcements', Record<never, never>, Record<never, never>>,
|
||||
'/server/metrics': RouteRecordInfo<'/server/metrics', '/server/metrics', Record<never, never>, Record<never, never>>,
|
||||
'/server/settings': RouteRecordInfo<'/server/settings', '/server/settings', Record<never, never>, Record<never, never>>,
|
||||
'/server/ui': RouteRecordInfo<'/server/ui', '/server/ui', Record<never, never>, Record<never, never>>,
|
||||
'/server/updates': RouteRecordInfo<'/server/updates', '/server/updates', Record<never, never>, Record<never, never>>,
|
||||
'/server/users': RouteRecordInfo<'/server/users', '/server/users', Record<never, never>, Record<never, never>>,
|
||||
'/startup': RouteRecordInfo<'/startup', '/startup', Record<never, never>, Record<never, never>>,
|
||||
}
|
||||
}
|
||||
22
next-ui/src/types/Actuator.ts
Normal file
22
next-ui/src/types/Actuator.ts
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
export interface ActuatorInfo {
|
||||
git: ActuatorGit,
|
||||
build: ActuatorBuild
|
||||
}
|
||||
|
||||
export interface ActuatorGit {
|
||||
commit: ActuatorGitCommit,
|
||||
branch: string
|
||||
}
|
||||
|
||||
export interface ActuatorGitCommit {
|
||||
time: Date,
|
||||
id: string
|
||||
}
|
||||
|
||||
export interface ActuatorBuild {
|
||||
version: string,
|
||||
artifact: string,
|
||||
name: string,
|
||||
group: string,
|
||||
time: Date
|
||||
}
|
||||
15
next-ui/src/types/RouterMeta.d.ts
vendored
Normal file
15
next-ui/src/types/RouterMeta.d.ts
vendored
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
// This can be directly added to any of your `.ts` files like `router.ts`
|
||||
// It can also be added to a `.d.ts` file. Make sure it's included in
|
||||
// project's tsconfig.json "files"
|
||||
import 'vue-router'
|
||||
import type {UserRoles} from '@/types/UserRoles.ts'
|
||||
|
||||
// To ensure it is treated as a module, add at least one `export` statement
|
||||
export {}
|
||||
|
||||
declare module 'vue-router' {
|
||||
interface RouteMeta {
|
||||
noAuth?: boolean
|
||||
requiresRole?: UserRoles
|
||||
}
|
||||
}
|
||||
7
next-ui/src/types/UserRoles.ts
Normal file
7
next-ui/src/types/UserRoles.ts
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
export enum UserRoles {
|
||||
ADMIN = 'ADMIN',
|
||||
FILE_DOWNLOAD = 'FILE_DOWNLOAD',
|
||||
PAGE_STREAMING = 'PAGE_STREAMING',
|
||||
KOBO_SYNC = 'KOBO_SYNC',
|
||||
KOREADER_SYNC = 'KOREADER_SYNC'
|
||||
}
|
||||
18
next-ui/tsconfig.app.json
Normal file
18
next-ui/tsconfig.app.json
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"extends": "@vue/tsconfig/tsconfig.dom.json",
|
||||
"include": ["env.d.ts", "src/**/*", "src/**/*.vue"],
|
||||
"exclude": ["src/**/__tests__/*"],
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "bundler",
|
||||
"noUncheckedIndexedAccess": true, // openapi-ts
|
||||
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
},
|
||||
"types": ["vite-plugin-vue-layouts-next/client"]
|
||||
}
|
||||
}
|
||||
11
next-ui/tsconfig.json
Normal file
11
next-ui/tsconfig.json
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"files": [],
|
||||
"references": [
|
||||
{
|
||||
"path": "./tsconfig.node.json"
|
||||
},
|
||||
{
|
||||
"path": "./tsconfig.app.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
18
next-ui/tsconfig.node.json
Normal file
18
next-ui/tsconfig.node.json
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"extends": "@tsconfig/node22/tsconfig.json",
|
||||
"include": [
|
||||
"vite.config.*",
|
||||
"vitest.config.*",
|
||||
"cypress.config.*",
|
||||
"nightwatch.conf.*",
|
||||
"playwright.config.*"
|
||||
],
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"noEmit": true,
|
||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "Bundler",
|
||||
"types": ["node"]
|
||||
}
|
||||
}
|
||||
84
next-ui/vite.config.mts
Normal file
84
next-ui/vite.config.mts
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
// Plugins
|
||||
import AutoImport from 'unplugin-auto-import/vite'
|
||||
import Components from 'unplugin-vue-components/vite'
|
||||
import ViteFonts from "unplugin-fonts/vite"
|
||||
import Layouts from 'vite-plugin-vue-layouts-next'
|
||||
import Vue from '@vitejs/plugin-vue'
|
||||
import VueRouter from 'unplugin-vue-router/vite'
|
||||
import Vuetify, { transformAssetUrls } from 'vite-plugin-vuetify'
|
||||
|
||||
// Utilities
|
||||
import { defineConfig } from 'vite'
|
||||
import { fileURLToPath, URL } from 'node:url'
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
VueRouter({
|
||||
dts: 'src/typed-router.d.ts',
|
||||
}),
|
||||
Layouts(),
|
||||
AutoImport({
|
||||
imports: [
|
||||
'vue',
|
||||
{
|
||||
'vue-router/auto': ['useRoute', 'useRouter'],
|
||||
}
|
||||
],
|
||||
dts: 'src/auto-imports.d.ts',
|
||||
eslintrc: {
|
||||
enabled: true,
|
||||
},
|
||||
vueTemplate: true,
|
||||
}),
|
||||
Components({
|
||||
dts: 'src/components.d.ts',
|
||||
}),
|
||||
Vue({
|
||||
template: { transformAssetUrls },
|
||||
}),
|
||||
// https://github.com/vuetifyjs/vuetify-loader/tree/master/packages/vite-plugin#readme
|
||||
Vuetify({
|
||||
autoImport: true,
|
||||
styles: {
|
||||
configFile: 'src/styles/settings.scss',
|
||||
},
|
||||
}),
|
||||
ViteFonts({
|
||||
fontsource: {
|
||||
families: [
|
||||
{
|
||||
name: "Roboto",
|
||||
weights: [100, 300, 400, 500, 700, 900],
|
||||
styles: ["normal", "italic"],
|
||||
},
|
||||
],
|
||||
},
|
||||
}),
|
||||
],
|
||||
define: { 'process.env': {} },
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': fileURLToPath(new URL('./src', import.meta.url)),
|
||||
},
|
||||
extensions: [
|
||||
'.js',
|
||||
'.json',
|
||||
'.jsx',
|
||||
'.mjs',
|
||||
'.ts',
|
||||
'.tsx',
|
||||
'.vue',
|
||||
],
|
||||
},
|
||||
server: {
|
||||
port: 3000,
|
||||
},
|
||||
css: {
|
||||
preprocessorOptions: {
|
||||
sass: {
|
||||
api: 'modern-compiler',
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
Loading…
Reference in a new issue