Compare commits

...

8 Commits
1.8 ... 1.8.2

Author SHA1 Message Date
Dennis
aa20c8a42b prod mix 2021-09-16 09:51:17 +02:00
Dennis
0f100d6159 Merge branch 'develop'
# Conflicts:
#	public/css/app.css
#	public/js/app.js
2021-09-16 09:50:07 +02:00
Dennis
7c896243a5 wip 2021-09-16 09:49:53 +02:00
Dennis
954fef7c3e Dynamic servers tab, fix for domain uppercase 2021-09-16 09:19:41 +02:00
Dennis
105126e498 prod mix 2021-08-25 09:20:18 +02:00
Dennis
7aa5a8949d Merge branch 'develop'
# Conflicts:
#	public/js/app.js
2021-08-25 09:19:45 +02:00
Dennis
27079928a7 added demo quick-login support 2021-08-25 09:19:10 +02:00
Dennis
99968e57ec wip 2021-08-17 13:00:47 +02:00
32 changed files with 335 additions and 187 deletions

View File

@@ -12,8 +12,8 @@ class ServiceController extends Controller
public function index()
{
return inertia('Admin/Services/Index', [
'servers' => Server::withCount('sites')->latest()->paginate(5, ['*'], 'servers_per_page'),
'sites' => Site::with('server:id,name')->latest()->paginate(5, ['*'], 'sites_per_page'),
'servers' => Server::query()->withCount('sites', 'users')->latest()->paginate(5, ['*'], 'servers_per_page'),
'sites' => Site::with('server:id,name')->withCount('users')->latest()->paginate(5, ['*'], 'sites_per_page'),
'providers' => Provider::query()
->withCount('regions', 'plans', 'servers')
->latest()

View File

@@ -37,4 +37,21 @@ class ProfileController extends Controller
return $mode;
}
public function destroy(Request $request)
{
/* @var $user \App\Models\User */
$user = $request->user();
$user->sites()->detach();
$user->servers()->detach();
$user->supportTicketReplies()->delete();
$user->supportTickets()->delete();
$user->delete();
auth()->logout();
return redirect()->route('login');
}
}

View File

@@ -68,6 +68,7 @@ class Kernel extends HttpKernel
'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class,
'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class,
'can' => \Illuminate\Auth\Middleware\Authorize::class,
'has.access' => \App\Http\Middleware\HasAccessToThisGroup::class,
'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
'global.api.authenticated' => \App\Http\Middleware\GlobalApiAuthenticated::class,
'password.confirm' => \Illuminate\Auth\Middleware\RequirePassword::class,

View File

@@ -10,7 +10,7 @@ class Demo
protected $safeRoutes = [
'login',
'logout',
//'profile/toggle-theme'
'profile/toggle-theme'
];
protected $allowedIps = [

View File

@@ -0,0 +1,27 @@
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Arr;
class HasAccessToThisGroup
{
public function handle(Request $request, Closure $next, $group)
{
if ($group === 'servers') {
$package = $request->user()->package ?? [];
if (
!Arr::get($package->server_permissions, 'create', false) &&
!Arr::get($package->server_permissions, 'update', false) &&
!Arr::get($package->server_permissions, 'delete', false)
) {
abort(404);
}
}
return $next($request);
}
}

View File

@@ -5,6 +5,9 @@ namespace App\Models;
use DateTimeInterface;
use Illuminate\Database\Eloquent\Model;
/**
* @property mixed|string domain
*/
class Site extends Model
{
const STATUS_BUSY = 'busy';
@@ -95,6 +98,10 @@ class Site extends Model
public static function booted()
{
static::creating(function (self $site) {
$site->domain = strtolower($site->domain);
});
static::created(function (self $site) {
$site->systemUsers()->create();
});

2
public/css/app.css vendored

File diff suppressed because one or more lines are too long

2
public/js/1603.js vendored

File diff suppressed because one or more lines are too long

2
public/js/2502.js vendored

File diff suppressed because one or more lines are too long

2
public/js/277.js vendored

File diff suppressed because one or more lines are too long

1
public/js/3481.js vendored Normal file

File diff suppressed because one or more lines are too long

1
public/js/3674.js vendored Normal file

File diff suppressed because one or more lines are too long

2
public/js/3686.js vendored

File diff suppressed because one or more lines are too long

2
public/js/4482.js vendored

File diff suppressed because one or more lines are too long

1
public/js/5354.js vendored Normal file

File diff suppressed because one or more lines are too long

1
public/js/614.js vendored Normal file

File diff suppressed because one or more lines are too long

1
public/js/7363.js vendored Normal file

File diff suppressed because one or more lines are too long

2
public/js/7366.js vendored

File diff suppressed because one or more lines are too long

2
public/js/7790.js vendored

File diff suppressed because one or more lines are too long

2
public/js/7982.js vendored

File diff suppressed because one or more lines are too long

2
public/js/8968.js vendored

File diff suppressed because one or more lines are too long

2
public/js/9053.js vendored

File diff suppressed because one or more lines are too long

1
public/js/9821.js vendored Normal file

File diff suppressed because one or more lines are too long

2
public/js/app.js vendored

File diff suppressed because one or more lines are too long

View File

@@ -25,6 +25,7 @@
<TableRow>
<TableHeader>{{ __('Name') }}</TableHeader>
<TableHeader>{{ __('Usage') }}</TableHeader>
<TableHeader>{{ __('Users') }}</TableHeader>
<TableHeader></TableHeader>
</TableRow>
</TableHead>
@@ -40,6 +41,7 @@
</Button>
</TableData>
<TableData>{{ server.sites_count }}/{{ server.maximum_sites }}</TableData>
<TableData>{{ server.users_count }}</TableData>
<TableData>
<inertia-link :href="route('admin.services.servers.edit', server.id)" class="text-primary font-medium">
{{ __('Edit') }}
@@ -63,6 +65,7 @@
<TableRow>
<TableHeader>{{ __('Name') }}</TableHeader>
<TableHeader>{{ __('Server') }}</TableHeader>
<TableHeader>{{ __('Users') }}</TableHeader>
<TableHeader></TableHeader>
</TableRow>
</TableHead>
@@ -74,6 +77,9 @@
<TableData>
{{ site.server ? site.server.name : '-' }}
</TableData>
<TableData>
{{ site.users_count }}
</TableData>
<TableData>
<inertia-link :href="route('admin.services.sites.edit', site.id)" class="text-primary font-medium">
{{ __('Edit') }}

View File

@@ -80,6 +80,14 @@ export default {
},
}
},
mounted() {
if (window.location.search.includes('demo=')) {
this.form.email = 'demo@ploi-core.io';
this.form.password = 'secret';
}
},
methods: {
submit() {
this.$inertia.post(this.route('login'), {

View File

@@ -1,6 +1,6 @@
<template>
<Page>
<TopBar :breadcrumbs="breadcrumbs" />
<TopBar :breadcrumbs="breadcrumbs"/>
<Content>
<Container>
@@ -12,7 +12,7 @@
<PageBody>
<div>
<ul class="grid grid-cols-1 md:grid-cols-3 gap-4">
<ul class="grid grid-cols-1 gap-4" :class="{'md:grid-cols-3': hasAccessToServers, 'md:grid-cols-2': !hasAccessToServers}">
<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>
@@ -24,7 +24,7 @@
</div>
</inertia-link>
</li>
<li class="p-6 rounded shadow bg-surface-3 transform transition duration-fast ease-in-out hover:scale-95">
<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"/>
@@ -36,7 +36,9 @@
</inertia-link>
</li>
<li class="p-6 rounded shadow bg-surface-3 transform transition duration-fast ease-in-out hover:scale-95">
<inertia-link :href="$page.props.settings.billing ? route('profile.billing.index') : route('profile.index')" class="flex space-x-4">
<inertia-link
:href="$page.props.settings.billing ? route('profile.billing.index') : route('profile.index')"
class="flex space-x-4">
<div>
<IconBox class="w-6 h-6"/>
</div>
@@ -55,7 +57,8 @@
{{ log.title }}
</template>
<template #subtitle>{{ log.description }}</template>
<template #suffix><span class="text-medium-emphasis">{{ log.created_at_human }}</span></template>
<template #suffix><span class="text-medium-emphasis">{{ log.created_at_human }}</span>
</template>
</ListItem>
</List>
</PageBody>
@@ -65,86 +68,92 @@
</template>
<script>
import TopBar from './components/TopBar'
import Container from '@/components/Container'
import Content from '@/components/Content'
import Page from '@/components/Page'
import PageHeader from '@/components/PageHeader'
import PageHeaderTitle from '@/components/PageHeaderTitle'
import PageBody from '@/components/PageBody'
import Button from '@/components/Button'
import List from '@/components/List'
import ListItem from '@/components/ListItem'
import StatusBubble from '@/components/StatusBubble'
import NotificationBadge from '@/components/NotificationBadge'
import MainLayout from '@/Layouts/MainLayout'
import IconBox from '@/components/icons/IconBox'
import IconGlobe from '@/components/icons/IconGlobe'
import IconStorage from '@/components/icons/IconStorage'
import TopBar from './components/TopBar'
import Container from '@/components/Container'
import Content from '@/components/Content'
import Page from '@/components/Page'
import PageHeader from '@/components/PageHeader'
import PageHeaderTitle from '@/components/PageHeaderTitle'
import PageBody from '@/components/PageBody'
import Button from '@/components/Button'
import List from '@/components/List'
import ListItem from '@/components/ListItem'
import StatusBubble from '@/components/StatusBubble'
import NotificationBadge from '@/components/NotificationBadge'
import MainLayout from '@/Layouts/MainLayout'
import IconBox from '@/components/icons/IconBox'
import IconGlobe from '@/components/icons/IconGlobe'
import IconStorage from '@/components/icons/IconStorage'
import { useNotification } from '@/hooks/notification'
import {useNotification} from '@/hooks/notification'
export default {
metaInfo() {
return {
title: `${this.__('Dashboard')}`,
}
},
export default {
metaInfo() {
return {
title: `${this.__('Dashboard')}`,
}
},
layout: MainLayout,
layout: MainLayout,
components: {
TopBar,
Container,
Content,
Page,
PageHeader,
PageHeaderTitle,
PageBody,
Button,
List,
ListItem,
StatusBubble,
NotificationBadge,
IconBox,
IconGlobe,
IconStorage,
},
components: {
TopBar,
Container,
Content,
Page,
PageHeader,
PageHeaderTitle,
PageBody,
Button,
List,
ListItem,
StatusBubble,
NotificationBadge,
IconBox,
IconGlobe,
IconStorage,
},
props: {
sites: Number,
servers: Number,
package: Object,
logs: Array,
},
props: {
sites: Number,
servers: Number,
package: Object,
logs: Array,
},
methods: {
useNotification
},
methods: {
useNotification
},
data () {
return{
breadcrumbs: [
{
title: this.$page.props.settings.name,
to: '/',
},
{
title: 'Dashboard',
to: '/',
},
],
}
},
data() {
return {
breadcrumbs: [
{
title: this.$page.props.settings.name,
to: '/',
},
{
title: 'Dashboard',
to: '/',
},
],
}
},
mounted() {
if (this.$page.props.flash.info) {
useNotification({
variant: 'info',
title: `Information`,
message: this.$page.props.flash.info,
})
}
},
}
computed: {
hasAccessToServers () {
return (this.$page.props.auth.can.servers.create || this.$page.props.auth.can.servers.update || this.$page.props.auth.can.servers.delete);
}
},
mounted() {
if (this.$page.props.flash.info) {
useNotification({
variant: 'info',
title: `Information`,
message: this.$page.props.flash.info,
})
}
},
}
</script>

View File

@@ -29,6 +29,14 @@ export default {
breadcrumbs: Array,
},
computed: {
hasServerAccess() {
return this.$page.props.auth.can.servers.create ||
this.$page.props.auth.can.servers.delete ||
this.$page.props.auth.can.servers.update
}
},
data() {
return {
tabBars: [
@@ -41,10 +49,10 @@ export default {
title: this.__('Sites'),
to: this.route('sites.index'),
},
{
(this.$page.props.auth.can.servers.create || this.$page.props.auth.can.servers.update || this.$page.props.auth.can.servers.delete) ? {
title: this.__('Servers'),
to: this.route('servers.index'),
},
} : null,
],
}
},

View File

@@ -15,9 +15,13 @@
<div>
<input id="keyboard_shortcuts" class="form-checkbox" type="checkbox"
v-model="form.keyboard_shortcuts">
<label for="keyboard_shortcuts" class="ml-2 text-sm">{{ __('Enable keyboard shortcuts') }}</label>
<label for="keyboard_shortcuts" class="ml-2 text-sm">{{
__('Enable keyboard shortcuts')
}}</label>
<p class="text-small mt-1 text-medium-emphasis">
{{ __('This will allow you to do keyboard shortcuts for navigation, which is visible when you press "/"') }}
{{
__('This will allow you to do keyboard shortcuts for navigation, which is visible when you press "/"')
}}
</p>
</div>
@@ -26,101 +30,124 @@
</FormActions>
</form>
</PageBody>
<div class="border-t border-low-emphasis"></div>
<PageBody>
<form class="space-y-4" @submit.prevent="deleteAccount">
<p>
You can remove your account here. This will remove all data of your account.
</p>
<FormActions>
<Button variant="danger">{{ __('Save changes') }}</Button>
</FormActions>
</form>
</PageBody>
</Container>
</Content>
</Page>
</template>
<script>
import TopBar from './components/TopBar'
import Container from '@/components/Container'
import Content from '@/components/Content'
import Page from '@/components/Page'
import PageHeader from '@/components/PageHeader'
import PageHeaderTitle from '@/components/PageHeaderTitle'
import PageBody from '@/components/PageBody'
import Button from '@/components/Button'
import List from '@/components/List'
import ListItem from '@/components/ListItem'
import StatusBubble from '@/components/StatusBubble'
import NotificationBadge from '@/components/NotificationBadge'
import MainLayout from '@/Layouts/MainLayout'
import IconBox from '@/components/icons/IconBox'
import IconGlobe from '@/components/icons/IconGlobe'
import IconStorage from '@/components/icons/IconStorage'
import Modal from '@/components/Modal'
import ModalContainer from '@/components/ModalContainer'
import FormInput from '@/components/forms/FormInput'
import FormSelect from '@/components/forms/FormSelect'
import FormActions from '@/components/FormActions'
import TopBar from './components/TopBar'
import Container from '@/components/Container'
import Content from '@/components/Content'
import Page from '@/components/Page'
import PageHeader from '@/components/PageHeader'
import PageHeaderTitle from '@/components/PageHeaderTitle'
import PageBody from '@/components/PageBody'
import Button from '@/components/Button'
import List from '@/components/List'
import ListItem from '@/components/ListItem'
import StatusBubble from '@/components/StatusBubble'
import NotificationBadge from '@/components/NotificationBadge'
import MainLayout from '@/Layouts/MainLayout'
import IconBox from '@/components/icons/IconBox'
import IconGlobe from '@/components/icons/IconGlobe'
import IconStorage from '@/components/icons/IconStorage'
import Modal from '@/components/Modal'
import ModalContainer from '@/components/ModalContainer'
import FormInput from '@/components/forms/FormInput'
import FormSelect from '@/components/forms/FormSelect'
import FormActions from '@/components/FormActions'
import {useConfirmDelete} from '@/hooks/confirm-delete'
export default {
metaInfo() {
return {
title: `${this.__('Settings')}`,
}
},
export default {
metaInfo() {
return {
title: `${this.__('Settings')}`,
}
},
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,
},
props: {
profile: Object,
},
data() {
return {
form: {
theme: this.profile.theme,
keyboard_shortcuts: this.profile.keyboard_shortcuts
data() {
return {
form: {
theme: this.profile.theme,
keyboard_shortcuts: this.profile.keyboard_shortcuts
},
breadcrumbs: [
{
title: this.$page.props.settings.name,
to: '/',
},
{
title: this.__('Profile'),
to: this.route('profile.index'),
},
{
title: this.__('Settings'),
to: this.route('profile.settings.index'),
},
],
}
},
breadcrumbs: [
{
title: this.$page.props.settings.name,
to: '/',
},
{
title: this.__('Profile'),
to: this.route('profile.index'),
},
{
title: this.__('Settings'),
to: this.route('profile.settings.index'),
},
],
}
methods: {
submit() {
this.$inertia.patch(this.route('profile.settings.update'), this.form, {
onStart: () => this.sending = true,
onFinish: () => this.sending = false,
});
},
methods: {
submit() {
this.$inertia.patch(this.route('profile.settings.update'), this.form, {
onStart: () => this.sending = true,
onFinish: () => this.sending = false,
});
}
},
}
deleteAccount() {
useConfirmDelete({
title: this.__('Are you sure?'),
message: `Are you sure you want to remove your account? All data will be deleted and services will be detached.`,
onConfirm: () => {
this.$inertia.delete(this.route('profile.delete-account'));
}
})
}
},
}
</script>

View File

@@ -42,10 +42,10 @@ export default {
to: this.route('sites.index'),
active: this.route().current('sites.*')
},
{
title: 'Servers',
(this.$page.props.auth.can.servers.create || this.$page.props.auth.can.servers.update || this.$page.props.auth.can.servers.delete) ? {
title: this.__('Servers'),
to: this.route('servers.index'),
},
} : null,
],
}
},

View File

@@ -5,8 +5,6 @@
@tailwind utilities;
@import 'nprogress';
//$balloon-bg: var(--color-surface-1);
@import "~balloon-css/src/balloon";
.bf-blur {
@@ -29,3 +27,36 @@
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
.theme--dark {
.prose {
color: var(--text-medium-emphasis);
}
.vue-simplemde {
.editor-toolbar {
border-left: 1px solid var(--color-surface-3);
border-top: 1px solid var(--color-surface-3);
border-right: 1px solid var(--color-surface-3);
.separator {
border-left: 1px solid var(--color-surface-3);
border-right: transparent;
}
a {
color: var(--text-medium-emphasis) !important;
}
a:hover {
background: var(--color-surface-3);
}
}
.CodeMirror {
background: var(--color-surface-1);
color: var(--text-medium-emphasis);
border: 1px solid var(--color-surface-3);
}
}
}

View File

@@ -75,7 +75,7 @@ Route::group(['middleware' => ['auth', 'auth.blocked']], function () {
});
// Server routes
Route::group(['prefix' => 'servers', 'as' => 'servers.'], function () {
Route::group(['prefix' => 'servers', 'as' => 'servers.', 'middleware' => 'has.access:servers'], function () {
Route::get('/', 'ServerController@index')->name('index');
Route::get('{provider}/plans-and-regions', 'ServerController@plansAndRegions')->name('plans-and-regions');
Route::get('{server}', 'ServerController@show')->name('show');
@@ -92,6 +92,7 @@ Route::group(['middleware' => ['auth', 'auth.blocked']], function () {
Route::group(['prefix' => 'profile', 'as' => 'profile.', 'namespace' => 'Profile'], function () {
Route::get('/', 'ProfileController@index')->name('index');
Route::patch('/', 'ProfileController@update')->name('update');
Route::delete('destroy', 'ProfileController@destroy')->name('delete-account');
// Security
Route::group(['prefix' => 'security', 'as' => 'security.'], function () {