Commit accaf649 authored by jfriedli's avatar jfriedli
Browse files

Merge branch 'develop' into 'master'

prepare v1.2.0

See merge request !127
parents 2dd5de53 779ba119
Pipeline #31312 passed with stages
in 54 minutes and 22 seconds
......@@ -13,7 +13,7 @@ module.exports = {
extends: [
// https://github.com/vuejs/eslint-plugin-vue#priority-a-essential-error-prevention
// consider switching to `plugin:vue/strongly-recommended` or `plugin:vue/recommended` for stricter rules.
'plugin:vue/essential',
'plugin:vue/strongly-recommended',
'@vue/standard'
],
......@@ -47,8 +47,6 @@ module.exports = {
'import/no-extraneous-dependencies': 'off',
'prefer-promise-reject-errors': 'off',
// allow console.log during development only
'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off',
// allow debugger during development only
'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off'
}
......
......@@ -6,6 +6,7 @@ stages:
- test
- review_build
- review
- container_sast
- renovate
cache:
......@@ -30,7 +31,7 @@ build site:
- dist/pwa
test:integration:
image: docker:19.03.3
image: docker:19.03.5
variables:
DOCKER_DRIVER: overlay2
......@@ -38,7 +39,7 @@ test:integration:
DOCKER_HOST: tcp://localhost:2375
services:
- docker:19.03.3-dind
- docker:19.03.5-dind
only:
- develop
- master
......@@ -104,7 +105,7 @@ build_tagged_container:
review_app:
stage: review
image: lachlanevenson/k8s-kubectl:v1.16.2
image: lachlanevenson/k8s-kubectl:v1.16.3
before_script:
- echo "deploying review app"
- apk add --no-cache gettext
......@@ -127,10 +128,12 @@ review_app:
on_stop: stop_review_app
only:
- merge_requests
- develop
- master
stop_review_app:
stage: review
image: lachlanevenson/k8s-kubectl:v1.16.2
image: lachlanevenson/k8s-kubectl:v1.16.3
before_script:
- echo "deleting review app"
- apk add --no-cache gettext
......@@ -161,3 +164,36 @@ renovate:
script:
- docker run -e GITLAB_TOKEN="$GITLAB_TOKEN" -e GITHUB_TOKEN="$GITHUB_TOKEN" -v $PWD/renovate-config.js:/usr/src/app/config.js renovate/renovate:13 $(cat repositories.txt | xargs)
allow_failure: true
container_sast:
stage: container_sast
image: docker:stable
services:
- docker:dind
variables:
DOCKER_DRIVER: overlay2
DOCKER_TLS_CERTDIR: ""
DOCKER_HOST: tcp://localhost:2375
allow_failure: true
before_script:
- echo "Running Container SAST"
script:
- docker run -d --name db arminc/clair-db:latest
- docker run -p 6060:6060 --link db:postgres -d --name clair --restart on-failure arminc/clair-local-scan:v2.0.1
- apk add -U wget ca-certificates
- docker pull $CI_REGISTRY_IMAGE:latest
- wget https://github.com/arminc/clair-scanner/releases/download/v8/clair-scanner_linux_amd64
- mv clair-scanner_linux_amd64 clair-scanner
- chmod +x clair-scanner
- touch clair-whitelist.yml
- while( ! wget -q -O /dev/null http://localhost:6060/v1/namespaces ) ; do sleep 1 ; done
- retries=0
- echo "Waiting for clair daemon to start"
- while( ! wget -T 10 -q -O /dev/null http://localhost:6060/v1/namespaces ) ; do sleep 1 ; echo -n "." ; if [ $retries -eq 10 ] ; then echo " Timeout, aborting." ; exit 1 ; fi ; retries=$(($retries+1)) ; done
- ./clair-scanner --threshold="Negligible" -c http://localhost:6060 --ip $(hostname -i) -r gl-container-scanning-report.json -l clair.log -w clair-whitelist.yml $CI_REGISTRY_IMAGE:latest
after_script:
- cat gl-container-scanning-report.json
artifacts:
paths: [gl-container-scanning-report.json]
only:
- master
......@@ -14,7 +14,7 @@ RUN yarn && \
# production stage
# https://jonathanmh.com/deploying-a-vue-js-single-page-app-including-router-with-docker/
# https://www.georg-ledermann.de/blog/2018/04/27/dockerize-and-configure-javascript-single-page-application/
FROM nginx:1.15.7-alpine as production-stage
FROM nginx:1.17.6-alpine as production-stage
RUN mkdir -p /tmp/nginx/vue-single-page-app && \
mkdir -p /var/log/nginx && \
mkdir -p /var/www/html
......
# https://jessie-barnett.dev/blog/articles/running-cypress-gitlab-ci/
FROM cypress/base:10
FROM cypress/included:3.4.1
WORKDIR /app
# This allows for creating a package.json within the Dockerfile itself
RUN npm init --yes && \
npm i cypress && \
npm i wait-on && \
npm i cypress-file-upload
# Copying both the test files and the config for cypress
COPY ./test/cypress cypress
COPY cypress.ci.json /app/cypress.json
COPY wait-for-it.sh ./wait-for-it.sh
RUN chmod +x wait-for-it.sh
ENTRYPOINT ./wait-for-it.sh mat2-web-frontend:8080 -s -t 0 -- cypress run --spec "**/*.spec.js"
# MAT2 Quasar Frontend (PWA)
![](mat-frontend.gif)
## Up and Running
To start developing use `docker-comse up` and if
this was successful you can access the app on:
`localhost:8080`. This will start the backend as well
using it's latest docker image.
## Configuration
To set the base url of the backend you have to define
`MAT2_API_URL_DEV` for dev builds and `MAT2_API_URL_PROD`
for production builds. If you use the docker environment
this is customizable in the `docker-compose.yml` file.
If none of these are set it will default to `http://localhost:5000/` (slash at the end).
This is a frontend for [MAT2-web](https://0xacab.org/jvoisin/mat2-web).
# How To Deploy
## Docker
## Manual Deployment with custom build
1) Install dependencies: `yarn install`
2) Export env variable `MAT2_API_URL_PROD` which points to your [MAT2-web backend](https://0xacab.org/jvoisin/mat2-web)
e.g. `MAT2_API_URL_PROD=https://mybackend.gnu/`. Alternatively you can define the URL in the `quasar.conf.js` file
on the following line: `API_URL: JSON.stringify(process.env.MAT2_API_URL_PROD)` and change it to:
`API_URL: 'https://mybackend.gnu/'`
3) `quasar build -m pwa` (Must have installed the quasar cli)
4) Copy the files from `./dist/pwa` to your hosting.
5) Enjoy :)
## Deployment with Docker
**Registry Frontend:** https://0xacab.org/jfriedli/mat2-quasar-frontend/container_registry
**Registry Backend:** https://0xacab.org/jvoisin/mat2-web/container_registry
......@@ -24,6 +27,7 @@ On every new tag/master a new Docker Container is built. To configure
its API Url you have to pass the environment variable
`MAT_API_HOST_PLACEHOLDER` which points to your backend. Make sure
it has a slash at the end ;).
Internally this replaces a placeholder string in the prebuilt JS/HTML/CSSS files and starts an NGINX server.
**Example:**
Build the container:
......@@ -33,6 +37,49 @@ Or alternatively get it from the registry prebuilt.
Then run it:
`docker run -it -e MAT_API_HOST_PLACEHOLDER='https://mybackend.gnu/' -p 80:80 mat2frontend`
# Contribute
## Up and Running for development
To start developing clone this repository and run `docker-compose up`. If
this was successful you can access the app on:
`localhost:8080`. This will start the backend as well
using it's latest docker image. Codechanges will trigger an instant updated in your browser.
If you update/add/remove dependencies you'll have to rebuild the container: `docker-compose up --build`.
If you don't want to use `docker-compose`.
1) `yarn install`
2) `yarn global add @quasar/cli`
3) `quasar dev`
Make sure you have a running backend instance that you can reference in the `quasar.js` file.
## Branching Workflow
For solving an issue you'll start by creating a branch and merge request for it.
When it's done you'll make a merge request for from your branch into the `develop` branch.
| Branch | Description |
| ---------|:-------------:|
| Develop | The develop branch MIGHT |
| Master | The Master branch MUST contain *operational code |
*operational: This means the code itself contains only working features and finished
tasks that have been tested and are known to be working.
### Tags
We do use tags to mark releases. A tag SHOULD reference the master branch.
On tag creation you have to submit a changelog.
### Configuration
To set the base url of the backend you have to define
`MAT2_API_URL_DEV` for dev builds and `MAT2_API_URL_PROD`
for production builds. If you use the docker environment
this is customizable in the `docker-compose.yml` file.
If none of these are set it will default to `http://localhost:5000/` (slash at the end).
## Translations
We'd love to receive any translation merge requests :).
## Dependency Management
We do use renovate which checks every night for updates and creates automated merge request.
......
module.exports = {
presets: [
'@quasar/babel-preset-app'
]
'@quasar/babel-preset-app',
],
plugins: ['@babel/plugin-proposal-optional-chaining']
}
......@@ -7,6 +7,7 @@
"supportFile": "cypress/support/index.js",
"videosFolder": "cypress/videos",
"video": false,
"waitForAnimations": false,
"json.schemas": [
{
"fileMatch": [
......
......@@ -7,6 +7,7 @@
"supportFile": "test/cypress/support/index.js",
"videosFolder": "test/cypress/videos",
"video": true,
"waitForAnimations": false,
"json.schemas": [
{
"fileMatch": [
......
......@@ -23,7 +23,3 @@ services:
build:
context: .
dockerfile: "Dockerfile.cypress"
command: [
"./wait-for-it.sh", "mat2-web-frontend:8080", "-s", "-t", "0", "--",
"npx", "cypress", "run"
]
......@@ -44,6 +44,7 @@ metadata:
kubernetes.io/ingress.class: nginx
certmanager.k8s.io/cluster-issuer: letsencrypt-prod
nginx.ingress.kubernetes.io/rewrite-target: /
nginx.ingress.kubernetes.io/proxy-body-size: 32m
spec:
tls:
- hosts:
......
......@@ -7,7 +7,8 @@ module.exports = function (ctx) {
// --> boot files are part of "main.js"
boot: [
'i18n',
'axios'
'axios',
'vue-router-transition'
],
css: [
......@@ -49,11 +50,24 @@ module.exports = function (ctx) {
'QCardActions',
'QBadge',
'QChip',
'QCircularProgress'
'QCircularProgress',
'QBtnDropdown',
'QItem',
'QItemSection',
'QItemLabel',
'QList',
'QDialog',
'QCardSection',
'QMenu',
'QTable',
'QTh',
'QTr',
'QTd'
],
directives: [
'Ripple'
'Ripple',
'ClosePopup'
],
// Quasar plugins
......@@ -98,7 +112,10 @@ module.exports = function (ctx) {
},
// animations: 'all', // --- includes all animations
animations: [],
animations: [
'fadeInLeft',
'fadeOutRight'
],
ssr: {
pwa: false
......
{}
{
"automerge": true,
"major": {
"automerge": false
}
}
<template>
<div id="q-app">
<router-view/>
<router-view />
</div>
</template>
......
......@@ -6,8 +6,8 @@ export default async ({ app, Vue }) => {
// Set i18n instance on app
app.i18n = new VueI18n({
locale: 'en-us',
fallbackLocale: 'en-us',
locale: 'en_US',
fallbackLocale: 'en_US',
messages
})
}
import VuePageTransition from 'vue-page-transition'
export default async ({ app, Vue }) => {
Vue.use(VuePageTransition)
}
<template>
<q-btn-dropdown
color="primary"
:label="language | truncate"
flat
remove-shadow
data-cy="locale-select"
>
<q-list>
<q-item
v-for="(lang, i) in $i18n.availableLocales"
:key="`Lang${i}`"
v-close-popup
:value="lang"
clickable
data-cy="locale-select-item"
@click="language = lang"
>
<q-item-section>
<q-item-label>{{ lang | truncate | capitalize() }}</q-item-label>
</q-item-section>
</q-item>
</q-list>
</q-btn-dropdown>
</template>
<script>
export default {
name: 'LanguageSelector',
filters: {
truncate: function (value) {
if (!value) return ''
value = value.toString()
return value.substr(0, 2)
},
capitalize: function (value) {
if (!value) return ''
value = value.toString()
return value.toUpperCase()
}
},
data: function () {
return {
language: this.$i18n.locale
}
},
watch: {
language (lang) {
this.$root.$emit('locale_changed', lang)
this.$root.$i18n.locale = lang
}
}
}
</script>
<template>
<q-menu cover>
<q-list>
<q-item
clickable
@click="showDialog()"
>
<q-item-section
data-cy="metadata-dialog-menu-entry-show"
>
{{ $t('show_removed_metadata') }}
</q-item-section>
</q-item>
</q-list>
<q-dialog
v-model="active"
transition-show="rotate"
transition-hide="rotate"
>
<q-card>
<q-card-section
class="row"
>
<q-btn
v-close-popup="2"
class="col-12 justify-end"
icon="close"
flat
round
dense
align="right"
data-cy="metadata-dialog-close-button"
/>
</q-card-section>
<q-card-section
v-if="getRemovedMetadataTableData.length < 1 && getRemainingMetadataTableData.length < 1"
>
{{ $t('no_removed_no_remaining_metadata') }}
</q-card-section>
<q-card-section>
<q-table
v-if="getRemovedMetadataTableData.length > 0"
:title="$t('removed_metadata')"
:data="getRemovedMetadataTableData"
:columns="columns"
row-key="name"
:pagination-label="getPaginationLabel"
:rows-per-page-label="$t('records_per_page')"
data-cy="metadata-removed-table"
>
<template v-slot:header="props">
<q-tr :props="props">
<q-th
v-for="col in props.cols"
:key="col.name"
:props="props"
>
<b>{{ col.label }}</b>
</q-th>
</q-tr>
</template>
</q-table>
</q-card-section>
<q-card-section>
<q-table
v-if="getRemainingMetadataTableData.length > 0"
:title="$t('remaining_metadata')"
:data="getRemainingMetadataTableData"
:columns="columns"
row-key="name"
:pagination-label="getPaginationLabel"
:rows-per-page-label="$t('records_per_page')"
data-cy="metadata-remaining-table"
>
<template v-slot:header="props">
<q-tr :props="props">
<q-th
v-for="col in props.cols"
:key="col.name"
:props="props"
>
<b>{{ col.label }}</b>
</q-th>
</q-tr>
</template>
</q-table>
</q-card-section>
</q-card>
</q-dialog>
</q-menu>
</template>
<script>
export default {
name: 'MetadataVisualizer',
props: {
deletedMetadata: {
type: Object,
default: function () {
return {}
}
},
remainingMetadata: {
type: Object,
default: function () {
return {}
}
}
},
data: function () {
return {
active: false,
columns: [
{ name: 'label', label: this.$t('label'), field: 'label', sortable: true },
{ name: 'value', label: this.$t('value'), field: 'value', sortable: true }
]
}
},
computed: {
getRemovedMetadataTableData () {
return this.restructureTableData(this.deletedMetadata)
},
getRemainingMetadataTableData () {
return this.restructureTableData(this.remainingMetadata)
}
},
methods: {
showDialog () {
this.active = true
},
restructureTableData (data) {
const tmpMeta = []
for (const key in data) {
const tmpObj = {}
tmpObj.label = key
tmpObj.value = data[key]
tmpMeta.push(tmpObj)
}
return tmpMeta
},
getPaginationLabel (firstRowIndex, endRowIndex, totalRowsNumber) {
return firstRowIndex + '-' + endRowIndex + ' ' + this.$t('of') + ' ' + totalRowsNumber
}
}
}
</script>
/**
* little helper class to import the languages from uppy
*/
export default class LanguageHelper {
de = require('@uppy/locales/lib/de_DE')
en = require('@uppy/locales/lib/en_US')
fr = require('@uppy/locales/lib/fr_FR')
es = require('@uppy/locales/lib/es_ES')
it = require('@uppy/locales/lib/it_IT')
/**
* return the uppy translations object for a given lang
* @param lang