Get things into a somewhat working state on the login form

This commit is contained in:
Dane Everitt 2018-03-31 15:52:11 -05:00
parent 7de2c8684c
commit 791cbaa5ce
No known key found for this signature in database
GPG key ID: EEA66103B3D71F53
15 changed files with 450 additions and 132 deletions

View file

@ -0,0 +1,34 @@
<?php
namespace Pterodactyl\Http\ViewComposers;
use Illuminate\View\View;
use Pterodactyl\Services\Helpers\AssetHashService;
class AssetComposer
{
/**
* @var \Pterodactyl\Services\Helpers\AssetHashService
*/
private $assetHashService;
/**
* AssetComposer constructor.
*
* @param \Pterodactyl\Services\Helpers\AssetHashService $assetHashService
*/
public function __construct(AssetHashService $assetHashService)
{
$this->assetHashService = $assetHashService;
}
/**
* Provide access to the asset service in the views.
*
* @param \Illuminate\View\View $view
*/
public function compose(View $view)
{
$view->with('asset', $this->assetHashService);
}
}

View file

@ -3,6 +3,7 @@
namespace Pterodactyl\Providers;
use Illuminate\Support\ServiceProvider;
use Pterodactyl\Http\ViewComposers\AssetComposer;
use Pterodactyl\Http\ViewComposers\ServerListComposer;
use Pterodactyl\Http\ViewComposers\Server\ServerDataComposer;
@ -13,6 +14,8 @@ class ViewComposerServiceProvider extends ServiceProvider
*/
public function boot()
{
$this->app->make('view')->composer('*', AssetComposer::class);
$this->app->make('view')->composer('server.*', ServerDataComposer::class);
// Add data to make the sidebar work when viewing a server.

View file

@ -2,7 +2,6 @@
namespace Pterodactyl\Services\Helpers;
use Illuminate\Container\Container;
use Illuminate\Filesystem\FilesystemManager;
use Illuminate\Contracts\Foundation\Application;
use Illuminate\Contracts\Cache\Repository as CacheRepository;
@ -56,20 +55,47 @@ class AssetHashService
*
* @throws \Illuminate\Contracts\Filesystem\FileNotFoundException
*/
public function getUrl(string $resource): string
public function url(string $resource): string
{
$file = last(explode('/', $resource));
return '/' . ltrim(str_replace($file, array_get($this->getManifest(), $file, $file), $resource), '/');
return '/' . ltrim(str_replace($file, array_get($this->manifest(), $file, $file), $resource), '/');
}
/**
* Return a built CSS import using the provided URL.
*
* @param string $resource
* @return string
*
* @throws \Illuminate\Contracts\Filesystem\FileNotFoundException
*/
public function css(string $resource): string
{
return '<link href="' . $this->url($resource) . '" rel="stylesheet preload" crossorigin="anonymous" referrerpolicy="no-referrer">';
}
/**
* Return a built JS import using the provided URL.
*
* @param string $resource
* @return string
*
* @throws \Illuminate\Contracts\Filesystem\FileNotFoundException
*/
public function js(string $resource): string
{
return '<script src="' . $this->url($resource) . '" crossorigin="anonymous"></script>';
}
/**
* Get the asset manifest and store it in the cache for quicker lookups.
*
* @return array
*
* @throws \Illuminate\Contracts\Filesystem\FileNotFoundException
*/
public function getManifest(): array
protected function manifest(): array
{
if (! is_null(self::$manifest)) {
return self::$manifest;
@ -88,41 +114,4 @@ class AssetHashService
return self::$manifest = $contents;
}
/**
* Get the URL for a resource in a static context.
*
* @param string $resource
* @return string
*
* @throws \Illuminate\Contracts\Filesystem\FileNotFoundException
*/
public static function url(string $resource): string
{
return Container::getInstance()->make(self::class)->getUrl($resource);
}
/**
* @param string $resource
* @return string
* @throws \Illuminate\Contracts\Filesystem\FileNotFoundException
*/
public static function css(string $resource): string
{
$path = self::url($resource);
return '<link href="' . $path . '" rel="stylesheet preload" crossorigin="anonymous" referrerpolicy="no-referrer">';
}
/**
* @param string $resource
* @return string
* @throws \Illuminate\Contracts\Filesystem\FileNotFoundException
*/
public static function js(string $resource): string
{
$path = self::url($resource);
return '<script src="' . $path . '" crossorigin="anonymous">';
}
}

View file

@ -1,27 +0,0 @@
/**
* This injects Tailwind's base styles, which is a combination of
* Normalize.css and some additional base styles.
*/
@import "tailwindcss/preflight";
/**
* This injects any component classes registered by plugins.
*/
@import "tailwindcss/components";
/**
* Here you would add any of your custom component classes; stuff that you'd
* want loaded *before* the utilities so that the utilities could still
* override them.
*/
/**
* This injects all of Tailwind's utility classes, generated based on your
* config file.
*/
@import "tailwindcss/utilities";
/**
* Here you would add any custom utilities you need that don't come out of the
* box with Tailwind.
*/

View file

@ -0,0 +1,46 @@
import Vue from 'vue';
import VueRouter from 'vue-router';
import { Ziggy } from './helpers/ziggy';
// Base Vuejs Templates
import Login from './components/auth/Login';
/**
* First we will load all of this project's JavaScript dependencies which
* includes Vue and other libraries. It is a great starting point when
* building robust, powerful web applications using Vue and Laravel.
*/
require('./bootstrap');
window.Ziggy = Ziggy;
/**
* Next, we will create a fresh Vue application instance and attach it to
* the page. Then, you may begin adding components to this application
* or customize the JavaScript scaffolding to fit your unique needs.
*/
const route = require('./../../../../vendor/tightenco/ziggy/src/js/route').default;
Vue.config.productionTip = false;
Vue.mixin({
methods: {
route: route,
},
});
Vue.use(VueRouter);
const router = new VueRouter({
routes: [
{
path: '/:action?',
component: Login,
}
]
});
const app = new Vue({
router,
}).$mount('#pterodactyl');

View file

@ -0,0 +1,35 @@
window._ = require('lodash');
/**
* We'll load jQuery and the Bootstrap jQuery plugin which provides support
* for JavaScript based Bootstrap features such as modals and tabs. This
* code may be modified to fit the specific needs of your application.
*/
try {
window.$ = window.jQuery = require('jquery');
} catch (e) {}
/**
* We'll load the axios HTTP library which allows us to easily issue requests
* to our Laravel back-end. This library automatically handles sending the
* CSRF token as a header based on the value of the "XSRF" token cookie.
*/
window.axios = require('axios');
window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
/**
* Next we will register the CSRF Token as a common header with Axios so that
* all outgoing HTTP requests automatically have it attached. This is just
* a simple convenience so we don't have to attach every token manually.
*/
let token = document.head.querySelector('meta[name="csrf-token"]');
if (token) {
window.axios.defaults.headers.common['X-CSRF-TOKEN'] = token.content;
} else {
console.error('CSRF token not found in document.');
}

View file

@ -0,0 +1,41 @@
<template>
<div>
<form class="bg-white shadow-lg rounded-lg pt-10 px-8 pb-6 mb-4 animate fadein" method="post">
<div class="flex flex-wrap -mx-3 mb-6">
<div class="input-open">
<input class="input" id="grid-email" type="text" aria-labelledby="grid-email" required
v-bind:value="email"
v-on:input="updateEmail($event)"
/>
<label for="grid-email">Email</label>
<p class="text-grey-darker text-xs">Enter your account email address to recive instructions on resetting your password.</p>
</div>
</div>
<div>
<button class="bg-blue hover:bg-blue-dark hover:border-blue-darker border-blue-dark border text-white p-4 rounded w-full uppercase tracking-wide text-sm"
type="submit">
Recover Account
</button>
</div>
<div class="pt-6 text-center">
<router-link to="/" class="text-xs text-grey tracking-wide no-underline uppercase hover:text-grey-dark">
Go to Login
</router-link>
</div>
</form>
</div>
</template>
<script>
export default {
name: 'forgot-password',
props: {
email: {type: String, required: true},
},
methods: {
updateEmail: function (event) {
this.$emit('update-email', event.target.value);
}
}
}
</script>

View file

@ -0,0 +1,37 @@
<template>
<div>
<login-form
v-if="this.$route.path === '/'"
v-bind:email="email"
v-on:update-email="onEmailUpdate"
/>
<forgot-password
v-if="this.$route.path === '/forgot-password'"
v-bind:email="email"
v-on:update-email="onEmailUpdate"
/>
</div>
</template>
<script>
import LoginForm from "./LoginForm";
import ForgotPassword from "./ForgotPassword";
export default {
name: 'login',
data: function () {
return {
email: '',
};
},
methods: {
onEmailUpdate: function (value) {
this.$data.email = value;
},
},
components: {
ForgotPassword,
LoginForm
},
}
</script>

View file

@ -0,0 +1,45 @@
<template>
<div>
<form class="bg-white shadow-lg rounded-lg pt-10 px-8 pb-6 mb-4 animate fadein" :action="route('auth.login')" method="post">
<div class="flex flex-wrap -mx-3 mb-6">
<div class="input-open">
<input class="input" id="grid-username" type="text" name="user" aria-labelledby="grid-username" required
v-bind:value="email"
v-on:input="updateEmail($event)"
/>
<label for="grid-username">Username or Email</label>
</div>
</div>
<div class="flex flex-wrap -mx-3 mb-6">
<div class="input-open">
<input class="input" id="grid-password" type="password" name="password" aria-labelledby="grid-password" required>
<label for="grid-password">Password</label>
</div>
</div>
<div>
<button class="bg-blue hover:bg-blue-dark hover:border-blue-darker border-blue-dark border text-white p-4 rounded w-full uppercase tracking-wide text-sm" type="submit">
Sign In
</button>
</div>
<div class="pt-6 text-center">
<router-link to="/forgot-password" class="text-xs text-grey tracking-wide no-underline uppercase hover:text-grey-dark">
Forgot Password?
</router-link>
</div>
</form>
</div>
</template>
<script>
export default {
name: 'login-form',
props: {
email: { type: String, required: true },
},
methods: {
updateEmail: function (event) {
this.$emit('update-email', event.target.value);
}
}
}
</script>

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,10 @@
.animate {
&.fadein {
animation: fadein 500ms;
}
}
@keyframes fadein {
from { opacity: 0; }
to { opacity: 1; }
}

View file

@ -0,0 +1,37 @@
.input-open {
@apply .w-full .px-3 .relative;
}
.input-open > .input {
@apply .appearance-none .block .w-full .text-grey-darker .border-b-2 .border-grey-light .py-3 .mb-3;
&:focus {
@apply .border-blue;
outline: 0;
transition: border 500ms ease-out;
}
&:focus + label, &:valid + label {
@apply .text-grey-darker .px-0 .cursor-pointer;
transform:translateY(-24px)
}
&:invalid + label {
@apply .text-grey .px-1;
transform:translateY(0)
}
&:required {
box-shadow: none;
}
}
.input-open > label {
@apply .block .uppercase .tracking-wide .text-grey .text-xs .mb-2 .absolute .px-1;
top: 14px;
transition: transform 200ms ease-out;
}
.login-box {
@apply .bg-white .shadow-lg .rounded-lg .pt-10 .px-8 .pb-6 .mb-4;
}

View file

@ -0,0 +1,23 @@
/**
* Tailwind Preflight Classes
*/
@import "tailwindcss/preflight";
@import "tailwindcss/components";
/**
* Pterodactyl Specific CSS
*/
@import "components/animations.css";
@import "components/authentication.css";
/**
* Tailwind Utilities
*/
@import "tailwindcss/utilities";
/**
* Assorted Other CSS
*/
body {
@apply .font-sans;
}

View file

@ -3,73 +3,73 @@
{{-- This software is licensed under the terms of the MIT license. --}}
{{-- https://opensource.org/licenses/MIT --}}
@extends('layouts.auth')
@extends('templates/auth.core')
@section('title')
Login
@endsection
@section('content')
<div class="row">
<div class="col-sm-offset-3 col-xs-offset-1 col-sm-6 col-xs-10">
@if (count($errors) > 0)
<div class="alert alert-danger">
<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">&times;</span></button>
@lang('auth.auth_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">
<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">&times;</span></button>
{!! $message !!}
</div>
@endforeach
@endforeach
</div>
</div>
<div class="row">
<div class="col-sm-offset-3 col-xs-offset-1 col-sm-6 col-xs-10 pterodactyl-login-box">
<form id="loginForm" action="{{ route('auth.login') }}" method="POST">
<div class="form-group has-feedback">
<div class="pterodactyl-login-input">
<input type="text" name="user" class="form-control input-lg" value="{{ old('user') }}" required placeholder="@lang('strings.user_identifier')" autofocus>
<span class="fa fa-envelope form-control-feedback fa-lg"></span>
</div>
</div>
<div class="form-group has-feedback">
<div class="pterodactyl-login-input">
<input type="password" name="password" class="form-control input-lg" required placeholder="@lang('strings.password')">
<span class="fa fa-lock form-control-feedback fa-lg"></span>
</div>
</div>
<div class="row">
<div class="col-xs-4">
<a href="{{ route('auth.password') }}"><button type="button" class="btn pterodactyl-login-button--left"><i class="fa fa-life-ring"></i></button></a>
</div>
<div class="col-xs-offset-4 col-xs-4">
{!! csrf_field() !!}
<button type="submit" class="btn btn-block g-recaptcha pterodactyl-login-button--main" @if(config('recaptcha.enabled')) data-sitekey="{{ config('recaptcha.website_key') }}" data-callback='onSubmit' @endif>@lang('auth.sign_in')</button>
</div>
</div>
</form>
</div>
</div>
@endsection
{{--@section('content')--}}
{{--<div class="row">--}}
{{--<div class="col-sm-offset-3 col-xs-offset-1 col-sm-6 col-xs-10">--}}
{{--@if (count($errors) > 0)--}}
{{--<div class="alert alert-danger">--}}
{{--<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">&times;</span></button>--}}
{{--@lang('auth.auth_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">--}}
{{--<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">&times;</span></button>--}}
{{--{!! $message !!}--}}
{{--</div>--}}
{{--@endforeach--}}
{{--@endforeach--}}
{{--</div>--}}
{{--</div>--}}
{{--<div class="row">--}}
{{--<div class="col-sm-offset-3 col-xs-offset-1 col-sm-6 col-xs-10 pterodactyl-login-box">--}}
{{--<form id="loginForm" action="{{ route('auth.login') }}" method="POST">--}}
{{--<div class="form-group has-feedback">--}}
{{--<div class="pterodactyl-login-input">--}}
{{--<input type="text" name="user" class="form-control input-lg" value="{{ old('user') }}" required placeholder="@lang('strings.user_identifier')" autofocus>--}}
{{--<span class="fa fa-envelope form-control-feedback fa-lg"></span>--}}
{{--</div>--}}
{{--</div>--}}
{{--<div class="form-group has-feedback">--}}
{{--<div class="pterodactyl-login-input">--}}
{{--<input type="password" name="password" class="form-control input-lg" required placeholder="@lang('strings.password')">--}}
{{--<span class="fa fa-lock form-control-feedback fa-lg"></span>--}}
{{--</div>--}}
{{--</div>--}}
{{--<div class="row">--}}
{{--<div class="col-xs-4">--}}
{{--<a href="{{ route('auth.password') }}"><button type="button" class="btn pterodactyl-login-button--left"><i class="fa fa-life-ring"></i></button></a>--}}
{{--</div>--}}
{{--<div class="col-xs-offset-4 col-xs-4">--}}
{{--{!! csrf_field() !!}--}}
{{--<button type="submit" class="btn btn-block g-recaptcha pterodactyl-login-button--main" @if(config('recaptcha.enabled')) data-sitekey="{{ config('recaptcha.website_key') }}" data-callback='onSubmit' @endif>@lang('auth.sign_in')</button>--}}
{{--</div>--}}
{{--</div>--}}
{{--</form>--}}
{{--</div>--}}
{{--</div>--}}
{{--@endsection--}}
@section('scripts')
@parent
@if(config('recaptcha.enabled'))
<script src="https://www.google.com/recaptcha/api.js" async defer></script>
<script>
function onSubmit(token) {
document.getElementById("loginForm").submit();
}
</script>
@endif
@endsection
{{--@section('scripts')--}}
{{--@parent--}}
{{--@if(config('recaptcha.enabled'))--}}
{{--<script src="https://www.google.com/recaptcha/api.js" async defer></script>--}}
{{--<script>--}}
{{--function onSubmit(token) {--}}
{{--document.getElementById("loginForm").submit();--}}
{{--}--}}
{{--</script>--}}
{{--@endif--}}
{{--@endsection--}}

View file

@ -0,0 +1,34 @@
<html>
<head>
<title>{{ config('app.name', 'Pterodactyl') }} - @yield('title')</title>
@section('meta')
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" name="viewport">
<meta name="csrf-token" content="{{ csrf_token() }}">
@show
@section('assets')
{!! $asset->css('assets/css/bundle.css') !!}
@show
@include('layouts.scripts')
</head>
<body class="bg-grey-darkest">
<div class="container" id="pterodactyl">
<div class="w-full max-w-xs sm:max-w-sm m-auto mt-8">
<div class="text-center">
<img src="/favicons/android-chrome-512x512.png" class="max-w-xxs">
</div>
<router-view></router-view>
<p class="text-center text-grey text-xs">
&copy; 2015 - {{ date('Y') }} Pterodactyl Software
</p>
</div>
</div>
@section('scripts')
{!! $asset->js('assets/scripts/bundle.js') !!}
@show
</body>
</html>