Rework title components

This commit is contained in:
Ralph J. Smit
2022-08-12 21:11:23 +02:00
parent 9861ff3a9b
commit dd5c074976
40 changed files with 1665 additions and 1809 deletions

12
package-lock.json generated
View File

@@ -2903,9 +2903,9 @@
}
},
"node_modules/postcss": {
"version": "8.4.14",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.14.tgz",
"integrity": "sha512-E398TUmfAYFPBSdzgeieK2Y1+1cpdxJx8yXbK/m57nRhKSmk1GB2tO4lbLBtlkfPQTDKfe4Xqv1ASWPpayPEig==",
"version": "8.4.16",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.16.tgz",
"integrity": "sha512-ipHE1XBvKzm5xI7hiHCZJCSugxvsdq2mPnsq5+UF+VHCjiBvtDrlxJfMBToWaP9D5XlgNmcFGqoHmUn0EYEaRQ==",
"dev": true,
"funding": [
{
@@ -6280,9 +6280,9 @@
"requires": {}
},
"postcss": {
"version": "8.4.14",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.14.tgz",
"integrity": "sha512-E398TUmfAYFPBSdzgeieK2Y1+1cpdxJx8yXbK/m57nRhKSmk1GB2tO4lbLBtlkfPQTDKfe4Xqv1ASWPpayPEig==",
"version": "8.4.16",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.16.tgz",
"integrity": "sha512-ipHE1XBvKzm5xI7hiHCZJCSugxvsdq2mPnsq5+UF+VHCjiBvtDrlxJfMBToWaP9D5XlgNmcFGqoHmUn0EYEaRQ==",
"dev": true,
"requires": {
"nanoid": "^3.3.4",

View File

@@ -1,15 +1,16 @@
<template>
<Head><title>{{ __('Login') }}</title></Head>
<div class="flex items-center justify-center w-full min-h-screen">
<Container size="small">
<form class="space-y-4" @submit.prevent="submit">
<div class="flex flex-col items-center space-y-5">
<img class="h-14" v-if="$page.props.settings.logo" :src="$page.props.settings.logo"/>
<img class="h-14" v-if="$page.props.settings.logo" :src="$page.props.settings.logo" />
<h1 class="font-semibold text-center text-title">
Confirm access to {{ $page.props.settings.name }}
</h1>
</div>
<FormInput :label="__('Code')" :autofocus="true" :errors="$page.props.errors.code" v-model="form.code" id="code" required/>
<FormInput :label="__('Code')" :autofocus="true" :errors="$page.props.errors.code" v-model="form.code" id="code" required />
<Button variant="primary" :disabled="sending" block>{{ __('Confirm') }}</Button>
@@ -42,8 +43,6 @@ import Button from '@/components/Button.vue'
import Container from '@/components/Container.vue'
export default {
metaInfo: {title: 'Login'},
components: {
TextDivider,
FormInput,

View File

@@ -1,4 +1,5 @@
<template>
<Head><title>{{ __('Reset password') }}</title></Head>
<div class="flex items-center justify-center w-full min-h-screen">
<Container size="small">
<form class="space-y-4" @submit.prevent="submit">
@@ -31,55 +32,49 @@ import Container from '@/components/Container.vue'
import {useNotification} from '@/hooks/notification'
export default {
metaInfo() {
return {
title: `${this.__('Reset password')}`,
}
},
components: {
TextDivider,
FormInput,
Button,
Container,
},
components: {
TextDivider,
FormInput,
Button,
Container,
},
props: {
errors: Object,
},
data() {
return {
sending: false,
form: {
email: null,
},
}
},
methods: {
useNotification,
submit() {
this.sending = true
this.$inertia.post(this.route('password.email'), {
email: this.form.email,
}, {
onFinish: () => {
this.sending = false
if (!Object.keys(this.$page.props.errors).length) {
this.form.email = null;
useNotification({
variant: 'success',
title: this.__('Reset password'),
message: this.$page.props.flash.success,
})
}
}
});
props: {
errors: Object,
},
data() {
return {
sending: false,
form: {
email: null,
},
}
},
methods: {
useNotification,
submit() {
this.sending = true
this.$inertia.post(this.route('password.email'), {
email: this.form.email,
}, {
onFinish: () => {
this.sending = false
if (!Object.keys(this.$page.props.errors).length) {
this.form.email = null;
useNotification({
variant: 'success',
title: this.__('Reset password'),
message: this.$page.props.flash.success,
})
}
}
});
},
}
},
}
</script>

View File

@@ -1,9 +1,10 @@
<template>
<Head><title>{{ __('Login') }}</title></Head>
<div class="flex items-center justify-center w-full min-h-screen">
<Container size="small">
<form class="space-y-4" @submit.prevent="submit">
<div class="flex flex-col items-center space-y-5">
<img class="h-14" v-if="$page.props.settings.logo" :src="$page.props.settings.logo"/>
<img class="h-14" v-if="$page.props.settings.logo" :src="$page.props.settings.logo" />
<h1 class="font-semibold text-center text-title">
Login to {{ $page.props.settings.name }}
</h1>
@@ -11,9 +12,9 @@
<FormInput :label="__('Email')" :autofocus="true" :errors="$page.props.errors.email"
v-model="form.email" id="email"
type="email" required/>
type="email" required />
<FormInput :label="__('Password')" v-model="form.password" id="password" type="password" required/>
<FormInput :label="__('Password')" v-model="form.password" id="password" type="password" required />
<Button variant="primary" :disabled="sending" block>{{ __('Login') }}</Button>
@@ -58,8 +59,6 @@ import Button from '@/components/Button.vue'
import Container from '@/components/Container.vue'
export default {
metaInfo: {title: 'Login'},
components: {
TextDivider,
FormInput,

View File

@@ -1,4 +1,5 @@
<template>
<Head><title>{{ __('Create password') }}</title></Head>
<div class="flex items-center justify-center w-full min-h-screen">
<Container size="small">
<form class="space-y-4" @submit.prevent="submit">
@@ -9,8 +10,8 @@
</h1>
</div>
<FormInput :label="__('Password')" :errors="$page.props.errors.password" v-model="form.password" id="password" type="password" required/>
<FormInput :label="__('Confirm password')" :errors="$page.props.errors.password_confirmation" v-model="form.password_confirmation" id="password_confirmation" type="password" required/>
<FormInput :label="__('Password')" :errors="$page.props.errors.password" v-model="form.password" id="password" type="password" required />
<FormInput :label="__('Confirm password')" :errors="$page.props.errors.password_confirmation" v-model="form.password_confirmation" id="password_confirmation" type="password" required />
<Button variant="primary" :disabled="sending" block>{{ __('Start') }}</Button>
</form>
@@ -26,48 +27,42 @@ import Container from '@/components/Container.vue'
import {useNotification} from '@/hooks/notification'
export default {
metaInfo() {
return {
title: `${this.__('Create password')}`,
}
},
components: {
TextDivider,
FormInput,
Button,
Container,
},
components: {
TextDivider,
FormInput,
Button,
Container,
},
props: {
email: String,
},
props: {
email: String,
},
data() {
return {
sending: false,
form: {
email: null,
password: null,
password_confirmation: null,
},
}
},
methods: {
useNotification,
submit() {
this.$inertia.post(this.route('password-creation.start', {
email: this.email
}), {
password: this.form.password,
password_confirmation: this.form.password_confirmation,
}, {
onStart: () => this.sending = true,
onFinish: () => this.sending = false
});
data() {
return {
sending: false,
form: {
email: null,
password: null,
password_confirmation: null,
},
}
},
methods: {
useNotification,
submit() {
this.$inertia.post(this.route('password-creation.start', {
email: this.email
}), {
password: this.form.password,
password_confirmation: this.form.password_confirmation,
}, {
onStart: () => this.sending = true,
onFinish: () => this.sending = false
});
},
}
},
}
</script>

View File

@@ -1,4 +1,5 @@
<template>
<Head><title>{{ __('Register') }}</title></Head>
<div class="flex items-center justify-center w-full min-h-screen">
<Container size="small">
<form class="space-y-4" @submit.prevent="submit">
@@ -9,16 +10,16 @@
</h1>
</div>
<FormInput :label="__('Name')" :errors="$page.props.errors.name" v-model="form.name" id="name" type="text" required/>
<FormInput :label="__('Email')" :errors="$page.props.errors.email" v-model="form.email" id="email" type="email" required/>
<FormInput :label="__('Password')" :errors="$page.props.errors.password" v-model="form.password" id="password" type="password" required/>
<FormInput :label="__('Confirm password')" :errors="$page.props.errors.password_confirmation" v-model="form.password_confirmation" id="password_confirmation" type="password" required/>
<FormInput :label="__('Name')" :errors="$page.props.errors.name" v-model="form.name" id="name" type="text" required />
<FormInput :label="__('Email')" :errors="$page.props.errors.email" v-model="form.email" id="email" type="email" required />
<FormInput :label="__('Password')" :errors="$page.props.errors.password" v-model="form.password" id="password" type="password" required />
<FormInput :label="__('Confirm password')" :errors="$page.props.errors.password_confirmation" v-model="form.password_confirmation" id="password_confirmation" type="password" required />
<div v-if="$page.props.settings.accept_terms_required">
<input id="terms_required" class="form-checkbox" type="checkbox"
v-model="form.terms">
<label for="terms_required" class="ml-2 text-sm">
{{__('Accept terms of service') }}
{{ __('Accept terms of service') }}
</label>
<ErrorText v-if="$page.props.errors.terms">{{ $page.props.errors.terms[0] }}</ErrorText>
</div>
@@ -62,42 +63,36 @@ import Container from '@/components/Container.vue'
import {useNotification} from '@/hooks/notification'
export default {
metaInfo() {
return {
title: `${this.__('Register')}`,
}
},
components: {
TextDivider,
FormInput,
Button,
Container,
ErrorText,
},
components: {
TextDivider,
FormInput,
Button,
Container,
ErrorText,
},
data() {
return {
sending: false,
form: {
terms: false,
name: null,
email: null,
password: null,
password_confirmation: null,
},
}
},
methods: {
useNotification,
submit() {
this.$inertia.post(this.route('register'), this.form, {
onStart: () => this.sending = true,
onFinish: () => this.sending = false,
});
data() {
return {
sending: false,
form: {
terms: false,
name: null,
email: null,
password: null,
password_confirmation: null,
},
}
},
methods: {
useNotification,
submit() {
this.$inertia.post(this.route('register'), this.form, {
onStart: () => this.sending = true,
onFinish: () => this.sending = false,
});
},
}
},
}
</script>

View File

@@ -1,4 +1,5 @@
<template>
<Head><title>{{ __('Reset password') }}</title></Head>
<div class="flex items-center justify-center w-full min-h-screen">
<Container size="small">
<form class="space-y-4" @submit.prevent="submit">
@@ -9,9 +10,9 @@
</h1>
</div>
<FormInput :label="__('Email')" :errors="$page.props.errors.email" v-model="form.email" id="email" type="email" required/>
<FormInput :label="__('Password')" :errors="$page.props.errors.password" v-model="form.password" id="password" type="password" required/>
<FormInput :label="__('Confirm password')" :errors="$page.props.errors.password_confirmation" v-model="form.password_confirmation" id="password_confirmation" type="password" required/>
<FormInput :label="__('Email')" :errors="$page.props.errors.email" v-model="form.email" id="email" type="email" required />
<FormInput :label="__('Password')" :errors="$page.props.errors.password" v-model="form.password" id="password" type="password" required />
<FormInput :label="__('Confirm password')" :errors="$page.props.errors.password_confirmation" v-model="form.password_confirmation" id="password_confirmation" type="password" required />
<Button variant="primary" :disabled="sending" block>{{ __('Reset') }}</Button>
@@ -33,49 +34,43 @@ import Container from '@/components/Container.vue'
import {useNotification} from '@/hooks/notification'
export default {
metaInfo() {
return {
title: `${this.__('Reset password')}`,
}
},
components: {
TextDivider,
FormInput,
Button,
Container,
},
components: {
TextDivider,
FormInput,
Button,
Container,
},
props: {
token: String,
email: String,
},
props: {
token: String,
email: String,
},
data() {
return {
sending: false,
form: {
email: null,
password: null,
password_confirmation: null,
},
}
},
methods: {
useNotification,
submit() {
this.$inertia.post(this.route('password.update'), {
email: this.form.email,
token: this.token,
password: this.form.password,
password_confirmation: this.form.password_confirmation,
}, {
onStart: () => this.sending = true,
onFinish: () => this.sending = false,
});
data() {
return {
sending: false,
form: {
email: null,
password: null,
password_confirmation: null,
},
}
},
methods: {
useNotification,
submit() {
this.$inertia.post(this.route('password.update'), {
email: this.form.email,
token: this.token,
password: this.form.password,
password_confirmation: this.form.password_confirmation,
}, {
onStart: () => this.sending = true,
onFinish: () => this.sending = false,
});
},
}
},
}
</script>

View File

@@ -1,4 +1,5 @@
<template>
<Head><title>{{ __('Installation incomplete') }}</title></Head>
<div class="flex items-center justify-center w-full min-h-screen">
<Container size="small">
<div class="space-y-4">
@@ -23,13 +24,11 @@ import Button from '@/components/Button.vue'
import Container from '@/components/Container.vue'
export default {
metaInfo: { title: 'Installation incomplete' },
components: {
TextDivider,
FormInput,
Button,
Container,
},
}
components: {
TextDivider,
FormInput,
Button,
Container,
},
}
</script>

View File

@@ -1,6 +1,7 @@
<template>
<Page>
<TopBar :breadcrumbs="breadcrumbs"/>
<Head><title>{{ __('Dashboard') }}</title></Head>
<TopBar :breadcrumbs="breadcrumbs" />
<Content>
<Container>
@@ -16,7 +17,7 @@
<li class="p-6 rounded shadow bg-surface-3 transform transition duration-fast ease-in-out hover:scale-95">
<inertia-link :href="route('sites.index')" class="flex space-x-4">
<div>
<IconGlobe class="w-6 h-6"/>
<IconGlobe class="w-6 h-6" />
</div>
<div>
<h3 class="font-semibold text-body">{{ sites }}</h3>
@@ -27,7 +28,7 @@
<li class="p-6 rounded shadow bg-surface-3 transform transition duration-fast ease-in-out hover:scale-95" v-if="hasAccessToServers">
<inertia-link :href="route('servers.index')" class="flex space-x-4">
<div>
<IconStorage class="w-6 h-6"/>
<IconStorage class="w-6 h-6" />
</div>
<div>
<h3 class="font-semibold text-body">{{ servers }}</h3>
@@ -40,7 +41,7 @@
:href="$page.props.settings.billing ? route('profile.billing.index') : route('profile.index')"
class="flex space-x-4">
<div>
<IconBox class="w-6 h-6"/>
<IconBox class="w-6 h-6" />
</div>
<div class="w-full">
<div class="flex justify-between">
@@ -94,13 +95,8 @@ import IconStorage from '@/components/icons/IconStorage.vue'
import {useNotification} from '@/hooks/notification'
export default {
metaInfo() {
return {
title: `${this.__('Dashboard')}`,
}
},
export default {
layout: MainLayout,
components: {
@@ -148,7 +144,7 @@ export default {
},
computed: {
hasAccessToServers () {
hasAccessToServers() {
return Object.keys(this.$page.props.auth.can).length && (this.$page.props.auth.can.servers.create || this.$page.props.auth.can.servers.update || this.$page.props.auth.can.servers.delete);
}
},

View File

@@ -1,6 +1,7 @@
<template>
<Page>
<TopBar :breadcrumbs="breadcrumbs"/>
<Head :title="article.title"></Head>
<TopBar :breadcrumbs="breadcrumbs" />
<Content>
<Container>
@@ -53,12 +54,6 @@ import SettingsLayout from '@/components/layouts/SettingsLayout.vue'
import Tabs from './Tabs.vue'
export default {
metaInfo() {
return {
title: this.article.title,
}
},
layout: MainLayout,
components: {

View File

@@ -1,6 +1,7 @@
<template>
<Page>
<TopBar :breadcrumbs="breadcrumbs"/>
<Head><title>{{ __('Documentation') }}</title></Head>
<TopBar :breadcrumbs="breadcrumbs" />
<Content>
<Container>
@@ -53,59 +54,53 @@ import SettingsLayout from '@/components/layouts/SettingsLayout.vue'
import Tabs from './Tabs.vue'
export default {
metaInfo() {
return {
title: `${this.__('Documentation')}`,
}
},
layout: MainLayout,
layout: MainLayout,
components: {
TopBar,
Container,
Content,
Page,
PageHeader,
PageHeaderTitle,
PageBody,
Button,
List,
ListItem,
StatusBubble,
NotificationBadge,
IconBox,
IconGlobe,
IconStorage,
Modal,
ModalContainer,
FormInput,
FormTextarea,
FormActions,
EmptyImage,
Tabs,
SettingsLayout,
SettingsSegment
},
components: {
TopBar,
Container,
Content,
Page,
PageHeader,
PageHeaderTitle,
PageBody,
Button,
List,
ListItem,
StatusBubble,
NotificationBadge,
IconBox,
IconGlobe,
IconStorage,
Modal,
ModalContainer,
FormInput,
FormTextarea,
FormActions,
EmptyImage,
Tabs,
SettingsLayout,
SettingsSegment
},
props: {
items: Object,
},
props: {
items: Object,
},
data() {
return {
breadcrumbs: [
{
title: this.$page.props.settings.name,
to: '/',
},
{
title: this.__('Documentation'),
to: this.route('documentation.index'),
},
],
}
},
data() {
return {
breadcrumbs: [
{
title: this.$page.props.settings.name,
to: '/',
},
{
title: this.__('Documentation'),
to: this.route('documentation.index'),
},
],
}
},
}
}
</script>

View File

@@ -1,6 +1,7 @@
<template>
<Page>
<TopBar :breadcrumbs="breadcrumbs"/>
<Head :title="category.title"></Head>
<TopBar :breadcrumbs="breadcrumbs" />
<Content>
<Container>
@@ -57,12 +58,6 @@ import SettingsLayout from '@/components/layouts/SettingsLayout.vue'
import Tabs from './Tabs.vue'
export default {
metaInfo() {
return {
title: this.category.title,
}
},
layout: MainLayout,
components: {

View File

@@ -1,5 +1,6 @@
<template>
<Page>
<Head><title>{{ __('You tried to do something that is not allowed.') }}</title></Head>
<TopBar :breadcrumbs="breadcrumbs" />
<Content>
@@ -34,36 +35,36 @@ import NotificationBadge from '@/components/NotificationBadge.vue'
import MainLayout from '@/Layouts/MainLayout.vue'
export default {
layout: MainLayout,
layout: MainLayout,
components: {
TopBar,
Container,
Content,
Page,
PageHeader,
PageHeaderTitle,
PageBody,
Button,
List,
ListItem,
StatusBubble,
NotificationBadge,
},
components: {
TopBar,
Container,
Content,
Page,
PageHeader,
PageHeaderTitle,
PageBody,
Button,
List,
ListItem,
StatusBubble,
NotificationBadge,
},
data() {
return {
breadcrumbs: [
{
title: this.$page.props.settings.name,
to: '/',
},
{
title: '403 not found',
to: '/'
}
]
}
data() {
return {
breadcrumbs: [
{
title: this.$page.props.settings.name,
to: '/',
},
{
title: '403 not found',
to: '/'
}
]
}
}
}
</script>

View File

@@ -1,5 +1,6 @@
<template>
<Page>
<Head><title>{{ __('We were unable to find this page.') }}</title></Head>
<Content>
<Container>
<PageHeader>
@@ -37,20 +38,20 @@ import NotificationBadge from '@/components/NotificationBadge.vue'
import MainLayout from '@/Layouts/MainLayout.vue'
export default {
layout: MainLayout,
layout: MainLayout,
components: {
Container,
Content,
Page,
PageHeader,
PageHeaderTitle,
PageBody,
Button,
List,
ListItem,
StatusBubble,
NotificationBadge,
}
components: {
Container,
Content,
Page,
PageHeader,
PageHeaderTitle,
PageBody,
Button,
List,
ListItem,
StatusBubble,
NotificationBadge,
}
}
</script>

View File

@@ -1,4 +1,5 @@
<template>
<Head><title>{{ __('Privacy Policy') }}</title></Head>
<div class="flex items-center justify-center w-full min-h-screen py-8 px-8">
<Container size="medium" class="py-4 space-y-8">
<div class="flex flex-col items-center space-y-5">
@@ -26,8 +27,6 @@ import Button from '@/components/Button.vue'
import Container from '@/components/Container.vue'
export default {
metaInfo: {title: 'Privacy Policy'},
components: {
TextDivider,
FormInput,

View File

@@ -1,4 +1,5 @@
<template>
<Head><title>{{ __('Terms of Service') }}</title></Head>
<div class="flex items-center justify-center w-full min-h-screen py-8 px-8">
<Container size="medium" class="py-4 space-y-8">
<div class="flex flex-col items-center space-y-5">
@@ -26,8 +27,6 @@ import Button from '@/components/Button.vue'
import Container from '@/components/Container.vue'
export default {
metaInfo: {title: 'Terms of Service'},
components: {
TextDivider,
FormInput,

View File

@@ -1,6 +1,7 @@
<template>
<Page>
<TopBar :breadcrumbs="breadcrumbs"/>
<Head><title>{{ __('Billing') }}</title></Head>
<TopBar :breadcrumbs="breadcrumbs" />
<Content>
<Container>
@@ -32,12 +33,12 @@
:errors="$page.props.errors.card_holder_name"
:disabled="sending"
id="card-holder-name"
:label="__('Card holder name')"/>
:label="__('Card holder name')" />
<form-input v-model="address"
:errors="$page.props.errors.address"
:disabled="sending"
:label="__('Address')"/>
:label="__('Address')" />
<form-select :disabled="sending" :label="__('Country')"
:errors="$page.props.errors.country" v-model="country">
<option :value="code" v-for="(country, code) in countries"
@@ -46,11 +47,11 @@
<form-input v-model="zip"
:errors="$page.props.errors.zip"
:disabled="sending"
:label="__('ZIP (postal code)')"/>
:label="__('ZIP (postal code)')" />
<form-input v-model="city"
:errors="$page.props.errors.city"
:disabled="sending"
:label="__('City')"/>
:label="__('City')" />
<div class="w-full pb-4">
<label class="form-label" for="card-element">{{ __('Card details') }}</label>
@@ -85,7 +86,7 @@
:disabled="sending"
:placeholder="__('Enter a coupon code if you have one, before subscribing')"
class="pb-4"
:label="__('Coupon')"/>
:label="__('Coupon')" />
<Table caption="Package list overview">
<TableHead>
<TableRow>
@@ -96,8 +97,8 @@
@click="requestFilterUrl({sortBy: {'name' : filters.sort.name === 'asc' ? 'desc' : 'asc'}})">
<span>{{ __('Name') }}</span>
<IconArrowUp v-if="filters.sort.name === 'asc'"/>
<IconArrowDown v-if="filters.sort.name === 'desc'"/>
<IconArrowUp v-if="filters.sort.name === 'asc'" />
<IconArrowDown v-if="filters.sort.name === 'desc'" />
</a>
</TableHeader>
<TableHeader>
@@ -107,8 +108,8 @@
@click="requestFilterUrl({sortBy: {'sites' : filters.sort.sites === 'asc' ? 'desc' : 'asc'}})">
<span>{{ __('Max sites') }}</span>
<IconArrowUp v-if="filters.sort.sites === 'asc'"/>
<IconArrowDown v-if="filters.sort.sites === 'desc'"/>
<IconArrowUp v-if="filters.sort.sites === 'asc'" />
<IconArrowDown v-if="filters.sort.sites === 'desc'" />
</a>
</TableHeader>
<TableHeader>
@@ -118,8 +119,8 @@
@click="requestFilterUrl({sortBy: {'servers' : filters.sort.servers === 'asc' ? 'desc' : 'asc'}})">
<span>{{ __('Max servers') }}</span>
<IconArrowUp v-if="filters.sort.servers === 'asc'"/>
<IconArrowDown v-if="filters.sort.servers === 'desc'"/>
<IconArrowUp v-if="filters.sort.servers === 'asc'" />
<IconArrowDown v-if="filters.sort.servers === 'desc'" />
</a>
</TableHeader>
<TableHeader>
@@ -129,14 +130,14 @@
@click="requestFilterUrl({sortBy: {'price' : filters.sort.price === 'asc' ? 'desc' : 'asc'}})">
<span>{{ __('Price') }}</span>
<IconArrowUp v-if="filters.sort.price === 'asc'"/>
<IconArrowDown v-if="filters.sort.price === 'desc'"/>
<IconArrowUp v-if="filters.sort.price === 'asc'" />
<IconArrowDown v-if="filters.sort.price === 'desc'" />
</a>
</TableHeader>
<TableHeader>
<inertia-link :href="route('profile.billing.index')" data-balloon-blunt
:aria-label="__('Clear sorting')" data-balloon-pos="up">
<IconClose/>
<IconClose />
</inertia-link>
</TableHeader>
</TableRow>
@@ -242,12 +243,6 @@ import {useNotification} from '@/hooks/notification'
import {useConfirm} from '@/hooks/confirm'
export default {
metaInfo() {
return {
title: `${this.__('Billing')}`,
}
},
layout: MainLayout,
components: {

View File

@@ -1,6 +1,7 @@
<template>
<Page>
<TopBar :breadcrumbs="breadcrumbs"/>
<Head><title>{{ __('Billing error') }}</title></Head>
<TopBar :breadcrumbs="breadcrumbs" />
<Content>
<Container>
@@ -44,54 +45,46 @@ import Modal from '@/components/Modal.vue'
import ModalContainer from '@/components/ModalContainer.vue'
export default {
metaInfo() {
return {
title: `${this.__('Billing error')}`,
}
},
layout: MainLayout,
layout: MainLayout,
components: {
TopBar,
Container,
Content,
Page,
PageHeader,
PageHeaderTitle,
PageBody,
Button,
List,
ListItem,
StatusBubble,
NotificationBadge,
IconBox,
IconGlobe,
IconStorage,
IconArrowDown,
IconArrowUp,
IconClose,
Modal,
ModalContainer,
},
components: {
TopBar,
Container,
Content,
Page,
PageHeader,
PageHeaderTitle,
PageBody,
Button,
List,
ListItem,
StatusBubble,
NotificationBadge,
IconBox,
IconGlobe,
IconStorage,
IconArrowDown,
IconArrowUp,
IconClose,
Modal,
ModalContainer,
},
props: {},
props: {
},
data() {
return {
breadcrumbs: [
{
title: this.$page.props.settings.name,
to: '/',
},
{
title: this.__('Billing error'),
to: this.route('profile.billing.index'),
},
],
}
},
}
data() {
return {
breadcrumbs: [
{
title: this.$page.props.settings.name,
to: '/',
},
{
title: this.__('Billing error'),
to: this.route('profile.billing.index'),
},
],
}
},
}
</script>

View File

@@ -1,6 +1,7 @@
<template>
<Page>
<TopBar :breadcrumbs="breadcrumbs"/>
<Head><title>{{ __('Profile') }}</title></Head>
<TopBar :breadcrumbs="breadcrumbs" />
<Content>
<Container>
@@ -58,75 +59,69 @@ import FormSelect from '@/components/forms/FormSelect.vue'
import FormActions from '@/components/FormActions.vue'
export default {
metaInfo() {
return {
title: `${this.__('Profile')}`,
}
},
layout: MainLayout,
layout: MainLayout,
components: {
TopBar,
Container,
Content,
Page,
PageHeader,
PageHeaderTitle,
PageBody,
Button,
List,
ListItem,
StatusBubble,
NotificationBadge,
IconBox,
IconGlobe,
IconStorage,
Modal,
ModalContainer,
FormInput,
FormSelect,
FormActions
},
components: {
TopBar,
Container,
Content,
Page,
PageHeader,
PageHeaderTitle,
PageBody,
Button,
List,
ListItem,
StatusBubble,
NotificationBadge,
IconBox,
IconGlobe,
IconStorage,
Modal,
ModalContainer,
FormInput,
FormSelect,
FormActions
},
props: {
profile: Object,
countries: Object,
},
props: {
profile: Object,
countries: Object,
},
data() {
return {
form: {
name: this.profile.name,
email: this.profile.email,
language: this.profile.language,
data() {
return {
form: {
name: this.profile.name,
email: this.profile.email,
language: this.profile.language,
address: this.profile.address,
country: this.profile.country,
zip: this.profile.zip,
city: this.profile.city,
},
address: this.profile.address,
country: this.profile.country,
zip: this.profile.zip,
city: this.profile.city,
breadcrumbs: [
{
title: this.$page.props.settings.name,
to: '/',
},
{
title: this.__('Profile'),
to: this.route('profile.index'),
},
],
}
},
breadcrumbs: [
{
title: this.$page.props.settings.name,
to: '/',
},
{
title: this.__('Profile'),
to: this.route('profile.index'),
},
],
}
},
methods: {
submit(){
this.$inertia.patch(this.route('profile.update'), this.form, {
onStart: () => this.sending = true,
onFinish: () => this.sending = false
});
}
},
}
methods: {
submit() {
this.$inertia.patch(this.route('profile.update'), this.form, {
onStart: () => this.sending = true,
onFinish: () => this.sending = false
});
}
},
}
</script>

View File

@@ -1,6 +1,7 @@
<template>
<Page>
<TopBar :breadcrumbs="breadcrumbs"/>
<Head><title>{{ __('Integrations') }}</title></Head>
<TopBar :breadcrumbs="breadcrumbs" />
<Content>
<Container>
@@ -12,10 +13,10 @@
</FormSelect>
<FormInput v-if="form.provider === 'cloudflare'" :label="__('API key')"
:errors="$page.props.errors['meta.api_key']" v-model="form.meta.api_key"/>
:errors="$page.props.errors['meta.api_key']" v-model="form.meta.api_key" />
<FormInput v-if="form.provider === 'cloudflare'" :label="__('Cloudflare email')"
:errors="$page.props.errors['meta.cloudflare_email']"
v-model="form.meta.cloudflare_email"/>
v-model="form.meta.cloudflare_email" />
<FormActions>
<Button>{{ __('Save') }}</Button>
@@ -81,12 +82,6 @@ import TableData from '@/components/TableData.vue'
import {useConfirm} from '@/hooks/confirm'
export default {
metaInfo() {
return {
title: `${this.__('Integrations')}`,
}
},
layout: MainLayout,
components: {

View File

@@ -1,20 +1,21 @@
<template>
<Page>
<TopBar :breadcrumbs="breadcrumbs"/>
<Head><title>{{ __('Security') }}</title></Head>
<TopBar :breadcrumbs="breadcrumbs" />
<Content>
<Container>
<PageBody>
<SettingsSegment>
<template #title>
{{ __('Password')}}
{{ __('Password') }}
</template>
<template #subtitle>
{{ __('Change your password') }}
</template>
<template #form>
<form class="space-y-4" @submit.prevent="submit">
<FormInput :label="__('Current password')" type="password" :errors="$page.props.errors.current_password" v-model="form.current_password"/>
<FormInput :label="__('Current password')" type="password" :errors="$page.props.errors.current_password" v-model="form.current_password" />
<div class="w-full flex space-x-4">
<FormInput :label="__('New password')" type="password" :errors="$page.props.errors.password" v-model="form.password" class="w-1/2" />
<FormInput :label="__('Confirm new password')" type="password" :errors="$page.props.errors.password_confirmation" v-model="form.password_confirmation" class="w-1/2" />
@@ -68,87 +69,81 @@ import SettingsSegment from "../../components/SettingsSegment.vue";
import TwoFactorAuthentication from "./components/TwoFactorAuthentication.vue";
export default {
metaInfo() {
return {
title: `${this.__('Security')}`,
}
},
layout: MainLayout,
layout: MainLayout,
components: {
TwoFactorAuthentication,
SettingsSegment,
TopBar,
Container,
Content,
Page,
PageHeader,
PageHeaderTitle,
PageBody,
Button,
List,
ListItem,
StatusBubble,
NotificationBadge,
IconBox,
IconGlobe,
IconStorage,
Modal,
ModalContainer,
FormInput,
FormSelect,
FormActions
},
components: {
TwoFactorAuthentication,
SettingsSegment,
TopBar,
Container,
Content,
Page,
PageHeader,
PageHeaderTitle,
PageBody,
Button,
List,
ListItem,
StatusBubble,
NotificationBadge,
IconBox,
IconGlobe,
IconStorage,
Modal,
ModalContainer,
FormInput,
FormSelect,
FormActions
},
props: {
profile: Object,
languages: Array,
twoFactor: Object,
},
props: {
profile: Object,
languages: Array,
twoFactor: Object,
},
data() {
return {
form: {
current_password: null,
password: null,
password_confirmation: null,
},
data() {
return {
form: {
current_password: null,
password: null,
password_confirmation: null,
breadcrumbs: [
{
title: this.$page.props.settings.name,
to: '/',
},
{
title: this.__('Profile'),
to: this.route('profile.index'),
},
{
title: this.__('Security'),
to: this.route('profile.security.index'),
},
],
}
},
breadcrumbs: [
{
title: this.$page.props.settings.name,
to: '/',
},
{
title: this.__('Profile'),
to: this.route('profile.index'),
},
{
title: this.__('Security'),
to: this.route('profile.security.index'),
},
],
}
},
methods: {
submit() {
this.$inertia.patch(this.route('profile.security.updatePassword'), this.form, {
onStart: () => this.sending = true,
onFinish: () => {
this.sending = false;
methods: {
submit() {
this.$inertia.patch(this.route('profile.security.updatePassword'), this.form, {
onStart: () => this.sending = true,
onFinish: () => {
this.sending = false;
if (!Object.keys(this.$page.props.errors).length) {
this.form = {
current_password: null,
password: null,
password_confirmation: null,
}
if (!Object.keys(this.$page.props.errors).length) {
this.form = {
current_password: null,
password: null,
password_confirmation: null,
}
}
});
}
},
}
}
});
}
},
}
</script>

View File

@@ -1,6 +1,7 @@
<template>
<Page>
<TopBar :breadcrumbs="breadcrumbs"/>
<Head><title>{{ __('Settings') }}</title></Head>
<TopBar :breadcrumbs="breadcrumbs" />
<Content>
<Container>
@@ -73,12 +74,6 @@ import FormActions from '@/components/FormActions.vue'
import {useConfirm} from '@/hooks/confirm'
export default {
metaInfo() {
return {
title: `${this.__('Settings')}`,
}
},
layout: MainLayout,
components: {

View File

@@ -1,5 +1,6 @@
<template>
<Page>
<Head><title>{{ __('Servers') }}</title></Head>
<Portal to="modals" v-if="can('servers', 'create')">
<ModalContainer>
<Modal @close="modalIsOpen = false" v-if="modalIsOpen" @submit="submit">
@@ -7,7 +8,7 @@
<template #form>
<FormInput :loading="loading" :label="__('Name')" placeholder="webserver-01"
:errors="$page.props.errors.name" v-model="form.name"/>
:errors="$page.props.errors.name" v-model="form.name" />
<FormSelect :loading="loading" :errors="$page.props.errors.provider_id" :label="__('Select provider')"
v-model="form.provider_id">
@@ -43,7 +44,7 @@
</ModalContainer>
</Portal>
<TopBar :breadcrumbs="breadcrumbs"/>
<TopBar :breadcrumbs="breadcrumbs" />
<Content>
<Container>
@@ -57,11 +58,11 @@
</PageHeader>
<PageBody>
<EmptyImage v-if="!servers.meta.total"/>
<EmptyImage v-if="!servers.meta.total" />
<List>
<ListItem v-for="server in servers.data" :key="server.id">
<template #prefix>
<StatusBubble :variant="server.status === 'busy' ? 'gray' : 'success'"/>
<StatusBubble :variant="server.status === 'busy' ? 'gray' : 'success'" />
</template>
<template #title>
<inertia-link class="text-primary font-medium" :href="route('servers.show', server.id)">
@@ -74,7 +75,7 @@
<template #suffix>
<Dropdown v-slot="{ isOpen, toggle, position }">
<IconButton @click="toggle">
<IconMore class="w-5 h-5"/>
<IconMore class="w-5 h-5" />
</IconButton>
<DropdownList :position="position" v-if="isOpen">
@@ -126,12 +127,6 @@ import DropdownListItemButton from '@/components/DropdownListItemButton.vue'
import {useConfirm} from '@/hooks/confirm'
export default {
metaInfo() {
return {
title: `${this.__('Servers')}`,
}
},
layout: MainLayout,
components: {

View File

@@ -1,6 +1,7 @@
<template>
<Page>
<TopBar :breadcrumbs="breadcrumbs"/>
<Head><title>{{ __('Servers') }}</title></Head>
<TopBar :breadcrumbs="breadcrumbs" />
<Content>
<Container>
@@ -13,7 +14,7 @@
<PageBody>
<SettingsLayout>
<template #nav>
<Tabs :server="server"/>
<Tabs :server="server" />
</template>
<template #segments>
<SettingsSegment v-if="can('servers', 'update')">
@@ -21,7 +22,7 @@
<template #form>
<form class="space-y-4" @submit.prevent="submit">
<FormInput :label="__('Name')" :errors="$page.props.errors.name"
v-model="form.name"/>
v-model="form.name" />
<FormActions>
<Button>{{ __('Save') }}</Button>
@@ -85,97 +86,91 @@ import TableData from '@/components/TableData.vue'
import {useConfirm} from '@/hooks/confirm'
export default {
metaInfo() {
return {
title: `${this.__('Servers')}`,
}
},
layout: MainLayout,
layout: MainLayout,
components: {
TopBar,
Container,
Content,
Page,
PageHeader,
PageHeaderTitle,
PageBody,
Button,
List,
IconButton,
IconMore,
ListItem,
StatusBubble,
NotificationBadge,
IconBox,
IconGlobe,
IconStorage,
EmptyImage,
Modal,
ModalContainer,
FormInput,
FormActions,
Dropdown,
DropdownList,
DropdownListItem,
DropdownListItemButton,
SettingsSegment,
SettingsLayout,
Tabs,
Table,
TableHead,
TableHeader,
TableRow,
TableBody,
TableData,
Pagination
},
components: {
TopBar,
Container,
Content,
Page,
PageHeader,
PageHeaderTitle,
PageBody,
Button,
List,
IconButton,
IconMore,
ListItem,
StatusBubble,
NotificationBadge,
IconBox,
IconGlobe,
IconStorage,
EmptyImage,
Modal,
ModalContainer,
FormInput,
FormActions,
Dropdown,
DropdownList,
DropdownListItem,
DropdownListItemButton,
SettingsSegment,
SettingsLayout,
Tabs,
Table,
TableHead,
TableHeader,
TableRow,
TableBody,
TableData,
Pagination
},
props: {
server: Object,
},
props: {
server: Object,
},
data() {
return {
form: {
name: this.server.name
},
data() {
return {
form: {
name: this.server.name
breadcrumbs: [
{
title: this.$page.props.settings.name,
to: '/',
},
{
title: this.server.name,
to: this.route('servers.show', this.server.id),
},
],
}
},
breadcrumbs: [
{
title: this.$page.props.settings.name,
to: '/',
},
{
title: this.server.name,
to: this.route('servers.show', this.server.id),
},
],
}
methods: {
useConfirm,
submit() {
this.$inertia.patch(this.route('servers.settings.update', this.server.id), this.form, {
onStart: () => this.sending = true,
onFinish: () => this.sending = false
})
},
methods: {
useConfirm,
submit() {
this.$inertia.patch(this.route('servers.settings.update', this.server.id), this.form, {
onStart: () => this.sending = true,
onFinish: () => this.sending = false
})
},
confirmDelete() {
useConfirm({
title: this.__('Are you sure?'),
message: this.__('Your server will be deleted completely, this action is irreversible.'),
onConfirm: () => this.delete(),
})
},
delete() {
this.$inertia.delete(this.route('servers.delete', this.server.id))
},
confirmDelete() {
useConfirm({
title: this.__('Are you sure?'),
message: this.__('Your server will be deleted completely, this action is irreversible.'),
onConfirm: () => this.delete(),
})
},
}
delete() {
this.$inertia.delete(this.route('servers.delete', this.server.id))
},
},
}
</script>

View File

@@ -1,6 +1,8 @@
<template>
<Page>
<TopBar :breadcrumbs="breadcrumbs"/>
<Head><title>{{ __('Servers') }}</title></Head>
<TopBar :breadcrumbs="breadcrumbs" />
<Content>
<Container>
@@ -17,7 +19,7 @@
<PageBody>
<SettingsLayout>
<template #nav>
<Tabs :server="server"/>
<Tabs :server="server" />
</template>
<template #segments>
<SettingsSegment>
@@ -47,7 +49,7 @@
</Table>
</div>
<pagination :links="sites"/>
<pagination :links="sites" />
</template>
</SettingsSegment>
</template>
@@ -98,73 +100,67 @@ import TableBody from '@/components/TableBody.vue'
import TableData from '@/components/TableData.vue'
export default {
metaInfo() {
return {
title: `${this.__('Servers')}`,
}
},
layout: MainLayout,
layout: MainLayout,
components: {
TopBar,
Container,
Content,
Page,
PageHeader,
PageHeaderTitle,
PageBody,
Button,
List,
IconButton,
IconMore,
ListItem,
StatusBubble,
NotificationBadge,
IconBox,
IconGlobe,
IconStorage,
EmptyImage,
Modal,
ModalContainer,
FormInput,
FormActions,
Dropdown,
DropdownList,
DropdownListItem,
DropdownListItemButton,
SettingsSegment,
SettingsLayout,
Tabs,
Table,
TableHead,
TableHeader,
TableRow,
TableBody,
TableData,
Pagination
},
components: {
TopBar,
Container,
Content,
Page,
PageHeader,
PageHeaderTitle,
PageBody,
Button,
List,
IconButton,
IconMore,
ListItem,
StatusBubble,
NotificationBadge,
IconBox,
IconGlobe,
IconStorage,
EmptyImage,
Modal,
ModalContainer,
FormInput,
FormActions,
Dropdown,
DropdownList,
DropdownListItem,
DropdownListItemButton,
SettingsSegment,
SettingsLayout,
Tabs,
Table,
TableHead,
TableHeader,
TableRow,
TableBody,
TableData,
Pagination
},
props: {
server: Object,
sites: Object,
},
props: {
server: Object,
sites: Object,
},
data() {
return {
breadcrumbs: [
{
title: this.$page.props.settings.name,
to: '/',
},
{
title: this.server.name,
to: this.route('servers.show', this.server.id),
},
],
}
},
data() {
return {
breadcrumbs: [
{
title: this.$page.props.settings.name,
to: '/',
},
{
title: this.server.name,
to: this.route('servers.show', this.server.id),
},
],
}
},
methods: {},
}
methods: {},
}
</script>

View File

@@ -1,6 +1,7 @@
<template>
<Page>
<TopBar :breadcrumbs="breadcrumbs"/>
<Head :title="`${this.__('Aliases')} - ${this.site.domain}`"></Head>
<TopBar :breadcrumbs="breadcrumbs" />
<Content>
<Container>
@@ -13,14 +14,14 @@
<PageBody>
<SettingsLayout>
<template #nav>
<Tabs :site="site"/>
<Tabs :site="site" />
</template>
<template #segments>
<SettingsSegment>
<template #title>{{ __('Create') }}</template>
<template #form>
<form class="space-y-4" @submit.prevent="submit">
<FormInput :label="__('Domain')" :errors="$page.props.errors.domain" v-model="form.domain"/>
<FormInput :label="__('Domain')" :errors="$page.props.errors.domain" v-model="form.domain" />
<FormActions>
<Button>{{ __('Save changes') }}</Button>
</FormActions>
@@ -44,7 +45,9 @@
</TableHead>
<TableBody>
<TableRow v-for="(alias, index) in aliases" :key="index">
<TableData><StatusBubble :variant="'success'"/></TableData>
<TableData>
<StatusBubble :variant="'success'" />
</TableData>
<TableData>{{ alias }}</TableData>
<TableData>
<Button variant="danger" size="sm"
@@ -98,113 +101,105 @@ import TableBody from '@/components/TableBody.vue'
import TableData from '@/components/TableData.vue'
export default {
metaInfo() {
return {
title: `${this.__('Aliases')} - ${this.site.domain}`,
}
},
layout: MainLayout,
layout: MainLayout,
components: {
TopBar,
Container,
Content,
Page,
PageHeader,
PageHeaderTitle,
PageBody,
Button,
List,
ListItem,
StatusBubble,
NotificationBadge,
FormInput,
FormSelect,
FormTextarea,
SettingsLayout,
SettingsSegment,
Form,
FormActions,
Pagination,
Tabs,
Table,
TableHead,
TableHeader,
TableRow,
TableBody,
TableData,
EmptyImage,
},
components: {
TopBar,
Container,
Content,
Page,
PageHeader,
PageHeaderTitle,
PageBody,
Button,
List,
ListItem,
StatusBubble,
NotificationBadge,
FormInput,
FormSelect,
FormTextarea,
SettingsLayout,
SettingsSegment,
Form,
FormActions,
Pagination,
Tabs,
Table,
TableHead,
TableHeader,
TableRow,
TableBody,
TableData,
EmptyImage,
},
data() {
return {
sending: false,
data() {
return {
sending: false,
form: {
domain: null,
},
form: {
domain: null,
breadcrumbs: [
{
title: this.$page.props.settings.name,
to: '/',
},
{
title: this.__('Sites'),
to: this.route('sites.index'),
},
{
title: this.site.domain,
to: this.route('sites.show', this.site.id),
},
{
title: this.__('Aliases'),
to: this.route('sites.aliases.index', this.site.id),
},
],
}
},
breadcrumbs: [
{
title: this.$page.props.settings.name,
to: '/',
},
{
title: this.__('Sites'),
to: this.route('sites.index'),
},
{
title: this.site.domain,
to: this.route('sites.show', this.site.id),
},
{
title: this.__('Aliases'),
to: this.route('sites.aliases.index', this.site.id),
},
],
}
mounted() {
},
watch: {},
computed: {},
props: {
site: Object,
aliases: [Object, Array],
},
methods: {
submit() {
this.sending = true
this.$inertia.post(this.route('sites.aliases.store', this.site.id), this.form, {
onFinish: () => {
this.sending = false
this.form.domain = null;
}
});
},
mounted() {
confirmDelete(alias) {
useConfirm({
title: this.__('Are you sure?'),
message: this.__('Your alias will be deleted permanently, this action cannot be undone.'),
onConfirm: () => this.delete(alias),
})
},
watch: {
delete(alias) {
this.$inertia.delete(this.route('sites.aliases.delete', [this.site.id, alias]), {
preserveScroll: true
})
},
computed: {
},
props: {
site: Object,
aliases: [Object, Array],
},
methods: {
submit() {
this.sending = true
this.$inertia.post(this.route('sites.aliases.store', this.site.id), this.form, {
onFinish: () => {
this.sending = false
this.form.domain = null;
}
});
},
confirmDelete(alias) {
useConfirm({
title: this.__('Are you sure?'),
message: this.__('Your alias will be deleted permanently, this action cannot be undone.'),
onConfirm: () => this.delete(alias),
})
},
delete(alias) {
this.$inertia.delete(this.route('sites.aliases.delete', [this.site.id, alias]), {
preserveScroll: true
})
},
},
}
},
}
</script>

View File

@@ -1,6 +1,7 @@
<template>
<Page>
<TopBar :breadcrumbs="breadcrumbs"/>
<Head><title>{{ __('App') }}</title></Head>
<TopBar :breadcrumbs="breadcrumbs" />
<Content>
<Container>
@@ -13,7 +14,7 @@
<PageBody>
<SettingsLayout>
<template #nav>
<Tabs :site="site"/>
<Tabs :site="site" />
</template>
<template #segments>
<SettingsSegment v-if="site.project">
@@ -67,7 +68,7 @@
<SettingsSegment v-if="type === 'nextcloud'">
<template #title>{{ __('Nextcloud') }}</template>
<template #subtitle>{{ __('Nextcloud is a suite of client-server software for creating and using file hosting services, it is comparable to Dropbox.')}}</template>
<template #subtitle>{{ __('Nextcloud is a suite of client-server software for creating and using file hosting services, it is comparable to Dropbox.') }}</template>
<template #content>
<Button @click="install">{{ __('Start installation') }}</Button>
<Button variant="secondary" @click="type = null">{{ __('Cancel') }}</Button>
@@ -118,94 +119,88 @@ import TableBody from '@/components/TableBody.vue'
import TableData from '@/components/TableData.vue'
export default {
metaInfo() {
return {
title: this.__('Apps'),
}
},
layout: MainLayout,
layout: MainLayout,
components: {
TopBar,
Container,
Content,
Page,
PageHeader,
PageHeaderTitle,
PageBody,
Button,
List,
ListItem,
StatusBubble,
NotificationBadge,
FormInput,
SettingsLayout,
SettingsSegment,
Form,
FormActions,
Tabs,
Table,
TableHead,
TableHeader,
TableRow,
TableBody,
TableData,
},
components: {
TopBar,
Container,
Content,
Page,
PageHeader,
PageHeaderTitle,
PageBody,
Button,
List,
ListItem,
StatusBubble,
NotificationBadge,
FormInput,
SettingsLayout,
SettingsSegment,
Form,
FormActions,
Tabs,
Table,
TableHead,
TableHeader,
TableRow,
TableBody,
TableData,
},
data() {
return {
sending: false,
data() {
return {
sending: false,
type: null,
options: {
create_database: false,
},
type: null,
options: {
create_database: false,
breadcrumbs: [
{
title: this.$page.props.settings.name,
to: '/',
},
{
title: this.__('Sites'),
to: this.route('sites.index'),
},
{
title: this.site.domain,
to: this.route('sites.show', this.site.id),
},
{
title: this.__('Apps'),
to: this.route('sites.apps.index', this.site.id),
},
],
}
},
breadcrumbs: [
{
title: this.$page.props.settings.name,
to: '/',
},
{
title: this.__('Sites'),
to: this.route('sites.index'),
},
{
title: this.site.domain,
to: this.route('sites.show', this.site.id),
},
{
title: this.__('Apps'),
to: this.route('sites.apps.index', this.site.id),
},
],
}
props: {
site: Object,
},
methods: {
prepareInstall(type) {
this.type = type;
},
props: {
site: Object,
install() {
this.$inertia.post(this.route('sites.apps.store', this.site.id), {
type: this.type,
options: this.options
}, {
onFinish: () => {
this.type = null
}
});
},
methods: {
prepareInstall(type) {
this.type = type;
},
install() {
this.$inertia.post(this.route('sites.apps.store', this.site.id), {
type: this.type,
options: this.options
}, {
onFinish: () => {
this.type = null
}
});
},
uninstall() {
this.$inertia.delete(this.route('sites.apps.delete', this.site.id))
}
uninstall() {
this.$inertia.delete(this.route('sites.apps.delete', this.site.id))
}
}
}
</script>

View File

@@ -1,6 +1,7 @@
<template>
<Page>
<TopBar :breadcrumbs="breadcrumbs"/>
<Head :title="`${this.__('Certificates')} - ${this.site.domain}`"></Head>
<TopBar :breadcrumbs="breadcrumbs" />
<Content>
<Container>
@@ -13,7 +14,7 @@
<PageBody>
<SettingsLayout>
<template #nav>
<Tabs :site="site"/>
<Tabs :site="site" />
</template>
<template #segments>
<SettingsSegment>
@@ -30,7 +31,7 @@
</FormSelect>
<div>
<FormInput v-if="form.type === 'letsencrypt'" :label="__('Domain')" :errors="$page.props.errors.domain" v-model="form.domain"/>
<FormInput v-if="form.type === 'letsencrypt'" :label="__('Domain')" :errors="$page.props.errors.domain" v-model="form.domain" />
<button type="button" @click="setDomainData(true)" class="text-primary text-small border-b border-dotted">Click here to add aliases</button>
</div>
@@ -59,7 +60,9 @@
</TableHead>
<TableBody>
<TableRow v-for="certificate in certificates.data" :key="certificate.id">
<TableData><StatusBubble :variant="certificate.status === 'busy' ? 'gray' : 'success'"/></TableData>
<TableData>
<StatusBubble :variant="certificate.status === 'busy' ? 'gray' : 'success'" />
</TableData>
<TableData>{{ certificate.domain }}</TableData>
<TableData>
<Button :disabled="certificate.status === 'busy'" variant="danger" size="sm"
@@ -71,7 +74,7 @@
</Table>
</div>
<pagination :links="certificates"/>
<pagination :links="certificates" />
</template>
</SettingsSegment>
</template>
@@ -115,177 +118,171 @@ import TableBody from '@/components/TableBody.vue'
import TableData from '@/components/TableData.vue'
export default {
metaInfo() {
return {
title: `${this.__('Certificates')} - ${this.site.domain}`,
}
},
layout: MainLayout,
layout: MainLayout,
components: {
TopBar,
Container,
Content,
Page,
PageHeader,
PageHeaderTitle,
PageBody,
Button,
List,
ListItem,
StatusBubble,
NotificationBadge,
FormInput,
FormSelect,
FormTextarea,
SettingsLayout,
SettingsSegment,
Form,
FormActions,
Pagination,
Tabs,
Table,
TableHead,
TableHeader,
TableRow,
TableBody,
TableData,
EmptyImage,
},
components: {
TopBar,
Container,
Content,
Page,
PageHeader,
PageHeaderTitle,
PageBody,
Button,
List,
ListItem,
StatusBubble,
NotificationBadge,
FormInput,
FormSelect,
FormTextarea,
SettingsLayout,
SettingsSegment,
Form,
FormActions,
Pagination,
Tabs,
Table,
TableHead,
TableHeader,
TableRow,
TableBody,
TableData,
EmptyImage,
},
data() {
return {
sending: false,
data() {
return {
sending: false,
form: {
domain: null,
type: 'letsencrypt',
certificate: null,
private: null,
},
form: {
domain: null,
type: 'letsencrypt',
certificate: null,
private: null,
breadcrumbs: [
{
title: this.$page.props.settings.name,
to: '/',
},
{
title: this.__('Sites'),
to: this.route('sites.index'),
},
{
title: this.site.domain,
to: this.route('sites.show', this.site.id),
},
{
title: this.__('Certificates'),
to: this.route('sites.certificates.index', this.site.id),
},
],
}
},
breadcrumbs: [
{
title: this.$page.props.settings.name,
to: '/',
},
{
title: this.__('Sites'),
to: this.route('sites.index'),
},
{
title: this.site.domain,
to: this.route('sites.show', this.site.id),
},
{
title: this.__('Certificates'),
to: this.route('sites.certificates.index', this.site.id),
},
],
mounted() {
if (this.shouldBePolling) {
this.startPollingInterval();
}
this.setDomainData();
},
watch: {
shouldBePolling: function (value) {
if (!value) {
this.clearPollingInterval();
return;
}
},
mounted() {
if(this.shouldBePolling){
if (!this.pollingInterval) {
this.startPollingInterval();
}
this.setDomainData();
},
watch: {
shouldBePolling: function (value) {
if (!value) {
this.clearPollingInterval();
return;
}
if(!this.pollingInterval){
this.startPollingInterval();
}
}
},
computed: {
shouldBePolling() {
return !!this.certificates.data.filter((certificate) => {
return certificate.status === 'busy';
}).length;
},
},
props: {
site: Object,
certificates: Object,
},
methods: {
startPollingInterval(){
this.pollingInterval = setInterval(function () {
this.poll();
}.bind(this), 3000);
},
clearPollingInterval(){
clearTimeout(this.pollingInterval);
this.pollingInterval = null;
},
poll() {
this.$inertia.get(this.route('sites.certificates.index', this.site.id), {
only: ['certificates'],
preserveScroll: true,
})
},
submit() {
this.sending = true
this.$inertia.post(this.route('sites.certificates.store', this.site.id), this.form, {
onFinish: () => {
this.sending = false
if (!Object.keys(this.$page.props.errors).length) {
this.setDomainData();
}
}
});
},
confirmDelete(certificate) {
useConfirm({
title: this.__('Are you sure?'),
message: `Your certificate will be deleted permanently, this action cannot be undone.`,
onConfirm: () => this.delete(certificate),
})
},
delete(certificate) {
this.$inertia.delete(this.route('sites.certificates.delete', [this.site.id, certificate.id]), {
preserveScroll: true
})
},
setDomainData(withAliases){
this.form.certificate = null;
this.form.private = null;
if (this.site.domain.startsWith('www.')) {
this.form.domain = this.site.domain + ',' + this.site.domain.replace('www.', '');
} else {
this.form.domain = this.site.domain + ',www.' + this.site.domain;
}
if(withAliases){
this.form.domain = this.form.domain + ',' + this.site.aliases.join(',');
}
}
},
beforeUnmount(){
this.clearPollingInterval();
}
},
computed: {
shouldBePolling() {
return !!this.certificates.data.filter((certificate) => {
return certificate.status === 'busy';
}).length;
},
},
props: {
site: Object,
certificates: Object,
},
methods: {
startPollingInterval() {
this.pollingInterval = setInterval(function () {
this.poll();
}.bind(this), 3000);
},
clearPollingInterval() {
clearTimeout(this.pollingInterval);
this.pollingInterval = null;
},
poll() {
this.$inertia.get(this.route('sites.certificates.index', this.site.id), {
only: ['certificates'],
preserveScroll: true,
})
},
submit() {
this.sending = true
this.$inertia.post(this.route('sites.certificates.store', this.site.id), this.form, {
onFinish: () => {
this.sending = false
if (!Object.keys(this.$page.props.errors).length) {
this.setDomainData();
}
}
});
},
confirmDelete(certificate) {
useConfirm({
title: this.__('Are you sure?'),
message: `Your certificate will be deleted permanently, this action cannot be undone.`,
onConfirm: () => this.delete(certificate),
})
},
delete(certificate) {
this.$inertia.delete(this.route('sites.certificates.delete', [this.site.id, certificate.id]), {
preserveScroll: true
})
},
setDomainData(withAliases) {
this.form.certificate = null;
this.form.private = null;
if (this.site.domain.startsWith('www.')) {
this.form.domain = this.site.domain + ',' + this.site.domain.replace('www.', '');
} else {
this.form.domain = this.site.domain + ',www.' + this.site.domain;
}
if (withAliases) {
this.form.domain = this.form.domain + ',' + this.site.aliases.join(',');
}
}
},
beforeUnmount() {
this.clearPollingInterval();
}
}
</script>

View File

@@ -1,6 +1,7 @@
<template>
<Page>
<TopBar :breadcrumbs="breadcrumbs"/>
<Head :title="`${this.__('Cronjobs')} - ${this.site.domain}`"></Head>
<TopBar :breadcrumbs="breadcrumbs" />
<Content>
<Container>
@@ -13,7 +14,7 @@
<PageBody>
<SettingsLayout>
<template #nav>
<Tabs :site="site"/>
<Tabs :site="site" />
</template>
<template #segments>
<SettingsSegment>
@@ -23,7 +24,7 @@
</template>
<template #form>
<form class="space-y-4" @submit.prevent="submit">
<FormInput :label="__('Command')" :errors="$page.props.errors.command" v-model="form.command"/>
<FormInput :label="__('Command')" :errors="$page.props.errors.command" v-model="form.command" />
<div>
<label class="inline-block text-small font-medium">
Frequency ({{ convertedFrequency }})
@@ -95,7 +96,9 @@
</TableHead>
<TableBody>
<TableRow v-for="cronjob in cronjobs.data" :key="cronjob.id">
<TableData><StatusBubble :variant="cronjob.status === 'busy' ? 'gray' : 'success'"/></TableData>
<TableData>
<StatusBubble :variant="cronjob.status === 'busy' ? 'gray' : 'success'" />
</TableData>
<TableData>{{ cronjob.command }}</TableData>
<TableData>{{ cronjob.frequency }}</TableData>
<TableData>
@@ -109,7 +112,7 @@
</Table>
</div>
<pagination :links="cronjobs"/>
<pagination :links="cronjobs" />
</template>
</SettingsSegment>
</template>
@@ -152,178 +155,172 @@ import TableBody from '@/components/TableBody.vue'
import TableData from '@/components/TableData.vue'
export default {
metaInfo() {
return {
title: `Cronjobs - ${this.site.domain}`,
}
},
layout: MainLayout,
layout: MainLayout,
components: {
TopBar,
Container,
Content,
Page,
PageHeader,
PageHeaderTitle,
PageBody,
Button,
List,
ListItem,
StatusBubble,
NotificationBadge,
FormInput,
SettingsLayout,
SettingsSegment,
Form,
FormActions,
Pagination,
Tabs,
Table,
TableHead,
TableHeader,
TableRow,
TableBody,
TableData,
EmptyImage
},
components: {
TopBar,
Container,
Content,
Page,
PageHeader,
PageHeaderTitle,
PageBody,
Button,
List,
ListItem,
StatusBubble,
NotificationBadge,
FormInput,
SettingsLayout,
SettingsSegment,
Form,
FormActions,
Pagination,
Tabs,
Table,
TableHead,
TableHeader,
TableRow,
TableBody,
TableData,
EmptyImage
},
data() {
return {
sending: false,
data() {
return {
sending: false,
form: {
command: `php /home/${this.$page.props.auth.user.user_name}/${this.site.domain}/script.php`,
interval: 'minutely',
frequency: '* * * * *',
},
form: {
command: `php /home/${this.$page.props.auth.user.user_name}/${this.site.domain}/script.php`,
interval: 'minutely',
frequency: '* * * * *',
breadcrumbs: [
{
title: this.$page.props.settings.name,
to: '/',
},
{
title: this.__('Sites'),
to: this.route('sites.index'),
},
{
title: this.site.domain,
to: this.route('sites.show', this.site.id),
},
{
title: this.__('Cronjobs'),
to: this.route('sites.cronjobs.index', this.site.id),
},
],
}
},
breadcrumbs: [
{
title: this.$page.props.settings.name,
to: '/',
},
{
title: this.__('Sites'),
to: this.route('sites.index'),
},
{
title: this.site.domain,
to: this.route('sites.show', this.site.id),
},
{
title: this.__('Cronjobs'),
to: this.route('sites.cronjobs.index', this.site.id),
},
],
mounted() {
if (this.shouldBePolling) {
this.startPollingInterval();
}
},
watch: {
shouldBePolling: function (value) {
if (!value) {
this.clearPollingInterval();
return;
}
},
mounted() {
if(this.shouldBePolling){
if (!this.pollingInterval) {
this.startPollingInterval();
}
}
},
computed: {
shouldBePolling() {
return !!this.cronjobs.data.filter((cronjob) => {
return cronjob.status === 'busy';
}).length;
},
watch: {
shouldBePolling: function (value) {
if (!value) {
this.clearPollingInterval();
return;
}
if(!this.pollingInterval){
this.startPollingInterval();
}
convertedFrequency: function () {
if (this.form.interval === 'minutely') {
return this.form.frequency = '* * * * *';
} else if (this.form.interval === 'hourly') {
return this.form.frequency = '0 * * * *';
} else if (this.form.interval === 'nightly') {
return this.form.frequency = '0 2 * * *';
} else if (this.form.interval === 'weekly') {
return this.form.frequency = '0 0 * * 0';
} else if (this.form.interval === 'monthly') {
return this.form.frequency = '0 0 1 * *';
} else {
return this.form.frequency;
}
}
},
props: {
site: Object,
cronjobs: Object,
},
methods: {
useNotification,
startPollingInterval() {
this.pollingInterval = setInterval(function () {
this.poll();
}.bind(this), 3000);
},
computed: {
shouldBePolling() {
return !!this.cronjobs.data.filter((cronjob) => {
return cronjob.status === 'busy';
}).length;
},
convertedFrequency: function () {
if (this.form.interval === 'minutely') {
return this.form.frequency = '* * * * *';
} else if (this.form.interval === 'hourly') {
return this.form.frequency = '0 * * * *';
} else if (this.form.interval === 'nightly') {
return this.form.frequency = '0 2 * * *';
} else if (this.form.interval === 'weekly') {
return this.form.frequency = '0 0 * * 0';
} else if (this.form.interval === 'monthly') {
return this.form.frequency = '0 0 1 * *';
} else {
return this.form.frequency;
}
}
clearPollingInterval() {
clearTimeout(this.pollingInterval);
this.pollingInterval = null;
},
props: {
site: Object,
cronjobs: Object,
poll() {
this.$inertia.get(this.route('sites.cronjobs.index', this.site.id), {
only: ['cronjobs'],
preserveScroll: true,
})
},
methods: {
useNotification,
submit() {
this.sending = true
startPollingInterval(){
this.pollingInterval = setInterval(function () {
this.poll();
}.bind(this), 3000);
},
this.$inertia.post(this.route('sites.cronjobs.store', this.site.id), this.form, {
onFinish: () => {
this.sending = false
clearPollingInterval(){
clearTimeout(this.pollingInterval);
this.pollingInterval = null;
},
poll() {
this.$inertia.get(this.route('sites.cronjobs.index', this.site.id), {
only: ['cronjobs'],
preserveScroll: true,
})
},
submit() {
this.sending = true
this.$inertia.post(this.route('sites.cronjobs.store', this.site.id), this.form, {
onFinish: () => {
this.sending = false
if (!Object.keys(this.$page.props.errors).length) {
this.form = {
command: `php /home/${this.$page.props.auth.user.user_name}/${this.site.domain}/script.php`,
interval: 'minutely',
frequency: '* * * * *',
}
if (!Object.keys(this.$page.props.errors).length) {
this.form = {
command: `php /home/${this.$page.props.auth.user.user_name}/${this.site.domain}/script.php`,
interval: 'minutely',
frequency: '* * * * *',
}
}
})
},
confirmDelete(cronjob) {
useConfirm({
title: this.__('Are you sure?'),
message: this.__('Your cronjob will be deleted permanently, this action cannot be undone.'),
onConfirm: () => this.delete(cronjob),
})
},
delete(cronjob) {
this.$inertia.delete(this.route('sites.cronjobs.delete', [this.site.id, cronjob.id]), {
preserveScroll: true
})
}
}
})
},
beforeUnmount(){
this.clearPollingInterval();
confirmDelete(cronjob) {
useConfirm({
title: this.__('Are you sure?'),
message: this.__('Your cronjob will be deleted permanently, this action cannot be undone.'),
onConfirm: () => this.delete(cronjob),
})
},
delete(cronjob) {
this.$inertia.delete(this.route('sites.cronjobs.delete', [this.site.id, cronjob.id]), {
preserveScroll: true
})
}
},
beforeUnmount() {
this.clearPollingInterval();
}
}
</script>

View File

@@ -1,6 +1,7 @@
<template>
<Page>
<TopBar :breadcrumbs="breadcrumbs"/>
<Head :title="`${this.__('Databases')} - ${this.site.domain}`"></Head>
<TopBar :breadcrumbs="breadcrumbs" />
<Content>
<Container>
@@ -13,7 +14,7 @@
<PageBody>
<SettingsLayout>
<template #nav>
<Tabs :site="site"/>
<Tabs :site="site" />
</template>
<template #segments>
<SettingsSegment>
@@ -24,11 +25,11 @@
<template #form>
<form class="space-y-4" @submit.prevent="submit">
<FormInput :label="__('Name')" :errors="$page.props.errors.name"
v-model="form.name"/>
v-model="form.name" />
<FormInput :label="__('User')" :errors="$page.props.errors.user_name"
v-model="form.user_name"/>
v-model="form.user_name" />
<FormInput :label="__('Password')" :errors="$page.props.errors.password"
v-model="form.password"/>
v-model="form.password" />
<FormActions>
<Button>{{ __('Save') }}</Button>
@@ -37,7 +38,7 @@
</template>
</SettingsSegment>
<EmptyImage v-if="!databases.meta.total"/>
<EmptyImage v-if="!databases.meta.total" />
<SettingsSegment v-if="databases.meta.total">
<template #title>{{ __('Databases') }}</template>
@@ -55,7 +56,7 @@
<TableRow v-for="database in databases.data" :key="database.id">
<TableData>
<StatusBubble
:variant="database.status === 'busy' ? 'gray' : 'success'"/>
:variant="database.status === 'busy' ? 'gray' : 'success'" />
</TableData>
<TableData>{{ database.name }}</TableData>
<TableData>
@@ -70,7 +71,7 @@
</Table>
</div>
<pagination :links="databases"/>
<pagination :links="databases" />
</template>
</SettingsSegment>
</template>

View File

@@ -1,6 +1,7 @@
<template>
<Page>
<TopBar :breadcrumbs="breadcrumbs"/>
<Head :title="`${this.__('DNS')} - ${this.site.domain}`"></Head>
<TopBar :breadcrumbs="breadcrumbs" />
<Content>
<Container>
@@ -13,7 +14,7 @@
<PageBody>
<SettingsLayout>
<template #nav>
<Tabs :site="site"/>
<Tabs :site="site" />
</template>
<template #segments>
<SettingsSegment>
@@ -23,8 +24,8 @@
</template>
<template #form>
<form class="space-y-4" @submit.prevent="submit">
<FormInput :disabled="sending" :label="__('Name')" :errors="$page.props.errors.name" v-model="form.name"/>
<FormInput :disabled="sending" :label="__('IPv4 address')" :errors="$page.props.errors.address" v-model="form.address"/>
<FormInput :disabled="sending" :label="__('Name')" :errors="$page.props.errors.name" v-model="form.name" />
<FormInput :disabled="sending" :label="__('IPv4 address')" :errors="$page.props.errors.address" v-model="form.address" />
<FormActions>
<Button>{{ __('Save') }}</Button>
@@ -109,136 +110,130 @@ import TableData from '@/components/TableData.vue'
import EmptyImage from '@/components/EmptyImage.vue'
export default {
metaInfo() {
return {
title: `${this.__('DNS')} - ${this.site.domain}`,
}
},
layout: MainLayout,
layout: MainLayout,
components: {
TopBar,
Container,
Content,
Page,
PageHeader,
PageHeaderTitle,
PageBody,
Button,
List,
ListItem,
StatusBubble,
NotificationBadge,
FormInput,
SettingsLayout,
SettingsSegment,
Form,
FormActions,
Pagination,
Tabs,
Table,
TableHead,
TableHeader,
TableRow,
TableBody,
TableData,
EmptyImage,
},
components: {
TopBar,
Container,
Content,
Page,
PageHeader,
PageHeaderTitle,
PageBody,
Button,
List,
ListItem,
StatusBubble,
NotificationBadge,
FormInput,
SettingsLayout,
SettingsSegment,
Form,
FormActions,
Pagination,
Tabs,
Table,
TableHead,
TableHeader,
TableRow,
TableBody,
TableData,
EmptyImage,
},
data() {
return {
sending: false,
loading: true,
data() {
return {
sending: false,
loading: true,
records: [],
records: [],
form: {
name: null,
address: null,
},
form: {
name: null,
address: null,
breadcrumbs: [
{
title: this.$page.props.settings.name,
to: '/',
},
{
title: this.__('Sites'),
to: this.route('sites.index'),
},
{
title: this.site.domain,
to: this.route('sites.show', this.site.id),
},
{
title: this.__('DNS'),
to: this.route('sites.dns.index', this.site.id),
},
],
}
},
props: {
site: Object,
},
mounted() {
this.getRecords();
},
methods: {
useNotification,
submit() {
this.$inertia.post(this.route('sites.dns.store', this.site.id), this.form, {
onStart: () => this.sending = true,
onFinish: () => {
this.sending = false;
this.records = [];
this.getRecords();
this.form = {
name: null,
address: null,
};
}
})
breadcrumbs: [
{
title: this.$page.props.settings.name,
to: '/',
},
{
title: this.__('Sites'),
to: this.route('sites.index'),
},
{
title: this.site.domain,
to: this.route('sites.show', this.site.id),
},
{
title: this.__('DNS'),
to: this.route('sites.dns.index', this.site.id),
},
],
}
},
props: {
site: Object,
},
getRecords() {
this.loading = true;
mounted () {
this.getRecords();
},
methods: {
useNotification,
submit() {
this.$inertia.post(this.route('sites.dns.store', this.site.id), this.form, {
onStart: () => this.sending = true,
onFinish: () => {
this.sending = false;
this.records = [];
this.getRecords();
this.form = {
name: null,
address: null,
};
}
window.axios.get(this.route('sites.dns.records', this.site.id))
.then(response => {
this.loading = false;
this.records = response.data
})
},
getRecords() {
this.loading = true;
window.axios.get(this.route('sites.dns.records', this.site.id))
.then(response => {
this.loading = false;
this.records = response.data
})
.catch(error => {
this.loading = false;
})
},
confirmDelete(record) {
useConfirm({
title: this.__('Are you sure?'),
message: this.__('Your DNS will be completely removed.'),
onConfirm: () => this.delete(record),
.catch(error => {
this.loading = false;
})
},
delete(record) {
this.$inertia.delete(this.route('sites.dns.delete', [this.site.id, record.id]), {
preserveScroll: true,
onStart: () => this.sending = true,
onFinish: () => {
this.sending = false;
this.records = [];
this.getRecords();
}
})
}
},
}
confirmDelete(record) {
useConfirm({
title: this.__('Are you sure?'),
message: this.__('Your DNS will be completely removed.'),
onConfirm: () => this.delete(record),
})
},
delete(record) {
this.$inertia.delete(this.route('sites.dns.delete', [this.site.id, record.id]), {
preserveScroll: true,
onStart: () => this.sending = true,
onFinish: () => {
this.sending = false;
this.records = [];
this.getRecords();
}
})
}
},
}
</script>

View File

@@ -1,12 +1,13 @@
<template>
<Page>
<Head><title>{{ __('Sites') }}</title></Head>
<Portal to="modals" v-if="can('sites', 'create')">
<ModalContainer>
<Modal @close="closeModal" v-if="modalIsOpen" @submit="submit">
<template #title>{{ __('Create a site') }}</template>
<template #form>
<FormInput :label="__('Domain')" :errors="$page.props.errors.domain" v-model="form.domain"/>
<FormInput :label="__('Domain')" :errors="$page.props.errors.domain" v-model="form.domain" />
<FormSelect v-if="Object.keys(availableServers).length" :label="__('Select server')" v-model="form.server_id">
<option :value="`${null}`">{{ __('Select random server') }}</option>
@@ -21,7 +22,7 @@
</ModalContainer>
</Portal>
<TopBar :breadcrumbs="breadcrumbs"/>
<TopBar :breadcrumbs="breadcrumbs" />
<Content>
<Container>
@@ -40,7 +41,7 @@
<List>
<ListItem v-for="site in sites.data" :key="site.id">
<template #prefix>
<StatusBubble :variant="site.status === 'busy' ? 'gray' : 'success'"/>
<StatusBubble :variant="site.status === 'busy' ? 'gray' : 'success'" />
</template>
<template #title>
<inertia-link class="text-primary font-medium" :href="route('sites.show', site.id)">
@@ -77,7 +78,7 @@
</ListItem>
</List>
<pagination :links="sites"/>
<pagination :links="sites" />
</PageBody>
</Container>
</Content>
@@ -118,165 +119,159 @@ import {useConfirm} from '@/hooks/confirm'
import Pagination from '@/components/Pagination.vue'
export default {
metaInfo() {
return {
title: `${this.__('Sites')}`,
layout: MainLayout,
components: {
TopBar,
Container,
Content,
Page,
PageHeader,
PageHeaderTitle,
PageBody,
Button,
List,
IconButton,
IconMore,
IconPhp,
ListItem,
StatusBubble,
NotificationBadge,
EmptyImage,
IconBox,
IconGlobe,
IconStorage,
Modal,
ModalContainer,
FormInput,
FormActions,
FormSelect,
Dropdown,
DropdownList,
DropdownListItem,
DropdownListItemButton,
Pagination
},
props: {
sites: Object,
availableServers: [Array, Object]
},
computed: {
shouldBePolling() {
return !!this.sites.data.filter((site) => {
return site.status === 'busy';
}).length;
}
},
mounted() {
if (this.shouldBePolling) {
this.startPollingInterval();
}
// If it includes a create true parameter, then we open the creation modal
if (window.location.search.includes('create=')) {
this.modalIsOpen = true;
}
if (window.location.search.includes('server=')) {
let urlParams = new URLSearchParams(window.location.search);
this.form.server_id = urlParams.get('server');
}
},
watch: {
shouldBePolling: function (value) {
if (!value) {
this.clearPollingInterval();
return;
}
},
layout: MainLayout,
components: {
TopBar,
Container,
Content,
Page,
PageHeader,
PageHeaderTitle,
PageBody,
Button,
List,
IconButton,
IconMore,
IconPhp,
ListItem,
StatusBubble,
NotificationBadge,
EmptyImage,
IconBox,
IconGlobe,
IconStorage,
Modal,
ModalContainer,
FormInput,
FormActions,
FormSelect,
Dropdown,
DropdownList,
DropdownListItem,
DropdownListItemButton,
Pagination
},
props: {
sites: Object,
availableServers: [Array, Object]
},
computed: {
shouldBePolling() {
return !!this.sites.data.filter((site) => {
return site.status === 'busy';
}).length;
}
},
mounted(){
if(this.shouldBePolling){
if (!this.pollingInterval) {
this.startPollingInterval();
}
// If it includes a create true parameter, then we open the creation modal
if(window.location.search.includes('create=')){
this.modalIsOpen = true;
}
if(window.location.search.includes('server=')){
let urlParams = new URLSearchParams(window.location.search);
this.form.server_id = urlParams.get('server');
}
},
watch: {
shouldBePolling: function (value) {
if (!value) {
this.clearPollingInterval();
return;
}
if(!this.pollingInterval){
this.startPollingInterval();
}
}
},
data() {
return {
form: {
domain: null,
server_id: null,
},
pollingInterval: null,
modalIsOpen: false,
breadcrumbs: [
{
title: this.$page.props.settings.name,
to: '/',
},
{
title: this.__('Sites'),
to: this.route('sites.index'),
},
],
}
},
methods: {
startPollingInterval(){
this.pollingInterval = setInterval(function () {
this.poll();
}.bind(this), 3000);
},
clearPollingInterval(){
clearTimeout(this.pollingInterval);
this.pollingInterval = null;
},
poll() {
this.$inertia.replace(this.route('sites.index'), {
only: ['sites'],
preserveScroll: true,
})
},
closeModal() {
this.modalIsOpen = false;
this.form.domain = null;
this.$page.props.errors = [];
},
submit() {
this.$inertia.post(this.route('sites.store'), this.form, {
only: ['errors', 'flash', 'sites'],
onFinish: () => {
if (!Object.keys(this.$page.props.errors).length) {
this.form.domain = null;
this.form.server_id = null;
this.modalIsOpen = false;
}
}
});
},
confirmDelete(site) {
useConfirm({
title: this.__('Are you sure?'),
message: this.__('Your site will be deleted completely, this action is irreversible.'),
onConfirm: () => this.delete(site),
})
},
delete(site) {
this.$inertia.delete(this.route('sites.delete', site.id))
}
},
beforeUnmount(){
this.clearPollingInterval();
}
},
data() {
return {
form: {
domain: null,
server_id: null,
},
pollingInterval: null,
modalIsOpen: false,
breadcrumbs: [
{
title: this.$page.props.settings.name,
to: '/',
},
{
title: this.__('Sites'),
to: this.route('sites.index'),
},
],
}
},
methods: {
startPollingInterval() {
this.pollingInterval = setInterval(function () {
this.poll();
}.bind(this), 3000);
},
clearPollingInterval() {
clearTimeout(this.pollingInterval);
this.pollingInterval = null;
},
poll() {
this.$inertia.replace(this.route('sites.index'), {
only: ['sites'],
preserveScroll: true,
})
},
closeModal() {
this.modalIsOpen = false;
this.form.domain = null;
this.$page.props.errors = [];
},
submit() {
this.$inertia.post(this.route('sites.store'), this.form, {
only: ['errors', 'flash', 'sites'],
onFinish: () => {
if (!Object.keys(this.$page.props.errors).length) {
this.form.domain = null;
this.form.server_id = null;
this.modalIsOpen = false;
}
}
});
},
confirmDelete(site) {
useConfirm({
title: this.__('Are you sure?'),
message: this.__('Your site will be deleted completely, this action is irreversible.'),
onConfirm: () => this.delete(site),
})
},
delete(site) {
this.$inertia.delete(this.route('sites.delete', site.id))
}
},
beforeUnmount() {
this.clearPollingInterval();
}
}
</script>

View File

@@ -1,6 +1,7 @@
<template>
<Page>
<TopBar :breadcrumbs="breadcrumbs"/>
<Head :title="`${this.__('Redirects')} - ${this.site.domain}`"></Head>
<TopBar :breadcrumbs="breadcrumbs" />
<Content>
<Container>
@@ -13,7 +14,7 @@
<PageBody>
<SettingsLayout>
<template #nav>
<Tabs :site="site"/>
<Tabs :site="site" />
</template>
<template #segments>
<SettingsSegment>
@@ -23,8 +24,8 @@
</template>
<template #form>
<form class="space-y-4" @submit.prevent="submit">
<FormInput :label="__('From')" :errors="$page.props.errors.redirect_from" v-model="form.redirect_from"/>
<FormInput :label="__('To')" :errors="$page.props.errors.redirect_to" v-model="form.redirect_to"/>
<FormInput :label="__('From')" :errors="$page.props.errors.redirect_from" v-model="form.redirect_from" />
<FormInput :label="__('To')" :errors="$page.props.errors.redirect_to" v-model="form.redirect_to" />
<FormSelect label="Type" v-model="form.type">
<option value="redirect">{{ __('Temporary') }} (302)</option>
<option value="permanent">{{ __('Permanent') }} (301)</option>
@@ -54,7 +55,9 @@
</TableHead>
<TableBody>
<TableRow v-for="redirect in redirects.data" :key="redirect.id">
<TableData><StatusBubble :variant="redirect.status === 'busy' ? 'gray' : 'success'"/></TableData>
<TableData>
<StatusBubble :variant="redirect.status === 'busy' ? 'gray' : 'success'" />
</TableData>
<TableData>{{ redirect.redirect_from }}</TableData>
<TableData>{{ redirect.redirect_to }}</TableData>
<TableData>{{ redirect.type }}</TableData>
@@ -69,7 +72,7 @@
</Table>
</div>
<pagination :links="redirects"/>
<pagination :links="redirects" />
</template>
</SettingsSegment>
</template>
@@ -112,160 +115,154 @@ import TableBody from '@/components/TableBody.vue'
import TableData from '@/components/TableData.vue'
export default {
metaInfo() {
return {
title: `${this.__('Redirects')} - ${this.site.domain}`,
}
},
layout: MainLayout,
layout: MainLayout,
components: {
TopBar,
Container,
Content,
Page,
PageHeader,
PageHeaderTitle,
PageBody,
Button,
List,
ListItem,
StatusBubble,
NotificationBadge,
FormInput,
FormSelect,
SettingsLayout,
SettingsSegment,
Form,
FormActions,
Pagination,
Tabs,
Table,
TableHead,
TableHeader,
TableRow,
TableBody,
TableData,
EmptyImage,
},
components: {
TopBar,
Container,
Content,
Page,
PageHeader,
PageHeaderTitle,
PageBody,
Button,
List,
ListItem,
StatusBubble,
NotificationBadge,
FormInput,
FormSelect,
SettingsLayout,
SettingsSegment,
Form,
FormActions,
Pagination,
Tabs,
Table,
TableHead,
TableHeader,
TableRow,
TableBody,
TableData,
EmptyImage,
},
data() {
return {
sending: false,
data() {
return {
sending: false,
form: {
redirect_from: null,
redirect_to: null,
type: 'redirect',
},
form: {
redirect_from: null,
redirect_to: null,
type: 'redirect',
breadcrumbs: [
{
title: this.$page.props.settings.name,
to: '/',
},
{
title: this.__('Sites'),
to: this.route('sites.index'),
},
{
title: this.site.domain,
to: this.route('sites.show', this.site.id),
},
{
title: this.__('Redirects'),
to: this.route('sites.redirects.index', this.site.id),
},
],
}
},
breadcrumbs: [
{
title: this.$page.props.settings.name,
to: '/',
},
{
title: this.__('Sites'),
to: this.route('sites.index'),
},
{
title: this.site.domain,
to: this.route('sites.show', this.site.id),
},
{
title: this.__('Redirects'),
to: this.route('sites.redirects.index', this.site.id),
},
],
mounted() {
if (this.shouldBePolling) {
this.startPollingInterval();
}
},
watch: {
shouldBePolling: function (value) {
if (!value) {
this.clearPollingInterval();
return;
}
},
mounted() {
if(this.shouldBePolling){
if (!this.pollingInterval) {
this.startPollingInterval();
}
},
watch: {
shouldBePolling: function (value) {
if (!value) {
this.clearPollingInterval();
return;
}
if(!this.pollingInterval){
this.startPollingInterval();
}
}
},
computed: {
shouldBePolling() {
return !!this.redirects.data.filter((redirect) => {
return redirect.status === 'busy';
}).length;
}
},
props: {
site: Object,
redirects: Object,
},
methods: {
startPollingInterval(){
this.pollingInterval = setInterval(function () {
this.poll();
}.bind(this), 3000);
},
clearPollingInterval(){
clearTimeout(this.pollingInterval);
this.pollingInterval = null;
},
poll() {
this.$inertia.get(this.route('sites.redirects.index', this.site.id), {
only: ['redirects'],
preserveScroll: true,
})
},
submit() {
this.sending = true
this.$inertia.post(this.route('sites.redirects.store', this.site.id), this.form, {
onFinish: () => {
this.sending = false
if (!Object.keys(this.$page.props.errors).length) {
this.form.redirect_from = null;
this.form.redirect_to = null;
this.form.type = 'redirect';
}
}
});
},
confirmDelete(redirect) {
useConfirm({
title: this.__('Are you sure?'),
message: this.__(`Your redirect will be deleted permanently, this action cannot be undone.`),
onConfirm: () => this.delete(redirect),
})
},
delete(redirect) {
this.$inertia.delete(this.route('sites.redirects.delete', [this.site.id, redirect.id]), {
preserveScroll: true
})
}
},
beforeUnmount(){
this.clearPollingInterval();
}
},
computed: {
shouldBePolling() {
return !!this.redirects.data.filter((redirect) => {
return redirect.status === 'busy';
}).length;
}
},
props: {
site: Object,
redirects: Object,
},
methods: {
startPollingInterval() {
this.pollingInterval = setInterval(function () {
this.poll();
}.bind(this), 3000);
},
clearPollingInterval() {
clearTimeout(this.pollingInterval);
this.pollingInterval = null;
},
poll() {
this.$inertia.get(this.route('sites.redirects.index', this.site.id), {
only: ['redirects'],
preserveScroll: true,
})
},
submit() {
this.sending = true
this.$inertia.post(this.route('sites.redirects.store', this.site.id), this.form, {
onFinish: () => {
this.sending = false
if (!Object.keys(this.$page.props.errors).length) {
this.form.redirect_from = null;
this.form.redirect_to = null;
this.form.type = 'redirect';
}
}
});
},
confirmDelete(redirect) {
useConfirm({
title: this.__('Are you sure?'),
message: this.__(`Your redirect will be deleted permanently, this action cannot be undone.`),
onConfirm: () => this.delete(redirect),
})
},
delete(redirect) {
this.$inertia.delete(this.route('sites.redirects.delete', [this.site.id, redirect.id]), {
preserveScroll: true
})
}
},
beforeUnmount() {
this.clearPollingInterval();
}
}
</script>

View File

@@ -1,6 +1,7 @@
<template>
<Page>
<TopBar :breadcrumbs="breadcrumbs"/>
<Head :title="site.domain"></Head>
<TopBar :breadcrumbs="breadcrumbs" />
<Content>
<Container>
@@ -13,7 +14,7 @@
<PageBody>
<SettingsLayout>
<template #nav>
<Tabs :site="site"/>
<Tabs :site="site" />
</template>
<template #segments>
<SettingsSegment>
@@ -21,7 +22,7 @@
<template #form>
<form class="space-y-4" @submit.prevent="submit">
<FormInput :label="__('Domain')" :errors="$page.props.errors.domain"
v-model="form.domain"/>
v-model="form.domain" />
<FormActions>
<Button>{{ __('Save') }}</Button>
@@ -70,7 +71,7 @@
<template #form>
<form class="space-y-4" @submit.prevent="submit">
<FormInput label="Cloudflare zone ID" :errors="$page.props.errors.dns_id"
v-model="form.dns_id"/>
v-model="form.dns_id" />
<FormActions>
<Button>{{ __('Save') }}</Button>
@@ -116,12 +117,6 @@ import {useConfirm} from '@/hooks/confirm'
import Tabs from './Tabs.vue'
export default {
metaInfo() {
return {
title: this.site.domain,
}
},
layout: MainLayout,
components: {

View File

@@ -1,5 +1,6 @@
<template>
<Page>
<Head :title="site.domain"></Head>
<Portal to="modals">
<ModalContainer>
<Modal @close="() => closeModal()" v-if="modalIsOpen" @submit="requestFtpPassword">
@@ -182,12 +183,6 @@ import ModalContainer from '@/components/ModalContainer.vue'
import Copy from '@/components/Copy.vue'
export default {
metaInfo() {
return {
title: this.site.domain,
}
},
layout: MainLayout,
components: {

View File

@@ -1,6 +1,7 @@
<template>
<Page>
<TopBar :breadcrumbs="breadcrumbs"/>
<Head><title>{{ __('Closed support requests') }}</title></Head>
<TopBar :breadcrumbs="breadcrumbs" />
<Content>
<Container>
@@ -49,61 +50,53 @@ import FormInput from '@/components/forms/FormInput.vue'
import FormActions from '@/components/FormActions.vue'
export default {
metaInfo() {
return {
title: `${this.__('Closed support requests')}`,
}
},
layout: MainLayout,
layout: MainLayout,
components: {
TopBar,
Container,
Content,
Page,
PageHeader,
PageHeaderTitle,
PageBody,
Button,
List,
ListItem,
StatusBubble,
NotificationBadge,
IconBox,
IconGlobe,
IconStorage,
Modal,
ModalContainer,
FormInput,
FormActions
},
components: {
TopBar,
Container,
Content,
Page,
PageHeader,
PageHeaderTitle,
PageBody,
Button,
List,
ListItem,
StatusBubble,
NotificationBadge,
IconBox,
IconGlobe,
IconStorage,
Modal,
ModalContainer,
FormInput,
FormActions
},
props: {
tickets: Object,
},
props: {
tickets: Object,
},
data() {
return {
breadcrumbs: [
{
title: this.$page.props.settings.name,
to: '/',
},
{
title: this.__('Support'),
to: this.route('support.index'),
},
{
title: this.__('Closed support requests'),
to: this.route('support.index.closed'),
},
],
}
},
data() {
return {
breadcrumbs: [
{
title: this.$page.props.settings.name,
to: '/',
},
{
title: this.__('Support'),
to: this.route('support.index'),
},
{
title: this.__('Closed support requests'),
to: this.route('support.index.closed'),
},
],
}
},
methods: {
},
}
methods: {},
}
</script>

View File

@@ -1,13 +1,14 @@
<template>
<Page>
<Head><title>{{ __('Support') }}</title></Head>
<Portal to="modals">
<ModalContainer>
<Modal @close="modalIsOpen = false" v-if="modalIsOpen" @submit="submit">
<template #title>{{ __('Create support request') }}</template>
<template #form>
<FormInput :label="__('Title')" :errors="$page.props.errors.title" v-model="form.title"/>
<FormTextarea :label="__('Content')" :errors="$page.props.errors.content" v-model="form.content"/>
<FormInput :label="__('Title')" :errors="$page.props.errors.title" v-model="form.title" />
<FormTextarea :label="__('Content')" :errors="$page.props.errors.content" v-model="form.content" />
</template>
<template #form-actions>
@@ -17,7 +18,7 @@
</ModalContainer>
</Portal>
<TopBar :breadcrumbs="breadcrumbs"/>
<TopBar :breadcrumbs="breadcrumbs" />
<Content>
<Container>
@@ -72,80 +73,74 @@ import FormActions from '@/components/FormActions.vue'
import EmptyImage from '@/components/EmptyImage.vue'
export default {
metaInfo() {
return {
title: `${this.__('Support')}`,
}
},
layout: MainLayout,
layout: MainLayout,
components: {
TopBar,
Container,
Content,
Page,
PageHeader,
PageHeaderTitle,
PageBody,
Button,
List,
ListItem,
StatusBubble,
NotificationBadge,
IconBox,
IconGlobe,
IconStorage,
Modal,
ModalContainer,
FormInput,
FormTextarea,
FormActions,
EmptyImage
},
components: {
TopBar,
Container,
Content,
Page,
PageHeader,
PageHeaderTitle,
PageBody,
Button,
List,
ListItem,
StatusBubble,
NotificationBadge,
IconBox,
IconGlobe,
IconStorage,
Modal,
ModalContainer,
FormInput,
FormTextarea,
FormActions,
EmptyImage
},
props: {
tickets: Object,
},
props: {
tickets: Object,
},
data() {
return {
loading: false,
modalIsOpen: false,
data() {
return {
loading: false,
modalIsOpen: false,
form: {
title: null,
content: null
},
form: {
title: null,
content: null
breadcrumbs: [
{
title: this.$page.props.settings.name,
to: '/',
},
{
title: this.__('Support'),
to: this.route('support.index'),
},
],
}
},
breadcrumbs: [
{
title: this.$page.props.settings.name,
to: '/',
},
{
title: this.__('Support'),
to: this.route('support.index'),
},
],
}
},
methods: {
submit() {
this.loading = true;
methods: {
submit() {
this.loading = true;
this.$inertia.post(this.route('support.store'), this.form, {
onFinish: () => {
if (!Object.keys(this.$page.props.errors).length) {
this.form.title = null;
this.form.content = null;
this.loading = false;
this.modalIsOpen = false;
}
this.$inertia.post(this.route('support.store'), this.form, {
onFinish: () => {
if (!Object.keys(this.$page.props.errors).length) {
this.form.title = null;
this.form.content = null;
this.loading = false;
this.modalIsOpen = false;
}
})
}
},
}
}
})
}
},
}
</script>

View File

@@ -1,5 +1,6 @@
<template>
<Page>
<Head><title>{{ __('Support') }}</title></Head>
<TopBar :breadcrumbs="breadcrumbs" />
<Content>
@@ -78,12 +79,6 @@ import FormTextarea from '@/components/forms/FormTextarea.vue'
import FormActions from '@/components/FormActions.vue'
export default {
metaInfo() {
return {
title: `${this.__('Support')}`,
}
},
layout: MainLayout,
components: {

10
resources/js/app.js vendored
View File

@@ -1,6 +1,6 @@
import {createInertiaApp, InertiaLink} from '@inertiajs/inertia-vue3'
import {createInertiaApp, Head, InertiaLink} from '@inertiajs/inertia-vue3'
import {resolvePageComponent} from "laravel-vite-plugin/inertia-helpers";
import {createApp, h} from 'vue';
import Vue, {createApp, h} from 'vue';
import {InertiaProgress} from '@inertiajs/progress'
import Store from '@/store';
import PortalVue from 'portal-vue'
@@ -11,12 +11,11 @@ import axios from 'axios';
import forEach from 'lodash/forEach';
import mitt from 'mitt';
import '../sass/app.scss';
import Vue from 'vue'
Vue.configureCompat({ RENDER_FUNCTION: false, COMPONENT_V_MODEL: false });
Vue.configureCompat({RENDER_FUNCTION: false, COMPONENT_V_MODEL: false});
createInertiaApp({
title: (title) => `${title} - ${appName}`,
title: (title) => `${title}`,
resolve: (name) => resolvePageComponent(`./Pages/${name}.vue`, import.meta.glob('./Pages/**/*.vue')),
setup({el, App, props, plugin}) {
createApp({
@@ -30,6 +29,7 @@ createInertiaApp({
.mixin({methods: {route: window.route}})
.mixin(mixins)
.component('InertiaLink', InertiaLink)
.component('Head', Head)
.mount(el);
},
});

View File

@@ -4,8 +4,6 @@
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{{ setting('name') }}</title>
<link href="https://fonts.googleapis.com/css?family=Inter&display=swap" rel="stylesheet">
@vite('resources/js/app.js')
@@ -26,6 +24,7 @@
{!! view('header')->render() !!}
@endif
@inertiaHead
@routes
</head>
<body>