Start implementing frontend

Signed-off-by: Knut Ahlers <knut@ahlers.me>
This commit is contained in:
Knut Ahlers 2021-11-30 02:04:01 +01:00
parent ca0e05c97e
commit fe63b10e21
Signed by: luzifer
GPG key ID: 0066F03ED215AD7D
7 changed files with 356 additions and 2 deletions

106
package-lock.json generated
View file

@ -5,12 +5,17 @@
"packages": { "packages": {
"": { "": {
"dependencies": { "dependencies": {
"@fortawesome/fontawesome-svg-core": "^1.2.36",
"@fortawesome/free-brands-svg-icons": "^5.15.4",
"@fortawesome/free-solid-svg-icons": "^5.15.4",
"@fortawesome/vue-fontawesome": "^2.0.6",
"axios": "^0.24.0", "axios": "^0.24.0",
"bootstrap": "^4.6.0", "bootstrap": "^4.6.0",
"bootstrap-vue": "^2.21.2", "bootstrap-vue": "^2.21.2",
"bootswatch": "^4.6.0", "bootswatch": "^4.6.0",
"moment": "^2.29.1", "moment": "^2.29.1",
"vue": "^2.6.14" "vue": "^2.6.14",
"vue-router": "^3.5.3"
}, },
"devDependencies": { "devDependencies": {
"babel-eslint": "^10.1.0", "babel-eslint": "^10.1.0",
@ -220,6 +225,60 @@
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
} }
}, },
"node_modules/@fortawesome/fontawesome-common-types": {
"version": "0.2.36",
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-0.2.36.tgz",
"integrity": "sha512-a/7BiSgobHAgBWeN7N0w+lAhInrGxksn13uK7231n2m8EDPE3BMCl9NZLTGrj9ZXfCmC6LM0QLqXidIizVQ6yg==",
"hasInstallScript": true,
"engines": {
"node": ">=6"
}
},
"node_modules/@fortawesome/fontawesome-svg-core": {
"version": "1.2.36",
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-1.2.36.tgz",
"integrity": "sha512-YUcsLQKYb6DmaJjIHdDWpBIGCcyE/W+p/LMGvjQem55Mm2XWVAP5kWTMKWLv9lwpCVjpLxPyOMOyUocP1GxrtA==",
"hasInstallScript": true,
"dependencies": {
"@fortawesome/fontawesome-common-types": "^0.2.36"
},
"engines": {
"node": ">=6"
}
},
"node_modules/@fortawesome/free-brands-svg-icons": {
"version": "5.15.4",
"resolved": "https://registry.npmjs.org/@fortawesome/free-brands-svg-icons/-/free-brands-svg-icons-5.15.4.tgz",
"integrity": "sha512-f1witbwycL9cTENJegcmcZRYyawAFbm8+c6IirLmwbbpqz46wyjbQYLuxOc7weXFXfB7QR8/Vd2u5R3q6JYD9g==",
"hasInstallScript": true,
"dependencies": {
"@fortawesome/fontawesome-common-types": "^0.2.36"
},
"engines": {
"node": ">=6"
}
},
"node_modules/@fortawesome/free-solid-svg-icons": {
"version": "5.15.4",
"resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-5.15.4.tgz",
"integrity": "sha512-JLmQfz6tdtwxoihXLg6lT78BorrFyCf59SAwBM6qV/0zXyVeDygJVb3fk+j5Qat+Yvcxp1buLTY5iDh1ZSAQ8w==",
"hasInstallScript": true,
"dependencies": {
"@fortawesome/fontawesome-common-types": "^0.2.36"
},
"engines": {
"node": ">=6"
}
},
"node_modules/@fortawesome/vue-fontawesome": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/@fortawesome/vue-fontawesome/-/vue-fontawesome-2.0.6.tgz",
"integrity": "sha512-V3vT3flY15AKbUS31aZOP12awQI3aAzkr2B1KnqcHLmwrmy51DW3pwyBczKdypV8QxBZ8U68Hl2XxK2nudTxpg==",
"peerDependencies": {
"@fortawesome/fontawesome-svg-core": "~1 || >=1.3.0-beta1",
"vue": "~2"
}
},
"node_modules/@humanwhocodes/config-array": { "node_modules/@humanwhocodes/config-array": {
"version": "0.5.0", "version": "0.5.0",
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.5.0.tgz", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.5.0.tgz",
@ -3301,6 +3360,11 @@
"resolved": "https://registry.npmjs.org/vue-functional-data-merge/-/vue-functional-data-merge-3.1.0.tgz", "resolved": "https://registry.npmjs.org/vue-functional-data-merge/-/vue-functional-data-merge-3.1.0.tgz",
"integrity": "sha512-leT4kdJVQyeZNY1kmnS1xiUlQ9z1B/kdBFCILIjYYQDqZgLqCLa0UhjSSeRX6c3mUe6U5qYeM8LrEqkHJ1B4LA==" "integrity": "sha512-leT4kdJVQyeZNY1kmnS1xiUlQ9z1B/kdBFCILIjYYQDqZgLqCLa0UhjSSeRX6c3mUe6U5qYeM8LrEqkHJ1B4LA=="
}, },
"node_modules/vue-router": {
"version": "3.5.3",
"resolved": "https://registry.npmjs.org/vue-router/-/vue-router-3.5.3.tgz",
"integrity": "sha512-FUlILrW3DGitS2h+Xaw8aRNvGTwtuaxrRkNSHWTizOfLUie7wuYwezeZ50iflRn8YPV5kxmU2LQuu3nM/b3Zsg=="
},
"node_modules/vue-template-compiler": { "node_modules/vue-template-compiler": {
"version": "2.6.14", "version": "2.6.14",
"resolved": "https://registry.npmjs.org/vue-template-compiler/-/vue-template-compiler-2.6.14.tgz", "resolved": "https://registry.npmjs.org/vue-template-compiler/-/vue-template-compiler-2.6.14.tgz",
@ -3538,6 +3602,41 @@
} }
} }
}, },
"@fortawesome/fontawesome-common-types": {
"version": "0.2.36",
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-0.2.36.tgz",
"integrity": "sha512-a/7BiSgobHAgBWeN7N0w+lAhInrGxksn13uK7231n2m8EDPE3BMCl9NZLTGrj9ZXfCmC6LM0QLqXidIizVQ6yg=="
},
"@fortawesome/fontawesome-svg-core": {
"version": "1.2.36",
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-1.2.36.tgz",
"integrity": "sha512-YUcsLQKYb6DmaJjIHdDWpBIGCcyE/W+p/LMGvjQem55Mm2XWVAP5kWTMKWLv9lwpCVjpLxPyOMOyUocP1GxrtA==",
"requires": {
"@fortawesome/fontawesome-common-types": "^0.2.36"
}
},
"@fortawesome/free-brands-svg-icons": {
"version": "5.15.4",
"resolved": "https://registry.npmjs.org/@fortawesome/free-brands-svg-icons/-/free-brands-svg-icons-5.15.4.tgz",
"integrity": "sha512-f1witbwycL9cTENJegcmcZRYyawAFbm8+c6IirLmwbbpqz46wyjbQYLuxOc7weXFXfB7QR8/Vd2u5R3q6JYD9g==",
"requires": {
"@fortawesome/fontawesome-common-types": "^0.2.36"
}
},
"@fortawesome/free-solid-svg-icons": {
"version": "5.15.4",
"resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-5.15.4.tgz",
"integrity": "sha512-JLmQfz6tdtwxoihXLg6lT78BorrFyCf59SAwBM6qV/0zXyVeDygJVb3fk+j5Qat+Yvcxp1buLTY5iDh1ZSAQ8w==",
"requires": {
"@fortawesome/fontawesome-common-types": "^0.2.36"
}
},
"@fortawesome/vue-fontawesome": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/@fortawesome/vue-fontawesome/-/vue-fontawesome-2.0.6.tgz",
"integrity": "sha512-V3vT3flY15AKbUS31aZOP12awQI3aAzkr2B1KnqcHLmwrmy51DW3pwyBczKdypV8QxBZ8U68Hl2XxK2nudTxpg==",
"requires": {}
},
"@humanwhocodes/config-array": { "@humanwhocodes/config-array": {
"version": "0.5.0", "version": "0.5.0",
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.5.0.tgz", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.5.0.tgz",
@ -5958,6 +6057,11 @@
"resolved": "https://registry.npmjs.org/vue-functional-data-merge/-/vue-functional-data-merge-3.1.0.tgz", "resolved": "https://registry.npmjs.org/vue-functional-data-merge/-/vue-functional-data-merge-3.1.0.tgz",
"integrity": "sha512-leT4kdJVQyeZNY1kmnS1xiUlQ9z1B/kdBFCILIjYYQDqZgLqCLa0UhjSSeRX6c3mUe6U5qYeM8LrEqkHJ1B4LA==" "integrity": "sha512-leT4kdJVQyeZNY1kmnS1xiUlQ9z1B/kdBFCILIjYYQDqZgLqCLa0UhjSSeRX6c3mUe6U5qYeM8LrEqkHJ1B4LA=="
}, },
"vue-router": {
"version": "3.5.3",
"resolved": "https://registry.npmjs.org/vue-router/-/vue-router-3.5.3.tgz",
"integrity": "sha512-FUlILrW3DGitS2h+Xaw8aRNvGTwtuaxrRkNSHWTizOfLUie7wuYwezeZ50iflRn8YPV5kxmU2LQuu3nM/b3Zsg=="
},
"vue-template-compiler": { "vue-template-compiler": {
"version": "2.6.14", "version": "2.6.14",
"resolved": "https://registry.npmjs.org/vue-template-compiler/-/vue-template-compiler-2.6.14.tgz", "resolved": "https://registry.npmjs.org/vue-template-compiler/-/vue-template-compiler-2.6.14.tgz",

View file

@ -8,11 +8,16 @@
"vue-template-compiler": "^2.6.14" "vue-template-compiler": "^2.6.14"
}, },
"dependencies": { "dependencies": {
"@fortawesome/fontawesome-svg-core": "^1.2.36",
"@fortawesome/free-brands-svg-icons": "^5.15.4",
"@fortawesome/free-solid-svg-icons": "^5.15.4",
"@fortawesome/vue-fontawesome": "^2.0.6",
"axios": "^0.24.0", "axios": "^0.24.0",
"bootstrap": "^4.6.0", "bootstrap": "^4.6.0",
"bootstrap-vue": "^2.21.2", "bootstrap-vue": "^2.21.2",
"bootswatch": "^4.6.0", "bootswatch": "^4.6.0",
"moment": "^2.29.1", "moment": "^2.29.1",
"vue": "^2.6.14" "vue": "^2.6.14",
"vue-router": "^3.5.3"
} }
} }

