Commit 586b7247 authored by jfriedli's avatar jfriedli
Browse files

Merge branch 'develop' into 'master'

Prepare 0.1

See merge request !18
parents cdb45756 085f9f86
Pipeline #27406 passed with stages
in 37 minutes and 42 seconds
# https://about.gitlab.com/2017/09/12/vuejs-app-gitlab/
# https://medium.com/joolsoftware/connect-gitlab-with-digitaloceans-kubernetes-439076b9de17
# https://blog.lwolf.org/post/how-to-create-ci-cd-pipeline-with-autodeploy-k8s-gitlab-helm/
stages:
- build
- test
- review_build
- review
- renovate
cache:
key: ${CI_COMMIT_REF_SLUG}
paths:
......@@ -11,6 +20,8 @@ before_script:
build site:
image: node:12
stage: build
except:
- schedules
script:
- quasar build -m pwa
artifacts:
......@@ -29,9 +40,9 @@ test:integration:
services:
- docker:19.03.0-dind
only:
- develop
- master
- develop
- merge_requests
- tags
before_script:
......@@ -43,3 +54,78 @@ test:integration:
- docker-compose build
# Without this the build will hang and will not fail on failing tests
- docker-compose -f docker-compose.ci.yml up --abort-on-container-exit --exit-code-from cypress
build_container:
image: docker:stable
services:
- docker:dind
variables:
DOCKER_DRIVER: overlay2
DOCKER_TLS_CERTDIR: ""
DOCKER_HOST: tcp://localhost:2375
before_script:
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
script:
- docker pull $CI_REGISTRY_IMAGE:latest || true
- docker build --cache-from $CI_REGISTRY_IMAGE:latest
--build-arg MAT2_API_URL_PROD=https://backend.matweb.info/
--tag $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
--tag $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG .
- docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
- docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG
stage: review_build
only:
- develop
- tags
- master
- merge_requests
review_app:
stage: review
image: lachlanevenson/k8s-kubectl:v1.15.3
before_script:
- echo "deploying review app"
- apk add --no-cache gettext
script:
- echo $CI_ENVIRONMENT_SLUG
- envsubst < kubernetes.template.yml > kubernetes.yml
- ls
- cat kubernetes.yml
- kubectl get pod -n gitlab-namespace
- kubectl apply -f kubernetes.yml -n gitlab-namespace
- kubectl apply -f backend.yml -n gitlab-namespace
# force a triggered update
- kubectl patch deployment $CI_ENVIRONMENT_SLUG-app -n gitlab-namespace -p \
"{\"spec\":{\"template\":{\"metadata\":{\"labels\":{\"date\":\"$(date +%s)\"}}}}}"
environment:
name: review/$CI_COMMIT_REF_NAME
url: https://$CI_ENVIRONMENT_SLUG.matweb.info
on_stop: stop_review_app
only:
- merge_requests
stop_review_app:
stage: review
image: lachlanevenson/k8s-kubectl:v1.15.3
before_script:
- echo "deleting review app"
- apk add --no-cache gettext
script:
- envsubst < kubernetes.template.yml > kubernetes.yml
- kubectl delete -f kubernetes.yml -n gitlab-namespace
when: manual
environment:
name: review/$CI_COMMIT_REF_NAME
action: stop
only:
- merge_requests
renovate:
image: node:12
only:
- schedules
stage: renovate
before_script:
- yarn install
script:
- yarn run renovate
# develop stage
FROM node:11.1-alpine as develop-stage
FROM node:12 as develop-stage
WORKDIR /app
COPY package*.json ./
RUN yarn global add @quasar/cli
......@@ -7,8 +7,10 @@ COPY . .
# build stage
FROM develop-stage as build-stage
WORKDIR /app
RUN yarn
RUN quasar build
ARG MAT2_API_URL_PROD
ENV MAT2_API_URL_PROD=$MAT2_API_URL_PROD
RUN yarn && \
quasar build
# production stage
FROM nginx:1.15.7-alpine as production-stage
COPY --from=build-stage /app/dist/spa /usr/share/nginx/html
......
......@@ -2,9 +2,9 @@
FROM cypress/base:10
WORKDIR /app
# This allows for creating a package.json within the Dockerfile itself
RUN npm init --yes
RUN npm i cypress
RUN npm i cypress-file-upload
RUN npm init --yes && \
npm i cypress && \
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
......
apiVersion: apps/v1
kind: Deployment
metadata:
name: mat-backend
labels:
app: mat-backend
spec:
replicas: 1
selector:
matchLabels:
app: mat-backend
template:
metadata:
labels:
app: mat-backend
spec:
containers:
- name: mat-backend
image: registry.0xacab.org/jvoisin/mat2-web:latest
imagePullPolicy: Always
ports:
- containerPort: 80
---
kind: Service
apiVersion: v1
metadata:
name: mat-backend-service
spec:
type: NodePort
selector:
app: mat-backend
ports:
- protocol: TCP
port: 80
targetPort: 80
name: http
// config for renovate! not quasar
module.exports = {
platform: 'gitlab',
endpoint: 'https://0xacab.org/api/v4/',
token: process.env.RENOVATE_TOKEN,
repositories: [
'jfriedli/mat2-quasar-frontend'
],
logLevel: 'info',
requireConfig: true,
onboarding: true,
onboardingConfig: {
extends: ['config:base'],
prConcurrentLimit: 5
},
enabledManagers: [
'yarn'
]
}
# Deployment Configuration (deployment-template.yaml)
apiVersion: apps/v1
kind: Deployment
metadata:
name: mat2-web-$CI_COMMIT_REF_SLUG
labels:
app: $CI_COMMIT_REF_SLUG
spec:
replicas: 1
selector:
matchLabels:
app: $CI_COMMIT_REF_SLUG
template:
metadata:
labels:
app: $CI_COMMIT_REF_SLUG
spec:
containers:
- name: $CI_COMMIT_REF_SLUG
image: registry.0xacab.org/jfriedli/mat2-quasar-frontend:$CI_COMMIT_REF_SLUG
ports:
- containerPort: 80
apiVersion: apps/v1
kind: Deployment
metadata:
name: ${CI_ENVIRONMENT_SLUG}-app
labels:
run: frontend
spec:
replicas: 1
revisionHistoryLimit: 2
selector:
matchLabels:
run: frontend
template:
metadata:
labels:
run: frontend
spec:
containers:
- name: frontend
image: registry.0xacab.org/jfriedli/mat2-quasar-frontend:${CI_COMMIT_REF_SLUG}
imagePullPolicy: Always
ports:
- containerPort: 80
---
kind: Service
apiVersion: v1
metadata:
name: ${CI_ENVIRONMENT_SLUG}-app-service
spec:
type: NodePort
selector:
run: frontend
ports:
- protocol: TCP
port: 80
targetPort: 80
name: http
---
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: ${CI_ENVIRONMENT_SLUG}-ingress
annotations:
kubernetes.io/ingress.class: nginx
certmanager.k8s.io/cluster-issuer: letsencrypt-prod
nginx.ingress.kubernetes.io/rewrite-target: /
spec:
tls:
- hosts:
- ${CI_ENVIRONMENT_SLUG}.matweb.info
secretName: ssl-${CI_ENVIRONMENT_SLUG}-frontend-crt
- hosts:
- backend.matweb.info
secretName: ssl-backend-crt
rules:
- host: ${CI_ENVIRONMENT_SLUG}.matweb.info
http:
paths:
- backend:
serviceName: ${CI_ENVIRONMENT_SLUG}-app-service
servicePort: 80
- host: backend.matweb.info
http:
paths:
- backend:
serviceName: mat-backend-service
servicePort: 80
......@@ -51,7 +51,8 @@ module.exports = function (ctx) {
'QItem',
'QItemSection',
'QChip',
'QCircularProgress'
'QCircularProgress',
'QAvatar'
],
directives: [
......
......@@ -12,30 +12,30 @@ register(process.env.SERVICE_WORKER_FILE, {
// registrationOptions: { scope: './' },
ready () {
console.log('App is being served from cache by a service worker.')
// console.log('App is being served from cache by a service worker.')
},
registered (registration) {
console.log('Service worker has been registered.')
// console.log('Service worker has been registered.')
},
cached (registration) {
console.log('Content has been cached for offline use.')
// console.log('Content has been cached for offline use.')
},
updatefound (registration) {
console.log('New content is downloading.')
// console.log('New content is downloading.')
},
updated (registration) {
console.log('New content is available; please refresh.')
// console.log('New content is available; please refresh.')
},
offline () {
console.log('No internet connection found. App is running in offline mode.')
},
error (err) {
console.error('Error during service worker registration:', err)
// console.log('No internet connection found. App is running in offline mode.')
}
//
// error (err) {
// // console.error('Error during service worker registration:', err)
// }
})
......@@ -11,7 +11,7 @@
$primary = #1B5EB4
$secondary = #99C1F1
$accent = #9C27B0
$accent = #1BBC9B
$positive = #21BA45
$negative = #C10015
......
......@@ -19,5 +19,9 @@ export default {
mat_debian: 'MAT2 on Debian',
mat_debian_available: 'MAT2 is available on Debian',
more_info: 'More info:',
supported_formats: 'Supported file formats'
supported_formats: 'Supported file formats',
bulk_download: 'Download',
error_bulk_download_creation: 'Creating Zip file failed!',
general_error: 'O Ooooh, Something went wrong',
error_report: 'If you keep getting this error report it <a href="https://0xacab.org/jfriedli/mat2-quasar-frontend/issues">here</a>'
}
......@@ -3,7 +3,7 @@
<q-header class="">
<div class="header-div column items-center">
<router-link to="/">
<img id="mat-logo" src="/assets/logo.svg" alt="Logo of the mat-2 project">
<img id="mat-logo" src="~/assets/logo.svg" alt="Logo of the mat-2 project">
</router-link>
</div>
</q-header>
......
......@@ -2,13 +2,25 @@
<q-page padding>
<div v-if="cleanedFiles">
<section class="row full-height full-width text-center">
<div class="mat-shadowed-box ol-xs-10 offset-xs-1
<div class="mat-shadowed-box col-xs-10 offset-xs-1
col-sm-6 offset-sm-3 col-md-8 offset-md-2
col-lg-6 offset-lg-3">
<div v-if="cleanedFiles.successful.length > 0">
col-lg-6 offset-lg-3"
v-if="cleanedFiles.successful.length > 0"
>
<h1 data-cy="removed-metadata-title">{{$t('metadata_removed')}}</h1>
<p data-cy="removed-metadata-paragraph">{{$t('metadata_managed_to_remove')}}</p>
<div class="row standard-padding">
<q-btn
v-if="cleanedFiles.successful.length > 3"
color="accent"
:label="$t('bulk_download')"
type="a"
:href="bulkZipLink"
icon="done_all"
:loading="zipCreating"
data-cy="zip-download-button"
>
</q-btn>
<div class="row standard-padding" v-if="cleanedFiles.successful.length < 4">
<div
class="self-center col col-xs-12 col-sm-12 col-md-4 col-lg-3 col-xl-4 file-download-col"
v-for="file of cleanedFiles.successful" :key="file.id"
......@@ -54,19 +66,32 @@
</div>
</div>
</div>
<div class="row standard-padding" v-if="cleanedFiles.failed.length > 0">
<div class="col col-12">
<h5>{{$t('removal_failed')}}</h5>
<p>
{{$t('could_not_clean_files')}}
</p>
<q-list bordered separator data-cy="failed-items-list">
<q-item v-for="file of cleanedFiles.failed" :key="file.id">
<q-item-section>{{file.data.name}}</q-item-section>
</q-item>
</q-list>
<div class="error-box col-xs-10 offset-xs-1
col-sm-6 offset-sm-3 col-md-8 offset-md-2
col-lg-6 offset-lg-3"
v-if="cleanedFiles.failed.length > 0"
>
<div class="row standard-padding mat-shadowed-box">
<div class="col col-12">
<h4 class="text-red">
<q-icon name="error_outline"></q-icon> {{$t('removal_failed')}}
</h4>
<p>
{{$t('could_not_clean_files')}}
</p>
</div>
</div>
</div>
<q-list separator data-cy="failed-items-list">
<q-item class="mat-shadowed-box failed-list" v-for="file of cleanedFiles.failed" :key="file.id">
<q-badge color="red" floating >
<q-icon name="warning"></q-icon>
</q-badge>
<q-item-section avatar>
<q-avatar rounded color="primary" text-color="white" icon="insert_drive_file" />
</q-item-section>
<q-item-section data-cy="failed-file-name">{{file.data.name | truncate(25) }}</q-item-section>
</q-item>
</q-list>
</div>
</section>
</div>
......@@ -79,6 +104,13 @@ import getIconByMime from '../uppy/getFileTypeIcon'
export default {
props: ['cleanedFiles', 'supportedExtensions', 'previews'],
name: 'DownloadPage',
data: function () {
return {
apiUrl: process.env.API_URL ? process.env.API_URL : 'http://localhost:5000/',
bulkZipLink: '',
zipCreating: false
}
},
methods: {
getIcon: function (mime) {
return getIconByMime(mime)
......@@ -98,17 +130,49 @@ export default {
}
})
return isImage
},
triggerBulkDownload () {
// needs at least two files to be a valid request
if (this.cleanedFiles.successful.length > 1) {
this.bulkZipLink = ''
let body = {
download_list: []
}
for (let ctr = 0; ctr < this.cleanedFiles.successful.length; ctr++) {
const data = this.cleanedFiles.successful[ctr].response.data
body.download_list.push({
file_name: data.output_filename,
key: data.key
})
}
this.zipCreating = true
this.$axios.post(this.apiUrl + 'api/download/bulk', body)
.then((response) => {
this.bulkZipLink = response.data.download_link
})
.catch(() => {
this.$q.notify({
color: 'negative',
position: 'top',
message: this.$t('error_bulk_download_creation'),
icon: 'report_problem'
})
this.$router.push('/error')
}).finally(() => {
this.zipCreating = false
})
}
}
},
mounted () {
if (!this.cleanedFiles) {
this.$router.push('/')
} else if (this.cleanedFiles.successful.length > 1) {
this.triggerBulkDownload()
}
},
filters: {
truncate: function (fullStr) {
const strLen = 35
truncate: function (fullStr, strLen = 35) {
if (fullStr.length <= strLen) return fullStr
const separator = '...'
......@@ -144,7 +208,7 @@ export default {
max-height 10rem
.q-card__actions
padding unset
.q-btn--rectangle
.q-card__actions .q-btn--rectangle
border-top-left-radius 0px
border-top-right-radius 0px
.q-card > div:not(:last-child), .q-card > img:not(:last-child)
......@@ -165,4 +229,8 @@ export default {
font-weight bold
padding-bottom 0.4rem
padding-top: 0.4rem
.error-box
margin-top 2rem
.failed-list
margin-top 1rem
</style>
<template>
<div class="fixed-center text-center">
<p data-cy="kitty-p">
<img
src="~assets/kitty-error.png"
class="crypto-kittie"
>
</p>
<p class="text-faded" data-cy="general-error-text">
<b>{{$t('general_error')}}</b>
</p>
<p class="text-faded" data-cy="general-error-report" v-html="$t('error_report')"></p>
<q-btn data-cy="error-btn" to="/" :label="$t('go_home')" color="primary" />
</div>
</template>
<script>
export default {
name: 'Error'
}
</script>
<style scoped lang="stylus">
.crypto-kittie
max-height 10rem
</style>
......@@ -66,6 +66,8 @@
position relative
.uppy-StatusBar-actions
padding unset
.uppy-size--md .uppy-DashboardAddFiles
border: unset
</style>
<script>
......@@ -90,6 +92,7 @@ export default {
autoProceed: false,
restrictions: {
maxFileSize: 16777216, // 16MB
maxNumberOfFiles: 10, // the max standard for api/download/bulk endpoint
allowedFileTypes: this.supportedExtensions
},
locale: {
......@@ -130,7 +133,6 @@ export default {
this.startUppy()
})
.catch((e) => {
console.log(e)
this.$q.notify({
color: 'negative',
position: 'top',
......
......@@ -6,7 +6,8 @@ const routes = [
children: [
{ path: '', component: () => import('pages/Upload.vue') },
{ path: 'download', name: 'download', props: true, component: () => import('pages/Download.vue') },
{ path: 'info', name: 'info', component: () => import('pages/Info.vue') }
{ path: 'info', name: 'info', component: () => import('pages/Info.vue') },
{ path: 'error', name: 'error', component: () => import('pages/Error.vue') }
]
}
]
......
......@@ -96,7 +96,7 @@ export default class JSONUploader extends Plugin {
})
.catch(function (error) {
if (axios.isCancel(error)) {
console.log('Request canceled', error.message)
// console.log('Request canceled', error.message)
} else {