From 5bff8d99cc7a83f22e0bd05536703bee6fa41037 Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Sat, 9 Feb 2019 21:14:58 -0800 Subject: [PATCH] Move everything back to vue SFCs --- resources/assets/scripts/components/Flash.ts | 89 -------- resources/assets/scripts/components/Flash.vue | 103 +++++++++ .../assets/scripts/components/MessageBox.ts | 14 -- .../assets/scripts/components/MessageBox.vue | 18 ++ .../scripts/components/auth/ForgotPassword.ts | 93 -------- .../components/auth/ForgotPassword.vue | 100 +++++++++ .../assets/scripts/components/auth/Login.ts | 42 ---- .../assets/scripts/components/auth/Login.vue | 18 ++ .../scripts/components/auth/LoginForm.ts | 112 ---------- .../scripts/components/auth/LoginForm.vue | 104 +++++++++ .../scripts/components/auth/ResetPassword.ts | 121 ----------- .../scripts/components/auth/ResetPassword.vue | 128 +++++++++++ .../scripts/components/auth/TwoFactorForm.ts | 80 ------- .../scripts/components/auth/TwoFactorForm.vue | 87 ++++++++ .../assets/scripts/components/core/Icon.ts | 14 -- .../assets/scripts/components/core/Icon.vue | 18 ++ .../assets/scripts/components/core/Modal.ts | 47 ---- .../assets/scripts/components/core/Modal.vue | 48 +++++ .../scripts/components/core/Navigation.ts | 135 ------------ .../scripts/components/core/Navigation.vue | 142 +++++++++++++ .../scripts/components/dashboard/Account.ts | 58 ----- .../scripts/components/dashboard/Account.vue | 62 ++++++ .../scripts/components/dashboard/Dashboard.ts | 125 ----------- .../components/dashboard/Dashboard.vue | 128 +++++++++++ .../scripts/components/dashboard/ServerBox.ts | 197 ----------------- .../components/dashboard/ServerBox.vue | 200 ++++++++++++++++++ .../dashboard/account/ChangePassword.ts | 91 -------- .../dashboard/account/ChangePassword.vue | 94 ++++++++ .../account/TwoFactorAuthentication.ts | 188 ---------------- .../account/TwoFactorAuthentication.vue | 193 +++++++++++++++++ .../dashboard/account/UpdateEmail.ts | 77 ------- .../dashboard/account/UpdateEmail.vue | 80 +++++++ .../assets/scripts/components/forms/CSRF.ts | 11 - .../assets/scripts/components/forms/CSRF.vue | 16 ++ .../scripts/components/server/Server.ts | 121 ----------- .../scripts/components/server/Server.vue | 123 +++++++++++ .../server/components/PowerButtons.ts | 48 ----- .../server/components/PowerButtons.vue | 51 +++++ .../server/components/ProgressBar.ts | 42 ---- .../database/CreateDatabaseModal.ts | 86 -------- .../database/CreateDatabaseModal.vue | 89 ++++++++ .../server/components/database/DatabaseRow.ts | 70 ------ .../components/database/DatabaseRow.vue | 73 +++++++ .../database/DeleteDatabaseModal.ts | 84 -------- .../database/DeleteDatabaseModal.vue | 89 ++++++++ .../components/filemanager/FileContextMenu.ts | 59 ------ .../filemanager/FileContextMenu.vue | 62 ++++++ .../server/components/filemanager/FileRow.ts | 99 --------- .../server/components/filemanager/FileRow.vue | 102 +++++++++ .../components/filemanager/FolderRow.ts | 44 ---- .../components/filemanager/FolderRow.vue | 47 ++++ .../assets/scripts/components/server/index.ts | 4 - .../components/server/subpages/Console.ts | 186 ---------------- .../components/server/subpages/Console.vue | 189 +++++++++++++++++ .../components/server/subpages/Databases.ts | 112 ---------- .../components/server/subpages/Databases.vue | 115 ++++++++++ .../{FileManager.ts => FileManager.vue} | 113 +++++----- resources/assets/scripts/router.ts | 35 +-- 58 files changed, 2558 insertions(+), 2518 deletions(-) delete mode 100644 resources/assets/scripts/components/Flash.ts create mode 100644 resources/assets/scripts/components/Flash.vue delete mode 100644 resources/assets/scripts/components/MessageBox.ts create mode 100644 resources/assets/scripts/components/MessageBox.vue delete mode 100644 resources/assets/scripts/components/auth/ForgotPassword.ts create mode 100644 resources/assets/scripts/components/auth/ForgotPassword.vue delete mode 100644 resources/assets/scripts/components/auth/Login.ts create mode 100644 resources/assets/scripts/components/auth/Login.vue delete mode 100644 resources/assets/scripts/components/auth/LoginForm.ts create mode 100644 resources/assets/scripts/components/auth/LoginForm.vue delete mode 100644 resources/assets/scripts/components/auth/ResetPassword.ts create mode 100644 resources/assets/scripts/components/auth/ResetPassword.vue delete mode 100644 resources/assets/scripts/components/auth/TwoFactorForm.ts create mode 100644 resources/assets/scripts/components/auth/TwoFactorForm.vue delete mode 100644 resources/assets/scripts/components/core/Icon.ts create mode 100644 resources/assets/scripts/components/core/Icon.vue delete mode 100644 resources/assets/scripts/components/core/Modal.ts create mode 100644 resources/assets/scripts/components/core/Modal.vue delete mode 100644 resources/assets/scripts/components/core/Navigation.ts create mode 100644 resources/assets/scripts/components/core/Navigation.vue delete mode 100644 resources/assets/scripts/components/dashboard/Account.ts create mode 100644 resources/assets/scripts/components/dashboard/Account.vue delete mode 100644 resources/assets/scripts/components/dashboard/Dashboard.ts create mode 100644 resources/assets/scripts/components/dashboard/Dashboard.vue delete mode 100644 resources/assets/scripts/components/dashboard/ServerBox.ts create mode 100644 resources/assets/scripts/components/dashboard/ServerBox.vue delete mode 100644 resources/assets/scripts/components/dashboard/account/ChangePassword.ts create mode 100644 resources/assets/scripts/components/dashboard/account/ChangePassword.vue delete mode 100644 resources/assets/scripts/components/dashboard/account/TwoFactorAuthentication.ts create mode 100644 resources/assets/scripts/components/dashboard/account/TwoFactorAuthentication.vue delete mode 100644 resources/assets/scripts/components/dashboard/account/UpdateEmail.ts create mode 100644 resources/assets/scripts/components/dashboard/account/UpdateEmail.vue delete mode 100644 resources/assets/scripts/components/forms/CSRF.ts create mode 100644 resources/assets/scripts/components/forms/CSRF.vue delete mode 100644 resources/assets/scripts/components/server/Server.ts create mode 100644 resources/assets/scripts/components/server/Server.vue delete mode 100644 resources/assets/scripts/components/server/components/PowerButtons.ts create mode 100644 resources/assets/scripts/components/server/components/PowerButtons.vue delete mode 100644 resources/assets/scripts/components/server/components/ProgressBar.ts delete mode 100644 resources/assets/scripts/components/server/components/database/CreateDatabaseModal.ts create mode 100644 resources/assets/scripts/components/server/components/database/CreateDatabaseModal.vue delete mode 100644 resources/assets/scripts/components/server/components/database/DatabaseRow.ts create mode 100644 resources/assets/scripts/components/server/components/database/DatabaseRow.vue delete mode 100644 resources/assets/scripts/components/server/components/database/DeleteDatabaseModal.ts create mode 100644 resources/assets/scripts/components/server/components/database/DeleteDatabaseModal.vue delete mode 100644 resources/assets/scripts/components/server/components/filemanager/FileContextMenu.ts create mode 100644 resources/assets/scripts/components/server/components/filemanager/FileContextMenu.vue delete mode 100644 resources/assets/scripts/components/server/components/filemanager/FileRow.ts create mode 100644 resources/assets/scripts/components/server/components/filemanager/FileRow.vue delete mode 100644 resources/assets/scripts/components/server/components/filemanager/FolderRow.ts create mode 100644 resources/assets/scripts/components/server/components/filemanager/FolderRow.vue delete mode 100644 resources/assets/scripts/components/server/index.ts delete mode 100644 resources/assets/scripts/components/server/subpages/Console.ts create mode 100644 resources/assets/scripts/components/server/subpages/Console.vue delete mode 100644 resources/assets/scripts/components/server/subpages/Databases.ts create mode 100644 resources/assets/scripts/components/server/subpages/Databases.vue rename resources/assets/scripts/components/server/subpages/{FileManager.ts => FileManager.vue} (70%) diff --git a/resources/assets/scripts/components/Flash.ts b/resources/assets/scripts/components/Flash.ts deleted file mode 100644 index 97fe18473..000000000 --- a/resources/assets/scripts/components/Flash.ts +++ /dev/null @@ -1,89 +0,0 @@ -import Vue from 'vue'; -import MessageBox from "./MessageBox"; - -export default Vue.component('flash', { - components: { - MessageBox - }, - props: { - container: {type: String, default: ''}, - timeout: {type: Number, default: 0}, - types: { - type: Object, - default: function () { - return { - base: 'alert', - success: 'alert success', - info: 'alert info', - warning: 'alert warning', - error: 'alert error', - } - } - } - }, - - data: function () { - return { - notifications: [], - }; - }, - - /** - * Listen for flash events. - */ - created: function () { - const self = this; - window.events.$on('flash', function (data: any) { - self.flash(data.message, data.title, data.severity); - }); - - window.events.$on('clear-flashes', function () { - self.clear(); - }); - }, - - methods: { - /** - * Flash a message to the screen when a flash event is emitted over - * the global event stream. - */ - flash: function (message: string, title: string, severity: string) { - this.$data.notifications.push({ - message, severity, title, class: this.$props.types[severity] || this.$props.types.base, - }); - - if (this.$props.timeout > 0) { - setTimeout(this.hide, this.$props.timeout); - } - }, - - /** - * Clear all of the flash messages from the screen. - */ - clear: function () { - this.notifications = []; - window.events.$emit('flashes-cleared'); - }, - - /** - * Hide a notification after a given amount of time. - */ - hide: function (item?: number) { - let key = this.$data.notifications.indexOf(item || this.$data.notifications[0]); - this.$data.notifications.splice(key, 1); - }, - }, - template: ` -
- -
- -
-
-
- `, -}) diff --git a/resources/assets/scripts/components/Flash.vue b/resources/assets/scripts/components/Flash.vue new file mode 100644 index 000000000..fad2821e6 --- /dev/null +++ b/resources/assets/scripts/components/Flash.vue @@ -0,0 +1,103 @@ + + + diff --git a/resources/assets/scripts/components/MessageBox.ts b/resources/assets/scripts/components/MessageBox.ts deleted file mode 100644 index 07e6237e9..000000000 --- a/resources/assets/scripts/components/MessageBox.ts +++ /dev/null @@ -1,14 +0,0 @@ -import Vue from 'vue'; - -export default Vue.component('message-box', { - props: { - title: {type: String, required: false}, - message: {type: String, required: true} - }, - template: ` - - `, -}) diff --git a/resources/assets/scripts/components/MessageBox.vue b/resources/assets/scripts/components/MessageBox.vue new file mode 100644 index 000000000..02a07aa22 --- /dev/null +++ b/resources/assets/scripts/components/MessageBox.vue @@ -0,0 +1,18 @@ + + + diff --git a/resources/assets/scripts/components/auth/ForgotPassword.ts b/resources/assets/scripts/components/auth/ForgotPassword.ts deleted file mode 100644 index e3c360201..000000000 --- a/resources/assets/scripts/components/auth/ForgotPassword.ts +++ /dev/null @@ -1,93 +0,0 @@ -import Vue from 'vue'; -import {isObject} from 'lodash'; -import {AxiosError, AxiosResponse} from "axios"; - -export default Vue.component('forgot-password', { - props: { - email: {type: String, required: true}, - }, - mounted: function () { - (this.$refs.email as HTMLElement).focus(); - }, - data: function () { - return { - X_CSRF_TOKEN: window.X_CSRF_TOKEN, - errors: [], - submitDisabled: false, - showSpinner: false, - }; - }, - methods: { - updateEmail: function (event: { target: HTMLInputElement }) { - this.$data.submitDisabled = false; - this.$emit('update-email', event.target.value); - }, - - submitForm: function () { - this.$data.submitDisabled = true; - this.$data.showSpinner = true; - this.$data.errors = []; - this.$flash.clear(); - - window.axios.post(this.route('auth.forgot-password'), { - email: this.$props.email, - }) - .then((response: AxiosResponse) => { - if (!(response.data instanceof Object)) { - throw new Error('An error was encountered while processing this request.'); - } - - this.$data.submitDisabled = false; - this.$data.showSpinner = false; - this.$flash.success(response.data.status); - this.$router.push({name: 'login'}); - }) - .catch((err: AxiosError) => { - this.$data.showSpinner = false; - if (!err.response) { - return console.error(err); - } - - const response = err.response; - if (response.data && isObject(response.data.errors)) { - response.data.errors.forEach((error: any) => { - this.$flash.error(error.detail); - }); - } - }); - } - }, - template: ` -
-
-
- - -

{{ $t('auth.forgot_password.label_help') }}

-
-
-
- -
-
- - {{ $t('auth.go_to_login') }} - -
-
- `, -}) diff --git a/resources/assets/scripts/components/auth/ForgotPassword.vue b/resources/assets/scripts/components/auth/ForgotPassword.vue new file mode 100644 index 000000000..20cadd67d --- /dev/null +++ b/resources/assets/scripts/components/auth/ForgotPassword.vue @@ -0,0 +1,100 @@ + + + diff --git a/resources/assets/scripts/components/auth/Login.ts b/resources/assets/scripts/components/auth/Login.ts deleted file mode 100644 index 95c652a84..000000000 --- a/resources/assets/scripts/components/auth/Login.ts +++ /dev/null @@ -1,42 +0,0 @@ -import Vue from 'vue'; -import LoginForm from "./LoginForm"; -import ForgotPassword from "./ForgotPassword"; -import TwoFactorForm from "./TwoFactorForm"; -import Flash from "../Flash"; - -export default Vue.component('login', { - data: function () { - return { - user: { - email: '' - }, - }; - }, - components: { - Flash, - LoginForm, - ForgotPassword, - TwoFactorForm, - }, - methods: { - onUpdateEmail: function (value: string) { - this.$data.user.email = value; - }, - }, - template: ` -
- - - - -
- `, -}); diff --git a/resources/assets/scripts/components/auth/Login.vue b/resources/assets/scripts/components/auth/Login.vue new file mode 100644 index 000000000..9ce85b1ee --- /dev/null +++ b/resources/assets/scripts/components/auth/Login.vue @@ -0,0 +1,18 @@ + + + diff --git a/resources/assets/scripts/components/auth/LoginForm.ts b/resources/assets/scripts/components/auth/LoginForm.ts deleted file mode 100644 index d5dae5232..000000000 --- a/resources/assets/scripts/components/auth/LoginForm.ts +++ /dev/null @@ -1,112 +0,0 @@ -import Vue from 'vue'; -import {isObject} from 'lodash'; - -export default Vue.component('login-form', { - props: { - user: { - type: Object, - required: false, - default: function () { - return { - email: '', - password: '', - }; - }, - } - }, - data: function () { - return { - showSpinner: false, - } - }, - mounted: function () { - (this.$refs.email as HTMLElement).focus(); - }, - methods: { - // Handle a login request eminating from the form. If 2FA is required the - // user will be presented with the 2FA modal window. - submitForm: function () { - this.$data.showSpinner = true; - - this.$flash.clear(); - this.$store.dispatch('auth/login', {user: this.$props.user.email, password: this.$props.user.password}) - .then(response => { - if (response.complete) { - return window.location = response.intended; - } - - this.$props.user.password = ''; - this.$data.showSpinner = false; - this.$router.push({name: 'checkpoint', query: {token: response.token}}); - }) - .catch(err => { - this.$props.user.password = ''; - this.$data.showSpinner = false; - (this.$refs.password as HTMLElement).focus(); - this.$store.commit('auth/logout'); - - if (!err.response) { - this.$flash.error('There was an error with the network request. Please try again.'); - return console.error(err); - } - - const response = err.response; - if (response.data && isObject(response.data.errors)) { - response.data.errors.forEach((error: any) => { - this.$flash.error(error.detail); - }); - } - }); - }, - - // Update the email address associated with the login form - // so that it is populated in the parent model automatically. - updateEmail: function (event: { target: HTMLInputElement }) { - this.$emit('update-email', event.target.value); - } - }, - template: ` -
-
-
- - -
-
-
-
- - -
-
-
- -
-
- - {{ $t('auth.forgot_password.label') }} - -
-
- `, -}); diff --git a/resources/assets/scripts/components/auth/LoginForm.vue b/resources/assets/scripts/components/auth/LoginForm.vue new file mode 100644 index 000000000..b021a3915 --- /dev/null +++ b/resources/assets/scripts/components/auth/LoginForm.vue @@ -0,0 +1,104 @@ + + + diff --git a/resources/assets/scripts/components/auth/ResetPassword.ts b/resources/assets/scripts/components/auth/ResetPassword.ts deleted file mode 100644 index d038fde31..000000000 --- a/resources/assets/scripts/components/auth/ResetPassword.ts +++ /dev/null @@ -1,121 +0,0 @@ -import Vue from 'vue'; -import {isObject} from 'lodash'; -import {AxiosError, AxiosResponse} from "axios"; - -export default Vue.component('reset-password', { - props: { - token: {type: String, required: true}, - email: {type: String, required: false}, - }, - mounted: function () { - if (this.$props.email.length > 0) { - (this.$refs.email as HTMLElement).setAttribute('value', this.$props.email); - (this.$refs.password as HTMLElement).focus(); - } - }, - data: function () { - return { - errors: [], - showSpinner: false, - password: '', - passwordConfirmation: '', - }; - }, - methods: { - updateEmailField: function (event: { target: HTMLInputElement }) { - this.$data.submitDisabled = event.target.value.length === 0; - }, - - submitForm: function () { - this.$data.showSpinner = true; - - this.$flash.clear(); - window.axios.post(this.route('auth.reset-password'), { - email: this.$props.email, - password: this.$data.password, - password_confirmation: this.$data.passwordConfirmation, - token: this.$props.token, - }) - .then((response: AxiosResponse) => { - if (!(response.data instanceof Object)) { - throw new Error('An error was encountered while processing this login.'); - } - - if (response.data.send_to_login) { - this.$flash.success('Your password has been reset, please login to continue.'); - return this.$router.push({ name: 'login' }); - } - - return window.location = response.data.redirect_to; - }) - .catch((err: AxiosError) => { - this.$data.showSpinner = false; - if (!err.response) { - return console.error(err); - } - - const response = err.response; - if (response.data && isObject(response.data.errors)) { - response.data.errors.forEach((error: any) => { - this.$flash.error(error.detail); - }); - (this.$refs.password as HTMLElement).focus(); - } - }); - } - }, - template: ` -
-
-
- - -
-
-
-
- - -

{{ $t('auth.password_requirements') }}

-
-
-
-
- - -
-
-
- -
-
- - {{ $t('auth.go_to_login') }} - -
-
- `, -}) diff --git a/resources/assets/scripts/components/auth/ResetPassword.vue b/resources/assets/scripts/components/auth/ResetPassword.vue new file mode 100644 index 000000000..74aa9410e --- /dev/null +++ b/resources/assets/scripts/components/auth/ResetPassword.vue @@ -0,0 +1,128 @@ + + + diff --git a/resources/assets/scripts/components/auth/TwoFactorForm.ts b/resources/assets/scripts/components/auth/TwoFactorForm.ts deleted file mode 100644 index 64287b4bd..000000000 --- a/resources/assets/scripts/components/auth/TwoFactorForm.ts +++ /dev/null @@ -1,80 +0,0 @@ -import Vue from 'vue'; -import {AxiosError, AxiosResponse} from "axios"; -import {isObject} from 'lodash'; - -export default Vue.component('two-factor-form', { - data: function () { - return { - code: '', - }; - }, - mounted: function () { - if ((this.$route.query.token || '').length < 1) { - return this.$router.push({ name: 'login' }); - } - - (this.$refs.code as HTMLElement).focus(); - }, - methods: { - submitToken: function () { - this.$flash.clear(); - window.axios.post(this.route('auth.login-checkpoint'), { - confirmation_token: this.$route.query.token, - authentication_code: this.$data.code, - }) - .then((response: AxiosResponse) => { - if (!(response.data instanceof Object)) { - throw new Error('An error was encountered while processing this login.'); - } - - localStorage.setItem('token', response.data.token); - this.$store.dispatch('login'); - - window.location = response.data.intended; - }) - .catch((err: AxiosError) => { - this.$store.dispatch('logout'); - if (!err.response) { - return console.error(err); - } - - const response = err.response; - if (response.data && isObject(response.data.errors)) { - response.data.errors.forEach((error: any) => { - this.$flash.error(error.detail); - }); - this.$router.push({ name: 'login' }); - } - }); - } - }, - template: ` -
-
-
- - -

{{ $t('auth.two_factor.label_help') }}

-
-
-
- -
-
- - Back to Login - -
-
- `, -}); diff --git a/resources/assets/scripts/components/auth/TwoFactorForm.vue b/resources/assets/scripts/components/auth/TwoFactorForm.vue new file mode 100644 index 000000000..248235298 --- /dev/null +++ b/resources/assets/scripts/components/auth/TwoFactorForm.vue @@ -0,0 +1,87 @@ + + + diff --git a/resources/assets/scripts/components/core/Icon.ts b/resources/assets/scripts/components/core/Icon.ts deleted file mode 100644 index 242935506..000000000 --- a/resources/assets/scripts/components/core/Icon.ts +++ /dev/null @@ -1,14 +0,0 @@ -import Vue from 'vue'; -import { replace } from 'feather-icons'; - -export default Vue.component('icon', { - props: { - name: {type: String, default: 'circle'}, - }, - mounted: function () { - replace(); - }, - template: ` - - `, -}); diff --git a/resources/assets/scripts/components/core/Icon.vue b/resources/assets/scripts/components/core/Icon.vue new file mode 100644 index 000000000..505369bba --- /dev/null +++ b/resources/assets/scripts/components/core/Icon.vue @@ -0,0 +1,18 @@ + + + diff --git a/resources/assets/scripts/components/core/Modal.ts b/resources/assets/scripts/components/core/Modal.ts deleted file mode 100644 index 2867ea6e2..000000000 --- a/resources/assets/scripts/components/core/Modal.ts +++ /dev/null @@ -1,47 +0,0 @@ -import Vue from 'vue'; -import Icon from "./Icon"; - -export default Vue.component('modal', { - components: { - Icon, - }, - - props: { - modalName: { type: String, default: 'modal' }, - show: { type: Boolean, default: false }, - closeOnEsc: { type: Boolean, default: true }, - }, - - mounted: function () { - if (this.$props.closeOnEsc) { - document.addEventListener('keydown', e => { - if (this.show && e.key === 'Escape') { - this.close(); - } - }) - } - }, - - methods: { - close: function () { - this.$emit('close', this.$props.modalName); - } - }, - - template: ` - - - - ` -}) diff --git a/resources/assets/scripts/components/core/Modal.vue b/resources/assets/scripts/components/core/Modal.vue new file mode 100644 index 000000000..32c58524e --- /dev/null +++ b/resources/assets/scripts/components/core/Modal.vue @@ -0,0 +1,48 @@ + + + diff --git a/resources/assets/scripts/components/core/Navigation.ts b/resources/assets/scripts/components/core/Navigation.ts deleted file mode 100644 index 97628cb34..000000000 --- a/resources/assets/scripts/components/core/Navigation.ts +++ /dev/null @@ -1,135 +0,0 @@ -import Vue from 'vue'; -import { debounce, isObject } from 'lodash'; -import { mapState } from 'vuex'; -import {AxiosError} from "axios"; - -export default Vue.component('navigation', { - data: function () { - return { - loadingResults: false, - searchActive: false, - }; - }, - - computed: { - ...mapState('dashboard', ['servers']), - searchTerm: { - get: function (): string { - return this.$store.getters['dashboard/getSearchTerm']; - }, - set: function (value: string): void { - this.$store.dispatch('dashboard/setSearchTerm', value); - } - } - }, - - created: function () { - document.addEventListener('click', this.documentClick); - }, - - beforeDestroy: function () { - document.removeEventListener('click', this.documentClick); - }, - - methods: { - search: debounce(function (this: any): void { - if (this.searchTerm.length >= 3) { - this.loadingResults = true; - this.gatherSearchResults(); - } - }, 500), - - gatherSearchResults: function (): void { - this.$store.dispatch('dashboard/loadServers') - .catch((err: AxiosError) => { - console.error(err); - - const response = err.response; - if (response && isObject(response.data.errors)) { - response.data.errors.forEach((error: any) => { - this.$flash.error(error.detail); - }); - } - }) - .then(() => { - this.loadingResults = false; - }); - }, - - doLogout: function () { - this.$store.commit('auth/logout'); - window.location.assign(this.route('auth.logout')); - }, - - documentClick: function (e: Event) { - if (this.$refs.searchContainer) { - if (this.$refs.searchContainer !== e.target && !(this.$refs.searchContainer as HTMLElement).contains(e.target as HTMLElement)) { - this.searchActive = false; - } - } - }, - }, - - template: ` - - ` -}) diff --git a/resources/assets/scripts/components/core/Navigation.vue b/resources/assets/scripts/components/core/Navigation.vue new file mode 100644 index 000000000..0942afc98 --- /dev/null +++ b/resources/assets/scripts/components/core/Navigation.vue @@ -0,0 +1,142 @@ + + + diff --git a/resources/assets/scripts/components/dashboard/Account.ts b/resources/assets/scripts/components/dashboard/Account.ts deleted file mode 100644 index 4ced6f87f..000000000 --- a/resources/assets/scripts/components/dashboard/Account.ts +++ /dev/null @@ -1,58 +0,0 @@ -import Vue from 'vue'; -import Navigation from "../core/Navigation"; -import Flash from "../Flash"; -import UpdateEmail from "./account/UpdateEmail"; -import ChangePassword from "./account/ChangePassword"; -import TwoFactorAuthentication from "./account/TwoFactorAuthentication"; -import Modal from "../core/Modal"; - -export default Vue.component('account', { - components: { - TwoFactorAuthentication, - Modal, - ChangePassword, - UpdateEmail, - Flash, - Navigation - }, - - data: function () { - return { - modalVisible: false, - }; - }, - - methods: { - openModal: function () { - this.modalVisible = true; - window.events.$emit('two_factor:open'); - }, - }, - - template: ` -
- -
- - - - -
-
-
- -
- -
-
-
-
- -
-
-
-
- `, -}) diff --git a/resources/assets/scripts/components/dashboard/Account.vue b/resources/assets/scripts/components/dashboard/Account.vue new file mode 100644 index 000000000..3fd420f08 --- /dev/null +++ b/resources/assets/scripts/components/dashboard/Account.vue @@ -0,0 +1,62 @@ + + + diff --git a/resources/assets/scripts/components/dashboard/Dashboard.ts b/resources/assets/scripts/components/dashboard/Dashboard.ts deleted file mode 100644 index 4b864e733..000000000 --- a/resources/assets/scripts/components/dashboard/Dashboard.ts +++ /dev/null @@ -1,125 +0,0 @@ -import Vue from 'vue'; -import { debounce, isObject } from 'lodash'; -import { mapState } from 'vuex'; -import Flash from "./../Flash"; -import Navigation from "./../core/Navigation"; -import {AxiosError} from "axios"; -import ServerBox from "./ServerBox"; - -type DataStructure = { - backgroundedAt: Date, - documentVisible: boolean, - loading: boolean, - servers?: Array, - searchTerm?: string, -} - -export default Vue.component('dashboard', { - components: { - ServerBox, - Navigation, - Flash - }, - - data: function (): DataStructure { - return { - backgroundedAt: new Date(), - documentVisible: true, - loading: false, - } - }, - - /** - * Start loading the servers before the DOM $.el is created. If we already have servers - * stored in vuex shows those and don't fire another API call just to load them again. - */ - created: function () { - if (!this.servers || this.servers.length === 0) { - this.loadServers(); - } - }, - - /** - * Once the page is mounted set a function to run every 10 seconds that will - * iterate through the visible servers and fetch their resource usage. - */ - mounted: function () { - (this.$refs.search as HTMLElement).focus(); - }, - - computed: { - ...mapState('dashboard', ['servers']), - searchTerm: { - get: function (): string { - return this.$store.getters['dashboard/getSearchTerm']; - }, - set: function (value: string): void { - this.$store.dispatch('dashboard/setSearchTerm', value); - }, - }, - }, - - methods: { - /** - * Load the user's servers and render them onto the dashboard. - */ - loadServers: function () { - this.loading = true; - this.$flash.clear(); - - this.$store.dispatch('dashboard/loadServers') - .then(() => { - if (!this.servers || this.servers.length === 0) { - this.$flash.info(this.$t('dashboard.index.no_matches')); - } - }) - .catch((err: AxiosError) => { - console.error(err); - const response = err.response; - if (response && isObject(response.data.errors)) { - response.data.errors.forEach((error: any) => { - this.$flash.error(error.detail); - }); - } - }) - .then(() => this.loading = false); - }, - - /** - * Handle a search for servers but only call the search function every 500ms - * at the fastest. - */ - onChange: debounce(function (this: any): void { - this.loadServers(); - }, 500), - }, - - template: ` -
- -
- - -
-
- -
-
- - - -
-
- ` -}); diff --git a/resources/assets/scripts/components/dashboard/Dashboard.vue b/resources/assets/scripts/components/dashboard/Dashboard.vue new file mode 100644 index 000000000..c7a322ba8 --- /dev/null +++ b/resources/assets/scripts/components/dashboard/Dashboard.vue @@ -0,0 +1,128 @@ + + + diff --git a/resources/assets/scripts/components/dashboard/ServerBox.ts b/resources/assets/scripts/components/dashboard/ServerBox.ts deleted file mode 100644 index 0d3ef5e69..000000000 --- a/resources/assets/scripts/components/dashboard/ServerBox.ts +++ /dev/null @@ -1,197 +0,0 @@ -import Vue from 'vue'; -import { get } from 'lodash'; -import { differenceInSeconds } from 'date-fns'; -import {AxiosError, AxiosResponse} from "axios"; - -type DataStructure = { - backgroundedAt: Date, - documentVisible: boolean, - resources: null | { [s: string]: any }, - cpu: number, - memory: number, - status: string, - link: { name: string, params: { id: string } }, - dataGetTimeout: undefined | number, -} - -export default Vue.component('server-box', { - props: { - server: { type: Object, required: true }, - }, - - data: function (): DataStructure { - return { - backgroundedAt: new Date(), - documentVisible: true, - resources: null, - cpu: 0, - memory: 0, - status: '', - link: { name: 'server', params: { id: this.server.identifier }}, - dataGetTimeout: undefined, - }; - }, - - watch: { - /** - * Watch the documentVisible item and perform actions when it is changed. If it becomes - * true, we want to check how long ago the last poll was, if it was more than 30 seconds - * we want to immediately trigger the resourceUse api call, otherwise we just want to restart - * the time. - * - * If it is now false, we want to clear the timer that checks resource use, since we know - * we won't be doing anything with them anyways. Might as well avoid extraneous resource - * usage by the browser. - */ - documentVisible: function (value) { - if (!value) { - window.clearTimeout(this.dataGetTimeout); - return; - } - - if (differenceInSeconds(new Date(), this.backgroundedAt) >= 30) { - this.getResourceUse(); - } - - this.dataGetTimeout = window.setInterval(() => { - this.getResourceUse(); - }, 10000); - }, - }, - - /** - * Grab the initial resource usage for this specific server instance and add a listener - * to monitor when this window is no longer visible. We don't want to needlessly poll the - * API when we aren't looking at the page. - */ - created: function () { - this.getResourceUse(); - document.addEventListener('visibilitychange', this._visibilityChange.bind(this)); - }, - - /** - * Poll the API for changes every 10 seconds when the component is mounted. - */ - mounted: function () { - this.dataGetTimeout = window.setInterval(() => { - this.getResourceUse(); - }, 10000); - }, - - /** - * Clear the timer and event listeners when we destroy the component. - */ - beforeDestroy: function () { - window.clearInterval(this.$data.dataGetTimeout); - document.removeEventListener('visibilitychange', this._visibilityChange.bind(this), false); - }, - - methods: { - /** - * Query the resource API to determine what this server's state and resource usage is. - */ - getResourceUse: function () { - window.axios.get(this.route('api.client.servers.resources', { server: this.server.identifier })) - .then((response: AxiosResponse) => { - if (!(response.data instanceof Object)) { - throw new Error('Received an invalid response object back from status endpoint.'); - } - - this.resources = response.data.attributes; - this.status = this.getServerStatus(); - this.memory = parseInt(parseFloat(get(this.resources, 'memory.current', '0')).toFixed(0)); - this.cpu = this._calculateCpu( - parseFloat(get(this.resources, 'cpu.current', '0')), - parseFloat(this.server.limits.cpu) - ); - }) - .catch((err: AxiosError) => console.warn('Error fetching server resource usage', { ...err })); - }, - - /** - * Set the CSS to use for displaying the server's current status. - */ - getServerStatus: function () { - if (!this.resources || !this.resources.installed || this.resources.suspended) { - return ''; - } - - switch (this.resources.state) { - case 'off': - return 'offline'; - case 'on': - case 'starting': - case 'stopping': - return 'online'; - default: - return ''; - } - }, - - /** - * Calculate the CPU usage for a given server relative to their set maximum. - * - * @private - */ - _calculateCpu: function (current: number, max: number) { - if (max === 0) { - return parseFloat(current.toFixed(1)); - } - - return parseFloat((current / max * 100).toFixed(1)); - }, - - /** - * Handle document visibility changes. - * - * @private - */ - _visibilityChange: function () { - this.documentVisible = document.visibilityState === 'visible'; - - if (!this.documentVisible) { - this.backgroundedAt = new Date(); - } - }, - }, - - template: ` -
-
-
- -

-
- {{ server.name[0] }} -
- {{ server.name }} -

-
-
-

{{ server.description }}

-
-
-
- {{ server.node }} -
-
- {{ server.allocation.ip }}:{{ server.allocation.port }} -
-
-
- -
-
- ` -}); diff --git a/resources/assets/scripts/components/dashboard/ServerBox.vue b/resources/assets/scripts/components/dashboard/ServerBox.vue new file mode 100644 index 000000000..5aeca1f42 --- /dev/null +++ b/resources/assets/scripts/components/dashboard/ServerBox.vue @@ -0,0 +1,200 @@ + + + diff --git a/resources/assets/scripts/components/dashboard/account/ChangePassword.ts b/resources/assets/scripts/components/dashboard/account/ChangePassword.ts deleted file mode 100644 index 7214add48..000000000 --- a/resources/assets/scripts/components/dashboard/account/ChangePassword.ts +++ /dev/null @@ -1,91 +0,0 @@ -import Vue from 'vue'; -import { isObject } from 'lodash'; -import {AxiosError} from "axios"; - -export default Vue.component('change-password', { - data: function () { - return { - current: '', - newPassword: '', - confirmNew: '', - }; - }, - - methods: { - submitForm: function () { - this.$flash.clear(); - this.$validator.pause(); - - window.axios.put(this.route('api.client.account.update-password'), { - current_password: this.current, - password: this.newPassword, - password_confirmation: this.confirmNew, - }) - .then(() => this.current = '') - .then(() => { - this.newPassword = ''; - this.confirmNew = ''; - - this.$flash.success(this.$t('dashboard.account.password.updated')); - }) - .catch((err: AxiosError) => { - if (!err.response) { - this.$flash.error('There was an error with the network request. Please try again.'); - console.error(err); - return; - } - - const response = err.response; - if (response.data && isObject(response.data.errors)) { - response.data.errors.forEach((error: any) => { - this.$flash.error(error.detail); - }); - } - }) - .then(() => { - this.$validator.resume(); - (this.$refs.current as HTMLElement).focus(); - }) - } - }, - - template: ` -
-
-
-

{{ $t('dashboard.account.password.title') }}

-
- - -
-
- - -

{{ errors.first('password') }}

-

{{ $t('dashboard.account.password.requirements') }}

-
-
- - -

{{ errors.first('password_confirmation') }}

-
-
- -
-
-
-
- `, -}); diff --git a/resources/assets/scripts/components/dashboard/account/ChangePassword.vue b/resources/assets/scripts/components/dashboard/account/ChangePassword.vue new file mode 100644 index 000000000..886f86cf1 --- /dev/null +++ b/resources/assets/scripts/components/dashboard/account/ChangePassword.vue @@ -0,0 +1,94 @@ + + + diff --git a/resources/assets/scripts/components/dashboard/account/TwoFactorAuthentication.ts b/resources/assets/scripts/components/dashboard/account/TwoFactorAuthentication.ts deleted file mode 100644 index b4c08bbf9..000000000 --- a/resources/assets/scripts/components/dashboard/account/TwoFactorAuthentication.ts +++ /dev/null @@ -1,188 +0,0 @@ -import Vue from 'vue'; -import {isObject} from 'lodash'; -import {AxiosError, AxiosResponse} from "axios"; - -export default Vue.component('two-factor-authentication', { - data: function () { - return { - spinner: true, - token: '', - submitDisabled: true, - response: { - enabled: false, - qr_image: '', - secret: '', - }, - }; - }, - - /** - * Before the component is mounted setup the event listener. This event is fired when a user - * presses the 'Configure 2-Factor' button on their account page. Once this happens we fire off - * a HTTP request to get their information. - */ - mounted: function () { - window.events.$on('two_factor:open', () => { - this.prepareModalContent(); - }); - }, - - watch: { - token: function (value) { - this.submitDisabled = value.length !== 6; - }, - }, - - methods: { - /** - * Determine the correct content to show in the modal. - */ - prepareModalContent: function () { - // Reset the data object when the modal is opened again. - // @ts-ignore - Object.assign(this.$data, this.$options.data()); - - this.$flash.clear(); - window.axios.get(this.route('account.two_factor')) - .then((response: AxiosResponse) => { - this.response = response.data; - this.spinner = false; - Vue.nextTick().then(() => { - (this.$refs.token as HTMLElement).focus(); - }) - }) - .catch((err: AxiosError) => { - if (!err.response) { - this.$flash.error(err.message); - console.error(err); - return; - } - - const response = err.response; - if (response.data && isObject(response.data.errors)) { - response.data.errors.forEach((error: any) => { - this.$flash.error(error.detail); - }); - } - - this.$emit('close'); - }); - }, - - /** - * Enable two-factor authentication on the account by validating the token provided by the user. - * Close the modal once the request completes so that the success or error message can be shown - * to the user. - */ - enableTwoFactor: function () { - return this._callInternalApi('account.two_factor.enable', 'enabled'); - }, - - /** - * Disables two-factor authentication for the client account and closes the modal. - */ - disableTwoFactor: function () { - return this._callInternalApi('account.two_factor.disable', 'disabled'); - }, - - /** - * Call the Panel API endpoint and handle errors. - * - * @private - */ - _callInternalApi: function (route: string, langKey: string) { - this.$flash.clear(); - this.spinner = true; - - window.axios.post(this.route(route), {token: this.token}) - .then((response: AxiosResponse) => { - if (response.data.success) { - this.$flash.success(this.$t(`dashboard.account.two_factor.${langKey}`)); - } else { - this.$flash.error(this.$t('dashboard.account.two_factor.invalid')); - } - }) - .catch((error: AxiosError) => { - if (!error.response) { - this.$flash.error(error.message); - return; - } - - const response = error.response; - if (response.data && isObject(response.data.errors)) { - response.data.errors.forEach((e: any) => { - this.$flash.error(e.detail); - }); - } - }) - .then(() => { - this.spinner = false; - this.$emit('close'); - }); - } - }, - - template: ` -
-
- -
-
-

{{ $t('dashboard.account.two_factor.disable.title') }}

-
- - -

{{ errors.first('token') }}

-
-
- - -
-
-
-

{{ $t('dashboard.account.two_factor.setup.title') }}

-
-
-
- Two-factor qr image -
-
-

{{ $t('dashboard.account.two_factor.setup.help') }}

-

{{response.secret}}

-
-
-
-
- - -

{{ errors.first('token') }}

-
-
- -
-
-
-
-
- ` -}) diff --git a/resources/assets/scripts/components/dashboard/account/TwoFactorAuthentication.vue b/resources/assets/scripts/components/dashboard/account/TwoFactorAuthentication.vue new file mode 100644 index 000000000..ed4e5c462 --- /dev/null +++ b/resources/assets/scripts/components/dashboard/account/TwoFactorAuthentication.vue @@ -0,0 +1,193 @@ + + + diff --git a/resources/assets/scripts/components/dashboard/account/UpdateEmail.ts b/resources/assets/scripts/components/dashboard/account/UpdateEmail.ts deleted file mode 100644 index 1879359fc..000000000 --- a/resources/assets/scripts/components/dashboard/account/UpdateEmail.ts +++ /dev/null @@ -1,77 +0,0 @@ -import Vue from 'vue'; -import { get, isObject } from 'lodash'; -import { mapState } from 'vuex'; -import {ApplicationState} from "../../../store/types"; -import {AxiosError} from "axios"; - -export default Vue.component('update-email', { - data: function () { - return { - email: get(this.$store.state, 'auth.user.email', ''), - password: '', - }; - }, - - computed: { - ...mapState({ - user: (state: ApplicationState) => state.auth.user, - }) - }, - - methods: { - /** - * Update a user's email address on the Panel. - */ - submitForm: function () { - this.$flash.clear(); - this.$store.dispatch('auth/updateEmail', { email: this.email, password: this.password }) - .then(() => { - this.$flash.success(this.$t('dashboard.account.email.updated')); - }) - .catch((error: AxiosError) => { - if (!error.response) { - this.$flash.error(error.message); - return; - } - - const response = error.response; - if (response.data && isObject(response.data.errors)) { - response.data.errors.forEach((e: any) => { - this.$flash.error(e.detail); - }); - } - }) - .then(() => { - this.$data.password = ''; - }); - }, - }, - - template: ` -
-
-
-

{{ $t('dashboard.account.email.title') }}

-
- - -

{{ errors.first('email') }}

-
-
- - -
-
- -
-
-
-
- `, -}); diff --git a/resources/assets/scripts/components/dashboard/account/UpdateEmail.vue b/resources/assets/scripts/components/dashboard/account/UpdateEmail.vue new file mode 100644 index 000000000..815a09697 --- /dev/null +++ b/resources/assets/scripts/components/dashboard/account/UpdateEmail.vue @@ -0,0 +1,80 @@ + + + diff --git a/resources/assets/scripts/components/forms/CSRF.ts b/resources/assets/scripts/components/forms/CSRF.ts deleted file mode 100644 index 91c22d4b6..000000000 --- a/resources/assets/scripts/components/forms/CSRF.ts +++ /dev/null @@ -1,11 +0,0 @@ -import Vue from 'vue'; - -export default Vue.component('csrf', { - data: function () { - return { - X_CSRF_TOKEN: window.X_CSRF_TOKEN, - }; - }, - - template: ``, -}); diff --git a/resources/assets/scripts/components/forms/CSRF.vue b/resources/assets/scripts/components/forms/CSRF.vue new file mode 100644 index 000000000..f0d9ec408 --- /dev/null +++ b/resources/assets/scripts/components/forms/CSRF.vue @@ -0,0 +1,16 @@ + + + diff --git a/resources/assets/scripts/components/server/Server.ts b/resources/assets/scripts/components/server/Server.ts deleted file mode 100644 index bbe63ebb5..000000000 --- a/resources/assets/scripts/components/server/Server.ts +++ /dev/null @@ -1,121 +0,0 @@ -import Vue from 'vue'; -import Navigation from '@/components/core/Navigation'; -import ProgressBar from './components/ProgressBar'; -import { mapState } from 'vuex'; -import * as io from 'socket.io-client'; -import { Socketio } from "@/mixins/socketio"; -import Icon from "@/components/core/Icon"; -import PowerButtons from "@/components/server/components/PowerButtons"; - -export default Vue.component('server', { - components: { ProgressBar, PowerButtons, Navigation, Icon }, - computed: { - ...mapState('server', ['server', 'credentials']), - ...mapState('socket', ['connected', 'connectionError']), - }, - - mixins: [ Socketio ], - - // Watch for route changes that occur with different server parameters. This occurs when a user - // uses the search bar. Because of the way vue-router works, it won't re-mount the server component - // so we will end up seeing the wrong server data if we don't perform this watch. - watch: { - '$route': function (toRoute, fromRoute) { - if (toRoute.params.id !== fromRoute.params.id) { - this.loadingServerData = true; - this.loadServer(); - } - } - }, - - data: function () { - return { - loadingServerData: true, - }; - }, - - mounted: function () { - this.loadServer(); - }, - - beforeDestroy: function () { - this.removeSocket(); - }, - - methods: { - /** - * Load the core server information needed for these pages to be functional. - */ - loadServer: function () { - Promise.all([ - this.$store.dispatch('server/getServer', {server: this.$route.params.id}), - this.$store.dispatch('server/getCredentials', {server: this.$route.params.id}) - ]) - .then(() => { - // Configure the socket.io implementation. This is a really ghetto way of handling things - // but all of these plugins assume you have some constant connection, which we don't. - const socket = io(`${this.credentials.node}/v1/ws/${this.server.uuid}`, { - query: `token=${this.credentials.key}`, - }); - - this.$socket().connect(socket); - this.loadingServerData = false; - }) - .catch(err => { - console.error('There was an error performing Server::loadServer', { err }); - }); - }, - }, - - template: ` -
- - -
-
-
-
-
-
-
- -
- -
-
-
-
-
- There was an error while attempting to connect to the Daemon websocket. Error reported was: "{{connectionError.message}}" -
-
-
- `, -}); diff --git a/resources/assets/scripts/components/server/Server.vue b/resources/assets/scripts/components/server/Server.vue new file mode 100644 index 000000000..db78a6bc3 --- /dev/null +++ b/resources/assets/scripts/components/server/Server.vue @@ -0,0 +1,123 @@ + + + diff --git a/resources/assets/scripts/components/server/components/PowerButtons.ts b/resources/assets/scripts/components/server/components/PowerButtons.ts deleted file mode 100644 index 525632248..000000000 --- a/resources/assets/scripts/components/server/components/PowerButtons.ts +++ /dev/null @@ -1,48 +0,0 @@ -import Vue from 'vue'; -import {mapState} from 'vuex'; -import Status from '../../../helpers/statuses'; -import {Socketio} from "@/mixins/socketio"; - -export default Vue.component('power-buttons', { - computed: { - ...mapState('socket', ['connected', 'status']), - }, - - mixins: [Socketio], - - data: function () { - return { - statuses: Status, - }; - }, - - methods: { - sendPowerAction: function (action: string) { - this.$socket().instance().emit('set status', action) - }, - }, - - template: ` -
-
- - -
- - - -
-
-
-
-
-
-
Connecting to node
-
-
-
- ` -}); diff --git a/resources/assets/scripts/components/server/components/PowerButtons.vue b/resources/assets/scripts/components/server/components/PowerButtons.vue new file mode 100644 index 000000000..100827904 --- /dev/null +++ b/resources/assets/scripts/components/server/components/PowerButtons.vue @@ -0,0 +1,51 @@ + + + diff --git a/resources/assets/scripts/components/server/components/ProgressBar.ts b/resources/assets/scripts/components/server/components/ProgressBar.ts deleted file mode 100644 index d067b078d..000000000 --- a/resources/assets/scripts/components/server/components/ProgressBar.ts +++ /dev/null @@ -1,42 +0,0 @@ -import Vue from 'vue'; - -export default Vue.component('progress-bar', { - props: { - percent: {type: Number, default: 0}, - title: {type: String} - }, - - computed: { - backgroundColor: function () { - if (this.percent < 70) { - return "bg-green-600"; - } else if (this.percent >= 70 && this.percent < 90) { - return "bg-yellow-dark"; - } else { - return "bg-red-600"; - } - }, - borderColor: function () { - if (this.percent < 70) { - return "border-green-600"; - } else if (this.percent >= 70 && this.percent < 90) { - return "border-yellow-dark"; - } else { - return "border-red-600"; - } - } - }, - - template: ` -
-
- {{ title }} -
-
-
- {{ percent }} % -
-
-
- `, -}); diff --git a/resources/assets/scripts/components/server/components/database/CreateDatabaseModal.ts b/resources/assets/scripts/components/server/components/database/CreateDatabaseModal.ts deleted file mode 100644 index a8214a2c0..000000000 --- a/resources/assets/scripts/components/server/components/database/CreateDatabaseModal.ts +++ /dev/null @@ -1,86 +0,0 @@ -import Vue from 'vue'; -import MessageBox from "@/components/MessageBox"; -import {createDatabase} from "@/api/server/createDatabase"; - -export default Vue.component('CreateDatabaseModal', { - components: {MessageBox}, - - data: function () { - return { - loading: false, - showSpinner: false, - database: '', - remote: '%', - errorMessage: '', - }; - }, - - computed: { - canSubmit: function () { - return this.database.length && this.remote.length; - }, - }, - - methods: { - submit: function () { - this.showSpinner = true; - this.errorMessage = ''; - this.loading = true; - - createDatabase(this.$route.params.id, this.database, this.remote) - .then((response) => { - this.$emit('database', response); - this.$emit('close'); - }) - .catch((err: Error | string): void => { - if (typeof err === 'string') { - this.errorMessage = err; - return; - } - - console.error('A network error was encountered while processing this request.', { err }); - }) - .then(() => { - this.loading = false; - this.showSpinner = false; - }); - } - }, - - template: ` -
- -

Create a new database

-
- - -

{{ errors.first('database_name') }}

-
-
- - -

{{ errors.first('remote') }}

-
-
- - -
-
- ` -}); diff --git a/resources/assets/scripts/components/server/components/database/CreateDatabaseModal.vue b/resources/assets/scripts/components/server/components/database/CreateDatabaseModal.vue new file mode 100644 index 000000000..a3dbcdac6 --- /dev/null +++ b/resources/assets/scripts/components/server/components/database/CreateDatabaseModal.vue @@ -0,0 +1,89 @@ + + + diff --git a/resources/assets/scripts/components/server/components/database/DatabaseRow.ts b/resources/assets/scripts/components/server/components/database/DatabaseRow.ts deleted file mode 100644 index d49bbfa6d..000000000 --- a/resources/assets/scripts/components/server/components/database/DatabaseRow.ts +++ /dev/null @@ -1,70 +0,0 @@ -import Vue from 'vue'; -import Icon from "@/components/core/Icon"; -import Modal from "@/components/core/Modal"; -import {ServerDatabase} from "@/api/server/types"; -import DeleteDatabaseModal from "@/components/server/components/database/DeleteDatabaseModal"; - -export default Vue.component('DatabaseRow', { - components: {DeleteDatabaseModal, Modal, Icon}, - props: { - database: { - type: Object as () => ServerDatabase, - required: true, - } - }, - - data: function () { - return { - showDeleteModal: false, - }; - }, - - methods: { - revealPassword: function () { - this.database.showPassword = !this.database.showPassword; - }, - }, - - template: ` -
-
- -
-

Database Name

-

{{database.name}}

-
-
-

Username

-

{{database.username}}

-
-
-

Password

-

- - - •••••• - - {{database.password}} - -

-
-
-

Server

-

{{database.host.address}}:{{database.host.port}}

-
-
- -
-
- - - -
- `, -}) diff --git a/resources/assets/scripts/components/server/components/database/DatabaseRow.vue b/resources/assets/scripts/components/server/components/database/DatabaseRow.vue new file mode 100644 index 000000000..303140695 --- /dev/null +++ b/resources/assets/scripts/components/server/components/database/DatabaseRow.vue @@ -0,0 +1,73 @@ + + + diff --git a/resources/assets/scripts/components/server/components/database/DeleteDatabaseModal.ts b/resources/assets/scripts/components/server/components/database/DeleteDatabaseModal.ts deleted file mode 100644 index ae5d3872f..000000000 --- a/resources/assets/scripts/components/server/components/database/DeleteDatabaseModal.ts +++ /dev/null @@ -1,84 +0,0 @@ -import Vue from 'vue'; -import {ServerDatabase} from "@/api/server/types"; - -export default Vue.component('DeleteDatabaseModal', { - props: { - database: { - type: Object as () => ServerDatabase, - required: true - }, - }, - - data: function () { - return { - showSpinner: false, - nameConfirmation: '', - }; - }, - - computed: { - /** - * Determine if the 'Delete' button should be enabled or not. This requires the user - * to enter the database name before actually deleting the DB. - */ - disabled: function () { - const splits: Array = this.database.name.split('_'); - - return ( - this.nameConfirmation !== this.database.name && this.nameConfirmation !== splits.slice(1).join('_') - ); - } - }, - - methods: { - /** - * Handle deleting the database for the server instance. - */ - deleteDatabase: function () { - this.nameConfirmation = ''; - this.showSpinner = true; - - window.axios.delete(this.route('api.client.servers.databases.delete', { - server: this.$route.params.id, - database: this.database.id, - })) - .then(() => { - window.events.$emit('server:deleted-database', this.database.id); - }) - .catch(err => { - this.$flash.clear(); - console.error({ err }); - - const response = err.response; - if (response.data && typeof response.data.errors === 'object') { - response.data.errors.forEach((error: any) => { - this.$flash.error(error.detail); - }); - } - }) - .then(() => { - this.$emit('close'); - }) - }, - }, - - template: ` -
-

Delete this database?

-

This action cannot be undone. This will permanetly delete the {{database.name}} database and remove all associated data.

-
- - -
-
- - -
-
- `, -}); diff --git a/resources/assets/scripts/components/server/components/database/DeleteDatabaseModal.vue b/resources/assets/scripts/components/server/components/database/DeleteDatabaseModal.vue new file mode 100644 index 000000000..e1a8f8e99 --- /dev/null +++ b/resources/assets/scripts/components/server/components/database/DeleteDatabaseModal.vue @@ -0,0 +1,89 @@ + + + diff --git a/resources/assets/scripts/components/server/components/filemanager/FileContextMenu.ts b/resources/assets/scripts/components/server/components/filemanager/FileContextMenu.ts deleted file mode 100644 index 5991ece85..000000000 --- a/resources/assets/scripts/components/server/components/filemanager/FileContextMenu.ts +++ /dev/null @@ -1,59 +0,0 @@ -import Vue from 'vue'; -import Icon from "../../../core/Icon"; - -export default Vue.component('file-context-menu', { - components: { Icon }, - - template: ` -
-
-
-
- -
-
Rename
-
-
-
- -
-
Move
-
-
-
- -
-
Copy
-
-
-
- -
-
Download
-
-
-
-
-
- -
-
New File
-
-
-
- -
-
New Folder
-
-
-
-
-
- -
-
Delete
-
-
-
- `, -}) diff --git a/resources/assets/scripts/components/server/components/filemanager/FileContextMenu.vue b/resources/assets/scripts/components/server/components/filemanager/FileContextMenu.vue new file mode 100644 index 000000000..e9c35f941 --- /dev/null +++ b/resources/assets/scripts/components/server/components/filemanager/FileContextMenu.vue @@ -0,0 +1,62 @@ + + + diff --git a/resources/assets/scripts/components/server/components/filemanager/FileRow.ts b/resources/assets/scripts/components/server/components/filemanager/FileRow.ts deleted file mode 100644 index 92bb0ded7..000000000 --- a/resources/assets/scripts/components/server/components/filemanager/FileRow.ts +++ /dev/null @@ -1,99 +0,0 @@ -import Vue from 'vue'; -import Icon from "../../../core/Icon"; -import {Vue as VueType} from "vue/types/vue"; -import { readableSize, formatDate } from '../../../../helpers' -import FileContextMenu from "./FileContextMenu"; - -export default Vue.component('file-row', { - components: { - Icon, - FileContextMenu, - }, - - props: { - file: {type: Object, required: true}, - editable: {type: Array, required: true} - }, - - data: function () { - return { - contextMenuVisible: false, - }; - }, - - mounted: function () { - document.addEventListener('click', this._clickListener); - - // If the parent component emits the collapse menu event check if the unique ID of the component - // is this one. If not, collapse the menu (we right clicked into another element). - this.$parent.$on('collapse-menus', (uid: string) => { - // @ts-ignore - if (this._uid !== uid) { - this.contextMenuVisible = false; - } - }) - }, - - beforeDestroy: function () { - document.removeEventListener('click', this._clickListener, false); - }, - - methods: { - /** - * Handle a right-click action on a file manager row. - */ - showContextMenu: function (e: MouseEvent) { - e.preventDefault(); - - // @ts-ignore - this.$parent.$emit('collapse-menus', this._uid); - - this.contextMenuVisible = true; - - const menuWidth = (this.$refs.contextMenu as VueType).$el.clientWidth; - const positionElement = e.clientX - Math.round(menuWidth / 2); - - (this.$refs.contextMenu as VueType).$el.setAttribute('style', `left: ${positionElement}; top: ${e.clientY}`); - }, - - /** - * Determine if a file can be edited on the Panel. - */ - canEdit: function (file: any): boolean { - return this.editable.indexOf(file.mime) >= 0; - }, - - /** - * Handle a click anywhere in the document and hide the context menu if that click is not - * a right click and isn't occurring somewhere in the currently visible context menu. - * - * @private - */ - _clickListener: function (e: MouseEvent) { - if (e.button !== 2 && this.contextMenuVisible) { - if (e.target !== (this.$refs.contextMenu as VueType).$el && !(this.$refs.contextMenu as VueType).$el.contains(e.target as Node)) { - this.contextMenuVisible = false; - } - } - }, - - readableSize: readableSize, - formatDate: formatDate, - }, - - template: ` -
-
-
- - -
-
{{file.name}}
-
{{readableSize(file.size)}}
-
{{formatDate(file.modified)}}
-
-
- -
- ` -}); diff --git a/resources/assets/scripts/components/server/components/filemanager/FileRow.vue b/resources/assets/scripts/components/server/components/filemanager/FileRow.vue new file mode 100644 index 000000000..aad283f69 --- /dev/null +++ b/resources/assets/scripts/components/server/components/filemanager/FileRow.vue @@ -0,0 +1,102 @@ + + + diff --git a/resources/assets/scripts/components/server/components/filemanager/FolderRow.ts b/resources/assets/scripts/components/server/components/filemanager/FolderRow.ts deleted file mode 100644 index 628447cab..000000000 --- a/resources/assets/scripts/components/server/components/filemanager/FolderRow.ts +++ /dev/null @@ -1,44 +0,0 @@ -import Vue from 'vue'; -import { formatDate } from "@/helpers"; -import Icon from "@/components/core/Icon"; - -export default Vue.component('folder-row', { - components: { Icon }, - - props: { - directory: {type: Object, required: true}, - }, - - data: function () { - return { - currentDirectory: this.$route.params.path || '/', - }; - }, - - methods: { - /** - * Return a formatted directory path that is used to switch to a nested directory. - */ - getClickablePath (directory: string): string { - return `${this.currentDirectory.replace(/\/$/, '')}/${directory}`; - }, - - formatDate: formatDate, - }, - - template: ` -
- -
- -
-
{{directory.name}}
-
-
{{formatDate(directory.modified)}}
-
-
-
- ` -}); diff --git a/resources/assets/scripts/components/server/components/filemanager/FolderRow.vue b/resources/assets/scripts/components/server/components/filemanager/FolderRow.vue new file mode 100644 index 000000000..6aba06e84 --- /dev/null +++ b/resources/assets/scripts/components/server/components/filemanager/FolderRow.vue @@ -0,0 +1,47 @@ + + + diff --git a/resources/assets/scripts/components/server/index.ts b/resources/assets/scripts/components/server/index.ts deleted file mode 100644 index 44e57a8a9..000000000 --- a/resources/assets/scripts/components/server/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -export {default as Server} from './Server'; -export {default as ConsolePage} from './subpages/Console'; -export {default as DatabasesPage} from './subpages/Databases'; -export {default as FileManagerPage} from './subpages/FileManager'; diff --git a/resources/assets/scripts/components/server/subpages/Console.ts b/resources/assets/scripts/components/server/subpages/Console.ts deleted file mode 100644 index f199a97d2..000000000 --- a/resources/assets/scripts/components/server/subpages/Console.ts +++ /dev/null @@ -1,186 +0,0 @@ -import Vue from 'vue'; -import {mapState} from "vuex"; -import {Terminal} from 'xterm'; -import * as TerminalFit from 'xterm/lib/addons/fit/fit'; -import {Socketio} from "@/mixins/socketio"; - -type DataStructure = { - terminal: Terminal | null, - command: string, - commandHistory: Array, - commandHistoryIndex: number, -} - -export default Vue.component('server-console', { - mixins: [Socketio], - computed: { - ...mapState('socket', ['connected']), - }, - - watch: { - /** - * Watch the connected variable and when it becomes true request the server logs. - */ - connected: function (state: boolean) { - if (state) { - this.$nextTick(() => { - this.mountTerminal(); - }); - } else { - this.terminal && this.terminal.clear(); - } - }, - }, - - /** - * Listen for specific socket.io emits from the server. - */ - sockets: { - 'server log': function (data: string) { - data.split(/\n/g).forEach((line: string): void => { - if (this.terminal) { - this.terminal.writeln(line + '\u001b[0m'); - } - }); - }, - - 'console': function (data: { line: string }) { - data.line.split(/\n/g).forEach((line: string): void => { - if (this.terminal) { - this.terminal.writeln(line + '\u001b[0m'); - } - }); - }, - }, - - /** - * Mount the component and setup all of the terminal actions. Also fetches the initial - * logs from the server to populate into the terminal if the socket is connected. If the - * socket is not connected this will occur automatically when it connects. - */ - mounted: function () { - if (this.connected) { - this.mountTerminal(); - } - }, - - data: function (): DataStructure { - return { - terminal: null, - command: '', - commandHistory: [], - commandHistoryIndex: -1, - }; - }, - - methods: { - /** - * Mount the terminal and grab the most recent server logs. - */ - mountTerminal: function () { - // Get a new instance of the terminal setup. - this.terminal = this._terminalInstance(); - - this.terminal.open((this.$refs.terminal as HTMLElement)); - // @ts-ignore - this.terminal.fit(); - this.terminal.clear(); - - this.$socket().instance().emit('send server log'); - }, - - /** - * Send a command to the server using the configured websocket. - */ - sendCommand: function () { - this.commandHistoryIndex = -1; - this.commandHistory.unshift(this.command); - this.$socket().instance().emit('send command', this.command); - this.command = ''; - }, - - /** - * Handle a user pressing up/down arrows when in the command field to scroll through thier - * command history for this server. - */ - handleArrowKey: function (e: KeyboardEvent) { - if (['ArrowUp', 'ArrowDown'].indexOf(e.key) < 0 || e.key === 'ArrowDown' && this.commandHistoryIndex < 0) { - return; - } - - e.preventDefault(); - e.stopPropagation(); - - if (e.key === 'ArrowUp' && (this.commandHistoryIndex + 1 > (this.commandHistory.length - 1))) { - return; - } - - this.commandHistoryIndex += (e.key === 'ArrowUp') ? 1 : -1; - this.command = this.commandHistoryIndex < 0 ? '' : this.commandHistory[this.commandHistoryIndex]; - }, - - /** - * Returns a new instance of the terminal to be used. - * - * @private - */ - _terminalInstance() { - Terminal.applyAddon(TerminalFit); - - return new Terminal({ - disableStdin: true, - cursorStyle: 'underline', - allowTransparency: true, - fontSize: 12, - fontFamily: 'Menlo, Monaco, Consolas, monospace', - rows: 30, - theme: { - background: 'transparent', - cursor: 'transparent', - black: '#000000', - red: '#E54B4B', - green: '#9ECE58', - yellow: '#FAED70', - blue: '#396FE2', - magenta: '#BB80B3', - cyan: '#2DDAFD', - white: '#d0d0d0', - brightBlack: 'rgba(255, 255, 255, 0.2)', - brightRed: '#FF5370', - brightGreen: '#C3E88D', - brightYellow: '#FFCB6B', - brightBlue: '#82AAFF', - brightMagenta: '#C792EA', - brightCyan: '#89DDFF', - brightWhite: '#ffffff', - }, - }); - } - }, - - template: ` -
-
-
-
-
-
-
-
-
-
- $ -
-
- -
-
-
-
- `, -}); diff --git a/resources/assets/scripts/components/server/subpages/Console.vue b/resources/assets/scripts/components/server/subpages/Console.vue new file mode 100644 index 000000000..57c80514b --- /dev/null +++ b/resources/assets/scripts/components/server/subpages/Console.vue @@ -0,0 +1,189 @@ + + + diff --git a/resources/assets/scripts/components/server/subpages/Databases.ts b/resources/assets/scripts/components/server/subpages/Databases.ts deleted file mode 100644 index 164275fbd..000000000 --- a/resources/assets/scripts/components/server/subpages/Databases.ts +++ /dev/null @@ -1,112 +0,0 @@ -import Vue from 'vue'; -import { map, filter } from 'lodash'; -import Modal from '@/components/core/Modal'; -import CreateDatabaseModal from './../components/database/CreateDatabaseModal'; -import Icon from "@/components/core/Icon"; -import {ServerDatabase} from "@/api/server/types"; -import DatabaseRow from "@/components/server/components/database/DatabaseRow"; - -type DataStructure = { - loading: boolean, - showCreateModal: boolean, - databases: Array, -} - -export default Vue.component('server-databases', { - components: {DatabaseRow, CreateDatabaseModal, Modal, Icon }, - - data: function (): DataStructure { - return { - databases: [], - loading: true, - showCreateModal: false, - }; - }, - - mounted: function () { - this.getDatabases(); - - window.events.$on('server:deleted-database', this.removeDatabase); - }, - - methods: { - /** - * Get all of the databases that exist for this server. - */ - getDatabases: function () { - this.$flash.clear(); - this.loading = true; - - window.axios.get(this.route('api.client.servers.databases', { - server: this.$route.params.id, - include: 'password' - })) - .then(response => { - this.databases = map(response.data.data, (object) => { - const data = object.attributes; - - data.password = data.relationships.password.attributes.password; - data.showPassword = false; - delete data.relationships; - - return data; - }); - }) - .catch(err => { - this.$flash.error('There was an error encountered while attempting to fetch databases for this server.'); - console.error(err); - }) - .then(() => { - this.loading = false; - }); - }, - - /** - * Add the database to the list of existing databases automatically when the modal - * is closed with a successful callback. - */ - handleModalCallback: function (data: ServerDatabase) { - this.databases.push(data); - }, - - /** - * Handle event that is removing a database. - */ - removeDatabase: function (databaseId: string) { - this.databases = filter(this.databases, (database) => { - return database.id !== databaseId; - }); - } - }, - - template: ` -
-
-
-
-
-
-
- -
-

You have no databases.

-
-
-
-
- -
-
- -
- - - -
-
- `, -}); diff --git a/resources/assets/scripts/components/server/subpages/Databases.vue b/resources/assets/scripts/components/server/subpages/Databases.vue new file mode 100644 index 000000000..8feb1a643 --- /dev/null +++ b/resources/assets/scripts/components/server/subpages/Databases.vue @@ -0,0 +1,115 @@ + + + diff --git a/resources/assets/scripts/components/server/subpages/FileManager.ts b/resources/assets/scripts/components/server/subpages/FileManager.vue similarity index 70% rename from resources/assets/scripts/components/server/subpages/FileManager.ts rename to resources/assets/scripts/components/server/subpages/FileManager.vue index dade32a93..9cf6f1e74 100644 --- a/resources/assets/scripts/components/server/subpages/FileManager.ts +++ b/resources/assets/scripts/components/server/subpages/FileManager.vue @@ -1,9 +1,62 @@ + + + diff --git a/resources/assets/scripts/router.ts b/resources/assets/scripts/router.ts index 6bb21038e..ce28861d4 100644 --- a/resources/assets/scripts/router.ts +++ b/resources/assets/scripts/router.ts @@ -1,25 +1,32 @@ import VueRouter, {Route} from 'vue-router'; import store from './store/index'; +import User from './models/user'; const route = require('./../../../vendor/tightenco/ziggy/src/js/route').default; // Base Vuejs Templates -import Login from './components/auth/Login'; -import Dashboard from './components/dashboard/Dashboard'; -import Account from './components/dashboard/Account'; -import ResetPassword from './components/auth/ResetPassword'; -import User from './models/user'; -import { - Server, - ConsolePage, - FileManagerPage, - DatabasesPage, -} from './components/server'; +import Login from './components/auth/Login.vue'; +import Dashboard from './components/dashboard/Dashboard.vue'; +import Account from './components/dashboard/Account.vue'; +import ResetPassword from './components/auth/ResetPassword.vue'; +import LoginForm from "@/components/auth/LoginForm.vue"; +import ForgotPassword from "@/components/auth/ForgotPassword.vue"; +import TwoFactorForm from "@/components/auth/TwoFactorForm.vue"; +import Server from "@/components/server/Server.vue"; +import ConsolePage from "@/components/server/subpages/Console.vue"; +import FileManagerPage from "@/components/server/subpages/FileManager.vue"; +import DatabasesPage from "@/components/server/subpages/Databases.vue"; const routes = [ - {name: 'login', path: '/auth/login', component: Login}, - {name: 'forgot-password', path: '/auth/password', component: Login}, - {name: 'checkpoint', path: '/auth/checkpoint', component: Login}, + { + path: '/auth', component: Login, + children: [ + { name: 'login', path: 'login', component: LoginForm }, + { name: 'forgot-password', path: 'password', component: ForgotPassword }, + { name: 'checkpoint', path: 'checkpoint', component: TwoFactorForm }, + ] + }, + { name: 'reset-password', path: '/auth/password/reset/:token',