mirror of
https://github.com/HangarMC/Hangar.git
synced 2025-01-30 14:30:08 +08:00
feat!: start moving to nuxt
This commit is contained in:
parent
c287c12b0d
commit
dad8789f7e
4
.github/workflows/deploy.yml
vendored
4
.github/workflows/deploy.yml
vendored
@ -62,7 +62,7 @@ jobs:
|
||||
run: mvn --batch-mode --errors --fail-at-end --show-version --no-transfer-progress -Dmaven.repo.local=$GITHUB_WORKSPACE/.m2/repository install
|
||||
|
||||
- name: Install frontend deps
|
||||
run: (cd frontend && pnpm install --frozen-lockfile && cd server && pnpm install --frozen-lockfile)
|
||||
run: (cd frontend && pnpm install --frozen-lockfile)
|
||||
|
||||
- name: Lint frontend
|
||||
run: (cd frontend && pnpm lint:eslint)
|
||||
@ -87,7 +87,7 @@ jobs:
|
||||
AUTH_HOST: "https://hangar-auth.benndorf.dev"
|
||||
PUBLIC_HOST: "https://hangar.benndorf.dev"
|
||||
DEBUG: "hangar:*"
|
||||
run: (cd frontend && pnpm build && cd server && pnpm build)
|
||||
run: (cd frontend && pnpm build)
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v2
|
||||
|
4
.github/workflows/frontend_build.yml
vendored
4
.github/workflows/frontend_build.yml
vendored
@ -43,7 +43,7 @@ jobs:
|
||||
- name: Install frontend deps
|
||||
env:
|
||||
CI: true
|
||||
run: (cd frontend && pnpm install --frozen-lockfile && cd server && pnpm install --frozen-lockfile)
|
||||
run: (cd frontend && pnpm install --frozen-lockfile)
|
||||
|
||||
- name: Lint frontend
|
||||
env:
|
||||
@ -58,5 +58,5 @@ jobs:
|
||||
AUTH_HOST: "https://hangar-auth.benndorf.dev"
|
||||
PUBLIC_HOST: "https://hangar.benndorf.dev"
|
||||
DEBUG: "hangar:*"
|
||||
run: (cd frontend && pnpm build && cd server && pnpm build)
|
||||
run: (cd frontend && pnpm build)
|
||||
|
||||
|
@ -6,7 +6,8 @@ ENV TERM xterm-256color
|
||||
ENV HOST 0.0.0.0
|
||||
|
||||
EXPOSE 1337
|
||||
ENV PORT=1337
|
||||
ENTRYPOINT ["./entrypoint.sh"]
|
||||
|
||||
COPY --chown=node:node --chmod=744 /chart/dockerfiles/frontend/entrypoint.sh /hangar-frontend/entrypoint.sh
|
||||
COPY --chown=node:node /frontend/ /hangar-frontend/
|
||||
COPY --chown=node:node /frontend/.output /frontend/
|
||||
|
@ -1,4 +1,2 @@
|
||||
#!/usr/bin/env sh
|
||||
#ls -ahl
|
||||
#ls -ahl /hangar-frontend/node_modules/.bin
|
||||
node /hangar-frontend/server
|
||||
node server/index.mjs
|
||||
|
3
frontend/.gitignore
vendored
3
frontend/.gitignore
vendored
@ -4,3 +4,6 @@ dist
|
||||
dist-ssr
|
||||
node_modules
|
||||
**/_*
|
||||
.nuxt
|
||||
src/server/middleware/@proxy/proxy.ts
|
||||
.output
|
||||
|
@ -7,3 +7,5 @@ pnpm-lock.yaml
|
||||
.vscode/**
|
||||
.idea/**
|
||||
server/**
|
||||
.output/**
|
||||
.nuxt/**
|
||||
|
@ -1,23 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/favicon/apple-touch-icon.png" />
|
||||
<link rel="icon" href="/favicon/favicon.svg" type="image/svg+xml" />
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/favicon/favicon-32x32.png" />
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/favicon/favicon-16x16.png" />
|
||||
<link rel="mask-icon" href="/favicon/safari-pinned-tab.svg" color="#5bbad5" />
|
||||
<!--<link rel="manifest" href="/manifest.webmanifest" /> TODO fix manifest -->
|
||||
<meta name="apple-mobile-web-app-title" content="Hangar | PaperMC" />
|
||||
<meta name="application-name" content="Hangar | PaperMC" />
|
||||
<meta name="theme-color" content="#ffffff" />
|
||||
</head>
|
||||
<body class="background-body text-[#262626] dark:text-[#E0E6f0]">
|
||||
<div id="app"></div>
|
||||
<script>
|
||||
window.global = window;
|
||||
</script>
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
</body>
|
||||
</html>
|
102
frontend/nuxt.config.ts
Normal file
102
frontend/nuxt.config.ts
Normal file
@ -0,0 +1,102 @@
|
||||
import path from "node:path";
|
||||
import VueI18n from "@intlify/vite-plugin-vue-i18n";
|
||||
import IconsResolver from "unplugin-icons/resolver";
|
||||
import Icons from "unplugin-icons/vite";
|
||||
import Components from "unplugin-vue-components/vite";
|
||||
import { ProxyOptions } from "@nuxtjs-alt/proxy";
|
||||
import prettier from "./src/lib/plugins/prettier";
|
||||
|
||||
const backendHost = process.env.BACKEND_HOST || "http://localhost:8080";
|
||||
const authHost = process.env.AUTH_HOST || "http://localhost:3001";
|
||||
|
||||
// https://v3.nuxtjs.org/api/configuration/nuxt.config
|
||||
export default defineNuxtConfig({
|
||||
imports: {
|
||||
autoImport: false,
|
||||
},
|
||||
srcDir: "src",
|
||||
runtimeConfig: {
|
||||
authHost,
|
||||
backendHost,
|
||||
},
|
||||
modules: ["nuxt-windicss", "@pinia/nuxt", "@nuxtjs-alt/proxy", "unplugin-icons/nuxt"],
|
||||
vite: {
|
||||
plugins: [
|
||||
// https://github.com/antfu/unplugin-vue-components
|
||||
Components({
|
||||
// we don't want to import components, just icons
|
||||
dirs: ["none"],
|
||||
// auto import icons
|
||||
resolvers: [
|
||||
// https://github.com/antfu/vite-plugin-icons
|
||||
IconsResolver({
|
||||
componentPrefix: "icon",
|
||||
enabledCollections: ["mdi"],
|
||||
}),
|
||||
],
|
||||
dts: "types/generated/icons.d.ts",
|
||||
}),
|
||||
|
||||
// https://github.com/antfu/unplugin-icons
|
||||
Icons({
|
||||
autoInstall: true,
|
||||
}),
|
||||
|
||||
// https://github.com/intlify/bundle-tools/tree/main/packages/vite-plugin-vue-i18n
|
||||
VueI18n({
|
||||
include: [path.resolve(__dirname, "src/locales/*.json")],
|
||||
}),
|
||||
|
||||
// TODO fix this
|
||||
// EslintPlugin({
|
||||
// fix: true,
|
||||
// }),
|
||||
prettier(),
|
||||
],
|
||||
ssr: {
|
||||
// Workaround until they support native ESM
|
||||
noExternal: ["vue3-popper"],
|
||||
},
|
||||
},
|
||||
experimental: {
|
||||
writeEarlyHints: false,
|
||||
},
|
||||
proxy: {
|
||||
enableProxy: true,
|
||||
proxies: {
|
||||
"/api/": defineProxyBackend(),
|
||||
"/signup": defineProxyBackend(),
|
||||
"/login": defineProxyBackend(),
|
||||
"/logout": defineProxyBackend(),
|
||||
"/handle-logout": defineProxyBackend(),
|
||||
"/refresh": defineProxyBackend(),
|
||||
"/invalidate": defineProxyBackend(),
|
||||
"/v2/api-docs/": defineProxyBackend(),
|
||||
"/robots.txt": defineProxyBackend(),
|
||||
"/sitemap.xml": defineProxyBackend(),
|
||||
"/global-sitemap.xml": defineProxyBackend(),
|
||||
"/*/sitemap.xml": defineProxyBackend(),
|
||||
"/statusz": defineProxyBackend(),
|
||||
// auth
|
||||
"/avatar": defineProxyAuth(),
|
||||
"/oauth/logout": defineProxyAuth(),
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
function defineProxyAuth(): ProxyOptions {
|
||||
return {
|
||||
configure: (proxy, options) => {
|
||||
options.target = process.env.AUTH_HOST || process.env.NITRO_AUTH_HOST || "http://localhost:3001";
|
||||
},
|
||||
changeOrigin: true,
|
||||
};
|
||||
}
|
||||
function defineProxyBackend(): ProxyOptions {
|
||||
return {
|
||||
configure: (proxy, options) => {
|
||||
options.target = process.env.BACKEND_HOST || process.env.NITRO_BACKEND_HOST || "http://localhost:8080";
|
||||
},
|
||||
changeOrigin: true,
|
||||
};
|
||||
}
|
@ -4,26 +4,27 @@
|
||||
"node": ">=16"
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "vite-ssr dev --port 3333",
|
||||
"dev:spa": "vite --port 3333",
|
||||
"build": "cross-env NODE_ENV=production vite-ssr build",
|
||||
"preview": "vite-ssr --port 1337 --open",
|
||||
"lint:eslint": "eslint --ext \".js,.vue,.ts,.json,.html\" --ignore-path .gitignore --ignore-pattern 'server/**' --fix .",
|
||||
"build": "nuxt build",
|
||||
"dev": "nuxt dev --port 3333",
|
||||
"generate": "nuxt generate",
|
||||
"preview": "nuxt preview",
|
||||
"previewBuild": "nuxt build && nuxt preview",
|
||||
"lint:eslint": "eslint --ext \".js,.vue,.ts,.html\" --ignore-path .gitignore --fix .",
|
||||
"lint:prettier": "prettier -w .",
|
||||
"prepare": "cd .. && husky install frontend/.husky"
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.{ts,js,vue,json,html}": [
|
||||
"*.{ts,js,vue,html}": [
|
||||
"prettier -c",
|
||||
"eslint"
|
||||
]
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": "./src/lib/config/vitessr.eslint.config.js"
|
||||
"extends": "./src/lib/config/eslint.config.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@headlessui/vue": "1.7.4",
|
||||
"@nuxt/devalue": "2.0.0",
|
||||
"@pinia/nuxt": "0.4.6",
|
||||
"@vuelidate/core": "2.0.0",
|
||||
"@vuelidate/validators": "2.0.0",
|
||||
"@vueuse/components": "9.6.0",
|
||||
@ -40,22 +41,21 @@
|
||||
"jwt-decode": "3.1.2",
|
||||
"lodash-es": "4.17.21",
|
||||
"nprogress": "0.2.0",
|
||||
"ohmyfetch": "0.4.21",
|
||||
"pinia": "2.0.27",
|
||||
"prism-theme-vars": "0.2.4",
|
||||
"qs": "6.11.0",
|
||||
"swagger-ui-dist": "4.15.5",
|
||||
"universal-cookie": "4.0.4",
|
||||
"vite-ssr": "0.16.0",
|
||||
"vue": "3.2.45",
|
||||
"vue-advanced-cropper": "2.8.6",
|
||||
"vue-i18n": "9.2.2",
|
||||
"vue-router": "4.1.6",
|
||||
"vue3-popper": "1.5.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@iconify/json": "2.1.147",
|
||||
"@iconify-json/mdi": "1.1.38",
|
||||
"@intlify/vite-plugin-vue-i18n": "6.0.3",
|
||||
"@nuxtjs-alt/proxy": "2.1.2",
|
||||
"@nuxtjs/eslint-config-typescript": "^12.0.0",
|
||||
"@types/accept-language-parser": "1.5.3",
|
||||
"@types/chartist": "0.11.1",
|
||||
"@types/debug": "4.1.7",
|
||||
@ -67,11 +67,8 @@
|
||||
"@types/prettier": "2.7.1",
|
||||
"@types/qs": "6.9.7",
|
||||
"@types/swagger-ui-dist": "3.30.1",
|
||||
"@vitejs/plugin-vue": "3.2.0",
|
||||
"@vue/compiler-sfc": "3.2.45",
|
||||
"@vue/eslint-config-typescript": "11.0.2",
|
||||
"@vue/server-renderer": "3.2.45",
|
||||
"cross-env": "7.0.3",
|
||||
"eslint": "8.29.0",
|
||||
"eslint-config-prettier": "8.5.0",
|
||||
"eslint-import-resolver-alias": "1.1.2",
|
||||
@ -81,7 +78,8 @@
|
||||
"eslint-plugin-vue": "9.8.0",
|
||||
"husky": "8.0.2",
|
||||
"lint-staged": "13.0.4",
|
||||
"node-fetch": "3.3.0",
|
||||
"nuxt": "3.0.0",
|
||||
"nuxt-windicss": "2.6.0",
|
||||
"pnpm": "7.18.0",
|
||||
"prettier": "2.8.0",
|
||||
"regenerator-runtime": "0.13.11",
|
||||
@ -91,10 +89,6 @@
|
||||
"unplugin-icons": "0.14.14",
|
||||
"unplugin-vue-components": "0.22.11",
|
||||
"vite": "3.2.4",
|
||||
"vite-plugin-eslint": "1.8.1",
|
||||
"vite-plugin-pages": "0.27.1",
|
||||
"vite-plugin-pwa": "0.13.3",
|
||||
"vite-plugin-vue-layouts": "0.7.0",
|
||||
"vite-plugin-windicss": "1.8.8"
|
||||
"vite-plugin-eslint": "1.8.1"
|
||||
}
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,22 +0,0 @@
|
||||
const backendHost = process.env.BACKEND_HOST || "http://localhost:8080";
|
||||
const authHost = process.env.AUTH_HOST || "http://localhost:3001";
|
||||
|
||||
exports["default"] = {
|
||||
// backend
|
||||
"/api/": backendHost,
|
||||
"/signup": backendHost,
|
||||
"/login": backendHost,
|
||||
"/logout": backendHost,
|
||||
"/handle-logout": backendHost,
|
||||
"/refresh": backendHost,
|
||||
"/invalidate": backendHost,
|
||||
"/v2/api-docs/": backendHost,
|
||||
"/robots.txt": backendHost,
|
||||
"/sitemap.xml": backendHost,
|
||||
"/global-sitemap.xml": backendHost,
|
||||
"/*/sitemap.xml": backendHost,
|
||||
"/statusz": backendHost,
|
||||
// auth
|
||||
"/avatar": authHost,
|
||||
"/oauth/logout": authHost,
|
||||
};
|
@ -1,23 +0,0 @@
|
||||
{
|
||||
"private": true,
|
||||
"main": "dist/index.js",
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"start": "node .",
|
||||
"build:start": "pnpm run build && pnpm run start",
|
||||
"build:all:start": "cd .. && pnpm run build && cd server && pnpm run build:start"
|
||||
},
|
||||
"dependencies": {
|
||||
"compression": "1.7.4",
|
||||
"express": "4.18.2",
|
||||
"http-proxy-middleware": "2.0.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/compression": "1.7.2",
|
||||
"@types/express": "4.17.14",
|
||||
"@types/http-proxy": "1.17.9",
|
||||
"@types/node": "18.11.10",
|
||||
"http-proxy": "1.18.1",
|
||||
"typescript": "4.9.3"
|
||||
}
|
||||
}
|
@ -1,624 +0,0 @@
|
||||
lockfileVersion: 5.4
|
||||
|
||||
specifiers:
|
||||
'@types/compression': 1.7.2
|
||||
'@types/express': 4.17.14
|
||||
'@types/http-proxy': 1.17.9
|
||||
'@types/node': 18.11.10
|
||||
compression: 1.7.4
|
||||
express: 4.18.2
|
||||
http-proxy: 1.18.1
|
||||
http-proxy-middleware: 2.0.6
|
||||
typescript: 4.9.3
|
||||
|
||||
dependencies:
|
||||
compression: 1.7.4
|
||||
express: 4.18.2
|
||||
http-proxy-middleware: 2.0.6_@types+express@4.17.14
|
||||
|
||||
devDependencies:
|
||||
'@types/compression': 1.7.2
|
||||
'@types/express': 4.17.14
|
||||
'@types/http-proxy': 1.17.9
|
||||
'@types/node': 18.11.10
|
||||
http-proxy: 1.18.1
|
||||
typescript: 4.9.3
|
||||
|
||||
packages:
|
||||
|
||||
/@types/body-parser/1.19.2:
|
||||
resolution: {integrity: sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==}
|
||||
dependencies:
|
||||
'@types/connect': 3.4.35
|
||||
'@types/node': 18.11.10
|
||||
|
||||
/@types/compression/1.7.2:
|
||||
resolution: {integrity: sha512-lwEL4M/uAGWngWFLSG87ZDr2kLrbuR8p7X+QZB1OQlT+qkHsCPDVFnHPyXf4Vyl4yDDorNY+mAhosxkCvppatg==}
|
||||
dependencies:
|
||||
'@types/express': 4.17.14
|
||||
dev: true
|
||||
|
||||
/@types/connect/3.4.35:
|
||||
resolution: {integrity: sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==}
|
||||
dependencies:
|
||||
'@types/node': 18.11.10
|
||||
|
||||
/@types/express-serve-static-core/4.17.28:
|
||||
resolution: {integrity: sha512-P1BJAEAW3E2DJUlkgq4tOL3RyMunoWXqbSCygWo5ZIWTjUgN1YnaXWW4VWl/oc8vs/XoYibEGBKP0uZyF4AHig==}
|
||||
dependencies:
|
||||
'@types/node': 18.11.10
|
||||
'@types/qs': 6.9.7
|
||||
'@types/range-parser': 1.2.4
|
||||
|
||||
/@types/express/4.17.14:
|
||||
resolution: {integrity: sha512-TEbt+vaPFQ+xpxFLFssxUDXj5cWCxZJjIcB7Yg0k0GMHGtgtQgpvx/MUQUeAkNbA9AAGrwkAsoeItdTgS7FMyg==}
|
||||
dependencies:
|
||||
'@types/body-parser': 1.19.2
|
||||
'@types/express-serve-static-core': 4.17.28
|
||||
'@types/qs': 6.9.7
|
||||
'@types/serve-static': 1.13.10
|
||||
|
||||
/@types/http-proxy/1.17.9:
|
||||
resolution: {integrity: sha512-QsbSjA/fSk7xB+UXlCT3wHBy5ai9wOcNDWwZAtud+jXhwOM3l+EYZh8Lng4+/6n8uar0J7xILzqftJdJ/Wdfkw==}
|
||||
dependencies:
|
||||
'@types/node': 18.11.10
|
||||
|
||||
/@types/mime/1.3.2:
|
||||
resolution: {integrity: sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==}
|
||||
|
||||
/@types/node/18.11.10:
|
||||
resolution: {integrity: sha512-juG3RWMBOqcOuXC643OAdSA525V44cVgGV6dUDuiFtss+8Fk5x1hI93Rsld43VeJVIeqlP9I7Fn9/qaVqoEAuQ==}
|
||||
|
||||
/@types/qs/6.9.7:
|
||||
resolution: {integrity: sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==}
|
||||
|
||||
/@types/range-parser/1.2.4:
|
||||
resolution: {integrity: sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==}
|
||||
|
||||
/@types/serve-static/1.13.10:
|
||||
resolution: {integrity: sha512-nCkHGI4w7ZgAdNkrEu0bv+4xNV/XDqW+DydknebMOQwkpDGx8G+HTlj7R7ABI8i8nKxVw0wtKPi1D+lPOkh4YQ==}
|
||||
dependencies:
|
||||
'@types/mime': 1.3.2
|
||||
'@types/node': 18.11.10
|
||||
|
||||
/accepts/1.3.8:
|
||||
resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==}
|
||||
engines: {node: '>= 0.6'}
|
||||
dependencies:
|
||||
mime-types: 2.1.34
|
||||
negotiator: 0.6.3
|
||||
dev: false
|
||||
|
||||
/array-flatten/1.1.1:
|
||||
resolution: {integrity: sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==}
|
||||
dev: false
|
||||
|
||||
/body-parser/1.20.1:
|
||||
resolution: {integrity: sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==}
|
||||
engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16}
|
||||
dependencies:
|
||||
bytes: 3.1.2
|
||||
content-type: 1.0.4
|
||||
debug: 2.6.9
|
||||
depd: 2.0.0
|
||||
destroy: 1.2.0
|
||||
http-errors: 2.0.0
|
||||
iconv-lite: 0.4.24
|
||||
on-finished: 2.4.1
|
||||
qs: 6.11.0
|
||||
raw-body: 2.5.1
|
||||
type-is: 1.6.18
|
||||
unpipe: 1.0.0
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: false
|
||||
|
||||
/braces/3.0.2:
|
||||
resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==}
|
||||
engines: {node: '>=8'}
|
||||
dependencies:
|
||||
fill-range: 7.0.1
|
||||
dev: false
|
||||
|
||||
/bytes/3.0.0:
|
||||
resolution: {integrity: sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==}
|
||||
engines: {node: '>= 0.8'}
|
||||
dev: false
|
||||
|
||||
/bytes/3.1.2:
|
||||
resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==}
|
||||
engines: {node: '>= 0.8'}
|
||||
dev: false
|
||||
|
||||
/call-bind/1.0.2:
|
||||
resolution: {integrity: sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==}
|
||||
dependencies:
|
||||
function-bind: 1.1.1
|
||||
get-intrinsic: 1.1.2
|
||||
dev: false
|
||||
|
||||
/compressible/2.0.18:
|
||||
resolution: {integrity: sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==}
|
||||
engines: {node: '>= 0.6'}
|
||||
dependencies:
|
||||
mime-db: 1.51.0
|
||||
dev: false
|
||||
|
||||
/compression/1.7.4:
|
||||
resolution: {integrity: sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==}
|
||||
engines: {node: '>= 0.8.0'}
|
||||
dependencies:
|
||||
accepts: 1.3.8
|
||||
bytes: 3.0.0
|
||||
compressible: 2.0.18
|
||||
debug: 2.6.9
|
||||
on-headers: 1.0.2
|
||||
safe-buffer: 5.1.2
|
||||
vary: 1.1.2
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: false
|
||||
|
||||
/content-disposition/0.5.4:
|
||||
resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==}
|
||||
engines: {node: '>= 0.6'}
|
||||
dependencies:
|
||||
safe-buffer: 5.2.1
|
||||
dev: false
|
||||
|
||||
/content-type/1.0.4:
|
||||
resolution: {integrity: sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==}
|
||||
engines: {node: '>= 0.6'}
|
||||
dev: false
|
||||
|
||||
/cookie-signature/1.0.6:
|
||||
resolution: {integrity: sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==}
|
||||
dev: false
|
||||
|
||||
/cookie/0.5.0:
|
||||
resolution: {integrity: sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==}
|
||||
engines: {node: '>= 0.6'}
|
||||
dev: false
|
||||
|
||||
/debug/2.6.9:
|
||||
resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==}
|
||||
peerDependencies:
|
||||
supports-color: '*'
|
||||
peerDependenciesMeta:
|
||||
supports-color:
|
||||
optional: true
|
||||
dependencies:
|
||||
ms: 2.0.0
|
||||
dev: false
|
||||
|
||||
/depd/2.0.0:
|
||||
resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==}
|
||||
engines: {node: '>= 0.8'}
|
||||
dev: false
|
||||
|
||||
/destroy/1.2.0:
|
||||
resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==}
|
||||
engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16}
|
||||
dev: false
|
||||
|
||||
/ee-first/1.1.1:
|
||||
resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==}
|
||||
dev: false
|
||||
|
||||
/encodeurl/1.0.2:
|
||||
resolution: {integrity: sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==}
|
||||
engines: {node: '>= 0.8'}
|
||||
dev: false
|
||||
|
||||
/escape-html/1.0.3:
|
||||
resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==}
|
||||
dev: false
|
||||
|
||||
/etag/1.8.1:
|
||||
resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==}
|
||||
engines: {node: '>= 0.6'}
|
||||
dev: false
|
||||
|
||||
/eventemitter3/4.0.7:
|
||||
resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==}
|
||||
|
||||
/express/4.18.2:
|
||||
resolution: {integrity: sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==}
|
||||
engines: {node: '>= 0.10.0'}
|
||||
dependencies:
|
||||
accepts: 1.3.8
|
||||
array-flatten: 1.1.1
|
||||
body-parser: 1.20.1
|
||||
content-disposition: 0.5.4
|
||||
content-type: 1.0.4
|
||||
cookie: 0.5.0
|
||||
cookie-signature: 1.0.6
|
||||
debug: 2.6.9
|
||||
depd: 2.0.0
|
||||
encodeurl: 1.0.2
|
||||
escape-html: 1.0.3
|
||||
etag: 1.8.1
|
||||
finalhandler: 1.2.0
|
||||
fresh: 0.5.2
|
||||
http-errors: 2.0.0
|
||||
merge-descriptors: 1.0.1
|
||||
methods: 1.1.2
|
||||
on-finished: 2.4.1
|
||||
parseurl: 1.3.3
|
||||
path-to-regexp: 0.1.7
|
||||
proxy-addr: 2.0.7
|
||||
qs: 6.11.0
|
||||
range-parser: 1.2.1
|
||||
safe-buffer: 5.2.1
|
||||
send: 0.18.0
|
||||
serve-static: 1.15.0
|
||||
setprototypeof: 1.2.0
|
||||
statuses: 2.0.1
|
||||
type-is: 1.6.18
|
||||
utils-merge: 1.0.1
|
||||
vary: 1.1.2
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: false
|
||||
|
||||
/fill-range/7.0.1:
|
||||
resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==}
|
||||
engines: {node: '>=8'}
|
||||
dependencies:
|
||||
to-regex-range: 5.0.1
|
||||
dev: false
|
||||
|
||||
/finalhandler/1.2.0:
|
||||
resolution: {integrity: sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==}
|
||||
engines: {node: '>= 0.8'}
|
||||
dependencies:
|
||||
debug: 2.6.9
|
||||
encodeurl: 1.0.2
|
||||
escape-html: 1.0.3
|
||||
on-finished: 2.4.1
|
||||
parseurl: 1.3.3
|
||||
statuses: 2.0.1
|
||||
unpipe: 1.0.0
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: false
|
||||
|
||||
/follow-redirects/1.14.9:
|
||||
resolution: {integrity: sha512-MQDfihBQYMcyy5dhRDJUHcw7lb2Pv/TuE6xP1vyraLukNDHKbDxDNaOE3NbCAdKQApno+GPRyo1YAp89yCjK4w==}
|
||||
engines: {node: '>=4.0'}
|
||||
peerDependencies:
|
||||
debug: '*'
|
||||
peerDependenciesMeta:
|
||||
debug:
|
||||
optional: true
|
||||
|
||||
/forwarded/0.2.0:
|
||||
resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==}
|
||||
engines: {node: '>= 0.6'}
|
||||
dev: false
|
||||
|
||||
/fresh/0.5.2:
|
||||
resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==}
|
||||
engines: {node: '>= 0.6'}
|
||||
dev: false
|
||||
|
||||
/function-bind/1.1.1:
|
||||
resolution: {integrity: sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==}
|
||||
dev: false
|
||||
|
||||
/get-intrinsic/1.1.2:
|
||||
resolution: {integrity: sha512-Jfm3OyCxHh9DJyc28qGk+JmfkpO41A4XkneDSujN9MDXrm4oDKdHvndhZ2dN94+ERNfkYJWDclW6k2L/ZGHjXA==}
|
||||
dependencies:
|
||||
function-bind: 1.1.1
|
||||
has: 1.0.3
|
||||
has-symbols: 1.0.3
|
||||
dev: false
|
||||
|
||||
/has-symbols/1.0.3:
|
||||
resolution: {integrity: sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==}
|
||||
engines: {node: '>= 0.4'}
|
||||
dev: false
|
||||
|
||||
/has/1.0.3:
|
||||
resolution: {integrity: sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==}
|
||||
engines: {node: '>= 0.4.0'}
|
||||
dependencies:
|
||||
function-bind: 1.1.1
|
||||
dev: false
|
||||
|
||||
/http-errors/2.0.0:
|
||||
resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==}
|
||||
engines: {node: '>= 0.8'}
|
||||
dependencies:
|
||||
depd: 2.0.0
|
||||
inherits: 2.0.4
|
||||
setprototypeof: 1.2.0
|
||||
statuses: 2.0.1
|
||||
toidentifier: 1.0.1
|
||||
dev: false
|
||||
|
||||
/http-proxy-middleware/2.0.6_@types+express@4.17.14:
|
||||
resolution: {integrity: sha512-ya/UeJ6HVBYxrgYotAZo1KvPWlgB48kUJLDePFeneHsVujFaW5WNj2NgWCAE//B1Dl02BIfYlpNgBy8Kf8Rjmw==}
|
||||
engines: {node: '>=12.0.0'}
|
||||
peerDependencies:
|
||||
'@types/express': ^4.17.13
|
||||
peerDependenciesMeta:
|
||||
'@types/express':
|
||||
optional: true
|
||||
dependencies:
|
||||
'@types/express': 4.17.14
|
||||
'@types/http-proxy': 1.17.9
|
||||
http-proxy: 1.18.1
|
||||
is-glob: 4.0.3
|
||||
is-plain-obj: 3.0.0
|
||||
micromatch: 4.0.5
|
||||
transitivePeerDependencies:
|
||||
- debug
|
||||
dev: false
|
||||
|
||||
/http-proxy/1.18.1:
|
||||
resolution: {integrity: sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==}
|
||||
engines: {node: '>=8.0.0'}
|
||||
dependencies:
|
||||
eventemitter3: 4.0.7
|
||||
follow-redirects: 1.14.9
|
||||
requires-port: 1.0.0
|
||||
transitivePeerDependencies:
|
||||
- debug
|
||||
|
||||
/iconv-lite/0.4.24:
|
||||
resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
dependencies:
|
||||
safer-buffer: 2.1.2
|
||||
dev: false
|
||||
|
||||
/inherits/2.0.4:
|
||||
resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==}
|
||||
dev: false
|
||||
|
||||
/ipaddr.js/1.9.1:
|
||||
resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==}
|
||||
engines: {node: '>= 0.10'}
|
||||
dev: false
|
||||
|
||||
/is-extglob/2.1.1:
|
||||
resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
dev: false
|
||||
|
||||
/is-glob/4.0.3:
|
||||
resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
dependencies:
|
||||
is-extglob: 2.1.1
|
||||
dev: false
|
||||
|
||||
/is-number/7.0.0:
|
||||
resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==}
|
||||
engines: {node: '>=0.12.0'}
|
||||
dev: false
|
||||
|
||||
/is-plain-obj/3.0.0:
|
||||
resolution: {integrity: sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA==}
|
||||
engines: {node: '>=10'}
|
||||
dev: false
|
||||
|
||||
/media-typer/0.3.0:
|
||||
resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==}
|
||||
engines: {node: '>= 0.6'}
|
||||
dev: false
|
||||
|
||||
/merge-descriptors/1.0.1:
|
||||
resolution: {integrity: sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==}
|
||||
dev: false
|
||||
|
||||
/methods/1.1.2:
|
||||
resolution: {integrity: sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==}
|
||||
engines: {node: '>= 0.6'}
|
||||
dev: false
|
||||
|
||||
/micromatch/4.0.5:
|
||||
resolution: {integrity: sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==}
|
||||
engines: {node: '>=8.6'}
|
||||
dependencies:
|
||||
braces: 3.0.2
|
||||
picomatch: 2.3.1
|
||||
dev: false
|
||||
|
||||
/mime-db/1.51.0:
|
||||
resolution: {integrity: sha512-5y8A56jg7XVQx2mbv1lu49NR4dokRnhZYTtL+KGfaa27uq4pSTXkwQkFJl4pkRMyNFz/EtYDSkiiEHx3F7UN6g==}
|
||||
engines: {node: '>= 0.6'}
|
||||
dev: false
|
||||
|
||||
/mime-types/2.1.34:
|
||||
resolution: {integrity: sha512-6cP692WwGIs9XXdOO4++N+7qjqv0rqxxVvJ3VHPh/Sc9mVZcQP+ZGhkKiTvWMQRr2tbHkJP/Yn7Y0npb3ZBs4A==}
|
||||
engines: {node: '>= 0.6'}
|
||||
dependencies:
|
||||
mime-db: 1.51.0
|
||||
dev: false
|
||||
|
||||
/mime/1.6.0:
|
||||
resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==}
|
||||
engines: {node: '>=4'}
|
||||
hasBin: true
|
||||
dev: false
|
||||
|
||||
/ms/2.0.0:
|
||||
resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==}
|
||||
dev: false
|
||||
|
||||
/ms/2.1.3:
|
||||
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
|
||||
dev: false
|
||||
|
||||
/negotiator/0.6.3:
|
||||
resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==}
|
||||
engines: {node: '>= 0.6'}
|
||||
dev: false
|
||||
|
||||
/object-inspect/1.12.2:
|
||||
resolution: {integrity: sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==}
|
||||
dev: false
|
||||
|
||||
/on-finished/2.4.1:
|
||||
resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==}
|
||||
engines: {node: '>= 0.8'}
|
||||
dependencies:
|
||||
ee-first: 1.1.1
|
||||
dev: false
|
||||
|
||||
/on-headers/1.0.2:
|
||||
resolution: {integrity: sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==}
|
||||
engines: {node: '>= 0.8'}
|
||||
dev: false
|
||||
|
||||
/parseurl/1.3.3:
|
||||
resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==}
|
||||
engines: {node: '>= 0.8'}
|
||||
dev: false
|
||||
|
||||
/path-to-regexp/0.1.7:
|
||||
resolution: {integrity: sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==}
|
||||
dev: false
|
||||
|
||||
/picomatch/2.3.1:
|
||||
resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==}
|
||||
engines: {node: '>=8.6'}
|
||||
dev: false
|
||||
|
||||
/proxy-addr/2.0.7:
|
||||
resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==}
|
||||
engines: {node: '>= 0.10'}
|
||||
dependencies:
|
||||
forwarded: 0.2.0
|
||||
ipaddr.js: 1.9.1
|
||||
dev: false
|
||||
|
||||
/qs/6.11.0:
|
||||
resolution: {integrity: sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==}
|
||||
engines: {node: '>=0.6'}
|
||||
dependencies:
|
||||
side-channel: 1.0.4
|
||||
dev: false
|
||||
|
||||
/range-parser/1.2.1:
|
||||
resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==}
|
||||
engines: {node: '>= 0.6'}
|
||||
dev: false
|
||||
|
||||
/raw-body/2.5.1:
|
||||
resolution: {integrity: sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==}
|
||||
engines: {node: '>= 0.8'}
|
||||
dependencies:
|
||||
bytes: 3.1.2
|
||||
http-errors: 2.0.0
|
||||
iconv-lite: 0.4.24
|
||||
unpipe: 1.0.0
|
||||
dev: false
|
||||
|
||||
/requires-port/1.0.0:
|
||||
resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==}
|
||||
|
||||
/safe-buffer/5.1.2:
|
||||
resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==}
|
||||
dev: false
|
||||
|
||||
/safe-buffer/5.2.1:
|
||||
resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==}
|
||||
dev: false
|
||||
|
||||
/safer-buffer/2.1.2:
|
||||
resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==}
|
||||
dev: false
|
||||
|
||||
/send/0.18.0:
|
||||
resolution: {integrity: sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==}
|
||||
engines: {node: '>= 0.8.0'}
|
||||
dependencies:
|
||||
debug: 2.6.9
|
||||
depd: 2.0.0
|
||||
destroy: 1.2.0
|
||||
encodeurl: 1.0.2
|
||||
escape-html: 1.0.3
|
||||
etag: 1.8.1
|
||||
fresh: 0.5.2
|
||||
http-errors: 2.0.0
|
||||
mime: 1.6.0
|
||||
ms: 2.1.3
|
||||
on-finished: 2.4.1
|
||||
range-parser: 1.2.1
|
||||
statuses: 2.0.1
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: false
|
||||
|
||||
/serve-static/1.15.0:
|
||||
resolution: {integrity: sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==}
|
||||
engines: {node: '>= 0.8.0'}
|
||||
dependencies:
|
||||
encodeurl: 1.0.2
|
||||
escape-html: 1.0.3
|
||||
parseurl: 1.3.3
|
||||
send: 0.18.0
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: false
|
||||
|
||||
/setprototypeof/1.2.0:
|
||||
resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==}
|
||||
dev: false
|
||||
|
||||
/side-channel/1.0.4:
|
||||
resolution: {integrity: sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==}
|
||||
dependencies:
|
||||
call-bind: 1.0.2
|
||||
get-intrinsic: 1.1.2
|
||||
object-inspect: 1.12.2
|
||||
dev: false
|
||||
|
||||
/statuses/2.0.1:
|
||||
resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==}
|
||||
engines: {node: '>= 0.8'}
|
||||
dev: false
|
||||
|
||||
/to-regex-range/5.0.1:
|
||||
resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
|
||||
engines: {node: '>=8.0'}
|
||||
dependencies:
|
||||
is-number: 7.0.0
|
||||
dev: false
|
||||
|
||||
/toidentifier/1.0.1:
|
||||
resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==}
|
||||
engines: {node: '>=0.6'}
|
||||
dev: false
|
||||
|
||||
/type-is/1.6.18:
|
||||
resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==}
|
||||
engines: {node: '>= 0.6'}
|
||||
dependencies:
|
||||
media-typer: 0.3.0
|
||||
mime-types: 2.1.34
|
||||
dev: false
|
||||
|
||||
/typescript/4.9.3:
|
||||
resolution: {integrity: sha512-CIfGzTelbKNEnLpLdGFgdyKhG23CKdKgQPOBc+OUNrkJ2vr+KSzsSV5kq5iWhEQbok+quxgGzrAtGWCyU7tHnA==}
|
||||
engines: {node: '>=4.2.0'}
|
||||
hasBin: true
|
||||
dev: true
|
||||
|
||||
/unpipe/1.0.0:
|
||||
resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==}
|
||||
engines: {node: '>= 0.8'}
|
||||
dev: false
|
||||
|
||||
/utils-merge/1.0.1:
|
||||
resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==}
|
||||
engines: {node: '>= 0.4.0'}
|
||||
dev: false
|
||||
|
||||
/vary/1.1.2:
|
||||
resolution: {integrity: sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=}
|
||||
engines: {node: '>= 0.8'}
|
||||
dev: false
|
@ -1,58 +0,0 @@
|
||||
import path from "path";
|
||||
import express, { Request, Response } from "express";
|
||||
import compression from "compression";
|
||||
import { createProxyMiddleware, Options } from "http-proxy-middleware";
|
||||
|
||||
const dist = `../../dist`;
|
||||
|
||||
// This contains a list of static routes (assets)
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const { ssr } = require(`${dist}/server/package.json`);
|
||||
|
||||
// The manifest is required for preloading assets
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const manifest = require(`${dist}/client/ssr-manifest.json`);
|
||||
|
||||
// This is the server renderer we just built
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const { default: renderPage } = require(`${dist}/server`);
|
||||
|
||||
// vite config
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const proxyConfig = require(`${dist}/../proxy.config.ts`).default;
|
||||
|
||||
const server = express();
|
||||
|
||||
// gzip is cool
|
||||
server.use(compression());
|
||||
|
||||
// Serve every static asset route
|
||||
for (const asset of ssr.assets || []) {
|
||||
server.use("/" + asset, express.static(path.join(__dirname, `${dist}/client/` + asset)));
|
||||
}
|
||||
|
||||
// proxy
|
||||
for (const proxy of Object.keys(proxyConfig)) {
|
||||
server.use(createProxyMiddleware(proxy, {target: proxyConfig[proxy] } as Options))
|
||||
}
|
||||
|
||||
// main
|
||||
server.get("*", async (request: Request, response: Response) => {
|
||||
const url = request.protocol + "://" + request.get("host") + request.originalUrl;
|
||||
|
||||
const { html, status, statusText, headers } = await renderPage(url, {
|
||||
manifest,
|
||||
preload: true,
|
||||
request,
|
||||
response,
|
||||
});
|
||||
|
||||
response.contentType("text/html");
|
||||
response.setHeader("X-Powered-By", "HangarSSR");
|
||||
response.writeHead(status || 200, statusText || headers, headers);
|
||||
response.end(html);
|
||||
});
|
||||
|
||||
const port = 1337;
|
||||
console.log(`Server started: http://localhost:${port}`);
|
||||
server.listen(port);
|
@ -1,16 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"esModuleInterop": true,
|
||||
"target": "es6",
|
||||
"noImplicitAny": true,
|
||||
"moduleResolution": "node",
|
||||
"sourceMap": true,
|
||||
"outDir": "dist",
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"*": ["node_modules/*"]
|
||||
}
|
||||
},
|
||||
"include": ["src/**/*"]
|
||||
}
|
@ -1,30 +1,31 @@
|
||||
<script setup lang="ts">
|
||||
import { useSettingsStore } from "~/store/useSettingsStore";
|
||||
import "./lib/styles/main.css";
|
||||
import { useHead } from "@vueuse/head";
|
||||
import { computed } from "vue";
|
||||
import { useSettingsStore } from "~/store/useSettingsStore";
|
||||
import { settingsLog } from "~/lib/composables/useLog";
|
||||
import { useAuthStore } from "~/store/auth";
|
||||
|
||||
// eslint-disable-next-line import/no-unresolved
|
||||
import "virtual:windi-devtools";
|
||||
import { computed } from "vue";
|
||||
import { useBackendDataStore } from "~/store/backendData";
|
||||
import "regenerator-runtime/runtime"; // popper needs this?
|
||||
|
||||
const authStore = useAuthStore();
|
||||
const settingsStore = useSettingsStore();
|
||||
settingsStore.loadSettingsClient();
|
||||
settingsStore.setupMobile();
|
||||
await useBackendDataStore().initBackendData();
|
||||
settingsLog("render for user", authStore.user?.name, "with darkmode", settingsStore.darkMode);
|
||||
useHead({
|
||||
htmlAttrs: {
|
||||
class: computed(() => (settingsStore.darkMode ? "dark" : "light")),
|
||||
},
|
||||
bodyAttrs: {
|
||||
class: "background-body text-[#262626] dark:text-[#E0E6f0]",
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<router-view v-slot="{ Component }">
|
||||
<Suspense>
|
||||
<component :is="Component" />
|
||||
<template #fallback> Loading... </template>
|
||||
</Suspense>
|
||||
</router-view>
|
||||
<NuxtLayout>
|
||||
<NuxtPage />
|
||||
</NuxtLayout>
|
||||
</template>
|
@ -1,11 +1,11 @@
|
||||
<script lang="ts" setup>
|
||||
import { useContext } from "vite-ssr/vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { useRoute } from "vue-router";
|
||||
import { useResolvedFlags, useUnresolvedFlags } from "~/composables/useApiHelper";
|
||||
import { handleRequestError } from "~/composables/useErrorHandling";
|
||||
import { ref } from "vue";
|
||||
import { Flag, HangarFlagNotification } from "hangar-internal";
|
||||
import { PaginatedResult } from "hangar-api";
|
||||
import { useResolvedFlags, useUnresolvedFlags } from "~/composables/useApiHelper";
|
||||
import { handleRequestError } from "~/composables/useErrorHandling";
|
||||
import { useInternalApi } from "~/composables/useApi";
|
||||
import UserAvatar from "~/components/UserAvatar.vue";
|
||||
import Card from "~/lib/components/design/Card.vue";
|
||||
@ -13,26 +13,24 @@ import Button from "~/lib/components/design/Button.vue";
|
||||
import VisibilityChangerModal from "~/components/modals/VisibilityChangerModal.vue";
|
||||
import ReportNotificationModal from "~/components/modals/ReportNotificationModal.vue";
|
||||
import Pagination from "~/lib/components/design/Pagination.vue";
|
||||
import { PaginatedResult } from "hangar-api";
|
||||
|
||||
const props = defineProps<{
|
||||
resolved: boolean;
|
||||
}>();
|
||||
|
||||
const ctx = useContext();
|
||||
const i18n = useI18n();
|
||||
const route = useRoute();
|
||||
const flags = await (props.resolved ? useResolvedFlags() : useUnresolvedFlags()).catch((e) => handleRequestError(e, ctx, i18n));
|
||||
const flags = await (props.resolved ? useResolvedFlags() : useUnresolvedFlags()).catch((e) => handleRequestError(e, i18n));
|
||||
const loading = ref<{ [key: number]: boolean }>({});
|
||||
|
||||
function resolve(flag: Flag) {
|
||||
loading.value[flag.id] = true;
|
||||
useInternalApi(`flags/${flag.id}/resolve/${props.resolved ? "false" : "true"}`, false, "POST")
|
||||
.catch<any>((e) => handleRequestError(e, ctx, i18n))
|
||||
.catch<any>((e) => handleRequestError(e, i18n))
|
||||
.then(async () => {
|
||||
if (flags && flags.value) {
|
||||
const newFlags = await useInternalApi<PaginatedResult<Flag>>("flags/" + (props.resolved ? "resolved" : "unresolved"), false).catch((e) =>
|
||||
handleRequestError(e, ctx, i18n)
|
||||
handleRequestError(e, i18n)
|
||||
);
|
||||
if (newFlags) {
|
||||
flags.value = newFlags;
|
||||
@ -44,7 +42,7 @@ function resolve(flag: Flag) {
|
||||
});
|
||||
}
|
||||
|
||||
//TODO: bake into hangarflag?
|
||||
// TODO: bake into hangarflag?
|
||||
const notifications = ref<HangarFlagNotification[]>([]);
|
||||
const currentId = ref(-1);
|
||||
async function getNotifications(flag: Flag) {
|
||||
@ -53,7 +51,7 @@ async function getNotifications(flag: Flag) {
|
||||
}
|
||||
|
||||
notifications.value = (await useInternalApi<HangarFlagNotification[]>(`flags/${flag.id}/notifications`, false, "get").catch((e) =>
|
||||
handleRequestError(e, ctx, i18n)
|
||||
handleRequestError(e, i18n)
|
||||
)) as HangarFlagNotification[];
|
||||
currentId.value = flag.id;
|
||||
}
|
||||
|
@ -1,12 +1,10 @@
|
||||
<script lang="ts" setup>
|
||||
import { computed, nextTick, ref, watch } from "vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { useInternalApi } from "~/composables/useApi";
|
||||
import { handleRequestError } from "~/composables/useErrorHandling";
|
||||
import { useContext } from "vite-ssr/vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import Spinner from "~/lib/components/design/Spinner.vue";
|
||||
|
||||
const ctx = useContext();
|
||||
const i18n = useI18n();
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
@ -28,7 +26,7 @@ async function fetch() {
|
||||
loading.value = true;
|
||||
renderedMarkdown.value = await useInternalApi<string>("pages/render", false, "post", {
|
||||
content: props.raw,
|
||||
}).catch<any>((e) => handleRequestError(e, ctx, i18n));
|
||||
}).catch<any>((e) => handleRequestError(e, i18n));
|
||||
loading.value = false;
|
||||
if (!import.meta.env.SSR) {
|
||||
await nextTick(setupAdmonition);
|
||||
@ -100,6 +98,6 @@ function setupAdmonition() {
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
@import "/src/lib/assets/css/admonition.css";
|
||||
@import "/src/lib/assets/css/markdown.scss";
|
||||
@import "@/lib/assets/css/admonition.css";
|
||||
@import "@/lib/assets/css/markdown.scss";
|
||||
</style>
|
||||
|
@ -1,10 +1,10 @@
|
||||
<script lang="ts" setup>
|
||||
import { reactive, ref, watch } from "vue";
|
||||
import { Pagination } from "hangar-api";
|
||||
import { hasSlotContent } from "~/lib/composables/useSlot";
|
||||
import Table from "~/lib/components/design/Table.vue";
|
||||
import { reactive, ref, watch } from "vue";
|
||||
import PaginationButtons from "~/lib/components/design/PaginationButtons.vue";
|
||||
import PaginationComponent from "~/lib/components/design/Pagination.vue";
|
||||
import { Pagination } from "hangar-api";
|
||||
|
||||
export interface Header {
|
||||
name: string;
|
||||
|
@ -1,16 +1,16 @@
|
||||
<script lang="ts" setup>
|
||||
import { computed } from "vue";
|
||||
|
||||
const props = defineProps<{
|
||||
name?: string;
|
||||
color?: Color;
|
||||
}>();
|
||||
|
||||
interface Color {
|
||||
foreground?: string;
|
||||
background?: string;
|
||||
}
|
||||
|
||||
const props = defineProps<{
|
||||
name?: string;
|
||||
color?: Color;
|
||||
}>();
|
||||
|
||||
const ccColor = computed(() => {
|
||||
if (props.color?.foreground) {
|
||||
return props.color;
|
||||
|
@ -25,10 +25,17 @@ const props = withDefaults(
|
||||
const errored = ref(false);
|
||||
|
||||
const sizeClass = computed(() => {
|
||||
if (props.size == "xs") return "w-32px h-32px";
|
||||
else if (props.size == "sm") return "w-50px h-50px";
|
||||
else if (props.size == "md") return "w-75px h-75px";
|
||||
else if (props.size == "lg") return "w-100px h-100px";
|
||||
switch (props.size) {
|
||||
case "xs":
|
||||
return "w-32px h-32px";
|
||||
case "sm":
|
||||
return "w-50px h-50px";
|
||||
case "md":
|
||||
return "w-75px h-75px";
|
||||
case "lg":
|
||||
return "w-100px h-100px";
|
||||
// No default
|
||||
}
|
||||
|
||||
return "w-200px h-200px";
|
||||
});
|
||||
|
@ -1,12 +1,12 @@
|
||||
<script lang="ts" setup>
|
||||
import { Organization } from "hangar-internal";
|
||||
import { User } from "hangar-api";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { computed } from "vue";
|
||||
import UserAvatar from "~/components/UserAvatar.vue";
|
||||
import { avatarUrl, forumUserUrl } from "~/composables/useUrlHelper";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import Card from "~/lib/components/design/Card.vue";
|
||||
import TaglineModal from "~/components/modals/TaglineModal.vue";
|
||||
import { computed } from "vue";
|
||||
import { NamedPermission } from "~/types/enums";
|
||||
import { hasPerms } from "~/composables/usePerm";
|
||||
import { useAuthStore } from "~/store/auth";
|
||||
|
@ -1,6 +1,8 @@
|
||||
<script setup lang="ts">
|
||||
import { Popover, PopoverButton, PopoverPanel } from "@headlessui/vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { HangarNotification } from "hangar-internal";
|
||||
import { ref } from "vue";
|
||||
import { useSettingsStore } from "~/store/useSettingsStore";
|
||||
import Announcement from "~/components/Announcement.vue";
|
||||
import DropdownButton from "~/lib/components/design/DropdownButton.vue";
|
||||
@ -37,18 +39,14 @@ import UserAvatar from "~/components/UserAvatar.vue";
|
||||
import Button from "~/lib/components/design/Button.vue";
|
||||
import { useRecentNotifications, useUnreadNotificationsCount } from "~/composables/useApiHelper";
|
||||
import { handleRequestError } from "~/composables/useErrorHandling";
|
||||
import { HangarNotification } from "hangar-internal";
|
||||
import { useContext } from "vite-ssr/vue";
|
||||
import { ref } from "vue";
|
||||
import Link from "~/lib/components/design/Link.vue";
|
||||
import { useInternalApi } from "~/composables/useApi";
|
||||
import { useConfig } from "~/lib/composables/useConfig";
|
||||
|
||||
const settings = useSettingsStore();
|
||||
const { t } = useI18n();
|
||||
const backendData = useBackendDataStore();
|
||||
const ctx = useContext();
|
||||
const i18n = useI18n();
|
||||
const t = i18n.t;
|
||||
const authStore = useAuthStore();
|
||||
|
||||
const notifications = ref<HangarNotification[]>([]);
|
||||
@ -63,7 +61,7 @@ if (authStore.user) {
|
||||
unreadNotifications.value = v.value;
|
||||
}
|
||||
});
|
||||
useRecentNotifications(true, 30)
|
||||
useRecentNotifications(30)
|
||||
.then((v) => {
|
||||
if (v && v.value) {
|
||||
// Only show notifications that are recent or unread (from the last 30 notifications)
|
||||
@ -81,7 +79,7 @@ if (authStore.user) {
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch((e) => handleRequestError(e, ctx, i18n));
|
||||
.catch((e) => handleRequestError(e, i18n));
|
||||
|
||||
if (hasPerms(NamedPermission.MOD_NOTES_AND_FLAGS)) {
|
||||
useInternalApi<number>("admin/approval/projectneedingapproval", false)
|
||||
@ -90,21 +88,21 @@ if (authStore.user) {
|
||||
projectApprovalQueue.value = v;
|
||||
}
|
||||
})
|
||||
.catch((e) => handleRequestError(e, ctx, i18n));
|
||||
.catch((e) => handleRequestError(e, i18n));
|
||||
useInternalApi<number>("admin/approval/versionsneedingapproval", false)
|
||||
.then((v) => {
|
||||
if (v) {
|
||||
versionApprovalQueue.value = v;
|
||||
}
|
||||
})
|
||||
.catch((e) => handleRequestError(e, ctx, i18n));
|
||||
.catch((e) => handleRequestError(e, i18n));
|
||||
useInternalApi<number>("flags/unresolvedamount", false)
|
||||
.then((v) => {
|
||||
if (v) {
|
||||
reportQueue.value = v;
|
||||
}
|
||||
})
|
||||
.catch((e) => handleRequestError(e, ctx, i18n));
|
||||
.catch((e) => handleRequestError(e, i18n));
|
||||
}
|
||||
}
|
||||
|
||||
@ -147,12 +145,12 @@ function markNotificationsRead() {
|
||||
}
|
||||
}
|
||||
|
||||
async function markNotificationRead(notification: HangarNotification) {
|
||||
function markNotificationRead(notification: HangarNotification) {
|
||||
if (!notification.read) {
|
||||
notification.read = true;
|
||||
unreadNotifications.value--;
|
||||
loadedUnreadNotifications.value--;
|
||||
useInternalApi(`notifications/${notification.id}`, true, "post").catch((e) => handleRequestError(e, ctx, i18n));
|
||||
useInternalApi(`notifications/${notification.id}`, true, "post").catch((e) => handleRequestError(e, i18n));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,15 +1,14 @@
|
||||
<script lang="ts" setup>
|
||||
import { useI18n } from "vue-i18n";
|
||||
import Button from "~/lib/components/design/Button.vue";
|
||||
import Modal from "~/lib/components/modals/Modal.vue";
|
||||
import { ProjectChannel } from "hangar-internal";
|
||||
import { computed, reactive, ref } from "vue";
|
||||
import { useVuelidate } from "@vuelidate/core";
|
||||
import Button from "~/lib/components/design/Button.vue";
|
||||
import Modal from "~/lib/components/modals/Modal.vue";
|
||||
import { useBackendDataStore } from "~/store/backendData";
|
||||
import { useContext } from "vite-ssr/vue";
|
||||
import InputText from "~/lib/components/ui/InputText.vue";
|
||||
import { required } from "~/lib/composables/useValidationHelpers";
|
||||
import { validChannelName, validChannelColor } from "~/composables/useHangarValidations";
|
||||
import { useVuelidate } from "@vuelidate/core";
|
||||
import InputCheckbox from "~/lib/components/ui/InputCheckbox.vue";
|
||||
import { ChannelFlag } from "~/types/enums";
|
||||
|
||||
@ -23,7 +22,6 @@ const emit = defineEmits<{
|
||||
}>();
|
||||
|
||||
const i18n = useI18n();
|
||||
const ctx = useContext();
|
||||
const v = useVuelidate();
|
||||
|
||||
const frozen = props.channel && props.channel.flags.includes(ChannelFlag.FROZEN);
|
||||
|
@ -1,19 +1,18 @@
|
||||
<script lang="ts" setup>
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { HangarProject, HangarVersion } from "hangar-internal";
|
||||
import { computed, onMounted, ref, watch } from "vue";
|
||||
import { useRoute, useRouter } from "vue-router";
|
||||
import { DependencyVersion, PluginDependency } from "hangar-api";
|
||||
import { cloneDeep } from "lodash-es";
|
||||
import { hasPerms } from "~/composables/usePerm";
|
||||
import Button from "~/lib/components/design/Button.vue";
|
||||
import Modal from "~/lib/components/modals/Modal.vue";
|
||||
import { HangarProject, HangarVersion } from "hangar-internal";
|
||||
import { useContext } from "vite-ssr/vue";
|
||||
import { computed, onMounted, ref, watch } from "vue";
|
||||
import { NamedPermission, Platform } from "~/types/enums";
|
||||
import { useBackendDataStore } from "~/store/backendData";
|
||||
import { useRoute, useRouter } from "vue-router";
|
||||
import { handleRequestError } from "~/composables/useErrorHandling";
|
||||
import { useInternalApi } from "~/composables/useApi";
|
||||
import DependencyTable from "~/components/projects/DependencyTable.vue";
|
||||
import { DependencyVersion, PluginDependency } from "hangar-api";
|
||||
import { cloneDeep } from "lodash-es";
|
||||
|
||||
const props = defineProps<{
|
||||
project: HangarProject;
|
||||
@ -21,7 +20,6 @@ const props = defineProps<{
|
||||
}>();
|
||||
|
||||
const i18n = useI18n();
|
||||
const ctx = useContext();
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const backendData = useBackendDataStore();
|
||||
@ -72,7 +70,7 @@ async function save() {
|
||||
});
|
||||
await router.go(0);
|
||||
} catch (e) {
|
||||
handleRequestError(e, ctx, i18n);
|
||||
handleRequestError(e, i18n);
|
||||
}
|
||||
loading.value = false;
|
||||
}
|
||||
|
@ -1,8 +1,8 @@
|
||||
<script lang="ts" setup>
|
||||
import { TranslateResult, useI18n } from "vue-i18n";
|
||||
import { type TranslateResult, useI18n } from "vue-i18n";
|
||||
import { DIFF_DELETE, DIFF_EQUAL, DIFF_INSERT, diff_match_patch as Diff } from "diff-match-patch";
|
||||
import Modal from "~/lib/components/modals/Modal.vue";
|
||||
import { computed } from "vue";
|
||||
import Modal from "~/lib/components/modals/Modal.vue";
|
||||
|
||||
const props = defineProps<{
|
||||
title: string | TranslateResult;
|
||||
|
@ -1,19 +1,18 @@
|
||||
<script lang="ts" setup>
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { useRouter } from "vue-router";
|
||||
import { ref } from "vue";
|
||||
import { HangarProject } from "hangar-internal";
|
||||
import { AxiosError } from "axios";
|
||||
import Button from "~/lib/components/design/Button.vue";
|
||||
import Modal from "~/lib/components/modals/Modal.vue";
|
||||
import Tooltip from "~/lib/components/design/Tooltip.vue";
|
||||
import { useContext } from "vite-ssr/vue";
|
||||
import { useRouter } from "vue-router";
|
||||
import InputRadio from "~/lib/components/ui/InputRadio.vue";
|
||||
import { useBackendDataStore } from "~/store/backendData";
|
||||
import { ref } from "vue";
|
||||
import InputTextarea from "~/lib/components/ui/InputTextarea.vue";
|
||||
import { required } from "~/lib/composables/useValidationHelpers";
|
||||
import { useInternalApi } from "~/composables/useApi";
|
||||
import { HangarProject } from "hangar-internal";
|
||||
import { handleRequestError } from "~/composables/useErrorHandling";
|
||||
import { AxiosError } from "axios";
|
||||
import { useNotificationStore } from "~/lib/store/notification";
|
||||
|
||||
const props = defineProps<{
|
||||
@ -22,7 +21,6 @@ const props = defineProps<{
|
||||
}>();
|
||||
|
||||
const i18n = useI18n();
|
||||
const ctx = useContext();
|
||||
const router = useRouter();
|
||||
const backendData = useBackendDataStore();
|
||||
|
||||
@ -40,7 +38,7 @@ async function submit(close: () => void) {
|
||||
useNotificationStore().success(i18n.t("project.flag.flagSend"));
|
||||
await router.go(0);
|
||||
} catch (e) {
|
||||
handleRequestError(e as AxiosError, ctx, i18n);
|
||||
handleRequestError(e as AxiosError, i18n);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
@ -1,15 +1,14 @@
|
||||
<script lang="ts" setup>
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { AxiosError } from "axios";
|
||||
import { User } from "hangar-api";
|
||||
import { ref } from "vue";
|
||||
import { useRouter } from "vue-router";
|
||||
import Button from "~/lib/components/design/Button.vue";
|
||||
import Modal from "~/lib/components/modals/Modal.vue";
|
||||
import { useInternalApi } from "~/composables/useApi";
|
||||
import { handleRequestError } from "~/composables/useErrorHandling";
|
||||
import { AxiosError } from "axios";
|
||||
import Tooltip from "~/lib/components/design/Tooltip.vue";
|
||||
import { useContext } from "vite-ssr/vue";
|
||||
import { User } from "hangar-api";
|
||||
import { ref } from "vue";
|
||||
import { useRouter } from "vue-router";
|
||||
import InputTextarea from "~/lib/components/ui/InputTextarea.vue";
|
||||
import { useNotificationStore } from "~/lib/store/notification";
|
||||
|
||||
@ -18,7 +17,6 @@ const props = defineProps<{
|
||||
}>();
|
||||
|
||||
const i18n = useI18n();
|
||||
const ctx = useContext();
|
||||
const router = useRouter();
|
||||
|
||||
const comment = ref<string>("");
|
||||
@ -32,7 +30,7 @@ async function confirm(close: () => void) {
|
||||
router.go(0);
|
||||
useNotificationStore().success(i18n.t(`author.lock.success${props.user.locked ? "Unlock" : "Lock"}`, [props.user.name]));
|
||||
} catch (e) {
|
||||
handleRequestError(e as AxiosError, ctx, i18n);
|
||||
handleRequestError(e as AxiosError, i18n);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
@ -1,5 +1,5 @@
|
||||
<script lang="ts" setup>
|
||||
import { TranslateResult, useI18n } from "vue-i18n";
|
||||
import { type TranslateResult, useI18n } from "vue-i18n";
|
||||
import Markdown from "~/components/Markdown.vue";
|
||||
import Modal from "~/lib/components/modals/Modal.vue";
|
||||
|
||||
|
@ -1,9 +1,8 @@
|
||||
<script lang="ts" setup>
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { useRouter } from "vue-router";
|
||||
import Button from "~/lib/components/design/Button.vue";
|
||||
import Modal from "~/lib/components/modals/Modal.vue";
|
||||
import { useContext } from "vite-ssr/vue";
|
||||
import { useRouter } from "vue-router";
|
||||
import { useInternalApi } from "~/composables/useApi";
|
||||
import { handleRequestError } from "~/composables/useErrorHandling";
|
||||
|
||||
@ -20,15 +19,14 @@ const props = withDefaults(
|
||||
);
|
||||
|
||||
const i18n = useI18n();
|
||||
const ctx = useContext();
|
||||
const router = useRouter();
|
||||
const name = props.organization ? props.author : props.slug;
|
||||
|
||||
async function leave() {
|
||||
function leave() {
|
||||
const url = props.organization ? `organizations/org/${props.author}/members/leave` : `projects/project/${props.author}/${props.slug}/members/leave`;
|
||||
useInternalApi(url, true, "post")
|
||||
.then(() => router.go(0))
|
||||
.catch((e) => handleRequestError(e, ctx, i18n));
|
||||
.catch((e) => handleRequestError(e, i18n));
|
||||
}
|
||||
</script>
|
||||
|
||||
|
@ -1,17 +1,16 @@
|
||||
<script lang="ts" setup>
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { HangarProjectPage } from "hangar-internal";
|
||||
import { computed, inject, ref, watch } from "vue";
|
||||
import { AxiosError } from "axios";
|
||||
import { useRoute, useRouter } from "vue-router";
|
||||
import Button from "~/lib/components/design/Button.vue";
|
||||
import Modal from "~/lib/components/modals/Modal.vue";
|
||||
import { useBackendDataStore } from "~/store/backendData";
|
||||
import { useContext } from "vite-ssr/vue";
|
||||
import { HangarProjectPage } from "hangar-internal";
|
||||
import { computed, inject, ref, watch } from "vue";
|
||||
import InputText from "~/lib/components/ui/InputText.vue";
|
||||
import InputSelect, { Option } from "~/lib/components/ui/InputSelect.vue";
|
||||
import { useInternalApi } from "~/composables/useApi";
|
||||
import { handleRequestError } from "~/composables/useErrorHandling";
|
||||
import { AxiosError } from "axios";
|
||||
import { useRoute, useRouter } from "vue-router";
|
||||
|
||||
const props = defineProps<{
|
||||
projectId: number;
|
||||
@ -19,7 +18,6 @@ const props = defineProps<{
|
||||
}>();
|
||||
|
||||
const i18n = useI18n();
|
||||
const ctx = useContext();
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const backendData = useBackendDataStore();
|
||||
@ -45,7 +43,7 @@ watch(name, async () => {
|
||||
})
|
||||
.catch((err: AxiosError) => {
|
||||
if (!err.response?.data.isHangarApiException) {
|
||||
return handleRequestError(err, ctx, i18n);
|
||||
return handleRequestError(err, i18n);
|
||||
}
|
||||
nameErrorMessages.value.push(i18n.t(err.response.data.message));
|
||||
})
|
||||
@ -79,7 +77,7 @@ async function createPage() {
|
||||
|
||||
await router.push(`/${route.params.user}/${route.params.project}/pages/${slug}`);
|
||||
} catch (e) {
|
||||
handleRequestError(e, ctx, i18n);
|
||||
handleRequestError(e, i18n);
|
||||
}
|
||||
loading.value = false;
|
||||
|
||||
|
@ -1,10 +1,9 @@
|
||||
<script lang="ts" setup>
|
||||
import { useI18n } from "vue-i18n";
|
||||
import Button from "~/lib/components/design/Button.vue";
|
||||
import Modal from "~/lib/components/modals/Modal.vue";
|
||||
import { useContext } from "vite-ssr/vue";
|
||||
import { useRouter } from "vue-router";
|
||||
import { ref } from "vue";
|
||||
import Button from "~/lib/components/design/Button.vue";
|
||||
import Modal from "~/lib/components/modals/Modal.vue";
|
||||
import InputTextarea from "~/lib/components/ui/InputTextarea.vue";
|
||||
import { useInternalApi } from "~/composables/useApi";
|
||||
import { handleRequestError } from "~/composables/useErrorHandling";
|
||||
@ -14,7 +13,6 @@ const props = defineProps<{
|
||||
}>();
|
||||
|
||||
const i18n = useI18n();
|
||||
const ctx = useContext();
|
||||
const router = useRouter();
|
||||
|
||||
const comment = ref<string>("");
|
||||
@ -24,7 +22,7 @@ async function deleteOrg() {
|
||||
loading.value = true;
|
||||
await useInternalApi(`organizations/org/${props.organization}/delete`, true, "post", {
|
||||
content: comment.value,
|
||||
}).catch((e) => handleRequestError(e, ctx, i18n));
|
||||
}).catch((e) => handleRequestError(e, i18n));
|
||||
await router.push("/");
|
||||
loading.value = false;
|
||||
}
|
||||
|
@ -1,14 +1,13 @@
|
||||
<script lang="ts" setup>
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { useRouter } from "vue-router";
|
||||
import { ref } from "vue";
|
||||
import { PaginatedResult, User } from "hangar-api";
|
||||
import Button from "~/lib/components/design/Button.vue";
|
||||
import Modal from "~/lib/components/modals/Modal.vue";
|
||||
import { useContext } from "vite-ssr/vue";
|
||||
import { useRouter } from "vue-router";
|
||||
import { useApi, useInternalApi } from "~/composables/useApi";
|
||||
import { handleRequestError } from "~/composables/useErrorHandling";
|
||||
import InputAutocomplete from "~/lib/components/ui/InputAutocomplete.vue";
|
||||
import { ref } from "vue";
|
||||
import { PaginatedResult, User } from "hangar-api";
|
||||
import { useNotificationStore } from "~/lib/store/notification";
|
||||
|
||||
const props = defineProps<{
|
||||
@ -16,7 +15,6 @@ const props = defineProps<{
|
||||
}>();
|
||||
|
||||
const i18n = useI18n();
|
||||
const ctx = useContext();
|
||||
const router = useRouter();
|
||||
const notificationStore = useNotificationStore();
|
||||
|
||||
@ -37,7 +35,7 @@ async function transfer() {
|
||||
loading.value = true;
|
||||
await useInternalApi<string>(`organizations/org/${props.organization}/transfer`, true, "post", {
|
||||
content: search.value,
|
||||
}).catch((e) => handleRequestError(e, ctx, i18n));
|
||||
}).catch((e) => handleRequestError(e, i18n));
|
||||
notificationStore.success(i18n.t("organization.settings.success.transferRequest", [search.value]));
|
||||
loading.value = false;
|
||||
}
|
||||
|
@ -1,11 +1,10 @@
|
||||
<script lang="ts" setup>
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { computed, ref } from "vue";
|
||||
import Markdown from "~/components/Markdown.vue";
|
||||
import Button from "~/lib/components/design/Button.vue";
|
||||
import Modal from "~/lib/components/modals/Modal.vue";
|
||||
import { useInternalApi } from "~/composables/useApi";
|
||||
import { computed, ref } from "vue";
|
||||
import { useContext } from "vite-ssr/vue";
|
||||
import { handleRequestError } from "~/composables/useErrorHandling";
|
||||
import InputCheckbox from "~/lib/components/ui/InputCheckbox.vue";
|
||||
|
||||
@ -14,7 +13,6 @@ const props = defineProps<{
|
||||
}>();
|
||||
|
||||
const i18n = useI18n();
|
||||
const ctx = useContext();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: "update:modelValue", value: { [key: string]: boolean }): void;
|
||||
@ -31,7 +29,7 @@ async function changeOrgVisibility(org: string) {
|
||||
loading.value = true;
|
||||
await useInternalApi<{ [key: string]: boolean }>(`organizations/${org}/userOrganizationsVisibility`, true, "POST", internalVisibility.value[org] as any, {
|
||||
"Content-Type": "application/json",
|
||||
}).catch((e) => handleRequestError(e, ctx, i18n));
|
||||
}).catch((e) => handleRequestError(e, i18n));
|
||||
loading.value = false;
|
||||
}
|
||||
</script>
|
||||
|
@ -1,13 +1,12 @@
|
||||
<script lang="ts" setup>
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { HangarProject, HangarVersion } from "hangar-internal";
|
||||
import { computed, ref } from "vue";
|
||||
import { useRoute, useRouter } from "vue-router";
|
||||
import Button from "~/lib/components/design/Button.vue";
|
||||
import Modal from "~/lib/components/modals/Modal.vue";
|
||||
import { HangarProject, HangarVersion } from "hangar-internal";
|
||||
import { useContext } from "vite-ssr/vue";
|
||||
import { computed, ref } from "vue";
|
||||
import { Platform } from "~/types/enums";
|
||||
import { useBackendDataStore } from "~/store/backendData";
|
||||
import { useRoute, useRouter } from "vue-router";
|
||||
import InputCheckbox from "~/lib/components/ui/InputCheckbox.vue";
|
||||
import { handleRequestError } from "~/composables/useErrorHandling";
|
||||
import { useInternalApi } from "~/composables/useApi";
|
||||
@ -19,7 +18,6 @@ const props = defineProps<{
|
||||
}>();
|
||||
|
||||
const i18n = useI18n();
|
||||
const ctx = useContext();
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const backendData = useBackendDataStore();
|
||||
@ -34,13 +32,13 @@ const projectVersion = computed(() => {
|
||||
const loading = ref(false);
|
||||
const selectedVersions = ref(projectVersion.value?.platformDependencies[platform.value?.name.toUpperCase() as Platform]);
|
||||
|
||||
async function save() {
|
||||
function save() {
|
||||
loading.value = true;
|
||||
useInternalApi(`versions/version/${props.project.id}/${projectVersion.value?.id}/savePlatformVersions`, true, "post", {
|
||||
platform: platform.value?.name?.toUpperCase(),
|
||||
versions: selectedVersions.value,
|
||||
})
|
||||
.catch((e) => handleRequestError(e, ctx, i18n))
|
||||
.catch((e) => handleRequestError(e, i18n))
|
||||
.then(async () => {
|
||||
await router.go(0);
|
||||
})
|
||||
|
@ -1,19 +1,18 @@
|
||||
<script lang="ts" setup>
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { computed, ref } from "vue";
|
||||
import { useRouter } from "vue-router";
|
||||
import { Flag } from "hangar-internal";
|
||||
import { Project } from "hangar-api";
|
||||
import Button from "~/lib/components/design/Button.vue";
|
||||
import Modal from "~/lib/components/modals/Modal.vue";
|
||||
import { Visibility } from "~/types/enums";
|
||||
import InputRadio from "~/lib/components/ui/InputRadio.vue";
|
||||
import { useBackendDataStore } from "~/store/backendData";
|
||||
import { computed, ref } from "vue";
|
||||
import InputTextarea from "~/lib/components/ui/InputTextarea.vue";
|
||||
import { useInternalApi } from "~/composables/useApi";
|
||||
import { handleRequestError } from "~/composables/useErrorHandling";
|
||||
import { useContext } from "vite-ssr/vue";
|
||||
import { useNotificationStore } from "~/lib/store/notification";
|
||||
import { useRouter } from "vue-router";
|
||||
import { Flag } from "hangar-internal";
|
||||
import { Project } from "hangar-api";
|
||||
import InputCheckbox from "~/lib/components/ui/InputCheckbox.vue";
|
||||
|
||||
const props = defineProps<{
|
||||
@ -22,7 +21,6 @@ const props = defineProps<{
|
||||
}>();
|
||||
|
||||
const i18n = useI18n();
|
||||
const ctx = useContext();
|
||||
const backendData = useBackendDataStore();
|
||||
const notification = useNotificationStore();
|
||||
const router = useRouter();
|
||||
@ -35,7 +33,7 @@ async function submit() {
|
||||
warning: warning.value,
|
||||
toReporter: props.sendToReporter,
|
||||
content: content.value,
|
||||
}).catch((e) => handleRequestError(e, ctx, i18n));
|
||||
}).catch((e) => handleRequestError(e, i18n));
|
||||
content.value = "";
|
||||
router.go(0);
|
||||
}
|
||||
|
@ -1,14 +1,13 @@
|
||||
<script lang="ts" setup>
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { ref } from "vue";
|
||||
import { useRouter } from "vue-router";
|
||||
import Button from "~/lib/components/design/Button.vue";
|
||||
import Modal from "~/lib/components/modals/Modal.vue";
|
||||
import { ref } from "vue";
|
||||
import { useNotificationStore } from "~/lib/store/notification";
|
||||
import InputText from "~/lib/components/ui/InputText.vue";
|
||||
import { useInternalApi } from "~/composables/useApi";
|
||||
import { handleRequestError } from "~/composables/useErrorHandling";
|
||||
import { useContext } from "vite-ssr/vue";
|
||||
import { useRouter } from "vue-router";
|
||||
import { useBackendDataStore } from "~/store/backendData";
|
||||
|
||||
const props = defineProps<{
|
||||
@ -18,7 +17,6 @@ const props = defineProps<{
|
||||
|
||||
const newTagline = ref(props.tagline);
|
||||
|
||||
const ctx = useContext();
|
||||
const router = useRouter();
|
||||
const i18n = useI18n();
|
||||
const backendData = useBackendDataStore();
|
||||
@ -32,7 +30,7 @@ async function save() {
|
||||
});
|
||||
router.go(0);
|
||||
} catch (e) {
|
||||
handleRequestError(e, ctx, i18n);
|
||||
handleRequestError(e, i18n);
|
||||
}
|
||||
loading.value = false;
|
||||
}
|
||||
|
@ -1,17 +1,16 @@
|
||||
<script lang="ts" setup>
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { computed, ref } from "vue";
|
||||
import { useRouter } from "vue-router";
|
||||
import Button from "~/lib/components/design/Button.vue";
|
||||
import Modal from "~/lib/components/modals/Modal.vue";
|
||||
import { Visibility } from "~/types/enums";
|
||||
import InputRadio from "~/lib/components/ui/InputRadio.vue";
|
||||
import { useBackendDataStore } from "~/store/backendData";
|
||||
import { computed, ref } from "vue";
|
||||
import InputTextarea from "~/lib/components/ui/InputTextarea.vue";
|
||||
import { useInternalApi } from "~/composables/useApi";
|
||||
import { handleRequestError } from "~/composables/useErrorHandling";
|
||||
import { useContext } from "vite-ssr/vue";
|
||||
import { useNotificationStore } from "~/lib/store/notification";
|
||||
import { useRouter } from "vue-router";
|
||||
|
||||
const props = defineProps<{
|
||||
type: "project" | "version";
|
||||
@ -20,7 +19,6 @@ const props = defineProps<{
|
||||
}>();
|
||||
|
||||
const i18n = useI18n();
|
||||
const ctx = useContext();
|
||||
const backendData = useBackendDataStore();
|
||||
const notification = useNotificationStore();
|
||||
const router = useRouter();
|
||||
@ -36,7 +34,7 @@ async function submit(): Promise<void> {
|
||||
await useInternalApi(props.postUrl, true, "post", {
|
||||
visibility: visibility.value,
|
||||
comment: setVisibility.value?.showModal ? reason.value : null,
|
||||
}).catch((e) => handleRequestError(e, ctx, i18n));
|
||||
}).catch((e) => handleRequestError(e, i18n));
|
||||
reason.value = "";
|
||||
if (setVisibility.value) {
|
||||
notification.success(i18n.t("visibility.modal.success", [props.type, i18n.t(setVisibility.value?.title)]));
|
||||
|
@ -1,7 +1,7 @@
|
||||
<script lang="ts" setup>
|
||||
import { ProjectApproval } from "hangar-internal";
|
||||
import Alert from "~/lib/components/design/Alert.vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import Alert from "~/lib/components/design/Alert.vue";
|
||||
import Markdown from "~/components/Markdown.vue";
|
||||
import Link from "~/lib/components/design/Link.vue";
|
||||
import VisibilityChangerModal from "~/components/modals/VisibilityChangerModal.vue";
|
||||
|
@ -1,16 +1,16 @@
|
||||
<script lang="ts" setup>
|
||||
import { DependencyVersion, PaginatedResult, PluginDependency, Project, ProjectNamespace } from "hangar-api";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { computed, ref } from "vue";
|
||||
import { useRoute } from "vue-router";
|
||||
import { Platform } from "~/types/enums";
|
||||
import Table from "~/lib/components/design/Table.vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import Button from "~/lib/components/design/Button.vue";
|
||||
import InputCheckbox from "~/lib/components/ui/InputCheckbox.vue";
|
||||
import InputText from "~/lib/components/ui/InputText.vue";
|
||||
import { required } from "~/lib/composables/useValidationHelpers";
|
||||
import { computed, ref } from "vue";
|
||||
import InputAutocomplete from "~/lib/components/ui/InputAutocomplete.vue";
|
||||
import { useApi } from "~/composables/useApi";
|
||||
import { useRoute } from "vue-router";
|
||||
import Tabs, { Tab } from "~/lib/components/design/Tabs.vue";
|
||||
|
||||
const route = useRoute();
|
||||
@ -93,7 +93,7 @@ const selectedUploadTabs: Tab[] = [
|
||||
{ value: "url", header: "URL" },
|
||||
];
|
||||
|
||||
defineExpose({ dependencies, reset: reset });
|
||||
defineExpose({ dependencies, reset });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
@ -1,14 +1,14 @@
|
||||
<script lang="ts" setup>
|
||||
import Button from "~/lib/components/design/Button.vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { HangarProject, PinnedVersion } from "hangar-internal";
|
||||
import { computed } from "vue";
|
||||
import { PlatformVersionDownload } from "hangar-api";
|
||||
import Button from "~/lib/components/design/Button.vue";
|
||||
import { Platform } from "~/types/enums";
|
||||
import DropdownButton from "~/lib/components/design/DropdownButton.vue";
|
||||
import { useBackendDataStore } from "~/store/backendData";
|
||||
import DropdownItem from "~/lib/components/design/DropdownItem.vue";
|
||||
import PlatformLogo from "~/components/logos/platforms/PlatformLogo.vue";
|
||||
import { PlatformVersionDownload } from "hangar-api";
|
||||
|
||||
const i18n = useI18n();
|
||||
const backendData = useBackendDataStore();
|
||||
|
@ -2,6 +2,8 @@
|
||||
import { computed, handleError, ref, watch } from "vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { JoinableMember } from "hangar-internal";
|
||||
import { PaginatedResult, Role, User } from "hangar-api";
|
||||
import { useRoute, useRouter } from "vue-router";
|
||||
import { NamedPermission } from "~/types/enums";
|
||||
import Card from "~/lib/components/design/Card.vue";
|
||||
import UserAvatar from "~/components/UserAvatar.vue";
|
||||
@ -11,10 +13,7 @@ import DropdownItem from "~/lib/components/design/DropdownItem.vue";
|
||||
import { avatarUrl } from "~/composables/useUrlHelper";
|
||||
import { hasPerms } from "~/composables/usePerm";
|
||||
import { useBackendDataStore } from "~/store/backendData";
|
||||
import { PaginatedResult, Role, User } from "hangar-api";
|
||||
import { useRoute, useRouter } from "vue-router";
|
||||
import { useApi, useInternalApi } from "~/composables/useApi";
|
||||
import { useContext } from "vite-ssr/vue";
|
||||
import IconMdiClock from "~icons/mdi/clock";
|
||||
import Tooltip from "~/lib/components/design/Tooltip.vue";
|
||||
import InputAutocomplete from "~/lib/components/ui/InputAutocomplete.vue";
|
||||
@ -22,6 +21,11 @@ import { useAuthStore } from "~/store/auth";
|
||||
import MemberLeaveModal from "~/components/modals/MemberLeaveModal.vue";
|
||||
import { handleRequestError } from "~/composables/useErrorHandling";
|
||||
|
||||
interface EditableMember {
|
||||
name: string;
|
||||
roleId: number;
|
||||
}
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
members: JoinableMember[];
|
||||
@ -54,7 +58,6 @@ const i18n = useI18n();
|
||||
const store = useBackendDataStore();
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const ctx = useContext();
|
||||
const authStore = useAuthStore();
|
||||
const roles: Role[] = props.organization ? store.orgRoles : store.projectRoles;
|
||||
|
||||
@ -88,7 +91,7 @@ function cancelTransfer() {
|
||||
const url = props.organization ? `organizations/org/${props.author}/canceltransfer` : `projects/project/${props.author}/${props.slug}/canceltransfer`;
|
||||
useInternalApi(url, true, "post")
|
||||
.then(() => router.go(0))
|
||||
.catch((e) => handleRequestError(e, ctx, i18n))
|
||||
.catch((e) => handleRequestError(e, i18n))
|
||||
.finally(() => (saving.value = false));
|
||||
}
|
||||
|
||||
@ -104,7 +107,7 @@ function invite(member: string, role: Role) {
|
||||
return "";
|
||||
}
|
||||
|
||||
async function post(member: EditableMember, action: "edit" | "add" | "remove") {
|
||||
function post(member: EditableMember, action: "edit" | "add" | "remove") {
|
||||
addErrors.value = [];
|
||||
if (member.name.length === 0) {
|
||||
addErrors.value.push(i18n.t("general.error.nameEmpty"));
|
||||
@ -144,11 +147,6 @@ async function doSearch(val: string) {
|
||||
});
|
||||
result.value = users.result.filter((u) => !props.members.some((m) => m.user.name === u.name)).map((u) => u.name);
|
||||
}
|
||||
|
||||
interface EditableMember {
|
||||
name: string;
|
||||
roleId: number;
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
@ -1,12 +1,12 @@
|
||||
<script setup lang="ts">
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { Project } from "hangar-api";
|
||||
import Card from "~/lib/components/design/Card.vue";
|
||||
import Link from "~/lib/components/design/Link.vue";
|
||||
import UserAvatar from "~/components/UserAvatar.vue";
|
||||
import { projectIconUrl } from "~/composables/useUrlHelper";
|
||||
import { lastUpdated } from "~/lib/composables/useTime";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import Tooltip from "~/lib/components/design/Tooltip.vue";
|
||||
import { Project } from "hangar-api";
|
||||
import { Visibility } from "~/types/enums";
|
||||
import CategoryLogo from "~/components/logos/categories/CategoryLogo.vue";
|
||||
|
||||
|
@ -1,14 +1,15 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, ref, Ref } from "vue";
|
||||
import { computed, ref, type Ref } from "vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { HangarProject } from "hangar-internal";
|
||||
import { AxiosError } from "axios";
|
||||
import { useRouter } from "vue-router";
|
||||
import UserAvatar from "~/components/UserAvatar.vue";
|
||||
import Button from "~/lib/components/design/Button.vue";
|
||||
import Card from "~/lib/components/design/Card.vue";
|
||||
import { projectIconUrl } from "~/composables/useUrlHelper";
|
||||
import { HangarProject } from "hangar-internal";
|
||||
import { useInternalApi } from "~/composables/useApi";
|
||||
import { handleRequestError } from "~/composables/useErrorHandling";
|
||||
import { useContext } from "vite-ssr/vue";
|
||||
import Tooltip from "~/lib/components/design/Tooltip.vue";
|
||||
import { useAuthStore } from "~/store/auth";
|
||||
import { useNotificationStore } from "~/lib/store/notification";
|
||||
@ -17,11 +18,8 @@ import Alert from "~/lib/components/design/Alert.vue";
|
||||
import { hasPerms } from "~/composables/usePerm";
|
||||
import { NamedPermission, Platform, ReviewState, Visibility } from "~/types/enums";
|
||||
import Markdown from "~/components/Markdown.vue";
|
||||
import { AxiosError } from "axios";
|
||||
import { useRouter } from "vue-router";
|
||||
import DownloadButton from "~/components/projects/DownloadButton.vue";
|
||||
|
||||
const ctx = useContext();
|
||||
const i18n = useI18n();
|
||||
const router = useRouter();
|
||||
const notification = useNotificationStore();
|
||||
@ -54,7 +52,7 @@ function toggleState(route: string, completedKey: string, revokedKey: string, va
|
||||
|
||||
notification.success(i18n.t("project.actions." + (value.value ? completedKey : revokedKey)));
|
||||
})
|
||||
.catch((err) => handleRequestError(err, ctx, i18n, i18n.t(`project.error.${route}`)));
|
||||
.catch((err) => handleRequestError(err, i18n, i18n.t(`project.error.${route}`)));
|
||||
}
|
||||
|
||||
function toggleStar() {
|
||||
@ -71,7 +69,7 @@ async function sendForApproval() {
|
||||
notification.success(i18n.t("projectApproval.sendForApproval"));
|
||||
await router.go(0);
|
||||
} catch (e) {
|
||||
handleRequestError(e as AxiosError, ctx, i18n);
|
||||
handleRequestError(e as AxiosError, i18n);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from "vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { HangarProject } from "hangar-internal";
|
||||
import { forumUrl } from "~/composables/useUrlHelper";
|
||||
import Card from "~/lib/components/design/Card.vue";
|
||||
import Link from "~/lib/components/design/Link.vue";
|
||||
@ -8,7 +9,6 @@ import DropdownButton from "~/lib/components/design/DropdownButton.vue";
|
||||
import DropdownItem from "~/lib/components/design/DropdownItem.vue";
|
||||
import { hasPerms } from "~/composables/usePerm";
|
||||
import { NamedPermission } from "~/types/enums";
|
||||
import { HangarProject } from "hangar-internal";
|
||||
import DonationModal from "~/components/donation/DonationModal.vue";
|
||||
import VisibilityChangerModal from "~/components/modals/VisibilityChangerModal.vue";
|
||||
|
||||
|
@ -1,6 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { PaginatedResult, Project } from "hangar-api";
|
||||
import { PropType } from "vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import Pagination from "~/lib/components/design/Pagination.vue";
|
||||
import ProjectCard from "~/components/projects/ProjectCard.vue";
|
||||
|
@ -1,10 +1,10 @@
|
||||
<script setup lang="ts">
|
||||
import ProjectNavItem from "~/components/projects/ProjectNavItem.vue";
|
||||
import { computed } from "vue";
|
||||
import { hasPerms } from "~/composables/usePerm";
|
||||
import { NamedPermission } from "~/types/enums";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { HangarProject } from "hangar-internal";
|
||||
import ProjectNavItem from "~/components/projects/ProjectNavItem.vue";
|
||||
import { hasPerms } from "~/composables/usePerm";
|
||||
import { NamedPermission } from "~/types/enums";
|
||||
import { avatarUrl, linkout } from "~/composables/useUrlHelper";
|
||||
|
||||
const props = defineProps<{
|
||||
|
@ -12,7 +12,7 @@ const route = useRoute();
|
||||
|
||||
const selected = computed(() => {
|
||||
const routerPath = route.fullPath.endsWith("/") ? route.fullPath.substr(0, route.fullPath.length - 1) : route.fullPath;
|
||||
return routerPath == props.to;
|
||||
return routerPath === props.to;
|
||||
});
|
||||
|
||||
const clazz = computed(() => {
|
||||
|
@ -1,13 +1,13 @@
|
||||
<script setup lang="ts">
|
||||
import { HangarProject } from "hangar-internal";
|
||||
import Card from "~/lib/components/design/Card.vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { useRoute } from "vue-router";
|
||||
import Card from "~/lib/components/design/Card.vue";
|
||||
import { hasPerms } from "~/composables/usePerm";
|
||||
import { NamedPermission } from "~/types/enums";
|
||||
import NewPageModal from "~/components/modals/NewPageModal.vue";
|
||||
import TreeView from "~/lib/components/design/TreeView.vue";
|
||||
import Link from "~/lib/components/design/Link.vue";
|
||||
import { useRoute } from "vue-router";
|
||||
|
||||
const props = defineProps<{
|
||||
project: HangarProject;
|
||||
|
@ -2,11 +2,10 @@
|
||||
import { HangarProject, HangarProjectPage } from "hangar-internal";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { useRoute, useRouter } from "vue-router";
|
||||
import { useProjectPage } from "~/composables/useProjectPage";
|
||||
import { useContext } from "vite-ssr";
|
||||
import { useHead } from "@vueuse/head";
|
||||
import { useSeo } from "~/composables/useSeo";
|
||||
import { inject } from "vue";
|
||||
import { useProjectPage } from "~/composables/useProjectPage";
|
||||
import { useSeo } from "~/composables/useSeo";
|
||||
import { useInternalApi } from "~/composables/useApi";
|
||||
import { handleRequestError } from "~/composables/useErrorHandling";
|
||||
import { projectIconUrl } from "~/composables/useUrlHelper";
|
||||
@ -17,12 +16,11 @@ const props = defineProps<{
|
||||
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const ctx = useContext();
|
||||
const i18n = useI18n();
|
||||
|
||||
const updateProjectPages = inject<(pages: HangarProjectPage[]) => void>("updateProjectPages");
|
||||
|
||||
const { editingPage, changeEditingPage, page, savePage, deletePage } = await useProjectPage(route, router, ctx, i18n, props.project);
|
||||
const { editingPage, changeEditingPage, page, savePage, deletePage } = await useProjectPage(route, router, i18n, props.project);
|
||||
if (page) {
|
||||
const title = page.value?.name === "Home" ? props.project.name : page.value?.name + " | " + props.project.name;
|
||||
useHead(useSeo(title, props.project.description, route, projectIconUrl(props.project.namespace.owner, props.project.namespace.slug)));
|
||||
@ -36,7 +34,7 @@ async function deletePageAndUpdateProject() {
|
||||
updateProjectPages(await useInternalApi<HangarProjectPage[]>(`pages/list/${props.project.id}`, false, "get"));
|
||||
}
|
||||
} catch (e: any) {
|
||||
handleRequestError(e, ctx, i18n);
|
||||
handleRequestError(e, i18n);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
@ -1,7 +1,7 @@
|
||||
<script lang="ts" setup>
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { hasSlotContent } from "~/lib/composables/useSlot";
|
||||
import { useSlots } from "vue";
|
||||
import { hasSlotContent } from "~/lib/composables/useSlot";
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
|
@ -2,14 +2,13 @@ import type { AxiosError, AxiosRequestConfig } from "axios";
|
||||
import type { HangarApiException } from "hangar-api";
|
||||
import qs from "qs";
|
||||
import Cookies from "universal-cookie";
|
||||
import { isEmpty } from "lodash-es";
|
||||
import type { Ref } from "vue";
|
||||
import { useAxios } from "~/composables/useAxios";
|
||||
import { useCookies } from "~/composables/useCookies";
|
||||
import { Ref } from "vue";
|
||||
import { authLog, fetchLog } from "~/lib/composables/useLog";
|
||||
import { isEmpty } from "lodash-es";
|
||||
import { useAuth } from "~/composables/useAuth";
|
||||
import { useRequest, useResponse } from "~/composables/useResReq";
|
||||
import { Context } from "vite-ssr/vue";
|
||||
import { useRequestEvent } from "#imports";
|
||||
|
||||
function request<T>(url: string, method: AxiosRequestConfig["method"], data: object, headers: Record<string, string> = {}, retry = false): Promise<T> {
|
||||
const cookies = useCookies();
|
||||
@ -42,7 +41,7 @@ function request<T>(url: string, method: AxiosRequestConfig["method"], data: obj
|
||||
resolve(data);
|
||||
})
|
||||
.catch(async (error: AxiosError) => {
|
||||
const { trace, ...err } = error.response?.data as { trace: any };
|
||||
const { trace, ...err } = (error.response?.data as { trace: any }) || {};
|
||||
authLog("failed", err);
|
||||
// do we have an expired token?
|
||||
if (error.response?.status === 403) {
|
||||
@ -78,7 +77,7 @@ function request<T>(url: string, method: AxiosRequestConfig["method"], data: obj
|
||||
});
|
||||
}
|
||||
|
||||
export async function useApi<T>(
|
||||
export function useApi<T>(
|
||||
url: string,
|
||||
authed = true,
|
||||
method: AxiosRequestConfig["method"] = "get",
|
||||
@ -89,7 +88,7 @@ export async function useApi<T>(
|
||||
return processAuthStuff(headers, authed, (headers) => request(`v1/${url}`, method, data, headers));
|
||||
}
|
||||
|
||||
export async function useInternalApi<T = void>(
|
||||
export function useInternalApi<T = void>(
|
||||
url: string,
|
||||
authed = true,
|
||||
method: AxiosRequestConfig["method"] = "get",
|
||||
@ -106,7 +105,7 @@ export async function processAuthStuff<T>(headers: Record<string, string>, authR
|
||||
let token = useCookies().get("HangarAuth");
|
||||
let refreshToken = useCookies().get("HangarAuth_REFRESH");
|
||||
if (!token) {
|
||||
const header = useResponse()?.getHeader("set-cookie") as string[];
|
||||
const header = useRequestEvent().node.res?.getHeader("set-cookie") as string[];
|
||||
if (header && header.join) {
|
||||
const cookies = new Cookies(header.join("; "));
|
||||
token = cookies.get("HangarAuth");
|
||||
@ -127,6 +126,7 @@ export async function processAuthStuff<T>(headers: Record<string, string>, authR
|
||||
} else {
|
||||
authLog("could not refresh, invalidate");
|
||||
await useAuth.invalidate();
|
||||
// eslint-disable-next-line no-throw-literal
|
||||
throw {
|
||||
isAxiosError: true,
|
||||
response: {
|
||||
@ -146,6 +146,7 @@ export async function processAuthStuff<T>(headers: Record<string, string>, authR
|
||||
} else {
|
||||
authLog("can't forward token, no cookie");
|
||||
if (authRequired) {
|
||||
// eslint-disable-next-line no-throw-literal
|
||||
throw {
|
||||
isAxiosError: true,
|
||||
response: {
|
||||
@ -173,6 +174,7 @@ export async function processAuthStuff<T>(headers: Record<string, string>, authR
|
||||
authLog("could not refresh, invalidate");
|
||||
await useAuth.invalidate();
|
||||
if (authRequired) {
|
||||
// eslint-disable-next-line no-throw-literal
|
||||
throw {
|
||||
isAxiosError: true,
|
||||
response: {
|
||||
@ -193,7 +195,7 @@ export async function processAuthStuff<T>(headers: Record<string, string>, authR
|
||||
}
|
||||
|
||||
function forwardHeader(): Record<string, string> {
|
||||
const req: Context["request"] = useRequest();
|
||||
const req = useRequestEvent().node.req;
|
||||
const result: Record<string, string> = {};
|
||||
if (!req) return result;
|
||||
|
||||
|
@ -1,6 +1,4 @@
|
||||
import { useApi, useInternalApi } from "~/composables/useApi";
|
||||
import { PaginatedResult, Project, ProjectCompact, User, Version } from "hangar-api";
|
||||
import { useInitialState } from "~/composables/useInitialState";
|
||||
import {
|
||||
Flag,
|
||||
HangarNotification,
|
||||
@ -17,151 +15,139 @@ import {
|
||||
ReviewQueueEntry,
|
||||
RoleTable,
|
||||
} from "hangar-internal";
|
||||
import { useApi, useInternalApi } from "~/composables/useApi";
|
||||
import { useAsyncData } from "#imports";
|
||||
|
||||
export async function useProjects(params: Record<string, any> = { limit: 25, offset: 0 }, blocking = true) {
|
||||
return useInitialState("useProjects", () => useApi<PaginatedResult<Project>>("projects", false, "get", params), blocking);
|
||||
export async function useProjects(params: Record<string, any> = { limit: 25, offset: 0 }) {
|
||||
return (await useAsyncData("useProjects", () => useApi<PaginatedResult<Project>>("projects", false, "get", params))).data;
|
||||
}
|
||||
|
||||
export async function useUser(user: string, blocking = true) {
|
||||
return useInitialState("useUser:" + user, () => useApi<User>("users/" + user, false), blocking);
|
||||
export async function useUser(user: string) {
|
||||
return (await useAsyncData("useUser:" + user, () => useApi<User>("users/" + user, false))).data;
|
||||
}
|
||||
|
||||
export async function useOrganization(user: string, blocking = true) {
|
||||
return useInitialState("useOrganization:" + user, () => useInternalApi<Organization>(`organizations/org/${user}`, false), blocking);
|
||||
export async function useOrganization(user: string) {
|
||||
return (await useAsyncData("useOrganization:" + user, () => useInternalApi<Organization>(`organizations/org/${user}`, false))).data;
|
||||
}
|
||||
|
||||
export async function useProject(user: string, project: string, blocking = true) {
|
||||
return useInitialState(
|
||||
"useProject:" + user + ":" + project,
|
||||
() => useInternalApi<HangarProject>("projects/project/" + user + "/" + project, false),
|
||||
blocking
|
||||
);
|
||||
export async function useProject(user: string, project: string) {
|
||||
return (await useAsyncData("useProject:" + user + ":" + project, () => useInternalApi<HangarProject>("projects/project/" + user + "/" + project, false)))
|
||||
.data;
|
||||
}
|
||||
|
||||
export async function useStargazers(user: string, project: string, blocking = true) {
|
||||
return useInitialState(
|
||||
"useStargazers:" + user + ":" + project,
|
||||
() => useApi<PaginatedResult<User>>(`projects/${user}/${project}/stargazers`, false),
|
||||
blocking
|
||||
);
|
||||
export async function useStargazers(user: string, project: string) {
|
||||
return (await useAsyncData("useStargazers:" + user + ":" + project, () => useApi<PaginatedResult<User>>(`projects/${user}/${project}/stargazers`, false)))
|
||||
.data;
|
||||
}
|
||||
|
||||
export async function useWatchers(user: string, project: string, blocking = true) {
|
||||
return useInitialState("useWatchers:" + user + ":" + project, () => useApi<PaginatedResult<User>>(`projects/${user}/${project}/watchers`, false), blocking);
|
||||
export async function useWatchers(user: string, project: string) {
|
||||
return (await useAsyncData("useWatchers:" + user + ":" + project, () => useApi<PaginatedResult<User>>(`projects/${user}/${project}/watchers`, false))).data;
|
||||
}
|
||||
|
||||
export async function useStaff(blocking = true) {
|
||||
return useInitialState("useStaff", () => useApi<PaginatedResult<User>>("staff", false), blocking);
|
||||
export async function useStaff() {
|
||||
return (await useAsyncData("useStaff", () => useApi<PaginatedResult<User>>("staff", false))).data;
|
||||
}
|
||||
|
||||
export async function useAuthors(blocking = true) {
|
||||
return useInitialState("useAuthors", () => useApi<PaginatedResult<User>>("authors", false), blocking);
|
||||
export async function useAuthors() {
|
||||
return (await useAsyncData("useAuthors", () => useApi<PaginatedResult<User>>("authors", false))).data;
|
||||
}
|
||||
|
||||
export async function useInvites(blocking = true) {
|
||||
return useInitialState("useInvites", () => useInternalApi<Invites>("invites", false), blocking);
|
||||
export async function useInvites() {
|
||||
return (await useAsyncData("useInvites", () => useInternalApi<Invites>("invites", false))).data;
|
||||
}
|
||||
|
||||
export async function useNotifications(blocking = true) {
|
||||
return useInitialState("useNotifications", () => useInternalApi<PaginatedResult<HangarNotification>>("notifications", false), blocking);
|
||||
export async function useNotifications() {
|
||||
return (await useAsyncData("useNotifications", () => useInternalApi<PaginatedResult<HangarNotification>>("notifications", false))).data;
|
||||
}
|
||||
|
||||
export async function useUnreadNotifications(blocking = true) {
|
||||
return useInitialState("useUnreadNotifications", () => useInternalApi<PaginatedResult<HangarNotification>>("unreadnotifications", false), blocking);
|
||||
export async function useUnreadNotifications() {
|
||||
return (await useAsyncData("useUnreadNotifications", () => useInternalApi<PaginatedResult<HangarNotification>>("unreadnotifications", false))).data;
|
||||
}
|
||||
|
||||
export async function useReadNotifications(blocking = true) {
|
||||
return useInitialState("useReadNotifications", () => useInternalApi<PaginatedResult<HangarNotification>>("readnotifications", false), blocking);
|
||||
export async function useReadNotifications() {
|
||||
return (await useAsyncData("useReadNotifications", () => useInternalApi<PaginatedResult<HangarNotification>>("readnotifications", false))).data;
|
||||
}
|
||||
|
||||
export async function useRecentNotifications(blocking = true, amount: number) {
|
||||
return useInitialState(
|
||||
"useRecentNotifications:" + amount,
|
||||
() => useInternalApi<HangarNotification[]>("recentnotifications?amount=" + amount, false),
|
||||
blocking
|
||||
);
|
||||
export async function useRecentNotifications(amount: number) {
|
||||
return (await useAsyncData("useRecentNotifications:" + amount, () => useInternalApi<HangarNotification[]>("recentnotifications?amount=" + amount, false)))
|
||||
.data;
|
||||
}
|
||||
|
||||
export async function useUnreadNotificationsCount(blocking = true) {
|
||||
return useInitialState("useUnreadNotificationsCount", () => useInternalApi<number>("unreadcount", false), blocking);
|
||||
export async function useUnreadNotificationsCount() {
|
||||
return (await useAsyncData("useUnreadNotificationsCount", () => useInternalApi<number>("unreadcount", false))).data;
|
||||
}
|
||||
|
||||
export async function useResolvedFlags(blocking = true) {
|
||||
return useInitialState("useResolvedFlags", () => useInternalApi<PaginatedResult<Flag>>("flags/resolved", false), blocking);
|
||||
export async function useResolvedFlags() {
|
||||
return (await useAsyncData("useResolvedFlags", () => useInternalApi<PaginatedResult<Flag>>("flags/resolved", false))).data;
|
||||
}
|
||||
|
||||
export async function useUnresolvedFlags(blocking = true) {
|
||||
return useInitialState("useUnresolvedFlags", () => useInternalApi<PaginatedResult<Flag>>("flags/unresolved", false), blocking);
|
||||
export async function useUnresolvedFlags() {
|
||||
return (await useAsyncData("useUnresolvedFlags", () => useInternalApi<PaginatedResult<Flag>>("flags/unresolved", false))).data;
|
||||
}
|
||||
|
||||
export async function useProjectFlags(projectId: number, blocking = true) {
|
||||
return useInitialState("useProjectFlags:" + projectId, () => useInternalApi<Flag[]>("flags/" + projectId, false), blocking);
|
||||
export async function useProjectFlags(projectId: number) {
|
||||
return (await useAsyncData("useProjectFlags:" + projectId, () => useInternalApi<Flag[]>("flags/" + projectId, false))).data;
|
||||
}
|
||||
|
||||
export async function useProjectNotes(projectId: number, blocking = true) {
|
||||
return useInitialState("useProjectNotes:" + projectId, () => useInternalApi<Note[]>("projects/notes/" + projectId, false), blocking);
|
||||
export async function useProjectNotes(projectId: number) {
|
||||
return (await useAsyncData("useProjectNotes:" + projectId, () => useInternalApi<Note[]>("projects/notes/" + projectId, false))).data;
|
||||
}
|
||||
|
||||
export async function useProjectChannels(user: string, project: string, blocking = true) {
|
||||
return useInitialState("useProjectChannels:" + user + ":" + project, () => useInternalApi<ProjectChannel[]>(`channels/${user}/${project}`, false), blocking);
|
||||
export async function useProjectChannels(user: string, project: string) {
|
||||
return (await useAsyncData("useProjectChannels:" + user + ":" + project, () => useInternalApi<ProjectChannel[]>(`channels/${user}/${project}`, false))).data;
|
||||
}
|
||||
|
||||
export async function useProjectVersions(user: string, project: string, blocking = true) {
|
||||
return useInitialState(
|
||||
"useProjectVersions:" + user + ":" + project,
|
||||
() => useApi<PaginatedResult<Version>>(`projects/${user}/${project}/versions`, false),
|
||||
blocking
|
||||
);
|
||||
export async function useProjectVersions(user: string, project: string) {
|
||||
return (
|
||||
await useAsyncData("useProjectVersions:" + user + ":" + project, () => useApi<PaginatedResult<Version>>(`projects/${user}/${project}/versions`, false))
|
||||
).data;
|
||||
}
|
||||
|
||||
export async function useProjectVersionsInternal(user: string, project: string, version: string, blocking = true) {
|
||||
return useInitialState(
|
||||
"useProjectVersionsInternal:" + user + ":" + project + ":" + version,
|
||||
() => useInternalApi<HangarVersion>(`versions/version/${user}/${project}/versions/${version}`, false),
|
||||
blocking
|
||||
);
|
||||
export async function useProjectVersionsInternal(user: string, project: string, version: string) {
|
||||
return (
|
||||
await useAsyncData("useProjectVersionsInternal:" + user + ":" + project + ":" + version, () =>
|
||||
useInternalApi<HangarVersion>(`versions/version/${user}/${project}/versions/${version}`, false)
|
||||
)
|
||||
).data;
|
||||
}
|
||||
|
||||
export async function usePage(user: string, project: string, path?: string, blocking = true) {
|
||||
return useInitialState(
|
||||
"usePage:" + user + ":" + project + ":" + path,
|
||||
() => useInternalApi<ProjectPage>(`pages/page/${user}/${project}` + (path ? "/" + path : ""), false),
|
||||
blocking
|
||||
);
|
||||
export async function usePage(user: string, project: string, path?: string) {
|
||||
return (
|
||||
await useAsyncData("usePage:" + user + ":" + project + ":" + path, () =>
|
||||
useInternalApi<ProjectPage>(`pages/page/${user}/${project}` + (path ? "/" + path : ""), false)
|
||||
)
|
||||
).data;
|
||||
}
|
||||
|
||||
export async function useHealthReport(blocking = true) {
|
||||
return useInitialState("useHealthReport", () => useInternalApi<HealthReport>("admin/health", false), blocking);
|
||||
export async function useHealthReport() {
|
||||
return (await useAsyncData("useHealthReport", () => useInternalApi<HealthReport>("admin/health", false))).data;
|
||||
}
|
||||
|
||||
export async function useActionLogs(blocking = true) {
|
||||
return useInitialState("useActionLogs", () => useInternalApi<PaginatedResult<LoggedAction>>("admin/log/", false), blocking);
|
||||
export async function useActionLogs() {
|
||||
return (await useAsyncData("useActionLogs", () => useInternalApi<PaginatedResult<LoggedAction>>("admin/log/", false))).data;
|
||||
}
|
||||
|
||||
export async function useVersionApprovals(blocking = true) {
|
||||
return useInitialState(
|
||||
"useVersionApprovals",
|
||||
() => useInternalApi<{ underReview: ReviewQueueEntry[]; notStarted: ReviewQueueEntry[] }>("admin/approval/versions", false),
|
||||
blocking
|
||||
);
|
||||
export async function useVersionApprovals() {
|
||||
return (
|
||||
await useAsyncData("useVersionApprovals", () =>
|
||||
useInternalApi<{ underReview: ReviewQueueEntry[]; notStarted: ReviewQueueEntry[] }>("admin/approval/versions", false)
|
||||
)
|
||||
).data;
|
||||
}
|
||||
|
||||
export async function usePossibleOwners(blocking = true) {
|
||||
return useInitialState("usePossibleOwners", () => useInternalApi<ProjectOwner[]>("projects/possibleOwners"), blocking);
|
||||
export async function usePossibleOwners() {
|
||||
return (await useAsyncData("usePossibleOwners", () => useInternalApi<ProjectOwner[]>("projects/possibleOwners"))).data;
|
||||
}
|
||||
|
||||
export async function useOrgVisibility(user: string, blocking = true) {
|
||||
return useInitialState(
|
||||
"useOrgVisibility:" + user,
|
||||
() => useInternalApi<{ [key: string]: boolean }>(`organizations/${user}/userOrganizationsVisibility`, true),
|
||||
blocking
|
||||
);
|
||||
export async function useOrgVisibility(user: string) {
|
||||
return (
|
||||
await useAsyncData("useOrgVisibility:" + user, () => useInternalApi<{ [key: string]: boolean }>(`organizations/${user}/userOrganizationsVisibility`, true))
|
||||
).data;
|
||||
}
|
||||
|
||||
export async function useUserData(user: string, blocking = true) {
|
||||
return useInitialState(
|
||||
"useUserData:" + user,
|
||||
async () => {
|
||||
export async function useUserData(user: string) {
|
||||
return (
|
||||
await useAsyncData("useUserData:" + user, async () => {
|
||||
// noinspection ES6MissingAwait
|
||||
const data = await Promise.all([
|
||||
useApi<PaginatedResult<ProjectCompact>>(`users/${user}/starred`, false),
|
||||
@ -171,7 +157,7 @@ export async function useUserData(user: string, blocking = true) {
|
||||
}),
|
||||
useInternalApi<{ [key: string]: RoleTable }>(`organizations/${user}/userOrganizations`, false),
|
||||
useApi<ProjectCompact[]>(`users/${user}/pinned`, false),
|
||||
] as Promise<any>[]);
|
||||
]);
|
||||
return {
|
||||
starred: data[0] as PaginatedResult<ProjectCompact>,
|
||||
watching: data[1] as PaginatedResult<ProjectCompact>,
|
||||
@ -179,7 +165,6 @@ export async function useUserData(user: string, blocking = true) {
|
||||
organizations: data[3] as { [key: string]: RoleTable },
|
||||
pinned: data[4] as ProjectCompact[],
|
||||
};
|
||||
},
|
||||
blocking
|
||||
);
|
||||
})
|
||||
).data;
|
||||
}
|
||||
|
@ -1,16 +1,14 @@
|
||||
import { HangarUser } from "hangar-internal";
|
||||
import { AxiosError, AxiosRequestHeaders } from "axios";
|
||||
import Cookies from "universal-cookie";
|
||||
import jwtDecode, { type JwtPayload } from "jwt-decode";
|
||||
import { useAuthStore } from "~/store/auth";
|
||||
import { useCookies } from "~/composables/useCookies";
|
||||
import { useInternalApi } from "~/composables/useApi";
|
||||
import { useAxios } from "~/composables/useAxios";
|
||||
import { authLog } from "~/lib/composables/useLog";
|
||||
import { HangarUser } from "hangar-internal";
|
||||
import * as domain from "~/composables/useDomain";
|
||||
import { Pinia } from "pinia";
|
||||
import { AxiosError, AxiosRequestHeaders } from "axios";
|
||||
import { useResponse } from "~/composables/useResReq";
|
||||
import Cookies from "universal-cookie";
|
||||
import jwtDecode, { JwtPayload } from "jwt-decode";
|
||||
import { useConfig } from "~/lib/composables/useConfig";
|
||||
import { useRequestEvent } from "#imports";
|
||||
|
||||
class Auth {
|
||||
loginUrl(redirectUrl: string): string {
|
||||
@ -20,7 +18,7 @@ class Auth {
|
||||
return `/login?returnUrl=${useConfig().publicHost}${redirectUrl}`;
|
||||
}
|
||||
|
||||
async logout() {
|
||||
logout() {
|
||||
location.replace(`/logout?returnUrl=${useConfig().publicHost}?loggedOut`);
|
||||
}
|
||||
|
||||
@ -36,6 +34,7 @@ class Auth {
|
||||
}
|
||||
|
||||
// TODO do we need to scope this to the user?
|
||||
// TODO do we even need this anymore?
|
||||
refreshPromise: Promise<boolean | string> | null = null;
|
||||
|
||||
async refreshToken() {
|
||||
@ -70,7 +69,7 @@ class Auth {
|
||||
resolve(false);
|
||||
} else if (import.meta.env.SSR) {
|
||||
if (response.headers["set-cookie"]) {
|
||||
useResponse()?.setHeader("set-cookie", response.headers["set-cookie"]);
|
||||
useRequestEvent().node.res?.setHeader("set-cookie", response.headers["set-cookie"]);
|
||||
const token = new Cookies(response.headers["set-cookie"]?.join("; ")).get("HangarAuth");
|
||||
if (token) {
|
||||
authLog("got token");
|
||||
@ -103,7 +102,7 @@ class Auth {
|
||||
}
|
||||
|
||||
async invalidate() {
|
||||
const store = useAuthStore(this.usePiniaIfPresent());
|
||||
const store = useAuthStore();
|
||||
store.$patch({
|
||||
user: null,
|
||||
authenticated: false,
|
||||
@ -120,13 +119,13 @@ class Auth {
|
||||
}
|
||||
|
||||
async updateUser(): Promise<void> {
|
||||
const authStore = useAuthStore(this.usePiniaIfPresent());
|
||||
const authStore = useAuthStore();
|
||||
if (authStore.invalidated) {
|
||||
authLog("no point in updating if we just invalidated");
|
||||
return;
|
||||
}
|
||||
const user = await useInternalApi<HangarUser>("users/@me", true).catch(async (err) => {
|
||||
authLog("no user");
|
||||
const user = await useInternalApi<HangarUser>("users/@me", true).catch((err) => {
|
||||
authLog("no user", Object.assign({}, err));
|
||||
return this.invalidate();
|
||||
});
|
||||
if (user) {
|
||||
@ -136,10 +135,6 @@ class Auth {
|
||||
authLog("user is now " + authStore.user?.name);
|
||||
}
|
||||
}
|
||||
|
||||
usePiniaIfPresent() {
|
||||
return import.meta.env.SSR ? domain.get<Pinia>("pinia") : null;
|
||||
}
|
||||
}
|
||||
|
||||
export const useAuth = new Auth();
|
||||
|
@ -1,11 +1,12 @@
|
||||
import { createCookies, useCookies as cookies } from "@vueuse/integrations/useCookies";
|
||||
import { useRequest, useResponse } from "~/composables/useResReq";
|
||||
import * as cookie from "cookie";
|
||||
import { useRequestEvent } from "#imports";
|
||||
|
||||
export const useCookies = () => {
|
||||
if (import.meta.env.SSR) {
|
||||
const req = useRequest();
|
||||
const res = useResponse();
|
||||
const event = useRequestEvent();
|
||||
const req = event.node.req;
|
||||
const res = event.node.res;
|
||||
if (!req || !req.headers) {
|
||||
console.error("req null?!");
|
||||
console.trace();
|
||||
|
@ -1,52 +0,0 @@
|
||||
import * as domain from "domain";
|
||||
import { Context } from "vite-ssr/vue";
|
||||
import { domainLog } from "~/lib/composables/useLog";
|
||||
|
||||
export function create(request: Context["request"], response: Context["response"]) {
|
||||
if (!import.meta.env.SSR) return null;
|
||||
domainLog("enter");
|
||||
const d = domain.create();
|
||||
d.add(request);
|
||||
d.add(response);
|
||||
d.on("error", (err) => {
|
||||
domainLog("domain error!", err);
|
||||
});
|
||||
d.context = {};
|
||||
d.enter();
|
||||
set("req", request);
|
||||
set("res", response);
|
||||
return d;
|
||||
}
|
||||
|
||||
export function exit(d: domain.Domain | null) {
|
||||
if (!import.meta.env.SSR || !d) return;
|
||||
d.context = null;
|
||||
d.exit();
|
||||
domainLog("exit");
|
||||
}
|
||||
|
||||
export function set(key: string, value: unknown) {
|
||||
if (!import.meta.env.SSR) return;
|
||||
if (!domain.active) {
|
||||
throw new Error("no active domain found to set key " + key);
|
||||
}
|
||||
if (!domain.active.context) {
|
||||
throw new Error("no context found on domain to set key " + key);
|
||||
}
|
||||
domain.active.context[key] = value;
|
||||
}
|
||||
|
||||
export function get<T>(key: string): T | null {
|
||||
if (!import.meta.env.SSR) return null;
|
||||
if (!domain.active) {
|
||||
throw new Error("no active domain found to get key " + key);
|
||||
}
|
||||
if (!domain.active.context) {
|
||||
throw new Error("no context found on domain to get key " + key);
|
||||
}
|
||||
return domain.active.context[key];
|
||||
}
|
||||
|
||||
export function isActive() {
|
||||
return import.meta.env.SSR && domain.active && domain.active.context;
|
||||
}
|
@ -1,20 +1,12 @@
|
||||
import { AxiosError } from "axios";
|
||||
import { HangarApiException, HangarValidationException, MultiHangarApiException } from "hangar-api";
|
||||
import { Composer, UseI18nOptions } from "vue-i18n";
|
||||
import { Context } from "vite-ssr/vue";
|
||||
import { useNotificationStore } from "~/lib/store/notification";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { ref } from "vue";
|
||||
import { useNotificationStore } from "~/lib/store/notification";
|
||||
|
||||
type I18nType = Composer<
|
||||
NonNullable<UseI18nOptions["messages"]>,
|
||||
NonNullable<UseI18nOptions["datetimeFormats"]>,
|
||||
NonNullable<UseI18nOptions["numberFormats"]>,
|
||||
NonNullable<UseI18nOptions["locale"]>
|
||||
>;
|
||||
|
||||
export function handleRequestError(err: AxiosError, { writeResponse }: Context, i18n: I18nType, msg: string | undefined = undefined) {
|
||||
export function handleRequestError(err: AxiosError, i18n: ReturnType<typeof useI18n>, msg: string | undefined = undefined) {
|
||||
if (import.meta.env.SSR) {
|
||||
_handleRequestError(err, writeResponse, i18n);
|
||||
_handleRequestError(err, i18n);
|
||||
return ref();
|
||||
}
|
||||
const notfication = useNotificationStore();
|
||||
@ -44,7 +36,11 @@ export function handleRequestError(err: AxiosError, { writeResponse }: Context,
|
||||
return ref();
|
||||
}
|
||||
|
||||
function _handleRequestError(err: AxiosError, writeResponse: Context["writeResponse"], i18n: I18nType) {
|
||||
function _handleRequestError(err: AxiosError, i18n: ReturnType<typeof useI18n>) {
|
||||
function writeResponse(object: unknown) {
|
||||
console.log("writeResponse", object);
|
||||
// throw new Error("TODO: Implement me"); // TODO
|
||||
}
|
||||
if (!err.isAxiosError) {
|
||||
// everything should be an AxiosError
|
||||
writeResponse({
|
||||
@ -79,7 +75,7 @@ function _handleRequestError(err: AxiosError, writeResponse: Context["writeRespo
|
||||
}
|
||||
}
|
||||
|
||||
function collectErrors(exception: HangarApiException | MultiHangarApiException, i18n: Context["app"]["i18n"]): string[] {
|
||||
function collectErrors(exception: HangarApiException | MultiHangarApiException, i18n: ReturnType<typeof useI18n>): string[] {
|
||||
if (!exception.isMultiException) {
|
||||
return [i18n.te(exception.message) ? i18n.t(exception.message, [exception.messageArgs]) : exception.message];
|
||||
} else {
|
||||
|
@ -12,7 +12,7 @@ export const validProjectName = withOverrideMessage((ownerId: () => string) =>
|
||||
try {
|
||||
await useInternalApi("projects/validateName", false, "get", {
|
||||
userId: ownerId(),
|
||||
value: value,
|
||||
value,
|
||||
});
|
||||
return { $valid: true };
|
||||
} catch (e: any) {
|
||||
@ -71,9 +71,9 @@ export const validChannelName = withOverrideMessage((projectId: string, existing
|
||||
}
|
||||
try {
|
||||
await useInternalApi("channels/checkName", true, "get", {
|
||||
projectId: projectId,
|
||||
projectId,
|
||||
name: value,
|
||||
existingName: existingName,
|
||||
existingName,
|
||||
});
|
||||
return { $valid: true };
|
||||
} catch (e: any) {
|
||||
@ -92,9 +92,9 @@ export const validChannelColor = withOverrideMessage((projectId: string, existin
|
||||
}
|
||||
try {
|
||||
await useInternalApi("channels/checkColor", true, "get", {
|
||||
projectId: projectId,
|
||||
projectId,
|
||||
color: value,
|
||||
existingColor: existingColor,
|
||||
existingColor,
|
||||
});
|
||||
return { $valid: true };
|
||||
} catch (e: any) {
|
||||
|
@ -1,49 +0,0 @@
|
||||
import type { Ref } from "vue";
|
||||
import { onDeactivated, onMounted, onUnmounted, ref } from "vue";
|
||||
import { useContext } from "vite-ssr/vue";
|
||||
import { initalStateLog } from "~/lib/composables/useLog";
|
||||
|
||||
export async function useInitialState<T>(key: string, handler: (type: "server" | "client") => Promise<T>, blocking = false): Promise<Ref<T | null>> {
|
||||
const { initialState } = useContext();
|
||||
const responseValue = ref(initialState[key] || null) as Ref<T | null>;
|
||||
|
||||
// remove data from initialState when component unmounts or deactivates
|
||||
const removeState = () => {
|
||||
if (!import.meta.env.SSR) {
|
||||
initialState[key] = null;
|
||||
}
|
||||
};
|
||||
|
||||
onUnmounted(removeState);
|
||||
onDeactivated(removeState);
|
||||
|
||||
if (import.meta.env.SSR) {
|
||||
// if on server, block until data can be stored in initialState
|
||||
initalStateLog("do request " + key);
|
||||
const data = await handler("server");
|
||||
initalStateLog("done " + key);
|
||||
initialState[key] = data;
|
||||
responseValue.value = data;
|
||||
} else {
|
||||
// if on client, check if we already have data and use that
|
||||
if (initialState[key]) {
|
||||
initalStateLog("found " + key);
|
||||
responseValue.value = initialState[key];
|
||||
} else {
|
||||
// else do the request ourselves, blocking if needed
|
||||
const fn = async () => {
|
||||
responseValue.value = await handler("client");
|
||||
initalStateLog("done " + key);
|
||||
};
|
||||
if (blocking) {
|
||||
initalStateLog("block " + key);
|
||||
await fn();
|
||||
} else {
|
||||
initalStateLog("onMounted " + key);
|
||||
onMounted(fn);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return responseValue;
|
||||
}
|
@ -2,7 +2,7 @@ import { RouteLocationNormalizedLoaded } from "vue-router";
|
||||
import { HangarProject } from "hangar-internal";
|
||||
import { ref, watch } from "vue";
|
||||
|
||||
export async function useOpenProjectPages(route: RouteLocationNormalizedLoaded, project: HangarProject) {
|
||||
export function useOpenProjectPages(route: RouteLocationNormalizedLoaded, project: HangarProject) {
|
||||
const open = ref<string[]>([]);
|
||||
|
||||
watch(
|
||||
|
@ -1,23 +1,14 @@
|
||||
import { ref } from "vue";
|
||||
import { useInternalApi } from "~/composables/useApi";
|
||||
import { handleRequestError } from "~/composables/useErrorHandling";
|
||||
import { RouteLocationNormalizedLoaded, Router } from "vue-router";
|
||||
import { Context } from "vite-ssr/vue";
|
||||
import { Composer, VueMessageType } from "vue-i18n";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { HangarProject } from "hangar-internal";
|
||||
import { handleRequestError } from "~/composables/useErrorHandling";
|
||||
import { useInternalApi } from "~/composables/useApi";
|
||||
import { usePage } from "~/composables/useApiHelper";
|
||||
import { useErrorRedirect } from "~/lib/composables/useErrorRedirect";
|
||||
|
||||
export async function useProjectPage(
|
||||
route: RouteLocationNormalizedLoaded,
|
||||
router: Router,
|
||||
ctx: Context,
|
||||
i18n: Composer<unknown, unknown, unknown, VueMessageType>,
|
||||
project: HangarProject
|
||||
) {
|
||||
const page = await usePage(route.params.user as string, route.params.project as string, route.params.all as string).catch((e) =>
|
||||
handleRequestError(e, ctx, i18n)
|
||||
);
|
||||
export async function useProjectPage(route: RouteLocationNormalizedLoaded, router: Router, i18n: ReturnType<typeof useI18n>, project: HangarProject) {
|
||||
const page = await usePage(route.params.user as string, route.params.project as string, route.params.all as string).catch((e) => handleRequestError(e, i18n));
|
||||
if (!page) {
|
||||
await router.replace(useErrorRedirect(route, 404, "Not found"));
|
||||
}
|
||||
@ -33,7 +24,7 @@ export async function useProjectPage(
|
||||
if (!page) return;
|
||||
await useInternalApi(`pages/save/${project.id}/${page.value?.id}`, true, "post", {
|
||||
content,
|
||||
}).catch((e) => handleRequestError(e, ctx, i18n, "page.new.error.save"));
|
||||
}).catch((e) => handleRequestError(e, i18n, "page.new.error.save"));
|
||||
if (page.value) {
|
||||
page.value.contents = content;
|
||||
}
|
||||
@ -42,7 +33,7 @@ export async function useProjectPage(
|
||||
|
||||
async function deletePage() {
|
||||
if (!page) return;
|
||||
await useInternalApi(`pages/delete/${project.id}/${page.value?.id}`, true, "post").catch((e) => handleRequestError(e, ctx, i18n, "page.new.error.save"));
|
||||
await useInternalApi(`pages/delete/${project.id}/${page.value?.id}`, true, "post").catch((e) => handleRequestError(e, i18n, "page.new.error.save"));
|
||||
await router.replace(`/${route.params.user}/${route.params.project}`);
|
||||
}
|
||||
|
||||
|
@ -1,40 +0,0 @@
|
||||
import { useContext } from "vite-ssr";
|
||||
import * as domain from "~/composables/useDomain";
|
||||
import { Context } from "vite-ssr/vue";
|
||||
|
||||
export const useRequest: () => Context["request"] | null = () => {
|
||||
if (import.meta.env.SSR) {
|
||||
if (domain.isActive()) {
|
||||
return domain.get<Context["request"]>("req");
|
||||
}
|
||||
const ctx = useContext();
|
||||
if (ctx) {
|
||||
return ctx.request;
|
||||
}
|
||||
console.error("request null!");
|
||||
console.trace();
|
||||
return null;
|
||||
}
|
||||
|
||||
console.error("useRequest called on client?!");
|
||||
console.trace();
|
||||
return null;
|
||||
};
|
||||
export const useResponse: () => Context["response"] | null = () => {
|
||||
if (import.meta.env.SSR) {
|
||||
if (domain.isActive()) {
|
||||
return domain.get<Context["response"]>("res");
|
||||
}
|
||||
const ctx = useContext();
|
||||
if (ctx) {
|
||||
return ctx.response;
|
||||
}
|
||||
console.error("response null!");
|
||||
console.trace();
|
||||
return null;
|
||||
}
|
||||
|
||||
console.error("useResponse called on client?!");
|
||||
console.trace();
|
||||
return null;
|
||||
};
|
9
frontend/src/env.d.ts
vendored
9
frontend/src/env.d.ts
vendored
@ -1,9 +0,0 @@
|
||||
/// <reference types="vite/client" />
|
||||
|
||||
interface ImportMetaEnv {
|
||||
readonly HANGAR_CONFIG_ENV: string;
|
||||
}
|
||||
|
||||
interface ImportMeta {
|
||||
readonly env: ImportMetaEnv;
|
||||
}
|
@ -1,10 +1,10 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from "vue";
|
||||
import { useRoute } from "vue-router";
|
||||
import Header from "~/components/layout/Header.vue";
|
||||
import Footer from "~/components/layout/Footer.vue";
|
||||
import Container from "~/lib/components/design/Container.vue";
|
||||
import Notifications from "~/lib/components/design/Notifications.vue";
|
||||
import { computed } from "vue";
|
||||
import { useRoute } from "vue-router";
|
||||
|
||||
const route = useRoute();
|
||||
const key = computed<string>(() => route.params.user as string);
|
||||
@ -14,17 +14,7 @@ const key = computed<string>(() => route.params.user as string);
|
||||
<main>
|
||||
<Header />
|
||||
<Container class="min-h-[80vh]">
|
||||
<Suspense>
|
||||
<router-view v-slot="{ Component }" v-bind="$attrs" :key="key">
|
||||
<transition name="slide">
|
||||
<!-- dummy diff to make the transition work on pages where template root has multiple elements -->
|
||||
<div id="#page">
|
||||
<component :is="Component" />
|
||||
</div>
|
||||
</transition>
|
||||
</router-view>
|
||||
<template #fallback> Loading... </template>
|
||||
</Suspense>
|
||||
<slot />
|
||||
</Container>
|
||||
<Notifications />
|
||||
<Footer />
|
||||
|
@ -1,16 +1,60 @@
|
||||
<template>
|
||||
<main>
|
||||
<div class="min-h-[60vh]">
|
||||
<Suspense>
|
||||
<router-view v-slot="{ Component }" v-bind="$attrs">
|
||||
<transition name="slide">
|
||||
<!-- dummy diff to make the transition work on pages where template root has multiple elements -->
|
||||
<div id="#page">
|
||||
<component :is="Component" />
|
||||
</div>
|
||||
</transition>
|
||||
</router-view>
|
||||
</Suspense>
|
||||
</div>
|
||||
<Header />
|
||||
<Container class="min-h-[80vh]">
|
||||
<Card max-width="400" class="mx-auto">
|
||||
<h1>{{ error.statusCode }}</h1>
|
||||
<p>{{ text }}</p>
|
||||
<!-- todo nuxt button? -->
|
||||
<Button nuxt to="/" color="secondary">
|
||||
<IconMdiHome />
|
||||
{{ t("general.home") }}
|
||||
</Button>
|
||||
</Card>
|
||||
</Container>
|
||||
<Notifications />
|
||||
<Footer />
|
||||
</main>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { type NuxtError } from "nuxt/app";
|
||||
import { computed } from "#imports";
|
||||
import Card from "~/lib/components/design/Card.vue";
|
||||
import Button from "~/lib/components/design/Button.vue";
|
||||
|
||||
const props = defineProps<{
|
||||
error: NuxtError;
|
||||
}>();
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const text = computed(() => {
|
||||
switch (props.error.statusCode) {
|
||||
case 404:
|
||||
return t("error.404");
|
||||
case 401:
|
||||
return t("error.401");
|
||||
case 403:
|
||||
return t("error.403");
|
||||
default:
|
||||
return t("error.unknown");
|
||||
}
|
||||
});
|
||||
|
||||
const head = computed(() => {
|
||||
let title = t("error.unknown");
|
||||
switch (props.error.statusCode) {
|
||||
case 404:
|
||||
title = t("error.404");
|
||||
break;
|
||||
case 401:
|
||||
title = props.error.message!;
|
||||
break;
|
||||
}
|
||||
return {
|
||||
title,
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
@ -1,25 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import Header from "~/components/layout/Header.vue";
|
||||
import Footer from "~/components/layout/Footer.vue";
|
||||
import Notifications from "~/lib/components/design/Notifications.vue";
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<main>
|
||||
<Header />
|
||||
<div class="min-h-[60vh]">
|
||||
<Suspense>
|
||||
<router-view v-slot="{ Component }" v-bind="$attrs">
|
||||
<transition name="slide">
|
||||
<!-- dummy diff to make the transition work on pages where template root has multiple elements -->
|
||||
<div id="#page">
|
||||
<component :is="Component" />
|
||||
</div>
|
||||
</transition>
|
||||
</router-view>
|
||||
</Suspense>
|
||||
</div>
|
||||
<Notifications />
|
||||
<Footer />
|
||||
</main>
|
||||
</template>
|
@ -1 +1 @@
|
||||
Subproject commit 1eb7a8f6a223477b51e6b57ecec5094c185ab722
|
||||
Subproject commit 5ceb57e631896689b945290997fbe58ec39940bf
|
@ -1,88 +0,0 @@
|
||||
import { createHead } from "@vueuse/head";
|
||||
import { createPinia } from "pinia";
|
||||
import { setupLayouts } from "virtual:generated-layouts";
|
||||
import generatedRoutes from "virtual:generated-pages";
|
||||
import viteSSR, { ClientOnly } from "vite-ssr";
|
||||
import "windi.css";
|
||||
import App from "~/App.vue";
|
||||
import { installI18n } from "~/lib/i18n";
|
||||
import "./lib/styles/main.css";
|
||||
import { useBackendDataStore } from "~/store/backendData";
|
||||
import devalue from "@nuxt/devalue";
|
||||
import { settingsLog } from "~/lib/composables/useLog";
|
||||
import * as domain from "~/composables/useDomain";
|
||||
|
||||
import "regenerator-runtime/runtime"; // popper needs this?
|
||||
import { RouterScrollBehavior } from "vue-router";
|
||||
|
||||
const routes = setupLayouts(generatedRoutes);
|
||||
// we need to override the path on the error route to have the patch math
|
||||
const errorRoute = routes.find((r) => r.path === "/error");
|
||||
if (errorRoute) {
|
||||
errorRoute.path = "/:pathMatch(.*)*";
|
||||
} else {
|
||||
console.error("No error route?!");
|
||||
}
|
||||
|
||||
const scrollBehavior: RouterScrollBehavior = (to, from, savedPosition) => {
|
||||
if (savedPosition) {
|
||||
return savedPosition;
|
||||
} else if (to.hash) {
|
||||
return { el: to.hash };
|
||||
} else {
|
||||
return { top: 0 };
|
||||
}
|
||||
};
|
||||
|
||||
const options: Parameters<typeof viteSSR>["1"] = {
|
||||
routes,
|
||||
pageProps: {
|
||||
passToPage: false,
|
||||
},
|
||||
routerOptions: {
|
||||
scrollBehavior,
|
||||
},
|
||||
transformState(state) {
|
||||
return import.meta.env.SSR ? devalue(state) : state;
|
||||
},
|
||||
};
|
||||
|
||||
export default viteSSR(App, options, async (ctx) => {
|
||||
const { app, initialState, initialRoute, request, response } = ctx;
|
||||
|
||||
app.component(ClientOnly.name, ClientOnly);
|
||||
|
||||
const d = domain.create(request, response);
|
||||
|
||||
const head = createHead();
|
||||
const pinia = createPinia();
|
||||
app.use(pinia).use(head);
|
||||
domain.set("pinia", pinia);
|
||||
|
||||
// install all modules under `modules/`
|
||||
for (const module of Object.values(import.meta.globEager("./modules/*.ts"))) {
|
||||
await module.install?.(ctx);
|
||||
}
|
||||
|
||||
if (!import.meta.env.SSR) {
|
||||
pinia.state.value = initialState.pinia;
|
||||
}
|
||||
|
||||
settingsLog("Load locale " + pinia.state.value.settings?.locale || "en");
|
||||
// Load default language async to avoid bundling all languages
|
||||
await installI18n(app, pinia.state.value.settings?.locale || "en");
|
||||
|
||||
// really don't need to do stuff for such meta routes
|
||||
if (!initialRoute.fullPath.startsWith("/@vite")) {
|
||||
await useBackendDataStore().initBackendData();
|
||||
}
|
||||
|
||||
if (import.meta.env.SSR) {
|
||||
initialState.pinia = pinia.state.value;
|
||||
request.ctx = ctx;
|
||||
request.pinia = pinia;
|
||||
}
|
||||
domain.exit(d);
|
||||
|
||||
return { head };
|
||||
});
|
@ -1,14 +0,0 @@
|
||||
import NProgress from "nprogress";
|
||||
import type { UserModule } from "~/types";
|
||||
import { nextTick } from "vue";
|
||||
|
||||
export const install: UserModule = ({ isClient, router }) => {
|
||||
if (isClient) {
|
||||
router.beforeEach(() => {
|
||||
NProgress.start();
|
||||
});
|
||||
router.afterEach(async () => {
|
||||
await nextTick(() => NProgress.done());
|
||||
});
|
||||
}
|
||||
};
|
@ -1,20 +1,18 @@
|
||||
<script lang="ts" setup>
|
||||
import { useOrganization, useUser } from "~/composables/useApiHelper";
|
||||
import { useRoute, useRouter } from "vue-router";
|
||||
import { handleRequestError } from "~/composables/useErrorHandling";
|
||||
import { useContext } from "vite-ssr/vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { useOrganization, useUser } from "~/composables/useApiHelper";
|
||||
import { handleRequestError } from "~/composables/useErrorHandling";
|
||||
import { useErrorRedirect } from "~/lib/composables/useErrorRedirect";
|
||||
|
||||
const ctx = useContext();
|
||||
const i18n = useI18n();
|
||||
const route = useRoute();
|
||||
const user = await useUser(route.params.user as string).catch((e) => handleRequestError(e, ctx, i18n));
|
||||
const user = await useUser(route.params.user as string).catch((e) => handleRequestError(e, i18n));
|
||||
let organization = null;
|
||||
if (!user || !user.value) {
|
||||
await useRouter().replace(useErrorRedirect(useRoute(), 404, "Not found"));
|
||||
} else if (user.value?.isOrganization) {
|
||||
organization = await useOrganization(route.params.user as string).catch((e) => handleRequestError(e, ctx, i18n));
|
||||
organization = await useOrganization(route.params.user as string).catch((e) => handleRequestError(e, i18n));
|
||||
}
|
||||
</script>
|
||||
|
||||
|
@ -1,15 +1,14 @@
|
||||
<script lang="ts" setup>
|
||||
import { PropType, provide } from "vue";
|
||||
import { type PropType, provide } from "vue";
|
||||
import { User } from "hangar-api";
|
||||
import { useContext } from "vite-ssr/vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { useProject } from "~/composables/useApiHelper";
|
||||
import { useRoute, useRouter } from "vue-router";
|
||||
import { HangarProjectPage } from "hangar-internal";
|
||||
import { useProject } from "~/composables/useApiHelper";
|
||||
import { handleRequestError } from "~/composables/useErrorHandling";
|
||||
import { useErrorRedirect } from "~/lib/composables/useErrorRedirect";
|
||||
import ProjectHeader from "~/components/projects/ProjectHeader.vue";
|
||||
import ProjectNav from "~/components/projects/ProjectNav.vue";
|
||||
import { HangarProjectPage } from "hangar-internal";
|
||||
|
||||
defineProps({
|
||||
user: {
|
||||
@ -18,10 +17,9 @@ defineProps({
|
||||
},
|
||||
});
|
||||
|
||||
const ctx = useContext();
|
||||
const i18n = useI18n();
|
||||
const route = useRoute();
|
||||
const project = await useProject(route.params.user as string, route.params.project as string).catch((e) => handleRequestError(e, ctx, i18n));
|
||||
const project = await useProject(route.params.user as string, route.params.project as string).catch((e) => handleRequestError(e, i18n));
|
||||
if (!project || !project.value) {
|
||||
await useRouter().replace(useErrorRedirect(route, 404, "Not found"));
|
||||
}
|
||||
|
@ -1,33 +1,36 @@
|
||||
<script lang="ts" setup>
|
||||
import Card from "~/lib/components/design/Card.vue";
|
||||
import { User } from "hangar-api";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { HangarProject, ProjectChannel } from "hangar-internal";
|
||||
import { useHead } from "@vueuse/head";
|
||||
import { useRoute } from "vue-router";
|
||||
import Card from "~/lib/components/design/Card.vue";
|
||||
import { Header } from "~/components/SortableTable.vue";
|
||||
import { ChannelFlag } from "~/types/enums";
|
||||
import { useContext } from "vite-ssr/vue";
|
||||
import { useProjectChannels } from "~/composables/useApiHelper";
|
||||
import { handleRequestError } from "~/composables/useErrorHandling";
|
||||
import { HangarProject, ProjectChannel } from "hangar-internal";
|
||||
import { useInternalApi } from "~/composables/useApi";
|
||||
import Table from "~/lib/components/design/Table.vue";
|
||||
import Tag from "~/components/Tag.vue";
|
||||
import Button from "~/lib/components/design/Button.vue";
|
||||
import { useBackendDataStore } from "~/store/backendData";
|
||||
import ChannelModal from "~/components/modals/ChannelModal.vue";
|
||||
import { useHead } from "@vueuse/head";
|
||||
import { useSeo } from "~/composables/useSeo";
|
||||
import { projectIconUrl } from "~/composables/useUrlHelper";
|
||||
import { useRoute } from "vue-router";
|
||||
import { useNotificationStore } from "~/lib/store/notification";
|
||||
import { definePageMeta } from "#imports";
|
||||
|
||||
definePageMeta({
|
||||
projectPermsRequired: ["EDIT_CHANNELS"],
|
||||
});
|
||||
|
||||
const props = defineProps<{
|
||||
user: User;
|
||||
project: HangarProject;
|
||||
}>();
|
||||
const i18n = useI18n();
|
||||
const ctx = useContext();
|
||||
const route = useRoute();
|
||||
const channels = await useProjectChannels(props.project.namespace.owner, props.project.namespace.slug).catch((e) => handleRequestError(e, ctx, i18n));
|
||||
const channels = await useProjectChannels(props.project.namespace.owner, props.project.namespace.slug).catch((e) => handleRequestError(e, i18n));
|
||||
const validations = useBackendDataStore().validations;
|
||||
const notifications = useNotificationStore();
|
||||
|
||||
@ -37,7 +40,7 @@ useHead(
|
||||
|
||||
async function refreshChannels() {
|
||||
const newChannels = await useInternalApi<ProjectChannel[]>(`channels/${props.project.namespace.owner}/${props.project.namespace.slug}`, false).catch((e) =>
|
||||
handleRequestError(e, ctx, i18n)
|
||||
handleRequestError(e, i18n)
|
||||
);
|
||||
if (channels && newChannels) {
|
||||
channels.value = newChannels;
|
||||
@ -50,7 +53,7 @@ async function deleteChannel(channel: ProjectChannel) {
|
||||
refreshChannels();
|
||||
notifications.warn(i18n.t("channel.modal.success.deletedChannel", [channel.name]));
|
||||
})
|
||||
.catch((e) => handleRequestError(e, ctx, i18n));
|
||||
.catch((e) => handleRequestError(e, i18n));
|
||||
}
|
||||
|
||||
async function addChannel(channel: ProjectChannel) {
|
||||
@ -63,7 +66,7 @@ async function addChannel(channel: ProjectChannel) {
|
||||
refreshChannels();
|
||||
notifications.success(i18n.t("channel.modal.success.addedChannel", [channel.name]));
|
||||
})
|
||||
.catch((e) => handleRequestError(e, ctx, i18n));
|
||||
.catch((e) => handleRequestError(e, i18n));
|
||||
}
|
||||
|
||||
async function editChannel(channel: ProjectChannel) {
|
||||
@ -78,7 +81,7 @@ async function editChannel(channel: ProjectChannel) {
|
||||
refreshChannels();
|
||||
notifications.success(i18n.t("channel.modal.success.editedChannel", [channel.name]));
|
||||
})
|
||||
.catch((e) => handleRequestError(e, ctx, i18n));
|
||||
.catch((e) => handleRequestError(e, i18n));
|
||||
}
|
||||
</script>
|
||||
|
||||
@ -134,8 +137,3 @@ async function editChannel(channel: ProjectChannel) {
|
||||
</ChannelModal>
|
||||
</Card>
|
||||
</template>
|
||||
|
||||
<route lang="yaml">
|
||||
meta:
|
||||
requireProjectPerm: ["EDIT_CHANNELS"]
|
||||
</route>
|
||||
|
@ -1,9 +1,9 @@
|
||||
<script lang="ts" setup>
|
||||
import { useHead } from "@vueuse/head";
|
||||
import { useSeo } from "~/composables/useSeo";
|
||||
import { projectIconUrl } from "~/composables/useUrlHelper";
|
||||
import { useRoute } from "vue-router";
|
||||
import { HangarProject } from "hangar-internal";
|
||||
import { useSeo } from "~/composables/useSeo";
|
||||
import { projectIconUrl } from "~/composables/useUrlHelper";
|
||||
|
||||
const route = useRoute();
|
||||
const props = defineProps<{
|
||||
|
@ -1,27 +1,30 @@
|
||||
<script lang="ts" setup>
|
||||
import Card from "~/lib/components/design/Card.vue";
|
||||
import Link from "~/lib/components/design/Link.vue";
|
||||
import { User } from "hangar-api";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import SortableTable, { Header } from "~/components/SortableTable.vue";
|
||||
import Alert from "~/lib/components/design/Alert.vue";
|
||||
import { useContext } from "vite-ssr/vue";
|
||||
import { useProjectFlags } from "~/composables/useApiHelper";
|
||||
import { handleRequestError } from "~/composables/useErrorHandling";
|
||||
import { Flag, HangarProject } from "hangar-internal";
|
||||
import { useHead } from "@vueuse/head";
|
||||
import { useRoute } from "vue-router";
|
||||
import Card from "~/lib/components/design/Card.vue";
|
||||
import Link from "~/lib/components/design/Link.vue";
|
||||
import SortableTable, { Header } from "~/components/SortableTable.vue";
|
||||
import Alert from "~/lib/components/design/Alert.vue";
|
||||
import { useProjectFlags } from "~/composables/useApiHelper";
|
||||
import { handleRequestError } from "~/composables/useErrorHandling";
|
||||
import { useSeo } from "~/composables/useSeo";
|
||||
import { projectIconUrl } from "~/composables/useUrlHelper";
|
||||
import { useRoute } from "vue-router";
|
||||
import { definePageMeta } from "#imports";
|
||||
|
||||
definePageMeta({
|
||||
projectPermsRequired: ["MOD_NOTES_AND_FLAGS"],
|
||||
});
|
||||
|
||||
const props = defineProps<{
|
||||
user: User;
|
||||
project: HangarProject;
|
||||
}>();
|
||||
const i18n = useI18n();
|
||||
const ctx = useContext();
|
||||
const route = useRoute();
|
||||
const flags = await useProjectFlags(props.project.id).catch((e) => handleRequestError(e, ctx, i18n));
|
||||
const flags = await useProjectFlags(props.project.id).catch((e) => handleRequestError(e, i18n));
|
||||
|
||||
const headers = [
|
||||
{ title: "Submitter", name: "user" },
|
||||
@ -65,8 +68,3 @@ useHead(useSeo("Flags | " + props.project.name, props.project.description, route
|
||||
</SortableTable>
|
||||
</Card>
|
||||
</template>
|
||||
|
||||
<route lang="yaml">
|
||||
meta:
|
||||
requireGlobalPerm: ["MOD_NOTES_AND_FLAGS"]
|
||||
</route>
|
||||
|
@ -1,18 +1,17 @@
|
||||
<script lang="ts" setup>
|
||||
import { User } from "hangar-api";
|
||||
import Card from "~/lib/components/design/Card.vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import ProjectInfo from "~/components/projects/ProjectInfo.vue";
|
||||
import { HangarProject, PinnedVersion } from "hangar-internal";
|
||||
import { useRoute, useRouter } from "vue-router";
|
||||
import { ref } from "vue";
|
||||
import Card from "~/lib/components/design/Card.vue";
|
||||
import ProjectInfo from "~/components/projects/ProjectInfo.vue";
|
||||
import MemberList from "~/components/projects/MemberList.vue";
|
||||
import MarkdownEditor from "~/components/MarkdownEditor.vue";
|
||||
import { hasPerms } from "~/composables/usePerm";
|
||||
import { NamedPermission } from "~/types/enums";
|
||||
import { useRoute, useRouter } from "vue-router";
|
||||
import { useContext } from "vite-ssr/vue";
|
||||
import Markdown from "~/components/Markdown.vue";
|
||||
import ProjectPageList from "~/components/projects/ProjectPageList.vue";
|
||||
import { ref } from "vue";
|
||||
import { useInternalApi } from "~/composables/useApi";
|
||||
import { handleRequestError } from "~/composables/useErrorHandling";
|
||||
import { useBackendDataStore } from "~/store/backendData";
|
||||
@ -28,9 +27,7 @@ const props = defineProps<{
|
||||
}>();
|
||||
const i18n = useI18n();
|
||||
const backendData = useBackendDataStore();
|
||||
const ctx = useContext();
|
||||
const route = useRoute();
|
||||
const context = useContext();
|
||||
const router = useRouter();
|
||||
const openProjectPages = await useOpenProjectPages(route, props.project);
|
||||
|
||||
@ -44,7 +41,7 @@ function saveSponsors(content: string) {
|
||||
sponsors.value = content;
|
||||
editingSponsors.value = false;
|
||||
})
|
||||
.catch((e) => handleRequestError(e, ctx, i18n, "page.new.error.save"));
|
||||
.catch((e) => handleRequestError(e, i18n, "page.new.error.save"));
|
||||
}
|
||||
|
||||
function createPinnedVersionUrl(version: PinnedVersion): string {
|
||||
|
@ -1,31 +1,34 @@
|
||||
<script lang="ts" setup>
|
||||
import Card from "~/lib/components/design/Card.vue";
|
||||
import Link from "~/lib/components/design/Link.vue";
|
||||
import { User } from "hangar-api";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import SortableTable, { Header } from "~/components/SortableTable.vue";
|
||||
import Alert from "~/lib/components/design/Alert.vue";
|
||||
import { useContext } from "vite-ssr/vue";
|
||||
import { useProjectNotes } from "~/composables/useApiHelper";
|
||||
import { handleRequestError } from "~/composables/useErrorHandling";
|
||||
import { HangarProject, Note } from "hangar-internal";
|
||||
import { ref } from "vue";
|
||||
import { useHead } from "@vueuse/head";
|
||||
import { useRoute } from "vue-router";
|
||||
import Card from "~/lib/components/design/Card.vue";
|
||||
import Link from "~/lib/components/design/Link.vue";
|
||||
import SortableTable, { Header } from "~/components/SortableTable.vue";
|
||||
import Alert from "~/lib/components/design/Alert.vue";
|
||||
import { useProjectNotes } from "~/composables/useApiHelper";
|
||||
import { handleRequestError } from "~/composables/useErrorHandling";
|
||||
import { useInternalApi } from "~/composables/useApi";
|
||||
import InputText from "~/lib/components/ui/InputText.vue";
|
||||
import Button from "~/lib/components/design/Button.vue";
|
||||
import { useHead } from "@vueuse/head";
|
||||
import { useSeo } from "~/composables/useSeo";
|
||||
import { projectIconUrl } from "~/composables/useUrlHelper";
|
||||
import { useRoute } from "vue-router";
|
||||
import { definePageMeta } from "#imports";
|
||||
|
||||
definePageMeta({
|
||||
projectPermsRequired: ["MOD_NOTES_AND_FLAGS"],
|
||||
});
|
||||
|
||||
const props = defineProps<{
|
||||
user: User;
|
||||
project: HangarProject;
|
||||
}>();
|
||||
const i18n = useI18n();
|
||||
const ctx = useContext();
|
||||
const route = useRoute();
|
||||
const notes = await useProjectNotes(props.project.id).catch((e) => handleRequestError(e, ctx, i18n));
|
||||
const notes = await useProjectNotes(props.project.id).catch((e) => handleRequestError(e, i18n));
|
||||
const text = ref("");
|
||||
const loading = ref(false);
|
||||
|
||||
@ -44,9 +47,9 @@ async function addNote() {
|
||||
loading.value = true;
|
||||
await useInternalApi(`projects/notes/${props.project.id}`, true, "post", {
|
||||
content: text.value,
|
||||
}).catch((e) => handleRequestError(e, ctx, i18n));
|
||||
}).catch((e) => handleRequestError(e, i18n));
|
||||
text.value = "";
|
||||
const newNotes = await useInternalApi<Note[]>("projects/notes/" + props.project.id, false).catch((e) => handleRequestError(e, ctx, i18n));
|
||||
const newNotes = await useInternalApi<Note[]>("projects/notes/" + props.project.id, false).catch((e) => handleRequestError(e, i18n));
|
||||
if (notes && newNotes) {
|
||||
notes.value = newNotes;
|
||||
}
|
||||
@ -81,8 +84,3 @@ async function addNote() {
|
||||
</SortableTable>
|
||||
</Card>
|
||||
</template>
|
||||
|
||||
<route lang="yaml">
|
||||
meta:
|
||||
requireGlobalPerm: ["MOD_NOTES_AND_FLAGS"]
|
||||
</route>
|
||||
|
@ -1,6 +1,5 @@
|
||||
<script lang="ts" setup>
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { useContext } from "vite-ssr/vue";
|
||||
import { useRoute, useRouter } from "vue-router";
|
||||
import { User } from "hangar-api";
|
||||
import { HangarProject } from "hangar-internal";
|
||||
@ -19,7 +18,6 @@ const props = defineProps<{
|
||||
}>();
|
||||
|
||||
const i18n = useI18n();
|
||||
const ctx = useContext();
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
|
||||
|
@ -1,43 +1,46 @@
|
||||
<script lang="ts" setup>
|
||||
import { useHead } from "@vueuse/head";
|
||||
import { useSeo } from "~/composables/useSeo";
|
||||
import { projectIconUrl } from "~/composables/useUrlHelper";
|
||||
import { useRoute, useRouter } from "vue-router";
|
||||
import { HangarProject } from "hangar-internal";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { computed, onMounted, reactive, ref, watch } from "vue";
|
||||
import { cloneDeep } from "lodash-es";
|
||||
import { useVuelidate } from "@vuelidate/core";
|
||||
import { Cropper, type CropperResult } from "vue-advanced-cropper";
|
||||
import { PaginatedResult, User } from "hangar-api";
|
||||
import { useSeo } from "~/composables/useSeo";
|
||||
import { projectIconUrl } from "~/composables/useUrlHelper";
|
||||
import Card from "~/lib/components/design/Card.vue";
|
||||
import MemberList from "~/components/projects/MemberList.vue";
|
||||
import { hasPerms } from "~/composables/usePerm";
|
||||
import { NamedPermission, Visibility } from "~/types/enums";
|
||||
import Button from "~/lib/components/design/Button.vue";
|
||||
import Tabs from "~/lib/components/design/Tabs.vue";
|
||||
import { computed, onMounted, reactive, ref, watch } from "vue";
|
||||
import InputSelect from "~/lib/components/ui/InputSelect.vue";
|
||||
import { useBackendDataStore } from "~/store/backendData";
|
||||
import InputText from "~/lib/components/ui/InputText.vue";
|
||||
import { cloneDeep } from "lodash-es";
|
||||
import InputCheckbox from "~/lib/components/ui/InputCheckbox.vue";
|
||||
import InputFile from "~/lib/components/ui/InputFile.vue";
|
||||
import { useApi, useInternalApi } from "~/composables/useApi";
|
||||
import { handleRequestError } from "~/composables/useErrorHandling";
|
||||
import { useContext } from "vite-ssr/vue";
|
||||
import { useNotificationStore } from "~/lib/store/notification";
|
||||
import InputTag from "~/lib/components/ui/InputTag.vue";
|
||||
import TextAreaModal from "~/lib/components/modals/TextAreaModal.vue";
|
||||
import ProjectSettingsSection from "~/components/projects/ProjectSettingsSection.vue";
|
||||
import { maxLength, required, requiredIf, url } from "~/lib/composables/useValidationHelpers";
|
||||
import { validProjectName } from "~/composables/useHangarValidations";
|
||||
import { useVuelidate } from "@vuelidate/core";
|
||||
import { Cropper, CropperResult } from "vue-advanced-cropper";
|
||||
|
||||
import "vue-advanced-cropper/dist/style.css";
|
||||
import { PaginatedResult, User } from "hangar-api";
|
||||
import InputAutocomplete from "~/lib/components/ui/InputAutocomplete.vue";
|
||||
import { definePageMeta } from "#imports";
|
||||
|
||||
definePageMeta({
|
||||
projectPermsRequired: ["EDIT_SUBJECT_SETTINGS"],
|
||||
});
|
||||
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const i18n = useI18n();
|
||||
const ctx = useContext();
|
||||
const backendData = useBackendDataStore();
|
||||
const v = useVuelidate();
|
||||
const notificationStore = useNotificationStore();
|
||||
@ -137,7 +140,7 @@ async function save() {
|
||||
});
|
||||
await router.go(0);
|
||||
} catch (e: any) {
|
||||
handleRequestError(e, ctx, i18n);
|
||||
handleRequestError(e, i18n);
|
||||
}
|
||||
loading.save = false;
|
||||
}
|
||||
@ -150,7 +153,7 @@ async function transfer() {
|
||||
});
|
||||
notificationStore.success(i18n.t("project.settings.success.transferRequest", [search.value]));
|
||||
} catch (e: any) {
|
||||
handleRequestError(e, ctx, i18n);
|
||||
handleRequestError(e, i18n);
|
||||
}
|
||||
loading.transfer = false;
|
||||
}
|
||||
@ -164,7 +167,7 @@ async function rename() {
|
||||
notificationStore.success(i18n.t("project.settings.success.rename", [newName.value]));
|
||||
await router.push("/" + route.params.user + "/" + newSlug);
|
||||
} catch (e: any) {
|
||||
handleRequestError(e, ctx, i18n);
|
||||
handleRequestError(e, i18n);
|
||||
}
|
||||
loading.rename = false;
|
||||
}
|
||||
@ -181,7 +184,7 @@ async function softDelete(comment: string) {
|
||||
await router.push("/");
|
||||
}
|
||||
} catch (e: any) {
|
||||
handleRequestError(e, ctx, i18n);
|
||||
handleRequestError(e, i18n);
|
||||
}
|
||||
}
|
||||
|
||||
@ -193,7 +196,7 @@ async function hardDelete(comment: string) {
|
||||
notificationStore.success(i18n.t("project.settings.success.hardDelete"));
|
||||
await router.push("/");
|
||||
} catch (e: any) {
|
||||
handleRequestError(e, ctx, i18n);
|
||||
handleRequestError(e, i18n);
|
||||
}
|
||||
}
|
||||
|
||||
@ -215,7 +218,7 @@ async function uploadIcon() {
|
||||
useNotificationStore().success(i18n.t("project.settings.success.changedIcon"));
|
||||
}
|
||||
} catch (e: any) {
|
||||
handleRequestError(e, ctx, i18n);
|
||||
handleRequestError(e, i18n);
|
||||
}
|
||||
loading.uploadIcon = false;
|
||||
}
|
||||
@ -232,7 +235,7 @@ async function resetIcon() {
|
||||
projectIcon.value = null;
|
||||
await loadIconIntoCropper();
|
||||
} catch (e: any) {
|
||||
handleRequestError(e, ctx, i18n);
|
||||
handleRequestError(e, i18n);
|
||||
}
|
||||
loading.resetIcon = false;
|
||||
}
|
||||
@ -447,8 +450,3 @@ useHead(
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<route lang="yaml">
|
||||
meta:
|
||||
requireProjectPerm: ["EDIT_SUBJECT_SETTINGS"]
|
||||
</route>
|
||||
|
@ -1,7 +1,8 @@
|
||||
<script lang="ts" setup>
|
||||
import { useRoute } from "vue-router";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { useContext } from "vite-ssr/vue";
|
||||
import { useHead } from "@vueuse/head";
|
||||
import { HangarProject } from "hangar-internal";
|
||||
import { handleRequestError } from "~/composables/useErrorHandling";
|
||||
import Card from "~/lib/components/design/Card.vue";
|
||||
import PageTitle from "~/lib/components/design/PageTitle.vue";
|
||||
@ -10,14 +11,11 @@ import { avatarUrl, projectIconUrl } from "~/composables/useUrlHelper";
|
||||
import Alert from "~/lib/components/design/Alert.vue";
|
||||
import { useStargazers } from "~/composables/useApiHelper";
|
||||
import Link from "~/lib/components/design/Link.vue";
|
||||
import { useHead } from "@vueuse/head";
|
||||
import { useSeo } from "~/composables/useSeo";
|
||||
import { HangarProject } from "hangar-internal";
|
||||
|
||||
const route = useRoute();
|
||||
const i18n = useI18n();
|
||||
const ctx = useContext();
|
||||
const stargazers = await useStargazers(route.params.user as string, route.params.project as string).catch<any>((e) => handleRequestError(e, ctx, i18n));
|
||||
const stargazers = await useStargazers(route.params.user as string, route.params.project as string).catch<any>((e) => handleRequestError(e, i18n));
|
||||
|
||||
const props = defineProps<{
|
||||
project: HangarProject;
|
||||
|
@ -1,14 +1,12 @@
|
||||
<script lang="ts" setup>
|
||||
import { useProjectVersionsInternal } from "~/composables/useApiHelper";
|
||||
import { useContext } from "vite-ssr/vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { useRoute, useRouter } from "vue-router";
|
||||
import { handleRequestError } from "~/composables/useErrorHandling";
|
||||
import { HangarProject } from "hangar-internal";
|
||||
import { handleRequestError } from "~/composables/useErrorHandling";
|
||||
import { useProjectVersionsInternal } from "~/composables/useApiHelper";
|
||||
import { useErrorRedirect } from "~/lib/composables/useErrorRedirect";
|
||||
import { Platform } from "~/types/enums";
|
||||
|
||||
const ctx = useContext();
|
||||
const i18n = useI18n();
|
||||
const route = useRoute();
|
||||
|
||||
@ -17,7 +15,7 @@ const props = defineProps<{
|
||||
}>();
|
||||
|
||||
const version = await useProjectVersionsInternal(route.params.user as string, route.params.project as string, route.params.version as string).catch((e) =>
|
||||
handleRequestError(e, ctx, i18n)
|
||||
handleRequestError(e, i18n)
|
||||
);
|
||||
if (!version || !version.value) {
|
||||
await useRouter().replace(useErrorRedirect(route, 404, "Not found"));
|
||||
|
@ -1,15 +1,17 @@
|
||||
<script lang="ts" setup>
|
||||
import { NamedPermission, Platform, ReviewState, Visibility, PinnedStatus } from "~/types/enums";
|
||||
import { HangarProject, HangarVersion, IPlatform } from "hangar-internal";
|
||||
import { computed, ref } from "vue";
|
||||
import { useRoute, useRouter } from "vue-router";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { useContext } from "vite-ssr/vue";
|
||||
import { User } from "hangar-api";
|
||||
import { useHead } from "@vueuse/head";
|
||||
import { AxiosError } from "axios";
|
||||
import { filesize } from "filesize";
|
||||
import { NamedPermission, Platform, ReviewState, Visibility, PinnedStatus } from "~/types/enums";
|
||||
import { useBackendDataStore } from "~/store/backendData";
|
||||
import { lastUpdated } from "~/lib/composables/useTime";
|
||||
import { useInternalApi } from "~/composables/useApi";
|
||||
import { handleRequestError } from "~/composables/useErrorHandling";
|
||||
import { User } from "hangar-api";
|
||||
import { useErrorRedirect } from "~/lib/composables/useErrorRedirect";
|
||||
import TagComponent from "~/components/Tag.vue";
|
||||
import { hasPerms } from "~/composables/usePerm";
|
||||
@ -18,24 +20,20 @@ import MarkdownEditor from "~/components/MarkdownEditor.vue";
|
||||
import Markdown from "~/components/Markdown.vue";
|
||||
import Card from "~/lib/components/design/Card.vue";
|
||||
import Link from "~/lib/components/design/Link.vue";
|
||||
import { useHead } from "@vueuse/head";
|
||||
import { useSeo } from "~/composables/useSeo";
|
||||
import { projectIconUrl } from "~/composables/useUrlHelper";
|
||||
import { useNotificationStore } from "~/lib/store/notification";
|
||||
import DropdownButton from "~/lib/components/design/DropdownButton.vue";
|
||||
import DropdownItem from "~/lib/components/design/DropdownItem.vue";
|
||||
import PlatformVersionEditModal from "~/components/modals/PlatformVersionEditModal.vue";
|
||||
import { AxiosError } from "axios";
|
||||
import Tooltip from "~/lib/components/design/Tooltip.vue";
|
||||
import DownloadButton from "~/components/projects/DownloadButton.vue";
|
||||
import PlatformLogo from "~/components/logos/platforms/PlatformLogo.vue";
|
||||
import TextAreaModal from "~/lib/components/modals/TextAreaModal.vue";
|
||||
import DependencyEditModal from "~/components/modals/DependencyEditModal.vue";
|
||||
import { filesize } from "filesize";
|
||||
|
||||
const route = useRoute();
|
||||
const i18n = useI18n();
|
||||
const ctx = useContext();
|
||||
const router = useRouter();
|
||||
const backendData = useBackendDataStore();
|
||||
const notification = useNotificationStore();
|
||||
@ -102,7 +100,7 @@ async function savePage(content: string) {
|
||||
}
|
||||
editingPage.value = false;
|
||||
} catch (err) {
|
||||
handleRequestError(err as AxiosError, ctx, i18n, "page.new.error.save");
|
||||
handleRequestError(err as AxiosError, i18n, "page.new.error.save");
|
||||
}
|
||||
}
|
||||
|
||||
@ -112,7 +110,7 @@ async function setPinned(value: boolean) {
|
||||
notification.success(i18n.t(`version.page.pinned.request.${value}`));
|
||||
router.go(0);
|
||||
} catch (e) {
|
||||
handleRequestError(e as AxiosError, ctx, i18n);
|
||||
handleRequestError(e as AxiosError, i18n);
|
||||
}
|
||||
}
|
||||
|
||||
@ -124,7 +122,7 @@ async function deleteVersion(comment: string) {
|
||||
notification.success(i18n.t("version.success.softDelete"));
|
||||
await router.replace(`/${route.params.user}/${route.params.project}/versions`);
|
||||
} catch (e) {
|
||||
handleRequestError(e as AxiosError, ctx, i18n);
|
||||
handleRequestError(e as AxiosError, i18n);
|
||||
}
|
||||
}
|
||||
|
||||
@ -141,7 +139,7 @@ async function hardDeleteVersion(comment: string) {
|
||||
},
|
||||
});
|
||||
} catch (e) {
|
||||
handleRequestError(e as AxiosError, ctx, i18n);
|
||||
handleRequestError(e as AxiosError, i18n);
|
||||
}
|
||||
}
|
||||
|
||||
@ -151,7 +149,7 @@ async function restoreVersion() {
|
||||
notification.success(i18n.t("version.success.restore"));
|
||||
await router.replace(`/${route.params.user}/${route.params.project}/versions`);
|
||||
} catch (e) {
|
||||
handleRequestError(e as AxiosError, ctx, i18n);
|
||||
handleRequestError(e as AxiosError, i18n);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
@ -1,34 +1,37 @@
|
||||
<script lang="ts" setup>
|
||||
import { useHead } from "@vueuse/head";
|
||||
import { useSeo } from "~/composables/useSeo";
|
||||
import { useRoute } from "vue-router";
|
||||
import { Platform, ReviewAction, ReviewState } from "~/types/enums";
|
||||
import { HangarProject, HangarReview, HangarReviewMessage, HangarVersion, IPlatform } from "hangar-internal";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { computed, reactive, ref } from "vue";
|
||||
import { useVuelidate } from "@vuelidate/core";
|
||||
import { useSeo } from "~/composables/useSeo";
|
||||
import { Platform, ReviewAction, ReviewState } from "~/types/enums";
|
||||
import { projectIconUrl } from "~/composables/useUrlHelper";
|
||||
import Button from "~/lib/components/design/Button.vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import InputCheckbox from "~/lib/components/ui/InputCheckbox.vue";
|
||||
import InputTextarea from "~/lib/components/ui/InputTextarea.vue";
|
||||
import Alert from "~/lib/components/design/Alert.vue";
|
||||
import { computed, reactive, ref } from "vue";
|
||||
import { useInternalApi } from "~/composables/useApi";
|
||||
import { useAuthStore } from "~/store/auth";
|
||||
import { prettyDate, prettyDateTime } from "~/lib/composables/useDate";
|
||||
import { useBackendDataStore } from "~/store/backendData";
|
||||
import { handleRequestError } from "~/composables/useErrorHandling";
|
||||
import { useContext } from "vite-ssr/vue";
|
||||
import { useVuelidate } from "@vuelidate/core";
|
||||
import Tag from "~/components/Tag.vue";
|
||||
import Accordeon from "~/lib/components/design/Accordeon.vue";
|
||||
import TextAreaModal from "~/lib/components/modals/TextAreaModal.vue";
|
||||
import DownloadButton from "~/components/projects/DownloadButton.vue";
|
||||
import { definePageMeta } from "#imports";
|
||||
|
||||
definePageMeta({
|
||||
globalPermsRequired: ["REVIEWER"],
|
||||
});
|
||||
|
||||
const route = useRoute();
|
||||
const authStore = useAuthStore();
|
||||
const backendDataStore = useBackendDataStore();
|
||||
const i18n = useI18n();
|
||||
const t = i18n.t;
|
||||
const ctx = useContext();
|
||||
const v = useVuelidate();
|
||||
|
||||
const props = defineProps<{
|
||||
@ -315,7 +318,7 @@ function sendReviewRequest(
|
||||
then();
|
||||
refresh();
|
||||
})
|
||||
.catch((e) => handleRequestError(e, ctx, i18n))
|
||||
.catch((e) => handleRequestError(e, i18n))
|
||||
.finally(final);
|
||||
}
|
||||
|
||||
@ -443,8 +446,3 @@ useHead(
|
||||
</Alert>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<route lang="yaml">
|
||||
meta:
|
||||
requireGlobalPerm: ["REVIEWER"]
|
||||
</route>
|
||||
|
@ -1,22 +1,21 @@
|
||||
<script lang="ts" setup>
|
||||
import Link from "~/lib/components/design/Link.vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { PaginatedResult, Version } from "hangar-api";
|
||||
import { computed, reactive, watch } from "vue";
|
||||
import { useRoute } from "vue-router";
|
||||
import { HangarProject } from "hangar-internal";
|
||||
import { useHead } from "@vueuse/head";
|
||||
import Link from "~/lib/components/design/Link.vue";
|
||||
import { hasPerms } from "~/composables/usePerm";
|
||||
import { NamedPermission, Platform, Visibility } from "~/types/enums";
|
||||
import Card from "~/lib/components/design/Card.vue";
|
||||
import InputCheckbox from "~/lib/components/ui/InputCheckbox.vue";
|
||||
import Tag from "~/components/Tag.vue";
|
||||
import Button from "~/lib/components/design/Button.vue";
|
||||
import { PaginatedResult, Version } from "hangar-api";
|
||||
import { computed, reactive, watch } from "vue";
|
||||
import { useBackendDataStore } from "~/store/backendData";
|
||||
import { useProjectChannels, useProjectVersions } from "~/composables/useApiHelper";
|
||||
import { useContext } from "vite-ssr/vue";
|
||||
import { handleRequestError } from "~/composables/useErrorHandling";
|
||||
import { useRoute } from "vue-router";
|
||||
import { useApi } from "~/composables/useApi";
|
||||
import { HangarProject } from "hangar-internal";
|
||||
import { useHead } from "@vueuse/head";
|
||||
import { useSeo } from "~/composables/useSeo";
|
||||
import { projectIconUrl } from "~/composables/useUrlHelper";
|
||||
import Alert from "~/lib/components/design/Alert.vue";
|
||||
@ -24,7 +23,6 @@ import Pagination from "~/lib/components/design/Pagination.vue";
|
||||
import PlatformLogo from "~/components/logos/platforms/PlatformLogo.vue";
|
||||
|
||||
const i18n = useI18n();
|
||||
const ctx = useContext();
|
||||
const route = useRoute();
|
||||
const backendData = useBackendDataStore();
|
||||
|
||||
@ -50,8 +48,8 @@ const requestOptions = computed(() => {
|
||||
};
|
||||
});
|
||||
|
||||
const channels = await useProjectChannels(route.params.user as string, route.params.project as string).catch((e) => handleRequestError(e, ctx, i18n));
|
||||
const versions = await useProjectVersions(route.params.user as string, route.params.project as string).catch((e) => handleRequestError(e, ctx, i18n));
|
||||
const channels = await useProjectChannels(route.params.user as string, route.params.project as string).catch((e) => handleRequestError(e, i18n));
|
||||
const versions = await useProjectVersions(route.params.user as string, route.params.project as string).catch((e) => handleRequestError(e, i18n));
|
||||
|
||||
if (channels) {
|
||||
filter.channels.push(...(channels.value?.map((c) => c.name) || []));
|
||||
@ -77,7 +75,7 @@ watch(
|
||||
false,
|
||||
"get",
|
||||
requestOptions.value
|
||||
).catch((e) => handleRequestError(e, ctx, i18n));
|
||||
).catch((e) => handleRequestError(e, i18n));
|
||||
if (newVersions) {
|
||||
versions.value = newVersions;
|
||||
}
|
||||
|
@ -1,13 +1,15 @@
|
||||
<script lang="ts" setup>
|
||||
import { PluginDependency } from "hangar-api";
|
||||
import { useHead } from "@vueuse/head";
|
||||
import { useSeo } from "~/composables/useSeo";
|
||||
import { projectIconUrl } from "~/composables/useUrlHelper";
|
||||
import { HangarProject, IPlatform, PendingVersion, ProjectChannel } from "hangar-internal";
|
||||
import { useRoute, useRouter } from "vue-router";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { computed, reactive, type Ref, ref } from "vue";
|
||||
import { remove } from "lodash-es";
|
||||
import { type ValidationRule } from "@vuelidate/core";
|
||||
import { useSeo } from "~/composables/useSeo";
|
||||
import { projectIconUrl } from "~/composables/useUrlHelper";
|
||||
import Steps, { Step } from "~/lib/components/design/Steps.vue";
|
||||
import { computed, reactive, Ref, ref } from "vue";
|
||||
import InputFile from "~/lib/components/ui/InputFile.vue";
|
||||
import InputText from "~/lib/components/ui/InputText.vue";
|
||||
import InputSelect from "~/lib/components/ui/InputSelect.vue";
|
||||
@ -18,21 +20,22 @@ import { required, url as validUrl } from "~/lib/composables/useValidationHelper
|
||||
import { useInternalApi } from "~/composables/useApi";
|
||||
import { Platform } from "~/types/enums";
|
||||
import { handleRequestError } from "~/composables/useErrorHandling";
|
||||
import { useContext } from "vite-ssr/vue";
|
||||
import { formatSize } from "~/lib/composables/useFile";
|
||||
import ChannelModal from "~/components/modals/ChannelModal.vue";
|
||||
import { remove } from "lodash-es";
|
||||
import { useBackendDataStore } from "~/store/backendData";
|
||||
import DependencyTable from "~/components/projects/DependencyTable.vue";
|
||||
import InputTag from "~/lib/components/ui/InputTag.vue";
|
||||
import Tabs, { Tab } from "~/lib/components/design/Tabs.vue";
|
||||
import PlatformLogo from "~/components/logos/platforms/PlatformLogo.vue";
|
||||
import { useProjectChannels } from "~/composables/useApiHelper";
|
||||
import { ValidationRule } from "@vuelidate/core";
|
||||
import { definePageMeta } from "#imports";
|
||||
|
||||
definePageMeta({
|
||||
projectPermsRequired: ["CREATE_VERSION"],
|
||||
});
|
||||
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const ctx = useContext();
|
||||
const i18n = useI18n();
|
||||
const t = i18n.t;
|
||||
const backendData = useBackendDataStore();
|
||||
@ -45,7 +48,7 @@ const steps: Step[] = [
|
||||
{
|
||||
value: "artifact",
|
||||
header: t("version.new.steps.1.header"),
|
||||
beforeNext: async () => {
|
||||
beforeNext: () => {
|
||||
return createPendingVersion();
|
||||
},
|
||||
disableNext: computed(() => {
|
||||
@ -211,7 +214,7 @@ async function createPendingVersion() {
|
||||
);
|
||||
|
||||
pendingVersion.value = await useInternalApi<PendingVersion>(`versions/version/${props.project.id}/upload`, true, "post", formData).catch<any>((e) =>
|
||||
handleRequestError(e, ctx, i18n)
|
||||
handleRequestError(e, i18n)
|
||||
);
|
||||
loading.create = false;
|
||||
|
||||
@ -257,7 +260,7 @@ async function createVersion() {
|
||||
await useInternalApi(`versions/version/${props.project.id}/create`, true, "post", pendingVersion.value);
|
||||
await router.push(`/${route.params.user}/${route.params.project}/versions`);
|
||||
} catch (e: any) {
|
||||
handleRequestError(e, ctx, i18n);
|
||||
handleRequestError(e, i18n);
|
||||
} finally {
|
||||
loading.submit = false;
|
||||
}
|
||||
@ -408,8 +411,3 @@ useHead(
|
||||
</template>
|
||||
</Steps>
|
||||
</template>
|
||||
|
||||
<route lang="yaml">
|
||||
meta:
|
||||
requireProjectPerm: ["CREATE_VERSION"]
|
||||
</route>
|
||||
|
@ -1,7 +1,8 @@
|
||||
<script lang="ts" setup>
|
||||
import { useRoute } from "vue-router";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { useContext } from "vite-ssr/vue";
|
||||
import { useHead } from "@vueuse/head";
|
||||
import { HangarProject } from "hangar-internal";
|
||||
import { handleRequestError } from "~/composables/useErrorHandling";
|
||||
import Card from "~/lib/components/design/Card.vue";
|
||||
import PageTitle from "~/lib/components/design/PageTitle.vue";
|
||||
@ -10,14 +11,11 @@ import { avatarUrl, projectIconUrl } from "~/composables/useUrlHelper";
|
||||
import Alert from "~/lib/components/design/Alert.vue";
|
||||
import { useWatchers } from "~/composables/useApiHelper";
|
||||
import Link from "~/lib/components/design/Link.vue";
|
||||
import { useHead } from "@vueuse/head";
|
||||
import { useSeo } from "~/composables/useSeo";
|
||||
import { HangarProject } from "hangar-internal";
|
||||
|
||||
const route = useRoute();
|
||||
const i18n = useI18n();
|
||||
const ctx = useContext();
|
||||
const watchers = await useWatchers(route.params.user as string, route.params.project as string).catch<any>((e) => handleRequestError(e, ctx, i18n));
|
||||
const watchers = await useWatchers(route.params.user as string, route.params.project as string).catch<any>((e) => handleRequestError(e, i18n));
|
||||
|
||||
const props = defineProps<{
|
||||
project: HangarProject;
|
||||
|
@ -1,22 +1,21 @@
|
||||
<script setup lang="ts">
|
||||
import { User } from "hangar-api";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { useRoute } from "vue-router";
|
||||
import { useHead } from "@vueuse/head";
|
||||
import { Organization } from "hangar-internal";
|
||||
import { computed, type FunctionalComponent } from "vue";
|
||||
import ProjectList from "~/components/projects/ProjectList.vue";
|
||||
import Card from "~/lib/components/design/Card.vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { avatarUrl } from "~/composables/useUrlHelper";
|
||||
import UserAvatar from "~/components/UserAvatar.vue";
|
||||
import Link from "~/lib/components/design/Link.vue";
|
||||
import MemberList from "~/components/projects/MemberList.vue";
|
||||
import { useContext } from "vite-ssr/vue";
|
||||
import { useOrgVisibility, useUserData } from "~/composables/useApiHelper";
|
||||
import { useBackendDataStore } from "~/store/backendData";
|
||||
import { useAuthStore } from "~/store/auth";
|
||||
import { useRoute } from "vue-router";
|
||||
import { useSeo } from "~/composables/useSeo";
|
||||
import { useHead } from "@vueuse/head";
|
||||
import { Organization } from "hangar-internal";
|
||||
import UserHeader from "~/components/UserHeader.vue";
|
||||
import { computed, FunctionalComponent } from "vue";
|
||||
import { hasPerms } from "~/composables/usePerm";
|
||||
import { NamedPermission } from "~/types/enums";
|
||||
import Button from "~/lib/components/design/Button.vue";
|
||||
@ -36,7 +35,6 @@ const props = defineProps<{
|
||||
organization: Organization;
|
||||
}>();
|
||||
const i18n = useI18n();
|
||||
const ctx = useContext();
|
||||
|
||||
const route = useRoute();
|
||||
const { starred, watching, projects, organizations, pinned } = (await useUserData(props.user.name)).value || {};
|
||||
@ -80,99 +78,104 @@ useHead(useSeo(props.user.name, props.user.name + " is an author on Hangar. " +
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UserHeader :user="user" :organization="organization" />
|
||||
<div class="flex-basis-full flex flex-col gap-2 flex-grow md:max-w-2/3 md:min-w-1/3">
|
||||
<div v-for="project in pinned" :key="project.namespace">
|
||||
<ProjectCard :project="project"></ProjectCard>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-4 flex-basis-full flex-col md:flex-row">
|
||||
<div>
|
||||
<UserHeader :user="user" :organization="organization" />
|
||||
<div class="flex-basis-full flex flex-col gap-2 flex-grow md:max-w-2/3 md:min-w-1/3">
|
||||
<ProjectList :projects="projects"></ProjectList>
|
||||
<div v-for="project in pinned" :key="project.namespace">
|
||||
<ProjectCard :project="project"></ProjectCard>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-basis-full flex-grow md:max-w-1/3 md:min-w-1/3">
|
||||
<Card v-if="buttons.length !== 0" class="mb-4 border-solid border-top-4 border-top-red-500 dark:border-top-red-500">
|
||||
<template #header>{{ i18n.t("author.management") }}</template>
|
||||
<template v-if="organization && hasPerms(NamedPermission.IS_SUBJECT_OWNER)">
|
||||
<Tooltip :content="i18n.t('author.tooltips.transfer')">
|
||||
<OrgTransferModal :organization="user.name" />
|
||||
</Tooltip>
|
||||
<Tooltip :content="i18n.t('author.tooltips.delete')">
|
||||
<OrgDeleteModal :organization="user.name" />
|
||||
|
||||
<div class="flex gap-4 flex-basis-full flex-col md:flex-row">
|
||||
<div class="flex-basis-full flex flex-col gap-2 flex-grow md:max-w-2/3 md:min-w-1/3">
|
||||
<ProjectList :projects="projects"></ProjectList>
|
||||
</div>
|
||||
<div class="flex-basis-full flex-grow md:max-w-1/3 md:min-w-1/3">
|
||||
<Card v-if="buttons.length !== 0" class="mb-4 border-solid border-top-4 border-top-red-500 dark:border-top-red-500">
|
||||
<template #header>{{ i18n.t("author.management") }}</template>
|
||||
<template v-if="organization && hasPerms(NamedPermission.IS_SUBJECT_OWNER)">
|
||||
<Tooltip :content="i18n.t('author.tooltips.transfer')">
|
||||
<OrgTransferModal :organization="user.name" />
|
||||
</Tooltip>
|
||||
<Tooltip :content="i18n.t('author.tooltips.delete')">
|
||||
<OrgDeleteModal :organization="user.name" />
|
||||
</Tooltip>
|
||||
</template>
|
||||
|
||||
<Tooltip v-for="btn in buttons" :key="btn.name">
|
||||
<template #content>
|
||||
{{ i18n.t(`author.tooltips.${btn.name}`) }}
|
||||
</template>
|
||||
<Link v-bind="btn.attr">
|
||||
<Button size="small" class="mr-1 inline-flex"><component :is="btn.icon" /></Button>
|
||||
</Link>
|
||||
</Tooltip>
|
||||
|
||||
<LockUserModal v-if="!isCurrentUser && !user.isOrganization && hasPerms(NamedPermission.IS_STAFF)" :user="user" />
|
||||
</Card>
|
||||
|
||||
<template v-if="!user.isOrganization">
|
||||
<Card class="mb-4" accent>
|
||||
<template #header>
|
||||
<div class="inline-flex w-full">
|
||||
<span class="flex-grow">{{ i18n.t("author.orgs") }}</span>
|
||||
<OrgVisibilityModal
|
||||
v-if="organizationVisibility && organizations && Object.keys(organizations).length !== 0"
|
||||
v-model="organizationVisibility"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<ul>
|
||||
<li v-for="(orgRole, orgName) in organizations" :key="orgName">
|
||||
<router-link :to="'/' + orgName" class="flex items-center mb-2">
|
||||
<UserAvatar :username="orgName" :avatar-url="avatarUrl(orgName)" size="xs" :disable-link="true" class="flex-shrink-0 mr-2" />
|
||||
{{ orgName }} ({{ orgRole.role.title }})
|
||||
<span class="flex-grow"></span>
|
||||
<IconMdiEyeOffOutline v-if="organizationVisibility && organizationVisibility[orgName]" class="ml-1" />
|
||||
</router-link>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<span v-if="!organizations || Object.keys(organizations).length === 0">
|
||||
{{ i18n.t("author.noOrgs", [props.user.name]) }}
|
||||
</span>
|
||||
</Card>
|
||||
|
||||
<Card class="mb-4" accent>
|
||||
<template #header>{{ i18n.t("author.stars") }}</template>
|
||||
|
||||
<ul>
|
||||
<li v-for="star in starred?.result" :key="star.name">
|
||||
<Link :to="'/' + star.namespace.owner + '/' + star.namespace.slug">
|
||||
{{ star.namespace.owner }}/<strong>{{ star.name }}</strong>
|
||||
</Link>
|
||||
</li>
|
||||
|
||||
<span v-if="!starred || starred?.result?.length === 0">
|
||||
{{ i18n.t("author.noStarred", [props.user.name]) }}
|
||||
</span>
|
||||
</ul>
|
||||
</Card>
|
||||
|
||||
<Card accent>
|
||||
<template #header>{{ i18n.t("author.watching") }}</template>
|
||||
|
||||
<ul>
|
||||
<li v-for="watch in watching?.result" :key="watch.name">
|
||||
<Link :to="'/' + watch.namespace.owner + '/' + watch.namespace.slug"
|
||||
>{{ watch.namespace.owner }}/<strong>{{ watch.name }}</strong></Link
|
||||
>
|
||||
</li>
|
||||
|
||||
<span v-if="!watching || watching?.result?.length === 0">
|
||||
{{ i18n.t("author.noWatching", [props.user.name]) }}
|
||||
</span>
|
||||
</ul>
|
||||
</Card>
|
||||
</template>
|
||||
|
||||
<Tooltip v-for="btn in buttons" :key="btn.name">
|
||||
<template #content>
|
||||
{{ i18n.t(`author.tooltips.${btn.name}`) }}
|
||||
</template>
|
||||
<Link v-bind="btn.attr">
|
||||
<Button size="small" class="mr-1 inline-flex"><component :is="btn.icon" /></Button>
|
||||
</Link>
|
||||
</Tooltip>
|
||||
|
||||
<LockUserModal v-if="!isCurrentUser && !user.isOrganization && hasPerms(NamedPermission.IS_STAFF)" :user="user" />
|
||||
</Card>
|
||||
|
||||
<template v-if="!user.isOrganization">
|
||||
<Card class="mb-4" accent>
|
||||
<template #header>
|
||||
<div class="inline-flex w-full">
|
||||
<span class="flex-grow">{{ i18n.t("author.orgs") }}</span>
|
||||
<OrgVisibilityModal v-if="organizationVisibility && organizations && Object.keys(organizations).length !== 0" v-model="organizationVisibility" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<ul>
|
||||
<li v-for="(orgRole, orgName) in organizations" :key="orgName">
|
||||
<router-link :to="'/' + orgName" class="flex items-center mb-2">
|
||||
<UserAvatar :username="orgName" :avatar-url="avatarUrl(orgName)" size="xs" :disable-link="true" class="flex-shrink-0 mr-2" />
|
||||
{{ orgName }} ({{ orgRole.role.title }})
|
||||
<span class="flex-grow"></span>
|
||||
<IconMdiEyeOffOutline v-if="organizationVisibility && organizationVisibility[orgName]" class="ml-1" />
|
||||
</router-link>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<span v-if="!organizations || Object.keys(organizations).length === 0">
|
||||
{{ i18n.t("author.noOrgs", [props.user.name]) }}
|
||||
</span>
|
||||
</Card>
|
||||
|
||||
<Card class="mb-4" accent>
|
||||
<template #header>{{ i18n.t("author.stars") }}</template>
|
||||
|
||||
<ul>
|
||||
<li v-for="star in starred.result" :key="star.name">
|
||||
<Link :to="'/' + star.namespace.owner + '/' + star.namespace.slug">
|
||||
{{ star.namespace.owner }}/<strong>{{ star.name }}</strong>
|
||||
</Link>
|
||||
</li>
|
||||
|
||||
<span v-if="!starred || starred.result.length === 0">
|
||||
{{ i18n.t("author.noStarred", [props.user.name]) }}
|
||||
</span>
|
||||
</ul>
|
||||
</Card>
|
||||
|
||||
<Card accent>
|
||||
<template #header>{{ i18n.t("author.watching") }}</template>
|
||||
|
||||
<ul>
|
||||
<li v-for="watch in watching.result" :key="watch.name">
|
||||
<Link :to="'/' + watch.namespace.owner + '/' + watch.namespace.slug"
|
||||
>{{ watch.namespace.owner }}/<strong>{{ watch.name }}</strong></Link
|
||||
>
|
||||
</li>
|
||||
|
||||
<span v-if="!watching || watching.result.length === 0">
|
||||
{{ i18n.t("author.noWatching", [props.user.name]) }}
|
||||
</span>
|
||||
</ul>
|
||||
</Card>
|
||||
</template>
|
||||
<MemberList v-else :members="organization.members" :roles="orgRoles" organization :author="user.name" :owner="organization.owner.userId" />
|
||||
<MemberList v-else :members="organization.members" :roles="orgRoles" organization :author="user.name" :owner="organization.owner.userId" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -1,13 +1,14 @@
|
||||
<script lang="ts" setup>
|
||||
import PageTitle from "~/lib/components/design/PageTitle.vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import InputText from "~/lib/components/ui/InputText.vue";
|
||||
import Button from "~/lib/components/design/Button.vue";
|
||||
import { reactive, ref } from "vue";
|
||||
import { useContext } from "vite-ssr/vue";
|
||||
import { useInternalApi } from "~/composables/useApi";
|
||||
import { ApiKey, User } from "hangar-api";
|
||||
import { useRoute } from "vue-router";
|
||||
import { useHead } from "@vueuse/head";
|
||||
import { useVuelidate } from "@vuelidate/core";
|
||||
import PageTitle from "~/lib/components/design/PageTitle.vue";
|
||||
import InputText from "~/lib/components/ui/InputText.vue";
|
||||
import Button from "~/lib/components/design/Button.vue";
|
||||
import { useInternalApi } from "~/composables/useApi";
|
||||
import { handleRequestError } from "~/composables/useErrorHandling";
|
||||
import InputCheckbox from "~/lib/components/ui/InputCheckbox.vue";
|
||||
import Table from "~/lib/components/design/Table.vue";
|
||||
@ -15,15 +16,18 @@ import Alert from "~/lib/components/design/Alert.vue";
|
||||
import Card from "~/lib/components/design/Card.vue";
|
||||
import { useSeo } from "~/composables/useSeo";
|
||||
import { avatarUrl } from "~/composables/useUrlHelper";
|
||||
import { useHead } from "@vueuse/head";
|
||||
import { useNotificationStore } from "~/lib/store/notification";
|
||||
import { maxLength, minLength, required } from "~/lib/composables/useValidationHelpers";
|
||||
import { validApiKeyName } from "~/composables/useHangarValidations";
|
||||
import { useVuelidate } from "@vuelidate/core";
|
||||
import InputGroup from "~/lib/components/ui/InputGroup.vue";
|
||||
import { NamedPermission } from "~/types/enums";
|
||||
import { definePageMeta } from "#imports";
|
||||
|
||||
definePageMeta({
|
||||
currentUserRequired: true,
|
||||
globalPermsRequired: ["EDIT_API_KEYS"],
|
||||
});
|
||||
|
||||
const ctx = useContext();
|
||||
const i18n = useI18n();
|
||||
const route = useRoute();
|
||||
const notification = useNotificationStore();
|
||||
@ -49,7 +53,7 @@ async function create() {
|
||||
const key = await useInternalApi<string>(`api-keys/create-key/${route.params.user}`, true, "post", {
|
||||
name: name.value,
|
||||
permissions: selectedPerms.value,
|
||||
}).catch((err) => handleRequestError(err, ctx, i18n));
|
||||
}).catch((err) => handleRequestError(err, i18n));
|
||||
if (key) {
|
||||
apiKeys.value.unshift({
|
||||
token: key,
|
||||
@ -69,7 +73,7 @@ async function deleteKey(key: ApiKey) {
|
||||
loadingDelete[key.name] = true;
|
||||
await useInternalApi(`api-keys/delete-key/${route.params.user}`, true, "post", {
|
||||
content: key.name,
|
||||
}).catch((err) => handleRequestError(err, ctx, i18n));
|
||||
}).catch((err) => handleRequestError(err, i18n));
|
||||
apiKeys.value = apiKeys.value.filter((k) => k.name !== key.name);
|
||||
notification.success(i18n.t("apiKeys.success.delete", [key.name]));
|
||||
loadingDelete[key.name] = false;
|
||||
@ -143,12 +147,6 @@ async function deleteKey(key: ApiKey) {
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<route lang="yaml">
|
||||
meta:
|
||||
requireCurrentUser: true
|
||||
requireGlobalPerm: ["EDIT_API_KEYS"]
|
||||
</route>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.autofix {
|
||||
grid-template-columns: repeat(auto-fit, 250px);
|
||||
|
@ -2,5 +2,3 @@
|
||||
<!-- only here to organize routes nicer -->
|
||||
<router-view></router-view>
|
||||
</template>
|
||||
|
||||
<route lang="yaml"></route>
|
||||
|
@ -1,23 +1,26 @@
|
||||
<script lang="ts" setup>
|
||||
import { useInternalApi } from "~/composables/useApi";
|
||||
import { useRoute } from "vue-router";
|
||||
import { handleRequestError } from "~/composables/useErrorHandling";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { useContext } from "vite-ssr/vue";
|
||||
import { FlagActivity, ReviewActivity } from "hangar-internal";
|
||||
import { useHead } from "@vueuse/head";
|
||||
import { useInternalApi } from "~/composables/useApi";
|
||||
import { handleRequestError } from "~/composables/useErrorHandling";
|
||||
import PageTitle from "~/lib/components/design/PageTitle.vue";
|
||||
import Card from "~/lib/components/design/Card.vue";
|
||||
import Table from "~/lib/components/design/Table.vue";
|
||||
import Link from "~/lib/components/design/Link.vue";
|
||||
import Alert from "~/lib/components/design/Alert.vue";
|
||||
import { useHead } from "@vueuse/head";
|
||||
import { useSeo } from "~/composables/useSeo";
|
||||
import { definePageMeta } from "#imports";
|
||||
|
||||
definePageMeta({
|
||||
globalPermsRequired: ["REVIEWER"],
|
||||
});
|
||||
|
||||
const route = useRoute();
|
||||
const i18n = useI18n();
|
||||
const ctx = useContext();
|
||||
const flagActivities = await useInternalApi<FlagActivity[]>(`admin/activity/${route.params.user}/flags`).catch((e) => handleRequestError(e, ctx, i18n));
|
||||
const reviewActivities = await useInternalApi<ReviewActivity[]>(`admin/activity/${route.params.user}/reviews`).catch((e) => handleRequestError(e, ctx, i18n));
|
||||
const flagActivities = await useInternalApi<FlagActivity[]>(`admin/activity/${route.params.user}/flags`).catch((e) => handleRequestError(e, i18n));
|
||||
const reviewActivities = await useInternalApi<ReviewActivity[]>(`admin/activity/${route.params.user}/reviews`).catch((e) => handleRequestError(e, i18n));
|
||||
|
||||
useHead(useSeo(i18n.t("userActivity.title", [route.params.user]) + route.params.constructor, null, route, null));
|
||||
|
||||
@ -32,61 +35,58 @@ function getRouteParams(activity: ReviewActivity) {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<PageTitle>{{ i18n.t("userActivity.title", [route.params.user]) }}</PageTitle>
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<Card>
|
||||
<template #header>{{ i18n.t("userActivity.reviews") }}</template>
|
||||
<div>
|
||||
<PageTitle>{{ i18n.t("userActivity.title", [route.params.user]) }}</PageTitle>
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<Card>
|
||||
<template #header>{{ i18n.t("userActivity.reviews") }}</template>
|
||||
|
||||
<Table v-if="reviewActivities && reviewActivities.length">
|
||||
<tbody>
|
||||
<tr v-for="(activity, idx) in reviewActivities" :key="`review-${idx}`">
|
||||
<td>{{ i18n.t("userActivity.reviewApproved") }}</td>
|
||||
<td>{{ activity.endedAt ? i18n.d(activity.endedAt, "time") : "" }}</td>
|
||||
<td>
|
||||
{{ `${activity.namespace.owner}/${activity.namespace.slug}/${activity.versionString}: ${activity.platforms[0].toLowerCase()}` }}
|
||||
</td>
|
||||
<td>
|
||||
<Link
|
||||
:to="{
|
||||
name: 'user-project-versions-version-platform-reviews',
|
||||
params: getRouteParams(activity),
|
||||
}"
|
||||
>
|
||||
<IconMdiListStatus class="float-left"></IconMdiListStatus>
|
||||
{{ i18n.t("version.page.reviewLogs") }}
|
||||
</Link>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</Table>
|
||||
<Alert v-else type="success">
|
||||
{{ i18n.t("health.empty") }}
|
||||
</Alert>
|
||||
</Card>
|
||||
<Card>
|
||||
<template #header>{{ i18n.t("userActivity.flags") }}</template>
|
||||
<Table v-if="reviewActivities && reviewActivities.length">
|
||||
<tbody>
|
||||
<tr v-for="(activity, idx) in reviewActivities" :key="`review-${idx}`">
|
||||
<td>{{ i18n.t("userActivity.reviewApproved") }}</td>
|
||||
<td>{{ activity.endedAt ? i18n.d(activity.endedAt, "time") : "" }}</td>
|
||||
<td>
|
||||
{{ `${activity.namespace.owner}/${activity.namespace.slug}/${activity.versionString}: ${activity.platforms[0].toLowerCase()}` }}
|
||||
</td>
|
||||
<td>
|
||||
<Link
|
||||
:to="{
|
||||
name: 'user-project-versions-version-platform-reviews',
|
||||
params: getRouteParams(activity),
|
||||
}"
|
||||
>
|
||||
<IconMdiListStatus class="float-left"></IconMdiListStatus>
|
||||
{{ i18n.t("version.page.reviewLogs") }}
|
||||
</Link>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</Table>
|
||||
<Alert v-else type="success">
|
||||
{{ i18n.t("health.empty") }}
|
||||
</Alert>
|
||||
</Card>
|
||||
<Card>
|
||||
<template #header>{{ i18n.t("userActivity.flags") }}</template>
|
||||
|
||||
<Table v-if="flagActivities && flagActivities.length">
|
||||
<tbody>
|
||||
<tr v-for="(activity, idx) in flagActivities" :key="`flag-${idx}`">
|
||||
<td>{{ i18n.t("userActivity.flagResolved") }}</td>
|
||||
<td>{{ activity.resolvedAt ? i18n.d(activity.resolvedAt, "time") : "" }}</td>
|
||||
<td>
|
||||
<Link :to="`/${activity.namespace.owner}/${activity.namespace.slug}`">
|
||||
{{ `${activity.namespace.owner}/${activity.namespace.slug}` }}
|
||||
</Link>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</Table>
|
||||
<Alert v-else type="success">
|
||||
{{ i18n.t("health.empty") }}
|
||||
</Alert>
|
||||
</Card>
|
||||
<Table v-if="flagActivities && flagActivities.length">
|
||||
<tbody>
|
||||
<tr v-for="(activity, idx) in flagActivities" :key="`flag-${idx}`">
|
||||
<td>{{ i18n.t("userActivity.flagResolved") }}</td>
|
||||
<td>{{ activity.resolvedAt ? i18n.d(activity.resolvedAt, "time") : "" }}</td>
|
||||
<td>
|
||||
<Link :to="`/${activity.namespace.owner}/${activity.namespace.slug}`">
|
||||
{{ `${activity.namespace.owner}/${activity.namespace.slug}` }}
|
||||
</Link>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</Table>
|
||||
<Alert v-else type="success">
|
||||
{{ i18n.t("health.empty") }}
|
||||
</Alert>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<route lang="yaml">
|
||||
meta:
|
||||
requireGlobalPerm: ["REVIEWER"]
|
||||
</route>
|
||||
|
@ -1,25 +1,28 @@
|
||||
<script lang="ts" setup>
|
||||
import { ProjectApproval } from "hangar-internal";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { useHead } from "@vueuse/head";
|
||||
import { useRoute } from "vue-router";
|
||||
import { useInternalApi } from "~/composables/useApi";
|
||||
import { handleRequestError } from "~/composables/useErrorHandling";
|
||||
import { useContext } from "vite-ssr/vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import Card from "~/lib/components/design/Card.vue";
|
||||
import AdminProjectList from "~/components/projects/AdminProjectList.vue";
|
||||
import { useHead } from "@vueuse/head";
|
||||
import { useSeo } from "~/composables/useSeo";
|
||||
import { useRoute } from "vue-router";
|
||||
import { definePageMeta } from "#imports";
|
||||
|
||||
interface ApprovalProjects {
|
||||
needsApproval: ProjectApproval[];
|
||||
waitingProjects: ProjectApproval[];
|
||||
}
|
||||
|
||||
const ctx = useContext();
|
||||
definePageMeta({
|
||||
globalPermsRequired: ["REVIEWER"],
|
||||
});
|
||||
|
||||
const i18n = useI18n();
|
||||
const route = useRoute();
|
||||
const data: ApprovalProjects = (await useInternalApi<ApprovalProjects>("admin/approval/projects").catch((e) =>
|
||||
handleRequestError(e, ctx, i18n)
|
||||
handleRequestError(e, i18n)
|
||||
)) as ApprovalProjects;
|
||||
|
||||
useHead(useSeo(i18n.t("projectApproval.title"), null, route, null));
|
||||
@ -37,8 +40,3 @@ useHead(useSeo(i18n.t("projectApproval.title"), null, route, null));
|
||||
</Card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<route lang="yaml">
|
||||
meta:
|
||||
requireGlobalPerm: ["REVIEWER"]
|
||||
</route>
|
||||
|
@ -1,23 +1,26 @@
|
||||
<script lang="ts" setup>
|
||||
import { useI18n } from "vue-i18n";
|
||||
import SortableTable, { Header } from "~/components/SortableTable.vue";
|
||||
import { Review, ReviewQueueEntry } from "hangar-internal";
|
||||
import { useHead } from "@vueuse/head";
|
||||
import { useRoute } from "vue-router";
|
||||
import SortableTable, { Header } from "~/components/SortableTable.vue";
|
||||
import { ReviewAction } from "~/types/enums";
|
||||
import { useContext } from "vite-ssr/vue";
|
||||
import { useVersionApprovals } from "~/composables/useApiHelper";
|
||||
import { handleRequestError } from "~/composables/useErrorHandling";
|
||||
import Card from "~/lib/components/design/Card.vue";
|
||||
import Link from "~/lib/components/design/Link.vue";
|
||||
import Tag from "~/components/Tag.vue";
|
||||
import { useHead } from "@vueuse/head";
|
||||
import { useSeo } from "~/composables/useSeo";
|
||||
import { useRoute } from "vue-router";
|
||||
import Button from "~/lib/components/design/Button.vue";
|
||||
import { definePageMeta } from "#imports";
|
||||
|
||||
definePageMeta({
|
||||
globalPermsRequired: ["REVIEWER"],
|
||||
});
|
||||
|
||||
const i18n = useI18n();
|
||||
const ctx = useContext();
|
||||
const route = useRoute();
|
||||
const data = await useVersionApprovals().catch((e) => handleRequestError(e, ctx, i18n));
|
||||
const data = await useVersionApprovals().catch((e) => handleRequestError(e, i18n));
|
||||
|
||||
const actions = {
|
||||
ongoing: [ReviewAction.START, ReviewAction.MESSAGE, ReviewAction.UNDO_APPROVAL, ReviewAction.REOPEN],
|
||||
@ -88,99 +91,96 @@ function getCount(entry: ReviewQueueEntry, ..._actions: ReviewAction[]) {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Card>
|
||||
<template #header>{{ i18n.t("versionApproval.approvalQueue") }}</template>
|
||||
<div>
|
||||
<Card>
|
||||
<template #header>{{ i18n.t("versionApproval.approvalQueue") }}</template>
|
||||
|
||||
<SortableTable v-if="data" :headers="notStartedHeaders" :items="data.notStarted">
|
||||
<template #item_project="{ item }">
|
||||
<Link :to="`/${item.namespace.owner}/${item.namespace.slug}`">
|
||||
{{ `${item.namespace.owner}/${item.namespace.slug}` }}
|
||||
</Link>
|
||||
</template>
|
||||
<template #item_date="{ item }">
|
||||
<span class="start-date">{{ i18n.d(item.versionCreatedAt, "time") }}</span>
|
||||
</template>
|
||||
<template #item_version="{ item }">
|
||||
<Link :to="{ name: 'user-project-versions-version-platform', params: getRouteParams(item) }">
|
||||
<Tag :color="{ background: item.channelColor }" :name="item.channelName" :data="item.versionString" />
|
||||
</Link>
|
||||
</template>
|
||||
<template #item_queuedBy="{ item }">
|
||||
<Link :to="`/${item.versionAuthor}`">
|
||||
{{ item.versionAuthor }}
|
||||
</Link>
|
||||
</template>
|
||||
<template #item_startBtn="{ item }">
|
||||
<Link :to="{ name: 'user-project-versions-version-platform-reviews', params: getRouteParams(item) }" nuxt>
|
||||
<Button>
|
||||
<IconMdiPlay />
|
||||
{{ i18n.t("version.page.reviewStart") }}
|
||||
</Button>
|
||||
</Link>
|
||||
</template>
|
||||
</SortableTable>
|
||||
</Card>
|
||||
<SortableTable v-if="data" :headers="notStartedHeaders" :items="data.notStarted">
|
||||
<template #item_project="{ item }">
|
||||
<Link :to="`/${item.namespace.owner}/${item.namespace.slug}`">
|
||||
{{ `${item.namespace.owner}/${item.namespace.slug}` }}
|
||||
</Link>
|
||||
</template>
|
||||
<template #item_date="{ item }">
|
||||
<span class="start-date">{{ i18n.d(item.versionCreatedAt, "time") }}</span>
|
||||
</template>
|
||||
<template #item_version="{ item }">
|
||||
<Link :to="{ name: 'user-project-versions-version-platform', params: getRouteParams(item) }">
|
||||
<Tag :color="{ background: item.channelColor }" :name="item.channelName" :data="item.versionString" />
|
||||
</Link>
|
||||
</template>
|
||||
<template #item_queuedBy="{ item }">
|
||||
<Link :to="`/${item.versionAuthor}`">
|
||||
{{ item.versionAuthor }}
|
||||
</Link>
|
||||
</template>
|
||||
<template #item_startBtn="{ item }">
|
||||
<Link :to="{ name: 'user-project-versions-version-platform-reviews', params: getRouteParams(item) }" nuxt>
|
||||
<Button>
|
||||
<IconMdiPlay />
|
||||
{{ i18n.t("version.page.reviewStart") }}
|
||||
</Button>
|
||||
</Link>
|
||||
</template>
|
||||
</SortableTable>
|
||||
</Card>
|
||||
|
||||
<Card class="mt-4">
|
||||
<template #header>{{ i18n.t("versionApproval.inReview") }}</template>
|
||||
<Card class="mt-4">
|
||||
<template #header>{{ i18n.t("versionApproval.inReview") }}</template>
|
||||
|
||||
<SortableTable v-if="data" :headers="underReviewHeaders" :items="data.underReview" expandable>
|
||||
<template #item_project="{ item }">
|
||||
<Link :to="`/${item.namespace.owner}/${item.namespace.slug}`">
|
||||
{{ `${item.namespace.owner}/${item.namespace.slug}` }}
|
||||
</Link>
|
||||
</template>
|
||||
<template #item_version="{ item }">
|
||||
<Link :to="{ name: 'user-project-versions-version-platform', params: getRouteParams(item) }">
|
||||
<Tag :color="{ background: item.channelColor }" :name="item.channelName" :data="item.versionString" />
|
||||
</Link>
|
||||
</template>
|
||||
<template #item_queuedBy="{ item }">
|
||||
<Link :to="`/${item.versionAuthor}`">
|
||||
{{ item.versionAuthor }}
|
||||
</Link>
|
||||
<br />
|
||||
<small>{{ i18n.d(item.versionCreatedAt, "time") }}</small>
|
||||
</template>
|
||||
<template #item_status="{ item }">
|
||||
<span class="text-yellow-400">
|
||||
{{ i18n.t("versionApproval.statuses.ongoing", [getOngoingCount(item)]) }}
|
||||
</span>
|
||||
<br />
|
||||
<span class="text-red-400">
|
||||
{{ i18n.t("versionApproval.statuses.stopped", [getStoppedCount(item)]) }}
|
||||
</span>
|
||||
<br />
|
||||
<span class="text-green-400"> {{ i18n.t("versionApproval.statuses.approved", [getApprovedCount(item)]) }}</span>
|
||||
</template>
|
||||
<template #item_reviewLogs="{ item }">
|
||||
<Link :to="{ name: 'user-project-versions-version-platform-reviews', params: getRouteParams(item) }">
|
||||
<IconMdiListStatus />
|
||||
{{ i18n.t("version.page.reviewLogs") }}
|
||||
</Link>
|
||||
</template>
|
||||
<template #expanded-item="{ item, headers }">
|
||||
<td :colspan="headers.length">
|
||||
<ul>
|
||||
<li v-for="entry in item.reviews" :key="entry.reviewerName" class="ml-4">
|
||||
<span
|
||||
class="font-bold mr-2"
|
||||
:class="{ 'text-yellow-400': isOngoing(entry), 'text-red-400': isStopped(entry), 'text-green-400': isApproved(entry) }"
|
||||
>{{ entry.reviewerName }}</span
|
||||
>
|
||||
<span>{{ i18n.t("versionApproval.started", [i18n.d(entry.reviewStarted, "time")]) }}</span>
|
||||
<span v-if="entry.reviewEnded" class="ml-4" :class="{ 'text-red-400': isStopped(entry), 'text-green-400': isApproved(entry) }">{{
|
||||
i18n.t("versionApproval.ended", [i18n.d(entry.reviewEnded, "time")])
|
||||
}}</span>
|
||||
</li>
|
||||
</ul>
|
||||
</td>
|
||||
</template>
|
||||
</SortableTable>
|
||||
</Card>
|
||||
<SortableTable v-if="data" :headers="underReviewHeaders" :items="data.underReview" expandable>
|
||||
<template #item_project="{ item }">
|
||||
<Link :to="`/${item.namespace.owner}/${item.namespace.slug}`">
|
||||
{{ `${item.namespace.owner}/${item.namespace.slug}` }}
|
||||
</Link>
|
||||
</template>
|
||||
<template #item_version="{ item }">
|
||||
<Link :to="{ name: 'user-project-versions-version-platform', params: getRouteParams(item) }">
|
||||
<Tag :color="{ background: item.channelColor }" :name="item.channelName" :data="item.versionString" />
|
||||
</Link>
|
||||
</template>
|
||||
<template #item_queuedBy="{ item }">
|
||||
<Link :to="`/${item.versionAuthor}`">
|
||||
{{ item.versionAuthor }}
|
||||
</Link>
|
||||
<br />
|
||||
<small>{{ i18n.d(item.versionCreatedAt, "time") }}</small>
|
||||
</template>
|
||||
<template #item_status="{ item }">
|
||||
<span class="text-yellow-400">
|
||||
{{ i18n.t("versionApproval.statuses.ongoing", [getOngoingCount(item)]) }}
|
||||
</span>
|
||||
<br />
|
||||
<span class="text-red-400">
|
||||
{{ i18n.t("versionApproval.statuses.stopped", [getStoppedCount(item)]) }}
|
||||
</span>
|
||||
<br />
|
||||
<span class="text-green-400"> {{ i18n.t("versionApproval.statuses.approved", [getApprovedCount(item)]) }}</span>
|
||||
</template>
|
||||
<template #item_reviewLogs="{ item }">
|
||||
<Link :to="{ name: 'user-project-versions-version-platform-reviews', params: getRouteParams(item) }">
|
||||
<IconMdiListStatus />
|
||||
{{ i18n.t("version.page.reviewLogs") }}
|
||||
</Link>
|
||||
</template>
|
||||
<template #expanded-item="{ item, headers }">
|
||||
<td :colspan="headers.length">
|
||||
<ul>
|
||||
<li v-for="entry in item.reviews" :key="entry.reviewerName" class="ml-4">
|
||||
<span
|
||||
class="font-bold mr-2"
|
||||
:class="{ 'text-yellow-400': isOngoing(entry), 'text-red-400': isStopped(entry), 'text-green-400': isApproved(entry) }"
|
||||
>{{ entry.reviewerName }}</span
|
||||
>
|
||||
<span>{{ i18n.t("versionApproval.started", [i18n.d(entry.reviewStarted, "time")]) }}</span>
|
||||
<span v-if="entry.reviewEnded" class="ml-4" :class="{ 'text-red-400': isStopped(entry), 'text-green-400': isApproved(entry) }">{{
|
||||
i18n.t("versionApproval.ended", [i18n.d(entry.reviewEnded, "time")])
|
||||
}}</span>
|
||||
</li>
|
||||
</ul>
|
||||
</td>
|
||||
</template>
|
||||
</SortableTable>
|
||||
</Card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<route lang="yaml">
|
||||
meta:
|
||||
requireGlobalPerm: ["REVIEWER"]
|
||||
</route>
|
||||
|
@ -1,20 +1,23 @@
|
||||
<script lang="ts" setup>
|
||||
import { useContext } from "vite-ssr/vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { useRoute } from "vue-router";
|
||||
import { ref } from "vue";
|
||||
import { useHead } from "@vueuse/head";
|
||||
import { useUnresolvedFlags } from "~/composables/useApiHelper";
|
||||
import { handleRequestError } from "~/composables/useErrorHandling";
|
||||
import { ref } from "vue";
|
||||
import PageTitle from "~/lib/components/design/PageTitle.vue";
|
||||
import { useHead } from "@vueuse/head";
|
||||
import { useSeo } from "~/composables/useSeo";
|
||||
import Flags from "~/components/Flags.vue";
|
||||
import Tabs, { Tab } from "~/lib/components/design/Tabs.vue";
|
||||
import { definePageMeta } from "#imports";
|
||||
|
||||
definePageMeta({
|
||||
globalPermsRequired: ["MOD_NOTES_AND_FLAGS"],
|
||||
});
|
||||
|
||||
const ctx = useContext();
|
||||
const i18n = useI18n();
|
||||
const route = useRoute();
|
||||
const flags = await useUnresolvedFlags().catch((e) => handleRequestError(e, ctx, i18n));
|
||||
const flags = await useUnresolvedFlags().catch((e) => handleRequestError(e, i18n));
|
||||
const loading = ref<{ [key: number]: boolean }>({});
|
||||
|
||||
const selectedTab = ref("unresolved");
|
||||
@ -27,18 +30,15 @@ useHead(useSeo(i18n.t("flagReview.title"), null, route, null));
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<PageTitle>{{ i18n.t("flagReview.title") }}</PageTitle>
|
||||
<Tabs v-model="selectedTab" :tabs="selectedTabs" :vertical="false">
|
||||
<template #unresolved>
|
||||
<Flags :resolved="false"></Flags>
|
||||
</template>
|
||||
<template #resolved>
|
||||
<Flags resolved></Flags>
|
||||
</template>
|
||||
</Tabs>
|
||||
<div>
|
||||
<PageTitle>{{ i18n.t("flagReview.title") }}</PageTitle>
|
||||
<Tabs v-model="selectedTab" :tabs="selectedTabs" :vertical="false">
|
||||
<template #unresolved>
|
||||
<Flags :resolved="false"></Flags>
|
||||
</template>
|
||||
<template #resolved>
|
||||
<Flags resolved></Flags>
|
||||
</template>
|
||||
</Tabs>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<route lang="yaml">
|
||||
meta:
|
||||
requireGlobalPerm: ["MOD_NOTES_AND_FLAGS"]
|
||||
</route>
|
||||
|
@ -1,108 +1,108 @@
|
||||
<script lang="ts" setup>
|
||||
import { useContext } from "vite-ssr/vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { useRoute } from "vue-router";
|
||||
import { useHead } from "@vueuse/head";
|
||||
import { useHealthReport } from "~/composables/useApiHelper";
|
||||
import { handleRequestError } from "~/composables/useErrorHandling";
|
||||
import Card from "~/lib/components/design/Card.vue";
|
||||
import Link from "~/lib/components/design/Link.vue";
|
||||
import PageTitle from "~/lib/components/design/PageTitle.vue";
|
||||
import { useHead } from "@vueuse/head";
|
||||
import { useSeo } from "~/composables/useSeo";
|
||||
import { definePageMeta } from "#imports";
|
||||
|
||||
definePageMeta({
|
||||
globalPermsRequired: ["VIEW_HEALTH"],
|
||||
});
|
||||
|
||||
const ctx = useContext();
|
||||
const i18n = useI18n();
|
||||
const route = useRoute();
|
||||
const healthReport = await useHealthReport().catch((e) => handleRequestError(e, ctx, i18n));
|
||||
const healthReport = await useHealthReport().catch((e) => handleRequestError(e, i18n));
|
||||
|
||||
useHead(useSeo(i18n.t("health.title"), null, route, null));
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<PageTitle>{{ i18n.t("health.title") }}</PageTitle>
|
||||
<div class="grid gap-8 grid-cols-1 md:grid-cols-2">
|
||||
<Card v-if="healthReport">
|
||||
<template #header> {{ i18n.t("health.noTopicProject") }}</template>
|
||||
<div>
|
||||
<PageTitle>{{ i18n.t("health.title") }}</PageTitle>
|
||||
<div class="grid gap-8 grid-cols-1 md:grid-cols-2">
|
||||
<Card v-if="healthReport">
|
||||
<template #header> {{ i18n.t("health.noTopicProject") }}</template>
|
||||
|
||||
<ul class="max-h-xs overflow-auto">
|
||||
<li v-for="project in healthReport.noTopicProjects" :key="project.namespace.slug + project.namespace.owner">
|
||||
<Link :to="'/' + project.namespace.owner + '/' + project.namespace.slug">
|
||||
{{ project.namespace.owner + "/" + project.namespace.slug }}
|
||||
</Link>
|
||||
</li>
|
||||
<li v-if="!healthReport.noTopicProjects || healthReport.noTopicProjects.length === 0">
|
||||
{{ i18n.t("health.empty") }}
|
||||
</li>
|
||||
</ul>
|
||||
</Card>
|
||||
<Card v-if="healthReport">
|
||||
<template #header> {{ i18n.t("health.erroredJobs") }}</template>
|
||||
<ul class="max-h-xs overflow-auto">
|
||||
<li v-for="project in healthReport.noTopicProjects" :key="project.namespace.slug + project.namespace.owner">
|
||||
<Link :to="'/' + project.namespace.owner + '/' + project.namespace.slug">
|
||||
{{ project.namespace.owner + "/" + project.namespace.slug }}
|
||||
</Link>
|
||||
</li>
|
||||
<li v-if="!healthReport.noTopicProjects || healthReport.noTopicProjects.length === 0">
|
||||
{{ i18n.t("health.empty") }}
|
||||
</li>
|
||||
</ul>
|
||||
</Card>
|
||||
<Card v-if="healthReport">
|
||||
<template #header> {{ i18n.t("health.erroredJobs") }}</template>
|
||||
|
||||
<ul class="max-h-xs overflow-auto">
|
||||
<li v-for="job in healthReport.erroredJobs" :key="job.jobType + new Date(job.lastUpdated).toISOString()">
|
||||
{{ i18n.t("health.jobText", [job.jobType, job.lastErrorDescriptor, i18n.d(job.lastUpdated, "time")]) }}
|
||||
</li>
|
||||
<li v-if="!healthReport.erroredJobs || healthReport.erroredJobs.length === 0">
|
||||
{{ i18n.t("health.empty") }}
|
||||
</li>
|
||||
</ul>
|
||||
</Card>
|
||||
<Card v-if="healthReport">
|
||||
<template #header> {{ i18n.t("health.staleProjects") }}</template>
|
||||
<ul class="max-h-xs overflow-auto">
|
||||
<li v-for="job in healthReport.erroredJobs" :key="job.jobType + new Date(job.lastUpdated).toISOString()">
|
||||
{{ i18n.t("health.jobText", [job.jobType, job.lastErrorDescriptor, i18n.d(job.lastUpdated, "time")]) }}
|
||||
</li>
|
||||
<li v-if="!healthReport.erroredJobs || healthReport.erroredJobs.length === 0">
|
||||
{{ i18n.t("health.empty") }}
|
||||
</li>
|
||||
</ul>
|
||||
</Card>
|
||||
<Card v-if="healthReport">
|
||||
<template #header> {{ i18n.t("health.staleProjects") }}</template>
|
||||
|
||||
<ul class="max-h-xs overflow-auto">
|
||||
<li v-for="project in healthReport.staleProjects" :key="project.namespace.slug + project.namespace.owner">
|
||||
<Link :to="'/' + project.namespace.owner + '/' + project.namespace.slug">
|
||||
{{ project.namespace.owner + "/" + project.namespace.slug }}
|
||||
</Link>
|
||||
</li>
|
||||
<li v-if="!healthReport.staleProjects || healthReport.staleProjects.length === 0">
|
||||
{{ i18n.t("health.empty") }}
|
||||
</li>
|
||||
</ul>
|
||||
</Card>
|
||||
<Card v-if="healthReport">
|
||||
<template #header> {{ i18n.t("health.notPublicProjects") }}</template>
|
||||
<ul class="max-h-xs overflow-auto">
|
||||
<li v-for="project in healthReport.staleProjects" :key="project.namespace.slug + project.namespace.owner">
|
||||
<Link :to="'/' + project.namespace.owner + '/' + project.namespace.slug">
|
||||
{{ project.namespace.owner + "/" + project.namespace.slug }}
|
||||
</Link>
|
||||
</li>
|
||||
<li v-if="!healthReport.staleProjects || healthReport.staleProjects.length === 0">
|
||||
{{ i18n.t("health.empty") }}
|
||||
</li>
|
||||
</ul>
|
||||
</Card>
|
||||
<Card v-if="healthReport">
|
||||
<template #header> {{ i18n.t("health.notPublicProjects") }}</template>
|
||||
|
||||
<ul class="max-h-xs overflow-auto">
|
||||
<li v-for="project in healthReport.nonPublicProjects" :key="project.namespace.slug + project.namespace.owner">
|
||||
<Link :to="'/' + project.namespace.owner + '/' + project.namespace.slug">
|
||||
<strong>{{ project.namespace.owner + "/" + project.namespace.slug }}</strong>
|
||||
<small class="ml-1">{{ i18n.t("visibility.name." + project.visibility) }}</small>
|
||||
</Link>
|
||||
</li>
|
||||
<li v-if="!healthReport.nonPublicProjects || healthReport.nonPublicProjects.length === 0">
|
||||
{{ i18n.t("health.empty") }}
|
||||
</li>
|
||||
</ul>
|
||||
</Card>
|
||||
<Card>
|
||||
<template #header>{{ i18n.t("health.noPlatform") }}</template>
|
||||
<ul class="max-h-xs overflow-auto">
|
||||
<li v-for="project in healthReport.nonPublicProjects" :key="project.namespace.slug + project.namespace.owner">
|
||||
<Link :to="'/' + project.namespace.owner + '/' + project.namespace.slug">
|
||||
<strong>{{ project.namespace.owner + "/" + project.namespace.slug }}</strong>
|
||||
<small class="ml-1">{{ i18n.t("visibility.name." + project.visibility) }}</small>
|
||||
</Link>
|
||||
</li>
|
||||
<li v-if="!healthReport.nonPublicProjects || healthReport.nonPublicProjects.length === 0">
|
||||
{{ i18n.t("health.empty") }}
|
||||
</li>
|
||||
</ul>
|
||||
</Card>
|
||||
<Card>
|
||||
<template #header>{{ i18n.t("health.noPlatform") }}</template>
|
||||
|
||||
<ul>
|
||||
<li>TODO: Implementation</li>
|
||||
<!--TODO idek what this is for?-->
|
||||
<!--<li v-if="!healthReport.noPlatform || healthReport.noPlatform.length === 0">{{ i18n.t('health.empty') }}</li>-->
|
||||
</ul>
|
||||
</Card>
|
||||
<Card v-if="healthReport">
|
||||
<template #header> {{ i18n.t("health.missingFileProjects") }}</template>
|
||||
<ul>
|
||||
<li>TODO: Implementation</li>
|
||||
<!--TODO idek what this is for?-->
|
||||
<!--<li v-if="!healthReport.noPlatform || healthReport.noPlatform.length === 0">{{ i18n.t('health.empty') }}</li>-->
|
||||
</ul>
|
||||
</Card>
|
||||
<Card v-if="healthReport">
|
||||
<template #header> {{ i18n.t("health.missingFileProjects") }}</template>
|
||||
|
||||
<ul class="max-h-xs overflow-auto">
|
||||
<li v-for="project in healthReport.missingFiles" :key="project.namespace.slug + project.namespace.owner">
|
||||
<Link :to="'/' + project.namespace.owner + '/' + project.namespace.slug">
|
||||
{{ project.namespace.owner + "/" + project.namespace.slug }}
|
||||
</Link>
|
||||
</li>
|
||||
<li v-if="!healthReport.missingFiles || healthReport.missingFiles.length === 0">
|
||||
{{ i18n.t("health.empty") }}
|
||||
</li>
|
||||
</ul>
|
||||
</Card>
|
||||
<ul class="max-h-xs overflow-auto">
|
||||
<li v-for="project in healthReport.missingFiles" :key="project.namespace.slug + project.namespace.owner">
|
||||
<Link :to="'/' + project.namespace.owner + '/' + project.namespace.slug">
|
||||
{{ project.namespace.owner + "/" + project.namespace.slug }}
|
||||
</Link>
|
||||
</li>
|
||||
<li v-if="!healthReport.missingFiles || healthReport.missingFiles.length === 0">
|
||||
{{ i18n.t("health.empty") }}
|
||||
</li>
|
||||
</ul>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<route lang="yaml">
|
||||
meta:
|
||||
requireGlobalPerm: ["VIEW_HEALTH"]
|
||||
</route>
|
||||
|
@ -1,8 +1,8 @@
|
||||
<script lang="ts" setup>
|
||||
import PageTitle from "~/lib/components/design/PageTitle.vue";
|
||||
import { useContext } from "vite-ssr/vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { useRoute } from "vue-router";
|
||||
import { useHead } from "@vueuse/head";
|
||||
import PageTitle from "~/lib/components/design/PageTitle.vue";
|
||||
import { useActionLogs } from "~/composables/useApiHelper";
|
||||
import { handleRequestError } from "~/composables/useErrorHandling";
|
||||
import Card from "~/lib/components/design/Card.vue";
|
||||
@ -11,13 +11,16 @@ import Link from "~/lib/components/design/Link.vue";
|
||||
import MarkdownModal from "~/components/modals/MarkdownModal.vue";
|
||||
import DiffModal from "~/components/modals/DiffModal.vue";
|
||||
import Button from "~/lib/components/design/Button.vue";
|
||||
import { useHead } from "@vueuse/head";
|
||||
import { useSeo } from "~/composables/useSeo";
|
||||
import { definePageMeta } from "#imports";
|
||||
|
||||
definePageMeta({
|
||||
globalPermsRequired: ["VIEW_LOGS"],
|
||||
});
|
||||
|
||||
const ctx = useContext();
|
||||
const i18n = useI18n();
|
||||
const route = useRoute();
|
||||
const loggedActions = await useActionLogs().catch((e) => handleRequestError(e, ctx, i18n));
|
||||
const loggedActions = await useActionLogs().catch((e) => handleRequestError(e, i18n));
|
||||
|
||||
const headers = [
|
||||
{ title: i18n.t("userActionLog.user"), name: "user", sortable: false },
|
||||
@ -34,86 +37,83 @@ useHead(useSeo(i18n.t("userActionLog.title"), null, route, null));
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<PageTitle>{{ i18n.t("userActionLog.title") }}</PageTitle>
|
||||
<Card>
|
||||
<SortableTable :headers="headers" :items="loggedActions?.result">
|
||||
<template #item_user="{ item }">
|
||||
<Link :to="'/' + item.userName">{{ item.userName }}</Link>
|
||||
</template>
|
||||
<template #item_time="{ item }">
|
||||
{{ i18n.d(item.createdAt, "time") }}
|
||||
</template>
|
||||
<template #item_action="{ item }">
|
||||
{{ i18n.t(item.action.description) }}
|
||||
</template>
|
||||
<template #item_context="{ item }">
|
||||
<template v-if="item.page">
|
||||
<Link :to="'/' + item.project.owner + '/' + item.project.slug + '/pages/' + item.page.slug">
|
||||
{{ item.project.owner + "/" + item.project.slug + "/" + item.page.slug }}
|
||||
</Link>
|
||||
<div>
|
||||
<PageTitle>{{ i18n.t("userActionLog.title") }}</PageTitle>
|
||||
<Card>
|
||||
<SortableTable :headers="headers" :items="loggedActions?.result">
|
||||
<template #item_user="{ item }">
|
||||
<Link :to="'/' + item.userName">{{ item.userName }}</Link>
|
||||
</template>
|
||||
<template v-else-if="item.version">
|
||||
<Link :to="'/' + item.project.owner + '/' + item.project.slug + '/versions/' + item.version.versionString">
|
||||
{{ `${item.project.owner}/${item.project.slug}/${item.version.versionString}` }}
|
||||
</Link>
|
||||
<template #item_time="{ item }">
|
||||
{{ i18n.d(item.createdAt, "time") }}
|
||||
</template>
|
||||
<template v-else-if="item.project && item.project.owner">
|
||||
<Link :to="'/' + item.project.owner + '/' + item.project.slug">{{ item.project.owner + "/" + item.project.slug }} </Link>
|
||||
<template #item_action="{ item }">
|
||||
{{ i18n.t(item.action.description) }}
|
||||
</template>
|
||||
<template v-else-if="item.subject">
|
||||
<Link :to="'/' + item.subject.name">{{ item.subject.name }}</Link>
|
||||
<template #item_context="{ item }">
|
||||
<template v-if="item.page">
|
||||
<Link :to="'/' + item.project.owner + '/' + item.project.slug + '/pages/' + item.page.slug">
|
||||
{{ item.project.owner + "/" + item.project.slug + "/" + item.page.slug }}
|
||||
</Link>
|
||||
</template>
|
||||
<template v-else-if="item.version">
|
||||
<Link :to="'/' + item.project.owner + '/' + item.project.slug + '/versions/' + item.version.versionString">
|
||||
{{ `${item.project.owner}/${item.project.slug}/${item.version.versionString}` }}
|
||||
</Link>
|
||||
</template>
|
||||
<template v-else-if="item.project && item.project.owner">
|
||||
<Link :to="'/' + item.project.owner + '/' + item.project.slug">{{ item.project.owner + "/" + item.project.slug }} </Link>
|
||||
</template>
|
||||
<template v-else-if="item.subject">
|
||||
<Link :to="'/' + item.subject.name">{{ item.subject.name }}</Link>
|
||||
</template>
|
||||
</template>
|
||||
</template>
|
||||
<template #item_oldState="{ item }">
|
||||
<template v-if="(item.contextType === 'PAGE' || item.action.pgLoggedAction === 'version_description_changed') && item.oldState">
|
||||
<MarkdownModal :markdown="item.oldState" :title="i18n.t('userActionLog.markdownView')">
|
||||
<template #activator="{ on }">
|
||||
<Button size="small" v-on="on">
|
||||
{{ i18n.t("userActionLog.markdownView") }}
|
||||
</Button>
|
||||
</template>
|
||||
</MarkdownModal>
|
||||
</template>
|
||||
<template v-else-if="item.action.pgLoggedAction === 'project_icon_changed'">
|
||||
<span v-if="item.oldState === '#empty'">default</span>
|
||||
<img v-else class="inline-img" :src="'data:image/png;base64,' + item.oldState" alt="" />
|
||||
</template>
|
||||
<template v-else>
|
||||
<span>{{ item.oldState && i18n.te(item.oldState) ? i18n.t(item.oldState) : item.oldState }}</span>
|
||||
</template>
|
||||
</template>
|
||||
<template #item_newState="{ item }">
|
||||
<template v-if="item.contextType === 'PAGE' || item.action.pgLoggedAction === 'version_description_changed'">
|
||||
<div class="flex gap-2">
|
||||
<MarkdownModal :markdown="item.newState" :title="i18n.t('userActionLog.markdownView')">
|
||||
<template #item_oldState="{ item }">
|
||||
<template v-if="(item.contextType === 'PAGE' || item.action.pgLoggedAction === 'version_description_changed') && item.oldState">
|
||||
<MarkdownModal :markdown="item.oldState" :title="i18n.t('userActionLog.markdownView')">
|
||||
<template #activator="{ on }">
|
||||
<Button size="small" v-on="on">
|
||||
{{ i18n.t("userActionLog.markdownView") }}
|
||||
</Button>
|
||||
</template>
|
||||
</MarkdownModal>
|
||||
<DiffModal :left="item.oldState" :right="item.newState" :title="i18n.t('userActionLog.diffView')">
|
||||
<template #activator="{ on }">
|
||||
<Button size="small" v-on="on">
|
||||
{{ i18n.t("userActionLog.diffView") }}
|
||||
</Button>
|
||||
</template>
|
||||
</DiffModal>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else-if="item.action.pgLoggedAction === 'project_icon_changed'">
|
||||
<span v-if="item.oldState === '#empty'">default</span>
|
||||
<img v-else class="inline-img" :src="'data:image/png;base64,' + item.oldState" alt="" />
|
||||
</template>
|
||||
<template v-else>
|
||||
<span>{{ item.oldState && i18n.te(item.oldState) ? i18n.t(item.oldState) : item.oldState }}</span>
|
||||
</template>
|
||||
</template>
|
||||
<template v-else-if="item.action.pgLoggedAction === 'project_icon_changed'">
|
||||
<span v-if="item.newState === '#empty'">default</span>
|
||||
<img v-else class="inline-img" :src="'data:image/png;base64,' + item.newState" alt="" />
|
||||
<template #item_newState="{ item }">
|
||||
<template v-if="item.contextType === 'PAGE' || item.action.pgLoggedAction === 'version_description_changed'">
|
||||
<div class="flex gap-2">
|
||||
<MarkdownModal :markdown="item.newState" :title="i18n.t('userActionLog.markdownView')">
|
||||
<template #activator="{ on }">
|
||||
<Button size="small" v-on="on">
|
||||
{{ i18n.t("userActionLog.markdownView") }}
|
||||
</Button>
|
||||
</template>
|
||||
</MarkdownModal>
|
||||
<DiffModal :left="item.oldState" :right="item.newState" :title="i18n.t('userActionLog.diffView')">
|
||||
<template #activator="{ on }">
|
||||
<Button size="small" v-on="on">
|
||||
{{ i18n.t("userActionLog.diffView") }}
|
||||
</Button>
|
||||
</template>
|
||||
</DiffModal>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else-if="item.action.pgLoggedAction === 'project_icon_changed'">
|
||||
<span v-if="item.newState === '#empty'">default</span>
|
||||
<img v-else class="inline-img" :src="'data:image/png;base64,' + item.newState" alt="" />
|
||||
</template>
|
||||
<template v-else>
|
||||
<span>{{ i18n.te(item.newState) ? i18n.t(item.newState) : item.newState }}</span>
|
||||
</template>
|
||||
</template>
|
||||
<template v-else>
|
||||
<span>{{ i18n.te(item.newState) ? i18n.t(item.newState) : item.newState }}</span>
|
||||
</template>
|
||||
</template>
|
||||
</SortableTable>
|
||||
</Card>
|
||||
</SortableTable>
|
||||
</Card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<route lang="yaml">
|
||||
meta:
|
||||
requireGlobalPerm: ["VIEW_LOGS"]
|
||||
</route>
|
||||
|
@ -1,20 +1,38 @@
|
||||
<script lang="ts" setup>
|
||||
import { useContext } from "vite-ssr/vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { useRoute } from "vue-router";
|
||||
import { handleRequestError } from "~/composables/useErrorHandling";
|
||||
import { ref, watch } from "vue";
|
||||
import Chartist, { IChartistSeriesData, ILineChartOptions } from "chartist";
|
||||
import { useHead } from "@vueuse/head";
|
||||
import { handleRequestError } from "~/composables/useErrorHandling";
|
||||
import { fromISOString, toISODateString } from "~/lib/composables/useDate";
|
||||
import { useInternalApi } from "~/composables/useApi";
|
||||
import Chart from "~/components/Chart.vue";
|
||||
import Chartist, { IChartistSeriesData, ILineChartOptions } from "chartist";
|
||||
import PageTitle from "~/lib/components/design/PageTitle.vue";
|
||||
import Card from "~/lib/components/design/Card.vue";
|
||||
import InputDate from "~/lib/components/ui/InputDate.vue";
|
||||
import { useHead } from "@vueuse/head";
|
||||
import { useSeo } from "~/composables/useSeo";
|
||||
import { definePageMeta } from "#imports";
|
||||
|
||||
definePageMeta({
|
||||
globalPermsRequired: ["VIEW_STATS"],
|
||||
});
|
||||
|
||||
interface DayStat {
|
||||
x: Date;
|
||||
y: number;
|
||||
}
|
||||
|
||||
interface DayStats {
|
||||
day: string;
|
||||
flagsClosed: number;
|
||||
flagsOpened: number;
|
||||
reviews: number;
|
||||
totalDownloads: number;
|
||||
unsafeDownloads: number;
|
||||
uploads: number;
|
||||
}
|
||||
|
||||
const ctx = useContext();
|
||||
const i18n = useI18n();
|
||||
const route = useRoute();
|
||||
|
||||
@ -26,7 +44,7 @@ const endDate = ref<string>(toISODateString(now));
|
||||
let data: DayStats[] = (await useInternalApi<DayStats[]>("admin/stats", true, "get", {
|
||||
from: startDate.value,
|
||||
to: endDate.value,
|
||||
}).catch((e) => handleRequestError(e, ctx, i18n))) as DayStats[];
|
||||
}).catch((e) => handleRequestError(e, i18n))) as DayStats[];
|
||||
|
||||
let reviews: DayStat[] = [];
|
||||
let uploads: DayStat[] = [];
|
||||
@ -104,7 +122,7 @@ async function updateDate() {
|
||||
data = (await useInternalApi<DayStats[]>("admin/stats", true, "get", {
|
||||
from: startDate.value,
|
||||
to: endDate.value,
|
||||
}).catch((e) => handleRequestError(e, ctx, i18n))) as DayStats[];
|
||||
}).catch((e) => handleRequestError(e, i18n))) as DayStats[];
|
||||
if (!data) {
|
||||
return;
|
||||
}
|
||||
@ -130,48 +148,30 @@ async function updateDate() {
|
||||
(flagData.value.series[0] as IChartistSeriesData).data = openedFlags;
|
||||
(flagData.value.series[1] as IChartistSeriesData).data = closedFlags;
|
||||
}
|
||||
|
||||
interface DayStat {
|
||||
x: Date;
|
||||
y: number;
|
||||
}
|
||||
|
||||
interface DayStats {
|
||||
day: string;
|
||||
flagsClosed: number;
|
||||
flagsOpened: number;
|
||||
reviews: number;
|
||||
totalDownloads: number;
|
||||
unsafeDownloads: number;
|
||||
uploads: number;
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<PageTitle>{{ i18n.t("stats.title") }}</PageTitle>
|
||||
<InputDate v-model="startDate" />
|
||||
<InputDate v-model="endDate" />
|
||||
<Card class="mt-4">
|
||||
<template #header> {{ i18n.t("stats.plugins") }}</template>
|
||||
<client-only>
|
||||
<Chart id="stats" :data="pluginData" :options="options" bar-type="Line" />
|
||||
</client-only>
|
||||
</Card>
|
||||
<Card class="mt-4">
|
||||
<template #header>{{ i18n.t("stats.downloads") }}</template>
|
||||
<client-only>
|
||||
<Chart id="downloads" :data="downloadData" :options="options" bar-type="Line" />
|
||||
</client-only>
|
||||
</Card>
|
||||
<Card class="mt-4">
|
||||
<template #header>{{ i18n.t("stats.flags") }}</template>
|
||||
<client-only>
|
||||
<Chart id="flags" :data="flagData" :options="options" bar-type="Line" />
|
||||
</client-only>
|
||||
</Card>
|
||||
<div>
|
||||
<PageTitle>{{ i18n.t("stats.title") }}</PageTitle>
|
||||
<InputDate v-model="startDate" />
|
||||
<InputDate v-model="endDate" />
|
||||
<Card class="mt-4">
|
||||
<template #header> {{ i18n.t("stats.plugins") }}</template>
|
||||
<client-only>
|
||||
<Chart id="stats" :data="pluginData" :options="options" bar-type="Line" />
|
||||
</client-only>
|
||||
</Card>
|
||||
<Card class="mt-4">
|
||||
<template #header>{{ i18n.t("stats.downloads") }}</template>
|
||||
<client-only>
|
||||
<Chart id="downloads" :data="downloadData" :options="options" bar-type="Line" />
|
||||
</client-only>
|
||||
</Card>
|
||||
<Card class="mt-4">
|
||||
<template #header>{{ i18n.t("stats.flags") }}</template>
|
||||
<client-only>
|
||||
<Chart id="flags" :data="flagData" :options="options" bar-type="Line" />
|
||||
</client-only>
|
||||
</Card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<route lang="yaml">
|
||||
meta:
|
||||
requireGlobalPerm: ["VIEW_STATS"]
|
||||
</route>
|
||||
|
@ -1,18 +1,18 @@
|
||||
<script lang="ts" setup>
|
||||
import PageTitle from "~/lib/components/design/PageTitle.vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { PaginatedResult, Project, User } from "hangar-api";
|
||||
import { useRoute, useRouter } from "vue-router";
|
||||
import { OrganizationRoleTable } from "hangar-internal";
|
||||
import { computed, ref } from "vue";
|
||||
import { useHead } from "@vueuse/head";
|
||||
import { AxiosError } from "axios";
|
||||
import PageTitle from "~/lib/components/design/PageTitle.vue";
|
||||
import Link from "~/lib/components/design/Link.vue";
|
||||
import Card from "~/lib/components/design/Card.vue";
|
||||
import { useApi, useInternalApi } from "~/composables/useApi";
|
||||
import { PaginatedResult, Project, User } from "hangar-api";
|
||||
import { useRoute, useRouter } from "vue-router";
|
||||
import { handleRequestError } from "~/composables/useErrorHandling";
|
||||
import { useContext } from "vite-ssr/vue";
|
||||
import { OrganizationRoleTable } from "hangar-internal";
|
||||
import { computed, ref } from "vue";
|
||||
import SortableTable, { Header } from "~/components/SortableTable.vue";
|
||||
import InputCheckbox from "~/lib/components/ui/InputCheckbox.vue";
|
||||
import { useHead } from "@vueuse/head";
|
||||
import { useSeo } from "~/composables/useSeo";
|
||||
import { authUrl, forumUserUrl } from "~/composables/useUrlHelper";
|
||||
import { useUser } from "~/composables/useApiHelper";
|
||||
@ -20,21 +20,24 @@ import Tag from "~/components/Tag.vue";
|
||||
import InputSelect from "~/lib/components/ui/InputSelect.vue";
|
||||
import { useBackendDataStore } from "~/store/backendData";
|
||||
import Button from "~/lib/components/design/Button.vue";
|
||||
import { AxiosError } from "axios";
|
||||
import { definePageMeta } from "#imports";
|
||||
|
||||
definePageMeta({
|
||||
globalPermsRequired: ["EDIT_ALL_USER_SETTINGS"],
|
||||
});
|
||||
|
||||
const i18n = useI18n();
|
||||
const route = useRoute();
|
||||
const ctx = useContext();
|
||||
const router = useRouter();
|
||||
const backendData = useBackendDataStore();
|
||||
|
||||
const projects = await useApi<PaginatedResult<Project>>("projects", false, "get", {
|
||||
owner: route.params.user,
|
||||
}).catch((e) => handleRequestError(e, ctx, i18n));
|
||||
}).catch((e) => handleRequestError(e, i18n));
|
||||
const orgs = await useInternalApi<{ [key: string]: OrganizationRoleTable }>(`organizations/${route.params.user}/userOrganizations`, false).catch((e) =>
|
||||
handleRequestError(e, ctx, i18n)
|
||||
handleRequestError(e, i18n)
|
||||
);
|
||||
const user = await useUser(route.params.user as string).catch((e) => handleRequestError(e, ctx, i18n));
|
||||
const user = await useUser(route.params.user as string).catch((e) => handleRequestError(e, i18n));
|
||||
|
||||
const projectsConfig = [
|
||||
{ title: i18n.t("userAdmin.project"), name: "name" },
|
||||
@ -69,7 +72,7 @@ async function processRole(add: boolean) {
|
||||
user.value = await useApi<User>(("users/" + route.params.user) as string);
|
||||
}
|
||||
} catch (e) {
|
||||
handleRequestError(e as AxiosError, ctx, i18n);
|
||||
handleRequestError(e as AxiosError, i18n);
|
||||
}
|
||||
}
|
||||
|
||||
@ -77,96 +80,93 @@ useHead(useSeo(i18n.t("userAdmin.title") + " " + route.params.user, null, route,
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<PageTitle
|
||||
>{{ i18n.t("userAdmin.title") }}
|
||||
<Link :to="'/' + $route.params.user">
|
||||
{{ $route.params.user }}
|
||||
</Link>
|
||||
</PageTitle>
|
||||
<div class="flex <md:flex-col mb-2 gap-2">
|
||||
<Card class="basis-full md:basis-8/12">
|
||||
<template #header>{{ i18n.t("userAdmin.roles") }}</template>
|
||||
<div class="space-x-1">
|
||||
<Tag v-for="role in user.roles" :key="role.value" :color="{ background: role.color }" :name="role.title" />
|
||||
</div>
|
||||
<div>
|
||||
<PageTitle
|
||||
>{{ i18n.t("userAdmin.title") }}
|
||||
<Link :to="'/' + $route.params.user">
|
||||
{{ $route.params.user }}
|
||||
</Link>
|
||||
</PageTitle>
|
||||
<div class="flex <md:flex-col mb-2 gap-2">
|
||||
<Card class="basis-full md:basis-8/12">
|
||||
<template #header>{{ i18n.t("userAdmin.roles") }}</template>
|
||||
<div class="space-x-1">
|
||||
<Tag v-for="role in user.roles" :key="role.value" :color="{ background: role.color }" :name="role.title" />
|
||||
</div>
|
||||
|
||||
<div class="flex mt-2">
|
||||
<div class="flex-grow">
|
||||
<InputSelect v-model="selectedRole" :values="backendData.globalRoles" item-text="title" item-value="value"></InputSelect>
|
||||
<div class="flex mt-2">
|
||||
<div class="flex-grow">
|
||||
<InputSelect v-model="selectedRole" :values="backendData.globalRoles" item-text="title" item-value="value"></InputSelect>
|
||||
</div>
|
||||
<div>
|
||||
<Button size="medium" :disabled="!selectedRole || user.roles.some((r) => r.value === selectedRole)" @click="processRole(true)">
|
||||
{{ i18n.t("general.add") }}
|
||||
</Button>
|
||||
</div>
|
||||
<div class="ml-2">
|
||||
<Button size="medium" :disabled="!selectedRole || !user.roles.some((r) => r.value === selectedRole)" @click="processRole(false)">
|
||||
{{ i18n.t("general.delete") }}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<Button size="medium" :disabled="!selectedRole || user.roles.some((r) => r.value === selectedRole)" @click="processRole(true)">
|
||||
{{ i18n.t("general.add") }}
|
||||
</Button>
|
||||
</div>
|
||||
<div class="ml-2">
|
||||
<Button size="medium" :disabled="!selectedRole || !user.roles.some((r) => r.value === selectedRole)" @click="processRole(false)">
|
||||
{{ i18n.t("general.delete") }}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
<Card class="basis-full md:basis-4/12">
|
||||
<template #header>{{ i18n.t("userAdmin.sidebar") }}</template>
|
||||
<ul>
|
||||
<li>
|
||||
<Link :href="_authUrl">{{ i18n.t("userAdmin.hangarAuth") }}</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link :href="_forumUserUrl">{{ i18n.t("userAdmin.forum") }}</Link>
|
||||
</li>
|
||||
</ul>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
<Card md="mb-2">
|
||||
<template #header>{{ i18n.t("userAdmin.organizations") }}</template>
|
||||
|
||||
<SortableTable :items="orgList" :headers="orgConfig">
|
||||
<template #item_name="{ item }">
|
||||
<Link :to="'/' + item.name">
|
||||
{{ item.name }}
|
||||
</Link>
|
||||
</template>
|
||||
<template #item_owner="{ item }">
|
||||
<Link :to="'/' + orgs[item.name].ownerName">
|
||||
{{ orgs[item.name].ownerName }}
|
||||
</Link>
|
||||
</template>
|
||||
<template #item_role="{ item }">
|
||||
{{ orgs[item.name].role.title }}
|
||||
</template>
|
||||
<template #item_accepted="{ item }">
|
||||
<InputCheckbox v-model="orgs[item.name].accepted" :disabled="true" />
|
||||
</template>
|
||||
</SortableTable>
|
||||
</Card>
|
||||
<Card class="basis-full md:basis-4/12">
|
||||
<template #header>{{ i18n.t("userAdmin.sidebar") }}</template>
|
||||
<ul>
|
||||
<li>
|
||||
<Link :href="_authUrl">{{ i18n.t("userAdmin.hangarAuth") }}</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link :href="_forumUserUrl">{{ i18n.t("userAdmin.forum") }}</Link>
|
||||
</li>
|
||||
</ul>
|
||||
<Card md="col-start-1">
|
||||
<template #header>{{ i18n.t("userAdmin.projects") }}</template>
|
||||
|
||||
<SortableTable v-if="projects" :items="projects.result" :headers="projectsConfig">
|
||||
<template #item_name="{ item }">
|
||||
<Link :to="'/' + item.namespace.owner + '/' + item.name">
|
||||
{{ item.name }}
|
||||
</Link>
|
||||
</template>
|
||||
<template #item_owner="{ item }">
|
||||
<Link :to="'/' + item.namespace.owner">
|
||||
{{ item.namespace.owner }}
|
||||
</Link>
|
||||
</template>
|
||||
<template #item_role="{ item }">
|
||||
<!-- todo add role -->
|
||||
<{{ item.name }}'s role>
|
||||
</template>
|
||||
<template #item_accepted="{ item }">
|
||||
<InputCheckbox :model-value="item.visibility === 'public'" :disabled="true" />
|
||||
</template>
|
||||
</SortableTable>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
<Card md="mb-2">
|
||||
<template #header>{{ i18n.t("userAdmin.organizations") }}</template>
|
||||
|
||||
<SortableTable :items="orgList" :headers="orgConfig">
|
||||
<template #item_name="{ item }">
|
||||
<Link :to="'/' + item.name">
|
||||
{{ item.name }}
|
||||
</Link>
|
||||
</template>
|
||||
<template #item_owner="{ item }">
|
||||
<Link :to="'/' + orgs[item.name].ownerName">
|
||||
{{ orgs[item.name].ownerName }}
|
||||
</Link>
|
||||
</template>
|
||||
<template #item_role="{ item }">
|
||||
{{ orgs[item.name].role.title }}
|
||||
</template>
|
||||
<template #item_accepted="{ item }">
|
||||
<InputCheckbox v-model="orgs[item.name].accepted" :disabled="true" />
|
||||
</template>
|
||||
</SortableTable>
|
||||
</Card>
|
||||
<Card md="col-start-1">
|
||||
<template #header>{{ i18n.t("userAdmin.projects") }}</template>
|
||||
|
||||
<SortableTable v-if="projects" :items="projects.result" :headers="projectsConfig">
|
||||
<template #item_name="{ item }">
|
||||
<Link :to="'/' + item.namespace.owner + '/' + item.name">
|
||||
{{ item.name }}
|
||||
</Link>
|
||||
</template>
|
||||
<template #item_owner="{ item }">
|
||||
<Link :to="'/' + item.namespace.owner">
|
||||
{{ item.namespace.owner }}
|
||||
</Link>
|
||||
</template>
|
||||
<template #item_role="{ item }">
|
||||
<!-- todo add role -->
|
||||
<{{ item.name }}'s role>
|
||||
</template>
|
||||
<template #item_accepted="{ item }">
|
||||
<InputCheckbox :model-value="item.visibility === 'public'" :disabled="true" />
|
||||
</template>
|
||||
</SortableTable>
|
||||
</Card>
|
||||
</template>
|
||||
|
||||
<route lang="yaml">
|
||||
meta:
|
||||
requireGlobalPerm: ["EDIT_ALL_USER_SETTINGS"]
|
||||
</route>
|
||||
|
@ -1,22 +1,25 @@
|
||||
<script lang="ts" setup>
|
||||
import { useContext } from "vite-ssr/vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { useRoute, useRouter } from "vue-router";
|
||||
import { handleRequestError } from "~/composables/useErrorHandling";
|
||||
import { computed, ref } from "vue";
|
||||
import { cloneDeep, isEqual } from "lodash-es";
|
||||
import { useHead } from "@vueuse/head";
|
||||
import { handleRequestError } from "~/composables/useErrorHandling";
|
||||
import { useBackendDataStore } from "~/store/backendData";
|
||||
import { useInternalApi } from "~/composables/useApi";
|
||||
import { cloneDeep, isEqual } from "lodash-es";
|
||||
import InputTag from "~/lib/components/ui/InputTag.vue";
|
||||
import Button from "~/lib/components/design/Button.vue";
|
||||
import PageTitle from "~/lib/components/design/PageTitle.vue";
|
||||
import Card from "~/lib/components/design/Card.vue";
|
||||
import Table from "~/lib/components/design/Table.vue";
|
||||
import { useHead } from "@vueuse/head";
|
||||
import { useSeo } from "~/composables/useSeo";
|
||||
import { useNotificationStore } from "~/lib/store/notification";
|
||||
import { definePageMeta } from "#imports";
|
||||
|
||||
definePageMeta({
|
||||
globalPermsRequired: ["MANUAL_VALUE_CHANGES"],
|
||||
});
|
||||
|
||||
const ctx = useContext();
|
||||
const i18n = useI18n();
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
@ -41,7 +44,7 @@ async function save() {
|
||||
router.go(0);
|
||||
} catch (e: any) {
|
||||
loading.value = false;
|
||||
handleRequestError(e, ctx, i18n);
|
||||
handleRequestError(e, i18n);
|
||||
}
|
||||
}
|
||||
|
||||
@ -53,35 +56,32 @@ const hasChanged = computed(() => !isEqual(platforms.value, originalPlatforms));
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<PageTitle>{{ i18n.t("platformVersions.title") }}</PageTitle>
|
||||
<Card>
|
||||
<Table class="w-full">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{{ i18n.t("platformVersions.platform") }}</th>
|
||||
<th>{{ i18n.t("platformVersions.versions") }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="platform in platforms" :key="platform.name">
|
||||
<td>{{ platform.name }}</td>
|
||||
<td>
|
||||
<InputTag v-model="platform.possibleVersions"></InputTag>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</Table>
|
||||
<div>
|
||||
<PageTitle>{{ i18n.t("platformVersions.title") }}</PageTitle>
|
||||
<Card>
|
||||
<Table class="w-full">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{{ i18n.t("platformVersions.platform") }}</th>
|
||||
<th>{{ i18n.t("platformVersions.versions") }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="platform in platforms" :key="platform.name">
|
||||
<td>{{ platform.name }}</td>
|
||||
<td>
|
||||
<InputTag v-model="platform.possibleVersions"></InputTag>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</Table>
|
||||
|
||||
<template #footer>
|
||||
<span class="flex justify-end">
|
||||
<Button :disabled="!hasChanged" @click="reset">{{ i18n.t("general.reset") }}</Button>
|
||||
<Button :disabled="loading || !hasChanged" class="ml-2" @click="save"> {{ i18n.t("platformVersions.saveChanges") }}</Button>
|
||||
</span>
|
||||
</template>
|
||||
</Card>
|
||||
<template #footer>
|
||||
<span class="flex justify-end">
|
||||
<Button :disabled="!hasChanged" @click="reset">{{ i18n.t("general.reset") }}</Button>
|
||||
<Button :disabled="loading || !hasChanged" class="ml-2" @click="save"> {{ i18n.t("platformVersions.saveChanges") }}</Button>
|
||||
</span>
|
||||
</template>
|
||||
</Card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<route lang="yaml">
|
||||
meta:
|
||||
requireGlobalPerm: ["MANUAL_VALUE_CHANGES"]
|
||||
</route>
|
||||
|
@ -1,9 +1,9 @@
|
||||
<script lang="ts" setup>
|
||||
import { onMounted } from "vue";
|
||||
import { useHead } from "@vueuse/head";
|
||||
import { useSeo } from "~/composables/useSeo";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { useRoute } from "vue-router";
|
||||
import { useSeo } from "~/composables/useSeo";
|
||||
import { useAuthStore } from "~/store/auth";
|
||||
|
||||
const i18n = useI18n();
|
||||
|
@ -1,22 +1,20 @@
|
||||
<script lang="ts" setup>
|
||||
import { useContext } from "vite-ssr/vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { useRoute } from "vue-router";
|
||||
import { useHead } from "@vueuse/head";
|
||||
import { PaginatedResult, User } from "hangar-api";
|
||||
import { computed, ref } from "vue";
|
||||
import { useAuthors } from "~/composables/useApiHelper";
|
||||
import { handleRequestError } from "~/composables/useErrorHandling";
|
||||
import SortableTable, { Header } from "~/components/SortableTable.vue";
|
||||
import PageTitle from "~/lib/components/design/PageTitle.vue";
|
||||
import UserAvatar from "~/components/UserAvatar.vue";
|
||||
import { useHead } from "@vueuse/head";
|
||||
import { useSeo } from "~/composables/useSeo";
|
||||
import Link from "~/lib/components/design/Link.vue";
|
||||
import { useApi } from "~/composables/useApi";
|
||||
import { PaginatedResult, User } from "hangar-api";
|
||||
import { computed, ref } from "vue";
|
||||
const ctx = useContext();
|
||||
const i18n = useI18n();
|
||||
const route = useRoute();
|
||||
const authors = await useAuthors().catch((e) => handleRequestError(e, ctx, i18n));
|
||||
const authors = await useAuthors().catch((e) => handleRequestError(e, i18n));
|
||||
|
||||
const headers = [
|
||||
{ name: "pic", title: "", sortable: false },
|
||||
@ -30,7 +28,7 @@ const sort = ref<string[]>([]);
|
||||
const requestParams = computed(() => {
|
||||
const limit = 25;
|
||||
return {
|
||||
limit: limit,
|
||||
limit,
|
||||
offset: page.value * limit,
|
||||
sort: sort.value,
|
||||
};
|
||||
@ -40,8 +38,8 @@ async function updateSort(col: string, sorter: Record<string, number>) {
|
||||
sort.value = [...Object.keys(sorter)]
|
||||
.map((k) => {
|
||||
const val = sorter[k];
|
||||
if (val == -1) return "-" + k;
|
||||
if (val == 1) return k;
|
||||
if (val === -1) return "-" + k;
|
||||
if (val === 1) return k;
|
||||
return null;
|
||||
})
|
||||
.filter((v) => v !== null) as string[];
|
||||
@ -62,12 +60,14 @@ useHead(useSeo(i18n.t("pages.authorsTitle"), "Hangar Project Authors", route, nu
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<PageTitle>Authors</PageTitle>
|
||||
<SortableTable :headers="headers" :items="authors?.result" :server-pagination="authors?.pagination" @update:sort="updateSort" @update:page="updatePage">
|
||||
<template #item_pic="{ item }"><UserAvatar :username="item.name" size="xs"></UserAvatar></template>
|
||||
<template #item_joinDate="{ item }">{{ i18n.d(item?.joinDate, "date") }}</template>
|
||||
<template #item_name="{ item }">
|
||||
<Link :to="'/' + item.name">{{ item.name }}</Link>
|
||||
</template>
|
||||
</SortableTable>
|
||||
<div>
|
||||
<PageTitle>Authors</PageTitle>
|
||||
<SortableTable :headers="headers" :items="authors?.result" :server-pagination="authors?.pagination" @update:sort="updateSort" @update:page="updatePage">
|
||||
<template #item_pic="{ item }"><UserAvatar :username="item.name" size="xs"></UserAvatar></template>
|
||||
<template #item_joinDate="{ item }">{{ i18n.d(item?.joinDate, "date") }}</template>
|
||||
<template #item_name="{ item }">
|
||||
<Link :to="'/' + item.name">{{ item.name }}</Link>
|
||||
</template>
|
||||
</SortableTable>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -19,7 +19,3 @@ useHead(useSeo((route.params.status || 404) + " " + (route.params.msg || "Not fo
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<route lang="yaml">
|
||||
layout: error
|
||||
</route>
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user