├── .prettierrc ├── static ├── v.png ├── favicon.ico └── README.md ├── plugins ├── vue-moment.js ├── vue-native-notification.js ├── README.md └── localStorage.js ├── assets ├── variables.scss └── README.md ├── components ├── README.md ├── PublishedRequests.vue ├── Publisher.vue └── SubscriptionItem.vue ├── jsconfig.json ├── .editorconfig ├── layouts ├── README.md ├── error.vue └── default.vue ├── pages ├── README.md ├── publisher.vue ├── about.vue └── index.vue ├── middleware └── README.md ├── store ├── README.md ├── index.js ├── publisher.js └── subscription.js ├── .vscode └── settings.json ├── package.json ├── LICENSE ├── README.md ├── .gitignore ├── .github └── workflows │ └── ci.yml └── nuxt.config.js /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": false, 3 | "singleQuote": true 4 | } 5 | -------------------------------------------------------------------------------- /static/v.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VictorioBerra/patch-me/HEAD/static/v.png -------------------------------------------------------------------------------- /static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VictorioBerra/patch-me/HEAD/static/favicon.ico -------------------------------------------------------------------------------- /plugins/vue-moment.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import VueMoment from 'vue-moment' 3 | 4 | Vue.use(VueMoment) -------------------------------------------------------------------------------- /assets/variables.scss: -------------------------------------------------------------------------------- 1 | // Ref: https://github.com/nuxt-community/vuetify-module#customvariables 2 | // 3 | // The variables you want to modify 4 | // $font-size-root: 20px; 5 | -------------------------------------------------------------------------------- /components/README.md: -------------------------------------------------------------------------------- 1 | # COMPONENTS 2 | 3 | **This directory is not required, you can delete it if you don't want to use it.** 4 | 5 | The components directory contains your Vue.js Components. 6 | 7 | _Nuxt.js doesn't supercharge these components._ 8 | -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "paths": { 5 | "~/*": ["./*"], 6 | "@/*": ["./*"], 7 | "~~/*": ["./*"], 8 | "@@/*": ["./*"] 9 | } 10 | }, 11 | "exclude": ["node_modules", ".nuxt", "dist"] 12 | } 13 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /plugins/vue-native-notification.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import VueNativeNotification from 'vue-native-notification' 3 | 4 | Vue.use(VueNativeNotification, { 5 | // Automatic permission request before 6 | // showing notification (default: true) 7 | requestOnNotify: true 8 | }) 9 | -------------------------------------------------------------------------------- /layouts/README.md: -------------------------------------------------------------------------------- 1 | # LAYOUTS 2 | 3 | **This directory is not required, you can delete it if you don't want to use it.** 4 | 5 | This directory contains your Application Layouts. 6 | 7 | More information about the usage of this directory in [the documentation](https://nuxtjs.org/guide/views#layouts). 8 | -------------------------------------------------------------------------------- /pages/README.md: -------------------------------------------------------------------------------- 1 | # PAGES 2 | 3 | This directory contains your Application Views and Routes. 4 | The framework reads all the `*.vue` files inside this directory and creates the router of your application. 5 | 6 | More information about the usage of this directory in [the documentation](https://nuxtjs.org/guide/routing). 7 | -------------------------------------------------------------------------------- /assets/README.md: -------------------------------------------------------------------------------- 1 | # ASSETS 2 | 3 | **This directory is not required, you can delete it if you don't want to use it.** 4 | 5 | This directory contains your un-compiled assets such as LESS, SASS, or JavaScript. 6 | 7 | More information about the usage of this directory in [the documentation](https://nuxtjs.org/guide/assets#webpacked). 8 | -------------------------------------------------------------------------------- /plugins/README.md: -------------------------------------------------------------------------------- 1 | # PLUGINS 2 | 3 | **This directory is not required, you can delete it if you don't want to use it.** 4 | 5 | This directory contains Javascript plugins that you want to run before mounting the root Vue.js application. 6 | 7 | More information about the usage of this directory in [the documentation](https://nuxtjs.org/guide/plugins). 8 | -------------------------------------------------------------------------------- /middleware/README.md: -------------------------------------------------------------------------------- 1 | # MIDDLEWARE 2 | 3 | **This directory is not required, you can delete it if you don't want to use it.** 4 | 5 | This directory contains your application middleware. 6 | Middleware let you define custom functions that can be run before rendering either a page or a group of pages. 7 | 8 | More information about the usage of this directory in [the documentation](https://nuxtjs.org/guide/routing#middleware). 9 | -------------------------------------------------------------------------------- /store/README.md: -------------------------------------------------------------------------------- 1 | # STORE 2 | 3 | **This directory is not required, you can delete it if you don't want to use it.** 4 | 5 | This directory contains your Vuex Store files. 6 | Vuex Store option is implemented in the Nuxt.js framework. 7 | 8 | Creating a file in this directory automatically activates the option in the framework. 9 | 10 | More information about the usage of this directory in [the documentation](https://nuxtjs.org/guide/vuex-store). 11 | -------------------------------------------------------------------------------- /pages/publisher.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 25 | -------------------------------------------------------------------------------- /static/README.md: -------------------------------------------------------------------------------- 1 | # STATIC 2 | 3 | **This directory is not required, you can delete it if you don't want to use it.** 4 | 5 | This directory contains your static files. 6 | Each file inside this directory is mapped to `/`. 7 | Thus you'd want to delete this README.md before deploying to production. 8 | 9 | Example: `/static/robots.txt` is mapped as `/robots.txt`. 10 | 11 | More information about the usage of this directory in [the documentation](https://nuxtjs.org/guide/assets#static). 12 | -------------------------------------------------------------------------------- /pages/about.vue: -------------------------------------------------------------------------------- 1 | 22 | -------------------------------------------------------------------------------- /plugins/localStorage.js: -------------------------------------------------------------------------------- 1 | import createPersistedState from 'vuex-persistedstate' 2 | 3 | export default ({ store }) => { 4 | window.onNuxtReady(() => { 5 | createPersistedState({ 6 | key: 'vuex', 7 | rehydrated: val => { 8 | // Clear any incomplete HTTP requests 9 | for (const [id, value] of Object.entries(val.state.subscription.subscriptions)) { 10 | if(!value.completedOn) { 11 | value.completedOn = new Date(), 12 | value.completedState = 'aborted' 13 | } 14 | } 15 | }, 16 | reducer (val) { 17 | if(val.reset === true) { 18 | return {} 19 | } 20 | return val 21 | } 22 | })(store) 23 | }) 24 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "peacock.color": "#0e1392", 3 | "workbench.colorCustomizations": { 4 | "activityBar.background": "#1219c1", 5 | "activityBar.activeBorder": "#e9161e", 6 | "activityBar.foreground": "#e7e7e7", 7 | "activityBar.inactiveForeground": "#e7e7e799", 8 | "activityBarBadge.background": "#e9161e", 9 | "activityBarBadge.foreground": "#e7e7e7", 10 | "titleBar.activeBackground": "#0e1392", 11 | "titleBar.inactiveBackground": "#0e139299", 12 | "titleBar.activeForeground": "#e7e7e7", 13 | "titleBar.inactiveForeground": "#e7e7e799", 14 | "statusBar.background": "#0e1392", 15 | "statusBarItem.hoverBackground": "#1219c1", 16 | "statusBar.foreground": "#e7e7e7", 17 | "activityBar.activeBackground": "#1219c1" 18 | } 19 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "patch-me", 3 | "version": "1.0.1", 4 | "description": "A GUI for services like https://patchbay.pub/", 5 | "author": "Victorio Berra", 6 | "private": true, 7 | "scripts": { 8 | "dev": "nuxt", 9 | "build": "nuxt build", 10 | "start": "nuxt start", 11 | "generate": "nuxt generate" 12 | }, 13 | "dependencies": { 14 | "@nuxtjs/axios": "^5.13.1", 15 | "build-url": "^6.0.1", 16 | "nuxt": "^2.15.3", 17 | "uuid": "^8.3.2", 18 | "vue-moment": "^4.1.0", 19 | "vue-native-notification": "^1.1.1", 20 | "vuetify-dialog": "^2.0.14", 21 | "vuex-persistedstate": "^3.2.0" 22 | }, 23 | "devDependencies": { 24 | "@nuxtjs/vuetify": "^1.11.3", 25 | "eslint-config-prettier": "^8.1.0", 26 | "eslint-plugin-prettier": "^3.3.1", 27 | "prettier": "^2.2.1" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /layouts/error.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 39 | 40 | 45 | -------------------------------------------------------------------------------- /store/index.js: -------------------------------------------------------------------------------- 1 | const { v4: uuidv4 } = require('uuid'); 2 | 3 | export const state = () => ({ 4 | // Serves as a starting default for the patchbay path across all components. 5 | // The idea is to not keep them synced after that initial state set. 6 | patchLink: uuidv4(), 7 | patchUrl: 'https://patchbay.pub/' 8 | }) 9 | 10 | export const actions = { 11 | updateDefaultPatchUrl({ commit }, payload) { 12 | commit('setDefaultPatchUrl', payload.url) 13 | }, 14 | newLinkCode({ commit }) { 15 | commit('generate', uuidv4()) 16 | }, 17 | reset({ commit }) { 18 | commit('reset') // this will cause the vue-persist-store plugin to set all state to empty 19 | location.reload() 20 | } 21 | } 22 | 23 | export const mutations = { 24 | reset(state) { 25 | state.reset = true 26 | }, 27 | setDefaultPatchUrl(state, url) { 28 | state.defaultPatchUrl = url 29 | }, 30 | generate(state, linkCode) { 31 | state.patchLink = linkCode 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /components/PublishedRequests.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2020 Victorio Berra 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PatchMe 2 | 3 | > GUI for services like https://patchbay.pub/ 4 | 5 | Live demo: https://patchme.link/ 6 | 7 | ## Contributing 8 | 9 | If https://patchbay.pub/ is down, you can run your own local patchbay server to develop against. https://github.com/patchbay-pub/patchbay-simple-server 10 | 11 | - Install golang https://golang.org/dl/ 12 | - `git clone https://github.com/patchbay-pub/patchbay-simple-server.git` 13 | - `cd patchbay-simple-server` 14 | - `go run main.go` 15 | 16 | You may want to allow CORS: 17 | 18 | ```golang 19 | // Put this in the first handler, maybe right before `if r.Method == "GET" {` 20 | enableCors(&w) 21 | 22 | func enableCors(w *http.ResponseWriter) { 23 | (*w).Header().Set("Access-Control-Allow-Origin", "*") 24 | } 25 | ``` 26 | 27 | See [Nuxt.js docs](https://nuxtjs.org). 28 | 29 | ``` bash 30 | # install dependencies 31 | $ npm run install 32 | 33 | # serve with hot reload at localhost:3000 34 | $ npm run dev 35 | 36 | # build for production and launch server 37 | $ npm run build 38 | $ npm run start 39 | 40 | # generate static project 41 | $ npm run generate 42 | ``` 43 | 44 | ## TODO 45 | 46 | - Constants file https://github.com/gothinkster/vue-realworld-example-app/blob/master/src/store/actions.type.js 47 | - Persist the URL and LinkCode on reload 48 | - Maybe ditch the publisher entirely and allow adding a payload/POST toggle? -------------------------------------------------------------------------------- /store/publisher.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | 3 | export const state = () => ({ 4 | publishedRequests: {}, 5 | 6 | patchLink: '', 7 | patchBaseUrl: '', 8 | 9 | nextId: 1 10 | }) 11 | 12 | export const getters = { 13 | getPatchLink: (state, getters, rootState) => { 14 | if(state.patchLink == '') { 15 | return rootState.patchLink; 16 | } 17 | return state.patchLink; 18 | }, 19 | 20 | getPatchBaseUrl: (state, getters, rootState) => { 21 | if(state.patchBaseUrl == '') { 22 | return rootState.patchUrl; 23 | } 24 | return state.patchBaseUrl; 25 | } 26 | } 27 | 28 | export const actions = { 29 | 30 | updatePatchBaseUrl({ commit }, patchBaseUrlRequest) { 31 | commit('setPatchBaseUrl', patchBaseUrlRequest.patchBaseUrl) 32 | }, 33 | updatePatchLink({ commit }, patchLinkRequest) { 34 | commit('setPatchLink', patchLinkRequest.patchLink) 35 | }, 36 | 37 | async addPublishedRequest({ commit, state }, publishedRequest) { 38 | commit('incrementLastId') 39 | commit('newPublishedRequest', { 40 | id: state.nextId, 41 | 42 | url: publishedRequest.patchUrl, 43 | payload: publishedRequest.patchPayload, 44 | pubsub: publishedRequest.patchPubSub 45 | }) 46 | } 47 | } 48 | 49 | export const mutations = { 50 | newPublishedRequest: (state, publishedRequest) => Vue.set(state.publishedRequests, publishedRequest.id, publishedRequest), 51 | incrementLastId: (state) => state.nextId++, 52 | 53 | setPatchBaseUrl: (state, patchBaseUrl) => state.patchBaseUrl = patchBaseUrl, 54 | setPatchLink: (state, patchLink) => state.patchLink = patchLink, 55 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | ### Node template 3 | # Logs 4 | /logs 5 | *.log 6 | npm-debug.log* 7 | yarn-debug.log* 8 | yarn-error.log* 9 | 10 | # Runtime data 11 | pids 12 | *.pid 13 | *.seed 14 | *.pid.lock 15 | 16 | # Directory for instrumented libs generated by jscoverage/JSCover 17 | lib-cov 18 | 19 | # Coverage directory used by tools like istanbul 20 | coverage 21 | 22 | # nyc test coverage 23 | .nyc_output 24 | 25 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 26 | .grunt 27 | 28 | # Bower dependency directory (https://bower.io/) 29 | bower_components 30 | 31 | # node-waf configuration 32 | .lock-wscript 33 | 34 | # Compiled binary addons (https://nodejs.org/api/addons.html) 35 | build/Release 36 | 37 | # Dependency directories 38 | node_modules/ 39 | jspm_packages/ 40 | 41 | # TypeScript v1 declaration files 42 | typings/ 43 | 44 | # Optional npm cache directory 45 | .npm 46 | 47 | # Optional eslint cache 48 | .eslintcache 49 | 50 | # Optional REPL history 51 | .node_repl_history 52 | 53 | # Output of 'npm pack' 54 | *.tgz 55 | 56 | # Yarn Integrity file 57 | .yarn-integrity 58 | 59 | # dotenv environment variables file 60 | .env 61 | 62 | # parcel-bundler cache (https://parceljs.org/) 63 | .cache 64 | 65 | # next.js build output 66 | .next 67 | 68 | # nuxt.js build output 69 | .nuxt 70 | 71 | # Nuxt generate 72 | dist 73 | 74 | # vuepress build output 75 | .vuepress/dist 76 | 77 | # Serverless directories 78 | .serverless 79 | 80 | # IDE / Editor 81 | .idea 82 | 83 | # Service worker 84 | sw.* 85 | 86 | # Mac OSX 87 | .DS_Store 88 | 89 | # Vim swap files 90 | *.swp 91 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | # Trigger the workflow on push or pull request, 5 | # but only for the master branch 6 | push: 7 | branches: 8 | - master 9 | pull_request: 10 | branches: 11 | - master 12 | 13 | jobs: 14 | build: 15 | 16 | runs-on: ubuntu-latest 17 | 18 | steps: 19 | - uses: actions/checkout@v1 20 | 21 | - name: Setup Node 22 | uses: actions/setup-node@v1 23 | with: 24 | node-version: '10.x' 25 | 26 | - name: Cache dependencies 27 | uses: actions/cache@v1 28 | with: 29 | path: ~/.npm 30 | key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} 31 | restore-keys: | 32 | ${{ runner.os }}-node- 33 | 34 | - run: npm ci 35 | 36 | - run: npm test 37 | 38 | - run: npm run generate 39 | 40 | # Fix timestamps (used so the Upload to S3 task doesnt modify unchanged files) 41 | - name: restore timestamps 42 | uses: chetan/git-restore-mtime-action@release 43 | 44 | # Upload to S3 45 | - name: sync s3 46 | # Do not deploy anything other than master 47 | if: github.ref == 'refs/heads/master' 48 | uses: jakejarvis/s3-sync-action@master 49 | with: 50 | args: --acl public-read --follow-symlinks --delete 51 | env: 52 | AWS_S3_BUCKET: ${{ secrets.AWS_S3_BUCKET }} 53 | AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} 54 | AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} 55 | AWS_REGION: 'us-east-1' 56 | SOURCE_DIR: 'dist' 57 | 58 | # Invalidate Cloudfront 59 | - name: invalidate 60 | # Do not deploy anything other than master 61 | if: github.ref == 'refs/heads/master' 62 | uses: chetan/invalidate-cloudfront-action@master 63 | env: 64 | DISTRIBUTION: ${{ secrets.DISTRIBUTION }} 65 | PATHS: '/index.html' 66 | AWS_REGION: 'us-east-1' 67 | AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} 68 | AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} -------------------------------------------------------------------------------- /nuxt.config.js: -------------------------------------------------------------------------------- 1 | import colors from 'vuetify/lib/util/colors' 2 | 3 | export default { 4 | mode: 'spa', 5 | server: { 6 | port: 8081 7 | }, 8 | /* 9 | ** Headers of the page 10 | */ 11 | head: { 12 | titleTemplate: '%s - ' + process.env.npm_package_name, 13 | title: process.env.npm_package_name || '', 14 | meta: [ 15 | { charset: 'utf-8' }, 16 | { name: 'viewport', content: 'width=device-width, initial-scale=1' }, 17 | { 18 | hid: 'description', 19 | name: 'description', 20 | content: process.env.npm_package_description || '' 21 | } 22 | ], 23 | link: [{ rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' }] 24 | }, 25 | /* 26 | ** Customize the progress-bar color 27 | */ 28 | loading: { color: '#fff' }, 29 | /* 30 | ** Global CSS 31 | */ 32 | css: [], 33 | /* 34 | ** Plugins to load before mounting the App 35 | */ 36 | plugins: [ 37 | { 38 | src: '~/plugins/vue-native-notification.js', 39 | mode: 'client' 40 | }, 41 | { 42 | src: '~/plugins/localStorage.js', 43 | ssr: false 44 | }, 45 | '~/plugins/vue-moment.js' 46 | ], 47 | /* 48 | ** Nuxt.js dev-modules 49 | */ 50 | buildModules: ['@nuxtjs/vuetify'], 51 | /* 52 | ** Nuxt.js modules 53 | */ 54 | modules: [ 55 | // Doc: https://axios.nuxtjs.org/usage 56 | '@nuxtjs/axios', 57 | ['vuetify-dialog/nuxt', { property: '$dialog' }] 58 | ], 59 | // Optionally passing options in module top level configuration 60 | vuetifyDialog: { 61 | property: '$dialog' 62 | }, 63 | /* 64 | ** Axios module configuration 65 | ** See https://axios.nuxtjs.org/options 66 | */ 67 | axios: {}, 68 | /* 69 | ** vuetify module configuration 70 | ** https://github.com/nuxt-community/vuetify-module 71 | */ 72 | vuetify: { 73 | customVariables: ['~/assets/variables.scss'], 74 | theme: { 75 | dark: true, 76 | themes: { 77 | dark: { 78 | primary: colors.blue.darken2, 79 | accent: colors.grey.darken3, 80 | secondary: colors.amber.darken3, 81 | info: colors.teal.lighten1, 82 | warning: colors.amber.base, 83 | error: colors.deepOrange.accent4, 84 | success: colors.green.accent3 85 | } 86 | } 87 | } 88 | }, 89 | /* 90 | ** Build configuration 91 | */ 92 | build: { 93 | /* 94 | ** You can extend webpack config here 95 | */ 96 | extend(config, ctx) {} 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /components/Publisher.vue: -------------------------------------------------------------------------------- 1 | 44 | 45 | 110 | -------------------------------------------------------------------------------- /layouts/default.vue: -------------------------------------------------------------------------------- 1 | 92 | 93 | 146 | -------------------------------------------------------------------------------- /pages/index.vue: -------------------------------------------------------------------------------- 1 | 98 | 99 | 162 | -------------------------------------------------------------------------------- /store/subscription.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import buildUrl from 'build-url'; 3 | const { v4: uuidv4 } = require('uuid'); 4 | 5 | export const state = () => ({ 6 | subscriptions: {}, 7 | 8 | patchLink: '', 9 | patchBaseUrl: '', 10 | 11 | nextId: 1 12 | }) 13 | 14 | export const getters = { 15 | subscriptions: (state) => state.subscriptions, 16 | getSubscriptionById: (state) => (id) => { 17 | return state.subscriptions[id]; 18 | }, 19 | 20 | getPatchLink: (state, getters, rootState) => { 21 | if(state.patchLink == '') { 22 | return rootState.patchLink; 23 | } 24 | return state.patchLink; 25 | }, 26 | 27 | getPatchBaseUrl: (state, getters, rootState) => { 28 | if(state.patchBaseUrl == '') { 29 | return rootState.patchUrl; 30 | } 31 | return state.patchBaseUrl; 32 | } 33 | } 34 | 35 | export const actions = { 36 | updatePatchBaseUrl({ commit }, patchBaseUrlRequest) { 37 | commit('setPatchBaseUrl', patchBaseUrlRequest.patchBaseUrl) 38 | }, 39 | updatePatchLink({ commit }, patchLinkRequest) { 40 | commit('setPatchLink', patchLinkRequest.patchLink) 41 | }, 42 | generateAndUpdatePatchLink({ commit }) { 43 | commit('setPatchLink', uuidv4()) 44 | }, 45 | setCompleted({ commit }, setCompletedRequest) { 46 | commit('setSubscriptionCompletedState', { 47 | id: setCompletedRequest.id, 48 | completedState: setCompletedRequest.completedState, 49 | completedOn: new Date(), 50 | }) 51 | }, 52 | async addSubscription({ commit, dispatch, state }, subscriptionRequest) { 53 | 54 | const id = state.nextId; 55 | 56 | const finalUrl = buildUrl(subscriptionRequest.url, { 57 | path: subscriptionRequest.path, 58 | queryParams: { 59 | pubsub: subscriptionRequest.pubsub 60 | } 61 | }); 62 | 63 | const newSubscription = Object.assign({}, { 64 | id, 65 | 66 | url: finalUrl, 67 | baseUrl: subscriptionRequest.url, 68 | path: subscriptionRequest.path, 69 | pubsub: subscriptionRequest.pubsub, 70 | 71 | notification: subscriptionRequest.notification, 72 | timeout: subscriptionRequest.timeout, 73 | loop: subscriptionRequest.loop, 74 | }); 75 | 76 | commit('incrementLastId') 77 | 78 | commit('newSubscription', newSubscription) 79 | 80 | const { CancelToken } = this.$axios 81 | 82 | try { 83 | const response = await this.$axios.$get(finalUrl, { 84 | timeout: newSubscription.timeout, 85 | cancelToken: new CancelToken(cancel => { 86 | commit('setSubscriptionCancel', { 87 | id, 88 | cancel 89 | }) 90 | }), 91 | responseType: 'text', 92 | // https://github.com/axios/axios/issues/907 93 | transformResponse: [(data) => { return data; }], 94 | }) 95 | 96 | await commit('setSubscriptionResponseText', { 97 | id, 98 | responseAsText: response 99 | }) 100 | 101 | dispatch('setCompleted', { 102 | id, 103 | completedState: 'success' 104 | }); 105 | 106 | if(newSubscription.loop) 107 | { 108 | dispatch('addSubscription', subscriptionRequest); 109 | } 110 | 111 | } catch (err) { 112 | if (!err.message) { 113 | // Axios doesn't seem to tell us anything if an abort happens 114 | dispatch('setCompleted', { 115 | id, 116 | completedStae: 'aborted' 117 | }); 118 | return; 119 | } 120 | if(err.message.indexOf('Network Error') > -1){ 121 | console.error(err) 122 | dispatch('setCompleted', { 123 | id, 124 | completedState: 'failed' 125 | }); 126 | } 127 | if(err.message.indexOf('timeout') > -1){ 128 | dispatch('setCompleted', { 129 | id, 130 | completedState: 'timedout' 131 | }); 132 | } 133 | } 134 | 135 | return state.nextId 136 | 137 | }, 138 | removeSubscription({ commit }, subscription) { 139 | commit('deleteSubscription', { id: subscription.id }) 140 | }, 141 | cloneSubscription({ state, dispatch }, subscription) { 142 | const stateSubscription = state.subscriptions[subscription.id]; 143 | dispatch('addSubscription', { 144 | url: stateSubscription.baseUrl, 145 | path: stateSubscription.path, 146 | pubsub: stateSubscription.pubsub, 147 | notification: stateSubscription.notification, 148 | timeout: stateSubscription.timeout, 149 | }); 150 | }, 151 | cancelSubscription({ dispatch, state }, payload){ 152 | const stateSubscription = { ...state.subscriptions[payload.id] }; 153 | // completedState can only be set once. 154 | if(stateSubscription) { 155 | stateSubscription.cancel(); 156 | dispatch('setCompleted', { 157 | id: payload.id, 158 | completedState: 'aborted' 159 | }); 160 | } 161 | } 162 | } 163 | 164 | export const mutations = { 165 | newSubscription: (state, subscription) => Vue.set(state.subscriptions, subscription.id, subscription), 166 | setPatchBaseUrl: (state, patchBaseUrl) => state.patchBaseUrl = patchBaseUrl, 167 | setPatchLink: (state, patchLink) => state.patchLink = patchLink, 168 | setSubscriptionCancel: (state, subscription) => { 169 | const stateSubscription = state.subscriptions[subscription.id]; 170 | // completedState can only be set once. 171 | if(stateSubscription) { 172 | stateSubscription.cancel = subscription.cancel; 173 | } 174 | }, 175 | deleteSubscription: (state, subscription) => { 176 | Vue.delete(state.subscriptions, subscription.id); 177 | }, 178 | setSubscriptionResponseText: (state, subscription) => { 179 | const stateSubscription = state.subscriptions[subscription.id]; 180 | // completedState can only be set once. 181 | if(stateSubscription) { 182 | Vue.set(stateSubscription, 'responseAsText', subscription.responseAsText) 183 | } 184 | }, 185 | setSubscriptionCompletedState: (state, subscription) => { 186 | const stateSubscription = state.subscriptions[subscription.id]; 187 | // completedState can only be set once. 188 | if(stateSubscription && !stateSubscription.completedOn) { 189 | Vue.set(stateSubscription, 'completedOn', subscription.completedOn) 190 | Vue.set(stateSubscription,'completedState', subscription.completedState) 191 | } 192 | }, 193 | incrementLastId: (state) => state.nextId++, 194 | } -------------------------------------------------------------------------------- /components/SubscriptionItem.vue: -------------------------------------------------------------------------------- 1 | 116 | 117 | 193 | --------------------------------------------------------------------------------