Push 'Account' and 'Security' pages as well as 'My Servers'

This commit is contained in:
Dane Everitt 2017-01-15 14:09:57 -05:00
parent 1c85b1fbc4
commit 2fc852c6a4
No known key found for this signature in database
GPG key ID: EEA66103B3D71F53
19 changed files with 1068 additions and 3 deletions

View file

@ -79,7 +79,9 @@ class SecurityController extends Controller
public function setTotp(Request $request)
{
if (! $request->has('token')) {
return response(null, 500);
return response()->json([
'error' => 'Request is missing token parameter.'
], 500);
}
$user = $request->user();
@ -101,7 +103,7 @@ class SecurityController extends Controller
if (! $request->has('token')) {
Alert::danger('Missing required `token` field in request.')->flash();
return redirect()->route('account.totp');
return redirect()->route('account.security');
}
$user = $request->user();

View file

@ -64,9 +64,11 @@ class BaseRoutes
'uses' => 'Base\AccountController@index',
]);
$router->post('/password', [
'as' => 'account.password',
'uses' => 'Base\AccountController@password',
]);
$router->post('/email', [
'as' => 'account.email',
'uses' => 'Base\AccountController@email',
]);
});
@ -113,6 +115,7 @@ class BaseRoutes
'uses' => 'Base\SecurityController@revoke',
]);
$router->put('/totp', [
'as' => 'account.security.totp',
'uses' => 'Base\SecurityController@generateTotp',
]);
$router->post('/totp', [

View file

@ -153,6 +153,7 @@ class ServerRoutes
$router->group(['prefix' => 'ajax'], function ($server) use ($router) {
// Returns Server Status
$router->get('status', [
'as' => 'server.ajax.status',
'uses' => 'Server\AjaxController@getStatus',
]);

View file

@ -27,7 +27,8 @@
"dingo/api": "1.0.0-beta6",
"aws/aws-sdk-php": "3.19.20",
"predis/predis": "1.1.1",
"laracasts/utilities": "2.1.0"
"laracasts/utilities": "2.1.0",
"lord/laroute": "^2.3"
},
"require-dev": {
"fzaninotto/faker": "~1.4",

View file

@ -159,6 +159,7 @@ return [
Prologue\Alerts\AlertsServiceProvider::class,
Krucas\Settings\Providers\SettingsServiceProvider::class,
Laracasts\Utilities\JavaScript\JavaScriptServiceProvider::class,
Lord\Laroute\LarouteServiceProvider::class,
],

58
config/laroute.php Normal file
View file

@ -0,0 +1,58 @@
<?php
return [
/*
* The destination path for the javascript file.
*/
'path' => 'public/js',
/*
* The destination filename for the javascript file.
*/
'filename' => 'laroute',
/*
* The namespace for the helper functions. By default this will bind them to
* `window.laroute`.
*/
'namespace' => 'Router',
/*
* Generate absolute URLs
*
* Set the Application URL in config/app.php
*/
'absolute' => false,
/*
* The Filter Method
*
* 'all' => All routes except "'laroute' => false"
* 'only' => Only "'laroute' => true" routes
* 'force' => All routes, ignored "laroute" route parameter
*/
'filter' => 'all',
/*
* Controller Namespace
*
* Set here your controller namespace (see RouteServiceProvider -> $namespace) for cleaner action calls
* e.g. 'App\Http\Controllers'
*/
'action_namespace' => '',
/*
* The path to the template `laroute.js` file. This is the file that contains
* the ported helper Laravel url/route functions and the route data to go
* with them.
*/
'template' => 'vendor/lord/laroute/src/templates/laroute.js',
/*
* Appends a prefix to URLs. By default the prefix is an empty string.
*
*/
'prefix' => '',
];

186
public/js/laroute.js Normal file

File diff suppressed because one or more lines are too long

View file

@ -36,3 +36,12 @@
.btn-clear {
background: transparent;
}
.user-panel > .info {
position: relative;
left: 0;
}
code {
font-size: 85%;
}

View file

@ -0,0 +1,76 @@
// Copyright (c) 2015 - 2016 Dane Everitt <dane@daneeveritt.com>
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
$(document).ready(function () {
$('#close_reload').click(function () {
location.reload();
});
$('#do_2fa').submit(function (event) {
event.preventDefault();
$.ajax({
type: 'PUT',
url: Router.route('account.security.totp'),
headers: {
'X-CSRF-TOKEN': $('meta[name="_token"]').attr('content'),
}
}).done(function (data) {
var image = new Image();
image.src = data.qrImage;
$(image).load(function () {
$('#hide_img_load').slideUp(function () {
$('#qr_image_insert').attr('src', image.src).slideDown();
});
});
$('#2fa_secret_insert').html(data.secret);
$('#open2fa').modal('show');
}).fail(function (jqXHR) {
alert('An error occured while attempting to load the 2FA setup modal. Please try again.');
console.error(jqXHR);
});
});
$('#2fa_token_verify').submit(function (event) {
event.preventDefault();
$('#submit_action').html('<i class="fa fa-spinner fa-spin"></i> Submit').addClass('disabled');
$.ajax({
type: 'POST',
url: Router.route('account.security.totp'),
headers: {
'X-CSRF-TOKEN': $('meta[name="_token"]').attr('content'),
},
data: {
token: $('#2fa_token').val()
}
}).done(function (data) {
$('#notice_box_2fa').hide();
if (data === 'true') {
$('#notice_box_2fa').html('<div class="alert alert-success">2-Factor Authentication has been enabled on your account. Press \'Close\' below to reload the page.</div>').slideDown();
} else {
$('#notice_box_2fa').html('<div class="alert alert-danger">The token provided was invalid.</div>').slideDown();
}
}).fail(function (jqXHR) {
$('#notice_box_2fa').html('<div class="alert alert-danger">There was an error while attempting to enable 2-Factor Authentication on this account.</div>').slideDown();
console.error(jqXHR);
}).always(function () {
$('#submit_action').html('Submit').removeClass('disabled');
});
});
});

View file

@ -0,0 +1,75 @@
// Copyright (c) 2015 - 2016 Dane Everitt <dane@daneeveritt.com>
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
var Status = {
0: 'Offline',
1: 'Online',
2: 'Starting',
3: 'Stopping'
};
(function updateServerStatus () {
$('.dynamic-update').each(function (index, data) {
var element = $(this);
var serverShortUUID = $(this).data('server');
$.ajax({
type: 'GET',
url: Router.route('server.ajax.status', { server: serverShortUUID }),
timeout: 5000,
headers: {
'X-CSRF-TOKEN': $('meta[name="_token"]').attr('content'),
}
}).done(function (data) {
if (typeof data.status === 'undefined') {
element.find('[data-action="status"]').html('<span class="label label-default">Error</span>');
return;
}
switch (data.status) {
case 0:
element.find('[data-action="status"]').html('<span class="label label-danger">Offline</span>');
break;
case 1:
element.find('[data-action="status"]').html('<span class="label label-success">Online</span>');
break;
case 2:
element.find('[data-action="status"]').html('<span class="label label-info">Starting</span>');
break;
case 3:
element.find('[data-action="status"]').html('<span class="label label-info">Stopping</span>');
break;
}
if (data.status !== 0) {
var cpuMax = element.find('[data-action="cpu"]').data('cpumax');
var currentCpu = data.proc.cpu.total;
if (cpuMax !== 0) {
currentCpu = parseFloat(((data.proc.cpu.total / cpuMax) * 100).toFixed(2).toString());
}
element.find('[data-action="memory"]').html(parseInt(data.proc.memory.total / (1024 * 1024)));
element.find('[data-action="cpu"]').html(currentCpu);
} else {
element.find('[data-action="memory"]').html('--');
element.find('[data-action="cpu"]').html('--');
}
}).fail(function (jqXHR) {
console.error(jqXHR);
element.find('[data-action="status"]').html('<span class="label label-default">Error</span>');
});
});
setTimeout(updateServerStatus, 10000);
})();

View file

@ -0,0 +1 @@
.skin-blue-light .main-header .navbar{background-color:#3c8dbc}.skin-blue-light .main-header .navbar .nav>li>a{color:#fff}.skin-blue-light .main-header .navbar .nav>li>a:hover,.skin-blue-light .main-header .navbar .nav>li>a:active,.skin-blue-light .main-header .navbar .nav>li>a:focus,.skin-blue-light .main-header .navbar .nav .open>a,.skin-blue-light .main-header .navbar .nav .open>a:hover,.skin-blue-light .main-header .navbar .nav .open>a:focus,.skin-blue-light .main-header .navbar .nav>.active>a{background:rgba(0,0,0,0.1);color:#f6f6f6}.skin-blue-light .main-header .navbar .sidebar-toggle{color:#fff}.skin-blue-light .main-header .navbar .sidebar-toggle:hover{color:#f6f6f6;background:rgba(0,0,0,0.1)}.skin-blue-light .main-header .navbar .sidebar-toggle{color:#fff}.skin-blue-light .main-header .navbar .sidebar-toggle:hover{background-color:#367fa9}@media (max-width:767px){.skin-blue-light .main-header .navbar .dropdown-menu li.divider{background-color:rgba(255,255,255,0.1)}.skin-blue-light .main-header .navbar .dropdown-menu li a{color:#fff}.skin-blue-light .main-header .navbar .dropdown-menu li a:hover{background:#367fa9}}.skin-blue-light .main-header .logo{background-color:#3c8dbc;color:#fff;border-bottom:0 solid transparent}.skin-blue-light .main-header .logo:hover{background-color:#3b8ab8}.skin-blue-light .main-header li.user-header{background-color:#3c8dbc}.skin-blue-light .content-header{background:transparent}.skin-blue-light .wrapper,.skin-blue-light .main-sidebar,.skin-blue-light .left-side{background-color:#f9fafc}.skin-blue-light .content-wrapper,.skin-blue-light .main-footer{border-left:1px solid #d2d6de}.skin-blue-light .user-panel>.info,.skin-blue-light .user-panel>.info>a{color:#444}.skin-blue-light .sidebar-menu>li{-webkit-transition:border-left-color .3s ease;-o-transition:border-left-color .3s ease;transition:border-left-color .3s ease}.skin-blue-light .sidebar-menu>li.header{color:#848484;background:#f9fafc}.skin-blue-light .sidebar-menu>li>a{border-left:3px solid transparent;font-weight:600}.skin-blue-light .sidebar-menu>li:hover>a,.skin-blue-light .sidebar-menu>li.active>a{color:#000;background:#f4f4f5}.skin-blue-light .sidebar-menu>li.active{border-left-color:#3c8dbc}.skin-blue-light .sidebar-menu>li.active>a{font-weight:600}.skin-blue-light .sidebar-menu>li>.treeview-menu{background:#f4f4f5}.skin-blue-light .sidebar a{color:#444}.skin-blue-light .sidebar a:hover{text-decoration:none}.skin-blue-light .treeview-menu>li>a{color:#777}.skin-blue-light .treeview-menu>li.active>a,.skin-blue-light .treeview-menu>li>a:hover{color:#000}.skin-blue-light .treeview-menu>li.active>a{font-weight:600}.skin-blue-light .sidebar-form{border-radius:3px;border:1px solid #d2d6de;margin:10px 10px}.skin-blue-light .sidebar-form input[type="text"],.skin-blue-light .sidebar-form .btn{box-shadow:none;background-color:#fff;border:1px solid transparent;height:35px}.skin-blue-light .sidebar-form input[type="text"]{color:#666;border-top-left-radius:2px;border-top-right-radius:0;border-bottom-right-radius:0;border-bottom-left-radius:2px}.skin-blue-light .sidebar-form input[type="text"]:focus,.skin-blue-light .sidebar-form input[type="text"]:focus+.input-group-btn .btn{background-color:#fff;color:#666}.skin-blue-light .sidebar-form input[type="text"]:focus+.input-group-btn .btn{border-left-color:#fff}.skin-blue-light .sidebar-form .btn{color:#999;border-top-left-radius:0;border-top-right-radius:2px;border-bottom-right-radius:2px;border-bottom-left-radius:0}@media (min-width:768px){.skin-blue-light.sidebar-mini.sidebar-collapse .sidebar-menu>li>.treeview-menu{border-left:1px solid #d2d6de}}.skin-blue-light .main-footer{border-top-color:#d2d6de}.skin-blue.layout-top-nav .main-header>.logo{background-color:#3c8dbc;color:#fff;border-bottom:0 solid transparent}.skin-blue.layout-top-nav .main-header>.logo:hover{background-color:#3b8ab8}

View file

@ -0,0 +1 @@
.skin-blue .main-header .navbar{background-color:#3c8dbc}.skin-blue .main-header .navbar .nav>li>a{color:#fff}.skin-blue .main-header .navbar .nav>li>a:hover,.skin-blue .main-header .navbar .nav>li>a:active,.skin-blue .main-header .navbar .nav>li>a:focus,.skin-blue .main-header .navbar .nav .open>a,.skin-blue .main-header .navbar .nav .open>a:hover,.skin-blue .main-header .navbar .nav .open>a:focus,.skin-blue .main-header .navbar .nav>.active>a{background:rgba(0,0,0,0.1);color:#f6f6f6}.skin-blue .main-header .navbar .sidebar-toggle{color:#fff}.skin-blue .main-header .navbar .sidebar-toggle:hover{color:#f6f6f6;background:rgba(0,0,0,0.1)}.skin-blue .main-header .navbar .sidebar-toggle{color:#fff}.skin-blue .main-header .navbar .sidebar-toggle:hover{background-color:#367fa9}@media (max-width:767px){.skin-blue .main-header .navbar .dropdown-menu li.divider{background-color:rgba(255,255,255,0.1)}.skin-blue .main-header .navbar .dropdown-menu li a{color:#fff}.skin-blue .main-header .navbar .dropdown-menu li a:hover{background:#367fa9}}.skin-blue .main-header .logo{background-color:#367fa9;color:#fff;border-bottom:0 solid transparent}.skin-blue .main-header .logo:hover{background-color:#357ca5}.skin-blue .main-header li.user-header{background-color:#3c8dbc}.skin-blue .content-header{background:transparent}.skin-blue .wrapper,.skin-blue .main-sidebar,.skin-blue .left-side{background-color:#222d32}.skin-blue .user-panel>.info,.skin-blue .user-panel>.info>a{color:#fff}.skin-blue .sidebar-menu>li.header{color:#4b646f;background:#1a2226}.skin-blue .sidebar-menu>li>a{border-left:3px solid transparent}.skin-blue .sidebar-menu>li:hover>a,.skin-blue .sidebar-menu>li.active>a{color:#fff;background:#1e282c;border-left-color:#3c8dbc}.skin-blue .sidebar-menu>li>.treeview-menu{margin:0 1px;background:#2c3b41}.skin-blue .sidebar a{color:#b8c7ce}.skin-blue .sidebar a:hover{text-decoration:none}.skin-blue .treeview-menu>li>a{color:#8aa4af}.skin-blue .treeview-menu>li.active>a,.skin-blue .treeview-menu>li>a:hover{color:#fff}.skin-blue .sidebar-form{border-radius:3px;border:1px solid #374850;margin:10px 10px}.skin-blue .sidebar-form input[type="text"],.skin-blue .sidebar-form .btn{box-shadow:none;background-color:#374850;border:1px solid transparent;height:35px}.skin-blue .sidebar-form input[type="text"]{color:#666;border-top-left-radius:2px;border-top-right-radius:0;border-bottom-right-radius:0;border-bottom-left-radius:2px}.skin-blue .sidebar-form input[type="text"]:focus,.skin-blue .sidebar-form input[type="text"]:focus+.input-group-btn .btn{background-color:#fff;color:#666}.skin-blue .sidebar-form input[type="text"]:focus+.input-group-btn .btn{border-left-color:#fff}.skin-blue .sidebar-form .btn{color:#999;border-top-left-radius:0;border-top-right-radius:2px;border-bottom-right-radius:2px;border-bottom-left-radius:0}.skin-blue.layout-top-nav .main-header>.logo{background-color:#3c8dbc;color:#fff;border-bottom:0 solid transparent}.skin-blue.layout-top-nav .main-header>.logo:hover{background-color:#3b8ab8}

View file

@ -0,0 +1,16 @@
/*! Copyright (c) 2011 Piotr Rochala (http://rocha.la)
* Dual licensed under the MIT (http://www.opensource.org/licenses/mit-license.php)
* and GPL (http://www.opensource.org/licenses/gpl-license.php) licenses.
*
* Version: 1.3.8
*
*/
(function(e){e.fn.extend({slimScroll:function(f){var a=e.extend({width:"auto",height:"250px",size:"7px",color:"#000",position:"right",distance:"1px",start:"top",opacity:.4,alwaysVisible:!1,disableFadeOut:!1,railVisible:!1,railColor:"#333",railOpacity:.2,railDraggable:!0,railClass:"slimScrollRail",barClass:"slimScrollBar",wrapperClass:"slimScrollDiv",allowPageScroll:!1,wheelStep:20,touchScrollStep:200,borderRadius:"7px",railBorderRadius:"7px"},f);this.each(function(){function v(d){if(r){d=d||window.event;
var c=0;d.wheelDelta&&(c=-d.wheelDelta/120);d.detail&&(c=d.detail/3);e(d.target||d.srcTarget||d.srcElement).closest("."+a.wrapperClass).is(b.parent())&&n(c,!0);d.preventDefault&&!k&&d.preventDefault();k||(d.returnValue=!1)}}function n(d,g,e){k=!1;var f=b.outerHeight()-c.outerHeight();g&&(g=parseInt(c.css("top"))+d*parseInt(a.wheelStep)/100*c.outerHeight(),g=Math.min(Math.max(g,0),f),g=0<d?Math.ceil(g):Math.floor(g),c.css({top:g+"px"}));l=parseInt(c.css("top"))/(b.outerHeight()-c.outerHeight());g=
l*(b[0].scrollHeight-b.outerHeight());e&&(g=d,d=g/b[0].scrollHeight*b.outerHeight(),d=Math.min(Math.max(d,0),f),c.css({top:d+"px"}));b.scrollTop(g);b.trigger("slimscrolling",~~g);w();p()}function x(){u=Math.max(b.outerHeight()/b[0].scrollHeight*b.outerHeight(),30);c.css({height:u+"px"});var a=u==b.outerHeight()?"none":"block";c.css({display:a})}function w(){x();clearTimeout(B);l==~~l?(k=a.allowPageScroll,C!=l&&b.trigger("slimscroll",0==~~l?"top":"bottom")):k=!1;C=l;u>=b.outerHeight()?k=!0:(c.stop(!0,
!0).fadeIn("fast"),a.railVisible&&m.stop(!0,!0).fadeIn("fast"))}function p(){a.alwaysVisible||(B=setTimeout(function(){a.disableFadeOut&&r||y||z||(c.fadeOut("slow"),m.fadeOut("slow"))},1E3))}var r,y,z,B,A,u,l,C,k=!1,b=e(this);if(b.parent().hasClass(a.wrapperClass)){var q=b.scrollTop(),c=b.siblings("."+a.barClass),m=b.siblings("."+a.railClass);x();if(e.isPlainObject(f)){if("height"in f&&"auto"==f.height){b.parent().css("height","auto");b.css("height","auto");var h=b.parent().parent().height();b.parent().css("height",
h);b.css("height",h)}else"height"in f&&(h=f.height,b.parent().css("height",h),b.css("height",h));if("scrollTo"in f)q=parseInt(a.scrollTo);else if("scrollBy"in f)q+=parseInt(a.scrollBy);else if("destroy"in f){c.remove();m.remove();b.unwrap();return}n(q,!1,!0)}}else if(!(e.isPlainObject(f)&&"destroy"in f)){a.height="auto"==a.height?b.parent().height():a.height;q=e("<div></div>").addClass(a.wrapperClass).css({position:"relative",overflow:"hidden",width:a.width,height:a.height});b.css({overflow:"hidden",
width:a.width,height:a.height});var m=e("<div></div>").addClass(a.railClass).css({width:a.size,height:"100%",position:"absolute",top:0,display:a.alwaysVisible&&a.railVisible?"block":"none","border-radius":a.railBorderRadius,background:a.railColor,opacity:a.railOpacity,zIndex:90}),c=e("<div></div>").addClass(a.barClass).css({background:a.color,width:a.size,position:"absolute",top:0,opacity:a.opacity,display:a.alwaysVisible?"block":"none","border-radius":a.borderRadius,BorderRadius:a.borderRadius,MozBorderRadius:a.borderRadius,
WebkitBorderRadius:a.borderRadius,zIndex:99}),h="right"==a.position?{right:a.distance}:{left:a.distance};m.css(h);c.css(h);b.wrap(q);b.parent().append(c);b.parent().append(m);a.railDraggable&&c.bind("mousedown",function(a){var b=e(document);z=!0;t=parseFloat(c.css("top"));pageY=a.pageY;b.bind("mousemove.slimscroll",function(a){currTop=t+a.pageY-pageY;c.css("top",currTop);n(0,c.position().top,!1)});b.bind("mouseup.slimscroll",function(a){z=!1;p();b.unbind(".slimscroll")});return!1}).bind("selectstart.slimscroll",
function(a){a.stopPropagation();a.preventDefault();return!1});m.hover(function(){w()},function(){p()});c.hover(function(){y=!0},function(){y=!1});b.hover(function(){r=!0;w();p()},function(){r=!1;p()});b.bind("touchstart",function(a,b){a.originalEvent.touches.length&&(A=a.originalEvent.touches[0].pageY)});b.bind("touchmove",function(b){k||b.originalEvent.preventDefault();b.originalEvent.touches.length&&(n((A-b.originalEvent.touches[0].pageY)/a.touchScrollStep,!0),A=b.originalEvent.touches[0].pageY)});
x();"bottom"===a.start?(c.css({top:b.outerHeight()-c.outerHeight()}),n(0,!0)):"top"!==a.start&&(n(e(a.start).position().top,null,!0),a.alwaysVisible||c.hide());window.addEventListener?(this.addEventListener("DOMMouseScroll",v,!1),this.addEventListener("mousewheel",v,!1)):document.attachEvent("onmousewheel",v)}});return this}});e.fn.extend({slimscroll:e.fn.slimScroll})})(jQuery);

View file

@ -0,0 +1,33 @@
<?php
return [
'validation_error' => 'There was an error with one or more fields in the request.',
'index' => [
'header' => 'Your Servers',
'header_sub' => 'Servers you own and have access to.',
'list' => 'Server List',
],
'account' => [
'header' => 'Your Account',
'header_sub' => 'Manage your account details.',
'update_pass' => 'Update Password',
'update_email' => 'Update Email Address',
'current_password' => 'Current Password',
'new_password' => 'New Password',
'new_password_again' => 'Repeat New Password',
'new_email' => 'New Email Address',
],
'security' => [
'header' => 'Account Security',
'header_sub' => 'Control active sessions and 2-Factor Authentication.',
'sessions' => 'Active Sessions',
'2fa_header' => '2-Factor Authentication',
'2fa_token_help' => 'Enter the 2FA Token generated by your app (Google Authenticatior, Authy, etc.).',
'disable_2fa' => 'Disable 2-Factor Authentication',
'2fa_enabled' => '2-Factor Authentication is enabled on this account and will be required in order to login to the panel. If you would like to disable 2FA, simply enter a valid token below and submit the form.',
'2fa_disabled' => '2-Factor Authentication is disabled on your account! You should enable 2FA in order to add an extra level of protection on your account.',
'enable_2fa' => 'Enable 2-Factor Authentication',
'2fa_qr' => 'Confgure 2FA on Your Device',
'2fa_checkpoint_help' => 'Use the 2FA application on your phone to take a picture of the QR code to the left, or manually enter the code under it. Once you have done so, generate a token and enter it below.',
],
];

View file

@ -5,4 +5,23 @@ return [
'password' => 'Password',
'confirm_password' => 'Confirm Password',
'login' => 'Login',
'home' => 'Home',
'servers' => 'Servers',
'id' => 'ID',
'name' => 'Name',
'node' => 'Node',
'connection' => 'Connection',
'memory' => 'Memory',
'cpu' => 'CPU',
'status' => 'Status',
'search' => 'Search',
'suspended' => 'Suspended',
'account' => 'Account',
'security' => 'Security',
'ip' => 'IP Address',
'last_activity' => 'Last Activitiy',
'revoke' => 'Revoke',
'2fa_token' => 'Authentication Token',
'submit' => 'Submit',
'close' => 'Close',
];

View file

@ -0,0 +1,98 @@
{{-- Copyright (c) 2015 - 2016 Dane Everitt <dane@daneeveritt.com> --}}
{{-- Permission is hereby granted, free of charge, to any person obtaining a copy --}}
{{-- of this software and associated documentation files (the "Software"), to deal --}}
{{-- in the Software without restriction, including without limitation the rights --}}
{{-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell --}}
{{-- copies of the Software, and to permit persons to whom the Software is --}}
{{-- furnished to do so, subject to the following conditions: --}}
{{-- The above copyright notice and this permission notice shall be included in all --}}
{{-- copies or substantial portions of the Software. --}}
{{-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR --}}
{{-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, --}}
{{-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE --}}
{{-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER --}}
{{-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, --}}
{{-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE --}}
{{-- SOFTWARE. --}}
@extends('layouts.master')
@section('title')
{{ trans('base.account.header') }}
@endsection
@section('content-header')
<h1>{{ trans('base.account.header') }}<small>{{ trans('base.account.header_sub')}}</small></h1>
<ol class="breadcrumb">
<li><a href="{{ route('index') }}">{{ trans('strings.home') }}</a></li>
<li class="active">{{ trans('strings.account') }}</li>
</ol>
@endsection
@section('content')
<div class="row">
<div class="col-sm-6">
<div class="box">
<div class="box-header with-border">
<h3 class="box-title">{{ trans('base.account.update_pass') }}</h3>
</div>
<form action="{{ route('account.password') }}" method="post">
<div class="box-body">
<div class="form-group">
<label for="current_password" class="control-label">{{ trans('base.account.current_password') }}</label>
<div>
<input type="password" class="form-control" name="current_password" />
</div>
</div>
<div class="form-group">
<label for="new_password" class="control-label">{{ trans('base.account.new_password') }}</label>
<div>
<input type="password" class="form-control" name="new_password" />
<p class="text-muted"><small>{{ trans('auth.password_requirements') }}</small></p>
</div>
</div>
<div class="form-group">
<label for="new_password_again" class="control-label">{{ trans('base.account.new_password_again') }}</label>
<div>
<input type="password" class="form-control" name="new_password_confirmation" />
</div>
</div>
</div>
<div class="box-footer">
{!! csrf_field() !!}
<input type="submit" class="btn btn-primary btn-sm" value="{{ trans('base.account.update_pass') }}" />
</div>
</form>
</div>
</div>
<div class="col-sm-6">
<div class="box">
<div class="box-header with-border">
<h3 class="box-title">{{ trans('base.account.update_email') }}</h3>
</div>
<form action="{{ route('account.email') }}" method="post">
<div class="box-body">
<div class="form-group">
<label for="new_email" class="control-label">{{ trans('base.account.new_email') }}</label>
<div>
<input type="text" class="form-control" name="new_email" />
</div>
</div>
<div class="form-group">
<label for="password" class="control-label">{{ trans('base.account.current_password') }}</label>
<div>
<input type="password" class="form-control" name="password" />
</div>
</div>
</div>
<div class="box-footer">
{!! csrf_field() !!}
<input type="submit" class="btn btn-primary btn-sm" value="{{ trans('base.account.update_email') }}" />
</div>
</form>
</div>
</div>
</div>
@endsection

View file

@ -0,0 +1,89 @@
{{-- Copyright (c) 2015 - 2016 Dane Everitt <dane@daneeveritt.com> --}}
{{-- Permission is hereby granted, free of charge, to any person obtaining a copy --}}
{{-- of this software and associated documentation files (the "Software"), to deal --}}
{{-- in the Software without restriction, including without limitation the rights --}}
{{-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell --}}
{{-- copies of the Software, and to permit persons to whom the Software is --}}
{{-- furnished to do so, subject to the following conditions: --}}
{{-- The above copyright notice and this permission notice shall be included in all --}}
{{-- copies or substantial portions of the Software. --}}
{{-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR --}}
{{-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, --}}
{{-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE --}}
{{-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER --}}
{{-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, --}}
{{-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE --}}
{{-- SOFTWARE. --}}
@extends('layouts.master')
@section('title')
{{ trans('base.index.header') }}
@endsection
@section('content-header')
<h1>{{ trans('base.index.header') }}<small>{{ trans('base.index.header_sub')}}</small></h1>
<ol class="breadcrumb">
<li><a href="{{ route('index') }}">{{ trans('strings.home') }}</a></li>
<li class="active">{{ trans('strings.servers') }}</li>
</ol>
@endsection
@section('content')
<div class="row">
<div class="col-xs-12">
<div class="box">
<div class="box-header">
<h3 class="box-title">{{ trans('base.index.list') }}</h3>
<div class="box-tools">
<div class="input-group input-group-sm" style="width: 150px;">
<input type="text" name="table_search" class="form-control pull-right" placeholder="{{ trans('strings.search') }}">
<div class="input-group-btn">
<button type="submit" class="btn btn-default"><i class="fa fa-search"></i></button>
</div>
</div>
</div>
</div>
<div class="box-body table-responsive no-padding">
<table class="table table-hover">
<tbody>
<tr>
<th>{{ trans('strings.id') }}</th>
<th>{{ trans('strings.name') }}</th>
<th>{{ trans('strings.node') }}</th>
<th>{{ trans('strings.connection') }}</th>
<th class="text-center hidden-sm hidden-xs">{{ trans('strings.memory') }}</th>
<th class="text-center hidden-sm hidden-xs">{{ trans('strings.cpu') }}</th>
<th class="text-center">{{ trans('strings.status') }}</th>
</tr>
@foreach($servers as $server)
<tr class="dynamic-update" data-server="{{ $server->uuidShort }}">
<td><code>{{ $server->uuidShort }}</code></td>
<td><a href="/server/{{ $server->uuidShort }}">{{ $server->name }}</a></td>
<td>{{ $server->nodeName }}</td>
<td><code>@if(!is_null($server->ip_alias)){{ $server->ip_alias }}@else{{ $server->ip }}@endif:{{ $server->port }}</code></td>
<td class="text-center hidden-sm hidden-xs"><span data-action="memory">--</span> / {{ $server->memory === 0 ? '&infin;' : $server->memory }} MB</td>
<td class="text-center hidden-sm hidden-xs"><span data-action="cpu" data-cpumax="{{ $server->cpu }}">--</span> %</td>
<td class="text-center" data-action="status">
@if($server->suspended === 1)
<span class="label label-warning">{{ trans('strings.suspended') }}</span>
@else
<span class="label label-default"><i class="fa fa-refresh fa-fw fa-spin"></i></span>
@endif
</td>
</tr>
@endforeach
</tbody>
</table>
</div>
</div>
</div>
</div>
@endsection
@section('footer-scripts')
@parent
{!! Theme::js('js/frontend/serverlist.js') !!}
@endsection

View file

@ -0,0 +1,143 @@
{{-- Copyright (c) 2015 - 2016 Dane Everitt <dane@daneeveritt.com> --}}
{{-- Permission is hereby granted, free of charge, to any person obtaining a copy --}}
{{-- of this software and associated documentation files (the "Software"), to deal --}}
{{-- in the Software without restriction, including without limitation the rights --}}
{{-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell --}}
{{-- copies of the Software, and to permit persons to whom the Software is --}}
{{-- furnished to do so, subject to the following conditions: --}}
{{-- The above copyright notice and this permission notice shall be included in all --}}
{{-- copies or substantial portions of the Software. --}}
{{-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR --}}
{{-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, --}}
{{-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE --}}
{{-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER --}}
{{-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, --}}
{{-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE --}}
{{-- SOFTWARE. --}}
@extends('layouts.master')
@section('title')
{{ trans('base.security.header') }}
@endsection
@section('content-header')
<h1>{{ trans('base.security.header') }}<small>{{ trans('base.security.header_sub')}}</small></h1>
<ol class="breadcrumb">
<li><a href="{{ route('index') }}">{{ trans('strings.home') }}</a></li>
<li><a href="{{ route('account') }}">{{ trans('strings.account') }}</a></li>
<li class="active">{{ trans('strings.security') }}</li>
</ol>
@endsection
@section('content')
<div class="row">
<div class="col-md-6">
<div class="box">
<div class="box-header with-border">
<h3 class="box-title">{{ trans('base.security.sessions') }}</h3>
</div>
<div class="box-body table-responsive no-padding">
<table class="table table-hover">
<tbody>
<tr>
<th>{{ trans('strings.id') }}</th>
<th>{{ trans('strings.ip') }}</th>
<th>{{ trans('strings.last_activity') }}</th>
<th></th>
</tr>
@foreach($sessions as $session)
<tr>
<td><code>{{ substr($session->id, 0, 6) }}</code></td>
<td>{{ $session->ip_address }}</td>
<td>{{ Carbon::createFromTimestamp($session->last_activity)->diffForHumans() }}</td>
<td>
<a href="{{ route('account.security.revoke', $session->id) }}">
<button class="btn btn-xs btn-danger"><i class="fa fa-trash-o"></i> {{ trans('strings.revoke') }}</button>
</a>
</td>
</tr>
@endforeach
</tbody>
</table>
</div>
</div>
</div>
<div class="col-md-6">
<div class="box {{ (Auth::user()->use_totp) ? 'box-success' : 'box-danger' }}">
<div class="box-header with-border">
<h3 class="box-title">{{ trans('base.security.2fa_header') }}</h3>
</div>
@if(Auth::user()->use_totp)
<form action="{{ route('account.security.totp') }}" method="post">
<div class="box-body">
<p>{{ trans('base.security.2fa_enabled') }}</p>
<div class="form-group">
<label for="new_password_again" class="control-label">{{ trans('strings.2fa_token') }}</label>
<div>
<input type="text" class="form-control" name="token" />
<p class="text-muted small">{{ trans('base.security.2fa_token_help') }}</p>
</div>
</div>
</div>
<div class="box-footer">
{!! csrf_field() !!}
{{ method_field('DELETE') }}
<button type="submit" class="btn btn-danger btn-sm">{{ trans('base.security.disable_2fa') }}</button>
</div>
</form>
@else
<form action="#" method="post" id="do_2fa">
<div class="box-body">
{{ trans('base.security.2fa_disabled') }}
</div>
<div class="box-footer">
{!! csrf_field() !!}
<button type="submit" class="btn btn-success btn-sm">{{ trans('base.security.enable_2fa') }}</button>
</div>
</form>
@endif
</div>
</div>
</div>
<div class="modal fade" id="open2fa" tabindex="-1" role="dialog" aria-labelledby="open2fa" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<form action="#" method="post" id="2fa_token_verify">
<div class="modal-header">
<h4 class="modal-title">{{ trans('base.security.2fa_qr') }}</h4>
</div>
<div class="modal-body" id="modal_insert_content">
<div class="row">
<div class="col-md-12" id="notice_box_2fa" style="display:none;"></div>
</div>
<div class="row">
<div class="col-md-6">
<center><span id="hide_img_load"><i class="fa fa-spinner fa-spin"></i> Loading QR Code...</span><img src="" id="qr_image_insert" style="display:none;"/><br /><code id="2fa_secret_insert"></code></center>
</div>
<div class="col-md-6">
<div class="alert alert-info">{{ trans('base.security.2fa_checkpoint_help') }}</div>
<div class="form-group">
<label class="control-label" for="2fa_token">{{ trans('strings.2fa_token') }}</label>
{!! csrf_field() !!}
<input class="form-control" type="text" name="2fa_token" id="2fa_token" />
</div>
</div>
</div>
</div>
<div class="modal-footer">
<button type="submit" class="btn btn-primary btn-sm" id="submit_action">{{ trans('strings.submit') }}</button>
<button type="button" class="btn btn-default btn-sm" data-dismiss="modal" id="close_reload">{{ trans('strings.close') }}</button>
</div>
</form>
</div>
</div>
</div>
@endsection
@section('footer-scripts')
@parent
{!! Theme::js('js/frontend/2fa-modal.js') !!}
@endsection

View file

@ -0,0 +1,253 @@
{{-- Copyright (c) 2015 - 2016 Dane Everitt <dane@daneeveritt.com> --}}
{{-- Permission is hereby granted, free of charge, to any person obtaining a copy --}}
{{-- of this software and associated documentation files (the "Software"), to deal --}}
{{-- in the Software without restriction, including without limitation the rights --}}
{{-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell --}}
{{-- copies of the Software, and to permit persons to whom the Software is --}}
{{-- furnished to do so, subject to the following conditions: --}}
{{-- The above copyright notice and this permission notice shall be included in all --}}
{{-- copies or substantial portions of the Software. --}}
{{-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR --}}
{{-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, --}}
{{-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE --}}
{{-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER --}}
{{-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, --}}
{{-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE --}}
{{-- SOFTWARE. --}}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>{{ Settings::get('company', 'Pterodactyl') }} - @yield('title')</title>
<meta content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" name="viewport">
<meta name="_token" content="{{ csrf_token() }}">
@section('scripts')
{!! Theme::css('vendor/bootstrap/bootstrap.min.css') !!}
{!! Theme::css('vendor/adminlte/admin.min.css') !!}
{!! Theme::css('vendor/adminlte/colors/skin-blue.min.css') !!}
{!! Theme::css('css/pterodactyl.css') !!}
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/ionicons/2.0.1/css/ionicons.min.css">
<!--[if lt IE 9]>
<script src="https://oss.maxcdn.com/html5shiv/3.7.3/html5shiv.min.js"></script>
<script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>
<![endif]-->
@show
</head>
<body class="hold-transition skin-blue fixed sidebar-mini">
<div class="wrapper">
<header class="main-header">
<a href="{{ route('index') }}" class="logo">
<span>{{ Settings::get('company', 'Pterodactyl') }}</span>
</a>
<nav class="navbar navbar-static-top">
<a href="#" class="sidebar-toggle" data-toggle="offcanvas" role="button">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</a>
<div class="navbar-custom-menu">
<ul class="nav navbar-nav">
<li class="dropdown user user-menu">
<a href="#" class="dropdown-toggle" data-toggle="dropdown">
<img src="https://www.gravatar.com/avatar/{{ md5(Auth::user()->email) }}?s=160" class="user-image" alt="User Image">
<span class="hidden-xs">{{ Auth::user()->name_first }} {{ Auth::user()->name_last }}</span>
</a>
<ul class="dropdown-menu">
<li class="user-header">
<p>
<small>Member since Nov. 2012</small>
</p>
</li>
<li class="user-body">
<div class="row">
<div class="col-xs-4 text-center">
<a href="#">Followers</a>
</div>
<div class="col-xs-4 text-center">
<a href="#">Sales</a>
</div>
<div class="col-xs-4 text-center">
<a href="#">Friends</a>
</div>
</div>
</li>
<li class="user-footer">
<div class="pull-left">
<a href="{{ route('admin.index') }}" class="btn btn-default btn-flat">Admin Control</a>
</div>
<div class="pull-right">
<a href="{{ route('auth.logout') }}" class="btn btn-default btn-flat">Sign out</a>
</div>
</li>
</ul>
</li>
<li>
<a href="#" data-toggle="control-sidebar"><i class="fa fa-gears" style="margin-top:4px;padding-bottom:2px;"></i></a>
</li>
</ul>
</div>
</nav>
</header>
<aside class="main-sidebar">
<section class="sidebar">
@if (isset($server->name) && isset($node->name))
<div class="user-panel">
<div class="info">
<p>{{ $server->name }}</p>
<a href="#"><i class="fa fa-circle text-success"></i> Online</a>
</div>
</div>
@endif
<ul class="sidebar-menu">
<li class="header">ACCOUNT MANAGEMENT</li>
<li class="{{ Route::currentRouteName() !== 'account' ?: 'active' }}">
<a href="{{ route('account')}}">
<i class="fa fa-user"></i> <span>My Account</span>
</a>
</li>
<li class="{{ Route::currentRouteName() !== 'account.security' ?: 'active' }}">
<a href="{{ route('account.security')}}">
<i class="fa fa-lock"></i> <span>Security Controls</span>
</a>
</li>
<li class="{{ Route::currentRouteName() !== 'account.api' ?: 'active' }}">
<a href="{{ route('account.api')}}">
<i class="fa fa-code"></i> <span>API Access</span>
</a>
</li>
<li class="{{ Route::currentRouteName() !== 'index' ?: 'active' }}">
<a href="{{ route('index')}}">
<i class="fa fa-server"></i> <span>My Servers</span>
</a>
</li>
@if (isset($server->name) && isset($node->name))
<li class="header">SERVER MANAGEMENT</li>
<li>
<a href="{{ route('server.index', $server->uuidShort) }}">
<i class="fa fa-terminal"></i> <span>Console</span>
</a>
</li>
<li class="treeview">
<a href="#">
<i class="fa fa-files-o"></i>
<span>File Management</span>
<span class="pull-right-container">
<i class="fa fa-angle-left pull-right"></i>
</span>
</a>
<ul class="treeview-menu">
<li><a href="{{ route('server.files.index', $server->uuidShort) }}"><i class="fa fa-angle-right"></i> File Browser</a></li>
<li><a href="{{ route('server.files.add', $server->uuidShort) }}"><i class="fa fa-angle-right"></i> Create File</a></li>
<li><a href=""><i class="fa fa-angle-right"></i> Upload Files</a></li>
</ul>
</li>
<li>
<a href="{{ route('server.subusers', $server->uuidShort)}}">
<i class="fa fa-users"></i> <span>Subusers</span>
</a>
</li>
<li>
<a href="{{ route('server.tasks', $server->uuidShort)}}">
<i class="fa fa-clock-o"></i> <span>Task Management</span>
<span class="pull-right-container">
<span class="label label-primary pull-right">4</span>
</span>
</a>
</li>
<li class="treeview">
<a href="#">
<i class="fa fa-gears"></i>
<span>Configuration</span>
<span class="pull-right-container">
<i class="fa fa-angle-left pull-right"></i>
</span>
</a>
<ul class="treeview-menu">
<li><a href="{{ route('server.settings', $server->uuidShort) }}"><i class="fa fa-angle-right"></i> SFTP Settings</a></li>
<li><a href=""><i class="fa fa-angle-right"></i> Startup Parameters</a></li>
<li><a href=""><i class="fa fa-angle-right"></i> Databases</a></li>
</ul>
</li>
@endif
</ul>
</section>
</aside>
<div class="content-wrapper">
<section class="content-header">
@yield('content-header')
</section>
<section class="content">
<div class="row">
<div class="col-xs-12">
@if (count($errors) > 0)
<div class="callout callout-danger">
{{ trans('base.validation_error') }}<br><br>
<ul>
@foreach ($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
@foreach (Alert::getMessages() as $type => $messages)
@foreach ($messages as $message)
<div class="callout callout-{{ $type }} alert-dismissable" role="alert">
{!! $message !!}
</div>
@endforeach
@endforeach
</div>
</div>
@yield('content')
</section>
</div>
<footer class="main-footer">
<div class="pull-right hidden-xs small text-gray">
<strong>v</strong> {{ config('app.version') }}
</div>
Copyright &copy; 2015 - {{ date('Y') }} <a href="https://pterodactyl.io/">Pterodactyl Software &amp; Design</a>.
</footer>
<aside class="control-sidebar control-sidebar-dark">
<ul class="nav nav-tabs nav-justified control-sidebar-tabs">
<li><a href="#control-sidebar-servers-tab" data-toggle="tab"><i class="fa fa-server"></i></a></li>
</ul>
<div class="tab-content">
<div class="tab-pane active" id="control-sidebar-servers-tab">
<ul class="control-sidebar-menu">
@foreach (Pterodactyl\Models\Server::getUserServers() as $s)
<li>
<a href="{{ route('server.index', $s->uuidShort) }}">
@if($s->owner === Auth::user()->id)
<i class="menu-icon fa fa-user bg-blue"></i>
@else
<i class="menu-icon fa fa-user-o bg-gray"></i>
@endif
<div class="menu-info">
<h4 class="control-sidebar-subheading">{{ $s->name }}</h4>
<p>{{ $s->uuidShort }}</p>
</div>
</a>
</li>
@endforeach
</ul>
</div>
</div>
</aside>
<div class="control-sidebar-bg"></div>
</div>
@section('footer-scripts')
{!! Theme::js('js/laroute.js') !!}
{!! Theme::js('js/vendor/jquery/jquery.min.js') !!}
{!! Theme::js('vendor/bootstrap/bootstrap.min.js') !!}
{!! Theme::js('vendor/slimscroll/jquery.slimscroll.min.js') !!}
{!! Theme::js('vendor/adminlte/app.min.js') !!}
@show
</body>
</html>