52
src/app.vue Normal file
View file

@ -0,0 +1,52 @@
<template>
<div>
<b-navbar
class="mb-2"
toggleable="lg"
type="dark"
variant="primary"
>
<b-navbar-brand :to="{name: 'index'}">
<font-awesome-icon
fixed-width
:icon="['fas', 'cloud-download-alt']"
/>
Go-LatestVer
</b-navbar-brand>
<b-navbar-toggle target="nav-collapse" />
<b-collapse
id="nav-collapse"
is-nav
>
<b-navbar-nav>
<b-nav-item :to="{name: 'log'}">
Log
</b-nav-item>
</b-navbar-nav>
<!-- Right aligned nav items -->
<b-navbar-nav class="ml-auto">
<b-nav-item href="/log.rss">
<font-awesome-icon
fixed-width
:icon="['fas', 'rss']"
/>
All Updates
</b-nav-item>
</b-navbar-nav>
</b-collapse>
</b-navbar>
<b-container>
<router-view />
</b-container>
</div>
</template>
<script>
export default {
name: 'GoLatestVerApp',
}
</script>

95
src/catalog_index.vue Normal file
View file

@ -0,0 +1,95 @@
<template>
<b-row>
<b-col>
<b-table
:fields="fields"
:items="catalog"
small
striped
>
<template #cell(_key)="data">
<router-link :to="{name: 'entry', params: { name:data.item.name, tag: data.item.tag }}">
{{ data.item.name }}:{{ data.item.tag }}
</router-link>
</template>
<template #cell(version_time)="data">
{{ moment(data.item.version_time).format('lll') }}
</template>
<template #cell(_links)="data">
<a
v-for="link in data.item.links"
:key="link.name"
:href="link.url"
rel="noopener noreferrer"
target="_blank"
>
<font-awesome-icon
fixed-width
:icon="iconClassesToIcon(link.icon_class)"
/>
{{ link.name }}
</a>
</template>
</b-table>
</b-col>
</b-row>
</template>
<script>
import axios from 'axios'
import moment from 'moment'
export default {
data() {
return {
catalog: [],
fields: [
{ key: '_key', label: 'Catalog Entry', sortable: true },
{ key: 'current_version', label: 'Version' },
{ key: 'version_time', label: 'Updated At', sortable: true },
{ key: '_links', label: 'External Links' },
],
}
},
methods: {
fetchCatalogIndex() {
axios.get('/v1/catalog')
.then(resp => {
this.catalog = resp.data
})
},
iconClassesToIcon(ic) {
let namespace = 'fas'
let icon = ''
for (const c of ic.split(' ')) {
if (c === 'fa-fw') {
continue
}
if (['fab', 'fas'].includes(c)) {
namespace = c
}
if (c.startsWith('fa-')) {
icon = c.replace('fa-', '')
}
}
return [namespace, icon]
},
moment,
},
mounted() {
this.fetchCatalogIndex()
},
name: 'GoLatestVerCatalogIndex',
}
</script>

