diff --git a/nuxt.config.js b/nuxt.config.js index e3a904c38..b4f46aaf6 100644 --- a/nuxt.config.js +++ b/nuxt.config.js @@ -86,7 +86,7 @@ export default { /* ** Customize the progress-bar color */ - loading: { color: '#88FB4F' }, + loading: { color: 'var(--ac-color)' }, /* ** Global CSS @@ -136,7 +136,9 @@ export default { return icons; })([48, 72, 96, 144, 192, 512]) } - }] + }], + + ['@nuxtjs/axios'] ], /* diff --git a/package-lock.json b/package-lock.json index 23c80fb45..1886a6dc1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1527,6 +1527,17 @@ } } }, + "@nuxtjs/axios": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/@nuxtjs/axios/-/axios-5.6.0.tgz", + "integrity": "sha512-Rl4nnudm+sSkMtgfSEAeA5bq6aFpbBoYVXLXWaDxfydslukRd2SdEDdGv0gHE7F/jtIw+JfptWDHCHnzuoO/Ng==", + "requires": { + "@nuxtjs/proxy": "^1.3.3", + "axios": "^0.19.0", + "axios-retry": "^3.1.2", + "consola": "^2.10.1" + } + }, "@nuxtjs/icon": { "version": "3.0.0-beta.16", "resolved": "https://registry.npmjs.org/@nuxtjs/icon/-/icon-3.0.0-beta.16.tgz", @@ -1557,6 +1568,15 @@ "@nuxtjs/pwa-utils": "3.0.0-beta.16" } }, + "@nuxtjs/proxy": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@nuxtjs/proxy/-/proxy-1.3.3.tgz", + "integrity": "sha512-ykpCUdOqPOH79mQG30QfWZmbRD8yjTD+TTSBbwow5GkROUQEtXw+HE+q6i+YFpuChvgJNbwVrXdZ3YmfXbZtTw==", + "requires": { + "consola": "^2.5.6", + "http-proxy-middleware": "^0.19.1" + } + }, "@nuxtjs/pwa": { "version": "3.0.0-beta.16", "resolved": "https://registry.npmjs.org/@nuxtjs/pwa/-/pwa-3.0.0-beta.16.tgz", @@ -2204,6 +2224,46 @@ "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==", "dev": true }, + "axios": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.19.0.tgz", + "integrity": "sha512-1uvKqKQta3KBxIz14F2v06AEHZ/dIoeKfbTRkK1E5oqjDnuEerLmYTgJB5AiQZHJcljpg1TuRzdjDR06qNk0DQ==", + "requires": { + "follow-redirects": "1.5.10", + "is-buffer": "^2.0.2" + }, + "dependencies": { + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "requires": { + "ms": "2.0.0" + } + }, + "follow-redirects": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.10.tgz", + "integrity": "sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==", + "requires": { + "debug": "=3.1.0" + } + }, + "is-buffer": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.3.tgz", + "integrity": "sha512-U15Q7MXTuZlrbymiz95PJpZxu8IlipAp4dtS3wOdgPXx3mqBnslrWU14kxfHB+Py/+2PVKSr37dMAgM2A4uArw==" + } + } + }, + "axios-retry": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/axios-retry/-/axios-retry-3.1.2.tgz", + "integrity": "sha512-+X0mtJ3S0mmia1kTVi1eA3DAC+oWnT2A29g3CpkzcBPMT6vJm+hn/WiV9wPt/KXLHVmg5zev9mWqkPx7bHMovg==", + "requires": { + "is-retry-allowed": "^1.1.0" + } + }, "babel-code-frame": { "version": "6.26.0", "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", @@ -4058,6 +4118,11 @@ "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" }, + "eventemitter3": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-3.1.2.tgz", + "integrity": "sha512-tvtQIeLVHjDkJYnzf2dgVMxfuSGJeM/7UCG17TT4EumTfNtF+0nebF/4zWOIkCreAbtNqhGEboB6BWrwqNaw4Q==" + }, "events": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/events/-/events-3.0.0.tgz", @@ -4453,6 +4518,29 @@ } } }, + "follow-redirects": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.8.1.tgz", + "integrity": "sha512-micCIbldHioIegeKs41DoH0KS3AXfFzgS30qVkM6z/XOE/GJgvmsoc839NUqa1B9udYe9dQxgv7KFwng6+p/dw==", + "requires": { + "debug": "^3.0.0" + }, + "dependencies": { + "debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + } + } + }, "for-each": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", @@ -5096,6 +5184,27 @@ "toidentifier": "1.0.0" } }, + "http-proxy": { + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.17.0.tgz", + "integrity": "sha512-Taqn+3nNvYRfJ3bGvKfBSRwy1v6eePlm3oc/aWVxZp57DQr5Eq3xhKJi7Z4hZpS8PC3H4qI+Yly5EmFacGuA/g==", + "requires": { + "eventemitter3": "^3.0.0", + "follow-redirects": "^1.0.0", + "requires-port": "^1.0.0" + } + }, + "http-proxy-middleware": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-0.19.1.tgz", + "integrity": "sha512-yHYTgWMQO8VvwNS22eLLloAkvungsKdKTLO8AJlftYIKNfJr3GK3zK0ZCfzDDGUBttdGc8xFy1mCitvNKQtC3Q==", + "requires": { + "http-proxy": "^1.17.0", + "is-glob": "^4.0.0", + "lodash": "^4.17.11", + "micromatch": "^3.1.10" + } + }, "http-signature": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", @@ -5418,6 +5527,11 @@ "resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.1.0.tgz", "integrity": "sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg==" }, + "is-retry-allowed": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-1.1.0.tgz", + "integrity": "sha1-EaBgVotnM5REAz0BJaYaINVk+zQ=" + }, "is-stream": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", @@ -8430,6 +8544,11 @@ "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=", "dev": true }, + "requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=" + }, "resolve": { "version": "1.12.0", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.12.0.tgz", diff --git a/package.json b/package.json index fce0bb2d3..fd6aec47a 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ "generate": "nuxt generate" }, "dependencies": { + "@nuxtjs/axios": "^5.6.0", "@nuxtjs/pwa": "^3.0.0-0", "nuxt": "^2.0.0", "vue-virtual-scroll-list": "^1.4.2", diff --git a/pages/index.vue b/pages/index.vue index df6963c11..2aae0028e 100644 --- a/pages/index.vue +++ b/pages/index.vue @@ -16,11 +16,11 @@
  • - +
  • - +
  • @@ -261,6 +261,7 @@ className: 'missing-data-response' } ]; + const parseHeaders = xhr => { const headers = xhr.getAllResponseHeaders().trim().split(/[\r\n]+/); const headerMap = {}; @@ -317,6 +318,7 @@ const validHostname = new RegExp(protocol + "(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])$"); return validIP.test(this.url) || validHostname.test(this.url); }, + hasRequestBody () { return['POST', 'PUT', 'PATCH'].includes(this.method); }, rawRequestBody() { const { bodyParams @@ -394,11 +396,17 @@ behavior: 'smooth' }) }, - sendRequest() { + + async sendRequest() { if (!this.isValidURL) { - alert('Please check the formatting of the URL'); - return + alert('Please check the formatting of the URL.'); + return; } + + // Start showing the loading bar as soon as possible. + // The nuxt axios module will hide it when the request is made. + this.$nuxt.$loading.start(); + if (this.$refs.response.$el.classList.contains('hidden')) { this.$refs.response.$el.classList.toggle('hidden') } @@ -408,51 +416,82 @@ this.previewEnabled = false; this.response.status = 'Fetching...'; this.response.body = 'Loading...'; - const xhr = new XMLHttpRequest(); - const user = this.auth === 'Basic' ? this.httpUser : null; - const password = this.auth === 'Basic' ? this.httpPassword : null; - xhr.open(this.method, this.url + this.path + this.queryString, true, user, password); - if (this.auth === 'Bearer Token') - xhr.setRequestHeader('Authorization', 'Bearer ' + this.bearerToken); - if (this.headers) { - this.headers.forEach(function(element) { - xhr.setRequestHeader(element.key, element.value) - }) - } - if (this.method === 'POST' || this.method === 'PUT' || this.method === 'PATCH') { + + const auth = this.auth === 'Basic' ? { + username: this.httpUser, + password: this.httpPassword + } : null; + + let headers = {}; + + // If the request has a request body, we want to ensure Content-Length and + // Content-Type are sent. + if (this.hasRequestBody) { const requestBody = this.rawInput ? this.rawParams : this.rawRequestBody; - xhr.setRequestHeader('Content-Length', requestBody.length); - xhr.setRequestHeader('Content-Type', `${this.contentType}; charset=utf-8`); - xhr.send(requestBody); - } else { - xhr.send(); + + Object.assign(headers, { + 'Content-Length': requestBody.length, + 'Content-Type': `${this.contentType}; charset=utf-8` + }); } - xhr.onload = e => { - this.response.status = xhr.status; - const headers = this.response.headers = parseHeaders(xhr); - this.response.body = xhr.responseText; - if (this.method != 'HEAD') { - if ((headers['content-type'] || '').startsWith('application/json')) { - this.response.body = JSON.stringify(JSON.parse(this.response.body), null, 2); + + // If the request uses a token for auth, we want to make sure it's sent here. + if(this.auth === 'Bearer Token') headers['Authorization'] = `Bearer ${this.bearerToken}`; + + headers = Object.assign( + // Clone the app headers object first, we don't want to + // mutate it with the request headers added by default. + Object.assign({}, this.headers), + + // We make our temporary headers object the source so + // that you can override the added headers if you + // specify them. + headers + ); + + try { + const payload = await this.$axios({ + method: this.method, + url: this.url + this.path + this.queryString, + auth, + headers + }); + + (() => { + const status = this.response.status = payload.status; + const headers = this.response.headers = payload.headers; + + // We don't need to bother parsing JSON, axios already handles it for us! + const body = this.response.body = payload.data; + + const date = new Date().toLocaleDateString(); + const time = new Date().toLocaleTimeString(); + + this.history.push({ + status, + date, + time, + + method: this.method, + url: this.url, + path: this.path + }); + + window.localStorage.setItem('history', JSON.stringify(this.history)); + })(); + } catch(error) { + if(error.response){ + this.response.headers = error.response.headers; + this.response.status = error.response.status; + this.response.body = error.response.data; + return; } - } - const d = new Date().toLocaleDateString(); - const t = new Date().toLocaleTimeString(); - this.history = [{ - status: xhr.status, - date: d, - time: t, - method: this.method, - url: this.url, - path: this.path - }, ...this.history]; - window.localStorage.setItem('history', JSON.stringify(this.history)); - }; - xhr.onerror = e => { - this.response.status = xhr.status; - this.response.body = xhr.statusText; + + this.response.status = error.message; + this.response.body = "See JavaScript console (F12) for details."; } }, + addRequestHeader() { this.headers.push({ key: '', diff --git a/pages/settings.vue b/pages/settings.vue index 74916a1f5..e528d79dc 100644 --- a/pages/settings.vue +++ b/pages/settings.vue @@ -31,6 +31,11 @@ +