diff --git a/Dockerfile b/Dockerfile index ea0b46048..65babb12a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,7 +3,9 @@ FROM openjdk:20-slim AS build COPY . /home/reposilite-build WORKDIR /home/reposilite-build RUN \ - apt-get update; apt-get install -y curl + rm -rf reposilite-frontend/node_modules +RUN \ + apt-get update; apt-get install -y curl RUN \ export GRADLE_OPTS="-Djdk.lang.Process.launchMechanism=vfork" && \ chmod +x gradlew && \ diff --git a/reposilite-frontend/fake-api/extensions.js b/reposilite-frontend/fake-api/extensions.js index f3bbd98b1..7e311b7f1 100644 --- a/reposilite-frontend/fake-api/extensions.js +++ b/reposilite-frontend/fake-api/extensions.js @@ -25,7 +25,7 @@ const basicAuth = (req) => { const authorized = (req, success, failure) => { const [login, password] = basicAuth(req) - if (login == 'name' && password == 'secret') { + if (login === 'name' && password === 'secret') { console.log('Authorization successful for request ' + req.url) success() } else failure && failure() @@ -66,4 +66,4 @@ module.exports = { createFileDetails, createDirectoryDetails, generateDayWiseTimeSeries -} \ No newline at end of file +} diff --git a/reposilite-frontend/fake-api/fake-reposilite-backend.js b/reposilite-frontend/fake-api/fake-reposilite-backend.js index 0e8c00b99..b9fcf1d37 100644 --- a/reposilite-frontend/fake-api/fake-reposilite-backend.js +++ b/reposilite-frontend/fake-api/fake-reposilite-backend.js @@ -210,7 +210,11 @@ application createdAt: Date.now(), description: "Description", }, - permissions: [{ identifier: "access-token:manager" }], + permissions: [ + { + identifier: "access-token:read" + } + ], routes: [ { path: "/", diff --git a/reposilite-frontend/package-lock.json b/reposilite-frontend/package-lock.json index 63e3fd9c1..a69a85b92 100644 --- a/reposilite-frontend/package-lock.json +++ b/reposilite-frontend/package-lock.json @@ -41,6 +41,7 @@ "eslint-plugin-vue": "^9.18.1", "express": "^4.18.2", "express-ws": "^5.0.2", + "jsdoc": "^4.0.2", "json-server": "^0.17.4", "nodemon": "^3.0.1", "rollup-plugin-visualizer": "^5.9.2", @@ -1007,6 +1008,18 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@jsdoc/salty": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/@jsdoc/salty/-/salty-0.2.6.tgz", + "integrity": "sha512-aA+awb5yoml8TQ3CzI5Ue7sM3VMRC4l1zJJW4fgZ8OCL1wshJZhNzaf0PL85DSnOUw6QuFgeHGD/eq/xwwAF2g==", + "dev": true, + "dependencies": { + "lodash": "^4.17.21" + }, + "engines": { + "node": ">=v12.0.0" + } + }, "node_modules/@jsonforms/core": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/@jsonforms/core/-/core-3.1.0.tgz", @@ -1070,6 +1083,28 @@ "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==" }, + "node_modules/@types/linkify-it": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-3.0.5.tgz", + "integrity": "sha512-yg6E+u0/+Zjva+buc3EIb+29XEg4wltq7cSmd4Uc2EE/1nUVmxyzpX6gUXD0V8jIrG0r7YeOGVIbYRkxeooCtw==", + "dev": true + }, + "node_modules/@types/markdown-it": { + "version": "12.2.3", + "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-12.2.3.tgz", + "integrity": "sha512-GKMHFfv3458yYy+v/N8gjufHO6MSZKCOXpZc5GXIWWy8uldwfmPn98vp81gZ5f9SVw8YYBctgfJ22a2d7AOMeQ==", + "dev": true, + "dependencies": { + "@types/linkify-it": "*", + "@types/mdurl": "*" + } + }, + "node_modules/@types/mdurl": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-1.0.5.tgz", + "integrity": "sha512-6L6VymKTzYSrEf4Nev4Xa1LCHKrlTlYCBMTlQKFuddo1CvQcE52I0mwfOJayueUC7MJuXOeHTcIU683lzd0cUA==", + "dev": true + }, "node_modules/@types/web-bluetooth": { "version": "0.0.20", "resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.20.tgz", @@ -1780,6 +1815,12 @@ "node": ">=8" } }, + "node_modules/bluebird": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", + "dev": true + }, "node_modules/body-parser": { "version": "1.20.2", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", @@ -1927,6 +1968,18 @@ } ] }, + "node_modules/catharsis": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/catharsis/-/catharsis-0.9.0.tgz", + "integrity": "sha512-prMTQVpcns/tzFgFVkVp6ak6RykZyWb3gu8ckUpd6YkTlacOd3DXGJjIpD4Q6zJirizvaiAjSSHlOsA+6sNh2A==", + "dev": true, + "dependencies": { + "lodash": "^4.17.15" + }, + "engines": { + "node": ">= 10" + } + }, "node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -3365,6 +3418,53 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/js2xmlparser": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/js2xmlparser/-/js2xmlparser-4.0.2.tgz", + "integrity": "sha512-6n4D8gLlLf1n5mNLQPRfViYzu9RATblzPEtm1SthMX1Pjao0r9YI9nw7ZIfRxQMERS87mcswrg+r/OYrPRX6jA==", + "dev": true, + "dependencies": { + "xmlcreate": "^2.0.4" + } + }, + "node_modules/jsdoc": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/jsdoc/-/jsdoc-4.0.2.tgz", + "integrity": "sha512-e8cIg2z62InH7azBBi3EsSEqrKx+nUtAS5bBcYTSpZFA+vhNPyhv8PTFZ0WsjOPDj04/dOLlm08EDcQJDqaGQg==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.20.15", + "@jsdoc/salty": "^0.2.1", + "@types/markdown-it": "^12.2.3", + "bluebird": "^3.7.2", + "catharsis": "^0.9.0", + "escape-string-regexp": "^2.0.0", + "js2xmlparser": "^4.0.2", + "klaw": "^3.0.0", + "markdown-it": "^12.3.2", + "markdown-it-anchor": "^8.4.1", + "marked": "^4.0.10", + "mkdirp": "^1.0.4", + "requizzle": "^0.2.3", + "strip-json-comments": "^3.1.0", + "underscore": "~1.13.2" + }, + "bin": { + "jsdoc": "jsdoc.js" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/jsdoc/node_modules/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/jsesc": { "version": "2.5.2", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", @@ -3442,6 +3542,15 @@ "node": ">=6" } }, + "node_modules/klaw": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/klaw/-/klaw-3.0.0.tgz", + "integrity": "sha512-0Fo5oir+O9jnXu5EefYbVK+mHMBeEVEy2cmctR1O1NECcCkPRreJKrS6Qt/j3KC2C148Dfo9i3pCmCMsdqGr0g==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.1.9" + } + }, "node_modules/kolorist": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/kolorist/-/kolorist-1.8.0.tgz", @@ -3461,6 +3570,15 @@ "node": ">= 0.8.0" } }, + "node_modules/linkify-it": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-3.0.3.tgz", + "integrity": "sha512-ynTsyrFSdE5oZ/O9GEf00kPngmOfVwazR5GKDq6EYfhlpFug3J2zybX56a2PRRpc9P+FuSoGNAwjlbDs9jJBPQ==", + "dev": true, + "dependencies": { + "uc.micro": "^1.0.1" + } + }, "node_modules/locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", @@ -3535,6 +3653,59 @@ "node": ">=12" } }, + "node_modules/markdown-it": { + "version": "12.3.2", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-12.3.2.tgz", + "integrity": "sha512-TchMembfxfNVpHkbtriWltGWc+m3xszaRD0CZup7GFFhzIgQqxIfn3eGj1yZpfuflzPvfkt611B2Q/Bsk1YnGg==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1", + "entities": "~2.1.0", + "linkify-it": "^3.0.1", + "mdurl": "^1.0.1", + "uc.micro": "^1.0.5" + }, + "bin": { + "markdown-it": "bin/markdown-it.js" + } + }, + "node_modules/markdown-it-anchor": { + "version": "8.6.7", + "resolved": "https://registry.npmjs.org/markdown-it-anchor/-/markdown-it-anchor-8.6.7.tgz", + "integrity": "sha512-FlCHFwNnutLgVTflOYHPW2pPcl2AACqVzExlkGQNsi4CJgqOHN7YTgDd4LuhgN1BFO3TS0vLAruV1Td6dwWPJA==", + "dev": true, + "peerDependencies": { + "@types/markdown-it": "*", + "markdown-it": "*" + } + }, + "node_modules/markdown-it/node_modules/entities": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.1.0.tgz", + "integrity": "sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w==", + "dev": true, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/marked": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/marked/-/marked-4.3.0.tgz", + "integrity": "sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A==", + "dev": true, + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 12" + } + }, + "node_modules/mdurl": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", + "integrity": "sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g==", + "dev": true + }, "node_modules/media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -3648,6 +3819,18 @@ "node": "*" } }, + "node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true, + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/morgan": { "version": "1.10.0", "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.0.tgz", @@ -4221,6 +4404,15 @@ "node": ">=0.10.0" } }, + "node_modules/requizzle": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/requizzle/-/requizzle-0.2.4.tgz", + "integrity": "sha512-JRrFk1D4OQ4SqovXOgdav+K8EAhSB/LJZqCz8tbX0KObcdeM15Ss59ozWMBWmmINMagCwmqn4ZNryUGpBsl6Jw==", + "dev": true, + "dependencies": { + "lodash": "^4.17.21" + } + }, "node_modules/resolve-from": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", @@ -4777,12 +4969,24 @@ "node": ">= 0.6" } }, + "node_modules/uc.micro": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz", + "integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==", + "dev": true + }, "node_modules/undefsafe": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", "dev": true }, + "node_modules/underscore": { + "version": "1.13.6", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.6.tgz", + "integrity": "sha512-+A5Sja4HP1M08MaXya7p5LvjuM7K6q/2EaC0+iovj/wOcMsTzMvDFbasi/oSapiwOlt252IqsKqPjCl7huKS0A==", + "dev": true + }, "node_modules/unhead": { "version": "1.7.4", "resolved": "https://registry.npmjs.org/unhead/-/unhead-1.7.4.tgz", @@ -5181,6 +5385,12 @@ "node": ">=12" } }, + "node_modules/xmlcreate": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/xmlcreate/-/xmlcreate-2.0.4.tgz", + "integrity": "sha512-nquOebG4sngPmGPICTS5EnxqhKbCmz5Ox5hsszI2T6U5qdrJizBc+0ilYSEjTSzU0yZcmvppztXe/5Al5fUwdg==", + "dev": true + }, "node_modules/y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", diff --git a/reposilite-frontend/package.json b/reposilite-frontend/package.json index c291a3477..301179eeb 100644 --- a/reposilite-frontend/package.json +++ b/reposilite-frontend/package.json @@ -39,8 +39,8 @@ "vue3-tabs": "^0.1.13-beta.3" }, "devDependencies": { - "@vitejs/plugin-vue-jsx": "^3.0.2", "@vitejs/plugin-vue": "^4.4.1", + "@vitejs/plugin-vue-jsx": "^3.0.2", "@vue/compiler-sfc": "^3.3.8", "body-parser": "^1.20.2", "concurrently": "^8.2.2", @@ -48,6 +48,7 @@ "eslint-plugin-vue": "^9.18.1", "express": "^4.18.2", "express-ws": "^5.0.2", + "jsdoc": "^4.0.2", "json-server": "^0.17.4", "nodemon": "^3.0.1", "rollup-plugin-visualizer": "^5.9.2", diff --git a/reposilite-frontend/src/components/browser/FileBrowserView.vue b/reposilite-frontend/src/components/browser/FileBrowserView.vue index 55d069504..3b5b74c95 100644 --- a/reposilite-frontend/src/components/browser/FileBrowserView.vue +++ b/reposilite-frontend/src/components/browser/FileBrowserView.vue @@ -36,13 +36,13 @@ const props = defineProps({ const parentPath = ref('') const files = ref({}) -const { client, hasPermissionTo } = useSession() +const { details, client, hasPermissionTo } = useSession() const { applyAdjustments } = useAdjustments() const { getParentPath } = useQualifier() const processedFiles = computed(() => ({ ...files.value, - list: applyAdjustments([...files.value.list] || []) + list: applyAdjustments([...files.value.list ?? []]) })) const canUpload = computed(() => { @@ -50,8 +50,12 @@ const canUpload = computed(() => { }) watch( - () => props.qualifier.watchable, + () => [props.qualifier.watchable, details.value], async () => { + if (details.value == null) { + return + } + files.value = { list: [] } @@ -59,15 +63,38 @@ watch( const qualifier = props.qualifier.path client.value.maven.details(qualifier) - .then(response => files.value = { - list: response.data.files, + .then(response => { + files.value = { + list: response.data.files, + isEmpty: response.data.files.length === 0, + error: false + } }) - .then(() => files.value.isEmpty = files.value.list.length == 0) .catch(error => { - createErrorToast(`${error.response.status}: ${error.response.data.message}`) - files.value = { - list: [], - error: true + // simulate intermediate directory if 403 & user has access to only one directory + const currentRoutes = details.value.routes?.filter(route => route.path.startsWith(`/${qualifier}`)) ?? [] + + if (error.response.status === 403 && currentRoutes.length > 0) { + const intermediateDirectories = currentRoutes.map(currentRoute => { + let currentSegment = currentRoute.path.substring(`/${qualifier}/`.replaceAll('//', '/').length) + return currentSegment.includes('/') ? currentSegment.substring(0, currentSegment.indexOf('/')) : currentSegment + }) + + files.value = { + list: intermediateDirectories.map(directory => ({ + name: directory, + type: 'DIRECTORY', + list: [] + })), + isEmpty: false, + error: false + } + } else { + createErrorToast(`${error.response.status}: ${error.response.data.message}`) + files.value = { + list: [], + error: true + } } }) @@ -77,10 +104,10 @@ watch( ) const fileBrowserCompactViewKey = 'file-browser-compact-view' -const fileBrowserCompactMode = ref(localStorage.getItem(fileBrowserCompactViewKey) == "true") +const fileBrowserCompactMode = ref(localStorage.getItem(fileBrowserCompactViewKey) === "true") const toggleCompactMode = () => { fileBrowserCompactMode.value = !fileBrowserCompactMode.value - localStorage.setItem(fileBrowserCompactViewKey, fileBrowserCompactMode.value) + localStorage.setItem(fileBrowserCompactViewKey, fileBrowserCompactMode.value.toString()) } const MenuButton = (_, context) => { @@ -126,4 +153,4 @@ const MenuButton = (_, context) => { - \ No newline at end of file + diff --git a/reposilite-frontend/src/components/browser/FileList.vue b/reposilite-frontend/src/components/browser/FileList.vue index 0dd93e508..cf3098e88 100644 --- a/reposilite-frontend/src/components/browser/FileList.vue +++ b/reposilite-frontend/src/components/browser/FileList.vue @@ -53,7 +53,7 @@ const closeDeleteModal = () => (deleteModalValue.value = undefined) const isDirectory = (file) => - file.type == 'DIRECTORY' + file.type === 'DIRECTORY' const LinkEntry = ({ file }, context) => { return ( diff --git a/reposilite-frontend/src/components/card/SnippetsCard.vue b/reposilite-frontend/src/components/card/SnippetsCard.vue index 1e1ea9ef9..be6c389e3 100644 --- a/reposilite-frontend/src/components/card/SnippetsCard.vue +++ b/reposilite-frontend/src/components/card/SnippetsCard.vue @@ -79,7 +79,7 @@ watchEffect(() => { client.value.maven.content(`${qualifier.substring(0, qualifier.indexOf(elements[elements.length-1])-1)}/maven-metadata.xml`) .then(response => displayArtifact(response.data, elements[elements.length-1])) .catch(error => { - if (error.message !== 'Request failed with status code 404') { + if (error.response?.status !== 404 && error.response?.status !== 403) { console.log(error) } displayRepository() diff --git a/reposilite-frontend/src/helpers/vue-extensions.js b/reposilite-frontend/src/helpers/vue-extensions.js index b7345dbf6..7cfaabd27 100644 --- a/reposilite-frontend/src/helpers/vue-extensions.js +++ b/reposilite-frontend/src/helpers/vue-extensions.js @@ -1,6 +1,6 @@ const property = (type, required) => - { type, required } + ({ type, required }) export { property -} \ No newline at end of file +} diff --git a/reposilite-frontend/src/store/session.js b/reposilite-frontend/src/store/session.js index 59b8e990d..1084eb3b9 100644 --- a/reposilite-frontend/src/store/session.js +++ b/reposilite-frontend/src/store/session.js @@ -30,7 +30,32 @@ watchEffect(() => { localStorage.setItem('token-secret', token.value.secret) }) -const details = ref() +/** + * @type {(undefined|{ + * accessToken: { + * identifier: { + * type: string, + * value: number + * }, + * name: string, + * createdAt: string, + * description: string + * }, + * permissions: { + * identifier: string, + * shortcut: string + * }[], + * routes: { + * path: string, + * permission: { + * identifier: string, + * shortcut: string +* } + * }[] + * })} + */ +const defaultDetails = undefined +const details = ref(defaultDetails) const logout = () => { setToken('', '') @@ -53,7 +78,7 @@ const isLogged = computed(() => details.value !== undefined) const isManager = computed(() => { return details.value ?.permissions - ?.some(entry => entry.identifier === 'access-token:manager') == true + ?.some(entry => entry.identifier === 'access-token:manager') === true }) const hasPermissionTo = (path, permission) => { @@ -77,4 +102,4 @@ export function useSession() { hasPermissionTo, initializeSession } -} \ No newline at end of file +}