59
src/log.vue Normal file
View file

@ -0,0 +1,59 @@
<template>
<b-row>
<b-col>
<b-table
:fields="fields"
:items="logs"
small
striped
>
<template #cell(_key)="data">
<router-link :to="{name: 'entry', params: { name:data.item.catalog_name, tag: data.item.catalog_tag }}">
{{ data.item.catalog_name }}:{{ data.item.catalog_tag }}
</router-link>
</template>
<template #cell(timestamp)="data">
{{ moment(data.item.timestamp).format('lll') }}
</template>
</b-table>
</b-col>
</b-row>
</template>
<script>
import axios from 'axios'
import moment from 'moment'
export default {
data() {
return {
fields: [
{ key: '_key', label: 'Catalog Entry' },
{ key: 'version_from', label: 'Version From' },
{ key: 'version_to', label: 'Version To' },
{ key: 'timestamp', label: 'Updated At' },
],
logs: [],
}
},
methods: {
fetchLog() {
axios.get('/v1/log')
.then(resp => {
this.logs = resp.data
})
},
moment,
},
mounted() {
this.fetchLog()
},
name: 'GoLatestVerLog',
}
</script>

View file

@ -4,17 +4,29 @@ import 'bootstrap/dist/css/bootstrap.css'
import 'bootstrap-vue/dist/bootstrap-vue.css' import 'bootstrap-vue/dist/bootstrap-vue.css'
import 'bootswatch/dist/darkly/bootstrap.css' import 'bootswatch/dist/darkly/bootstrap.css'
import { library } from '@fortawesome/fontawesome-svg-core'
import { fab } from '@fortawesome/free-brands-svg-icons'
import { fas } from '@fortawesome/free-solid-svg-icons'
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome'
library.add(fab, fas)
import Vue from 'vue' import Vue from 'vue'
import { BootstrapVue } from 'bootstrap-vue' import { BootstrapVue } from 'bootstrap-vue'
import VueRouter from 'vue-router'
import App from './app.vue' import App from './app.vue'
import router from './router.js'
Vue.config.devtools = process.env.NODE_ENV === 'dev' Vue.config.devtools = process.env.NODE_ENV === 'dev'
Vue.component('FontAwesomeIcon', FontAwesomeIcon)
Vue.use(BootstrapVue) Vue.use(BootstrapVue)
Vue.use(VueRouter)
new Vue({ new Vue({
components: { App }, components: { App },
el: '#app', el: '#app',
name: 'GoLatestVer', name: 'GoLatestVer',
render: h => h(App), render: h => h(App),
router,
}) })

27
src/router.js Normal file
View file

@ -0,0 +1,27 @@
import CatalogEntry from './catalog_entry.vue'
import CatalogIndex from './catalog_index.vue'
import Log from './log.vue'
import VueRouter from 'vue-router'
const router = new VueRouter({
mode: 'history',
routes: [
{
component: CatalogIndex,
name: 'index',
path: '/',
},
{
component: CatalogEntry,
name: 'entry',
path: '/:name/:tag',
},
{
component: Log,
name: 'log',
path: '/log',
},
],
})
export default router