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: `
-
- `,
-})
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: `
-
- `,
-});
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: `
-
- `,
-})
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: `
-
- `,
-});
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: `
-
-
-
-
- Pterodactyl
-
-
-
-
-
-
-
-
-
-
-
- {{ server.name }}
- {{ server.description }}
-
-
- {{ server.node }}
-
-
-
-
-
-
-
-
-
- `
-})
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 @@
+
+
+
+
+
+ Pterodactyl
+
+
+
+
+
+
+
+
+
+
+
+ {{ server.name }}
+ {{ server.description }}
+
+
+ {{ server.node }}
+
+
+
+
+
+
+
+
+
+
+
+
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 @@
+
+
+
+
+
+
+
+ {{ server.name[0] }}
+
+ {{ server.name }}
+
+
+
+
{{ server.description }}
+
+
+
+ {{ server.node }}
+
+
+ {{ server.allocation.ip }}:{{ server.allocation.port }}
+
+
+
+
+
+
+
+
+
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: `
-
- `,
-});
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: `
-
- `
-})
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: `
-
- `,
-});
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 @@
+
+
+
+
+
+
+
+
+ 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/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: `
-
-
-
-
-
-
-
-
-
-
-
-
-
- `
-});
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: `
-
- `,
-});
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 @@
+
+
+
+
Create a new database
+
+
+
+
{{ errors.first('database_name') }}
+
+
+
+
+
{{ errors.first('remote') }}
+
+
+
+
+
+
+
+
+
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 @@
+
+
+
+
+
+
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/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 @@
+
+
+
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/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: `
-
- `,
-})
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 @@
+
+
+
+
+
+
+
+
{{file.name}}
+
{{readableSize(file.size)}}
+
{{formatDate(file.modified)}}
+
+
+
+
+
+
+
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 @@
+
+
+
+
+
+
+ {{directory.name}}
+
+ {{formatDate(directory.modified)}}
+
+
+
+
+
+
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 @@
+
+
+
+
+
+
+
+
+
You have no databases.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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 @@
+
+
+
+ /home/container
+
+ /{{crumb.directoryName}}
+
+
+ /{{crumb.directoryName}}
+
+
+
+
+
+
+
+
This directory is empty.
+
+
+
+
+
+
+
+
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',