This commit is contained in:
chenc 2022-09-21 14:17:05 +08:00
parent 55f3f167e9
commit 206e67c319
18 changed files with 2052 additions and 1 deletions

7
.gitignore vendored Normal file
View File

@ -0,0 +1,7 @@
.DS_Store
phpunit.phar
/vendor
composer.phar
composer.lock
*.project
.idea/

20
LICENSE Normal file
View File

@ -0,0 +1,20 @@
The MIT License (MIT)
Copyright (c) 2015 Jens Segers
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.

View File

@ -1,2 +1,37 @@
# laravel-admin-ext-helpers
Helpers for laravel-admin
=========================
[![StyleCI](https://styleci.io/repos/97900966/shield?branch=master)](https://styleci.io/repos/97900966)
[![Packagist](https://img.shields.io/packagist/l/laravel-admin-ext/helpers.svg?maxAge=2592000)](https://packagist.org/packages/laravel-admin-ext/helpers)
[![Total Downloads](https://img.shields.io/packagist/dt/laravel-admin-ext/helpers.svg?style=flat-square)](https://packagist.org/packages/laravel-admin-ext/helpers)
[![Pull request welcome](https://img.shields.io/badge/pr-welcome-green.svg?style=flat-square)]()
[Documentation](http://laravel-admin.org/docs/#/en/extension-helpers) | [中文文档](http://laravel-admin.org/docs/#/zh/extension-helpers)
DEMO [helpers](http://demo.laravel-admin.org/helpers/scaffold)
Login using `admin/admin`
## Installation
```
$ composer require laravel-admin-ext/helpers
$ php artisan admin:import helpers
```
## Usage
See [wiki](http://laravel-admin.org/docs/#/en/extension-helpers?id=helpers)
## Donate
> Help keeping the project development going, by donating a little. Thanks in advance.
[![PayPal Me](https://img.shields.io/badge/Donate-PayPal-green.svg)](https://www.paypal.me/zousong)
![-1](https://cloud.githubusercontent.com/assets/1479100/23287423/45c68202-fa78-11e6-8125-3e365101a313.jpg)
License
------------
Licensed under [The MIT License (MIT)](LICENSE).

34
composer.json Normal file
View File

@ -0,0 +1,34 @@
{
"name": "aix/laravel-admin-ext-helpers",
"description": "Helpers extension for laravel-admin",
"version": "1.0.1",
"type": "library",
"keywords": ["laravel-admin", "helpers"],
"homepage": "https://github.com/laravel-admin-extensions/helpers",
"license": "MIT",
"authors": [
{
"name": "z-song",
"email": "zosong@126.com"
}
],
"require": {
"php": ">=7.0.0",
"aix/laravel-admin": "1.0.*"
},
"require-dev": {
"phpunit/phpunit": "~6.0"
},
"autoload": {
"psr-4": {
"Encore\\Admin\\Helpers\\": "src/"
}
},
"extra": {
"laravel": {
"providers": [
"Encore\\Admin\\Helpers\\HelpersServiceProvider"
]
}
}
}

View File

@ -0,0 +1,173 @@
<script>
$(function () {
var storageKey = function () {
var connection = $('#connections').val();
return 'la-'+connection+'-history'
};
$('#terminal-box').slimScroll({
height: $('#pjax-container').height() - 247 +'px'
});
function History () {
this.index = this.count() - 1;
}
History.prototype.store = function () {
var history = localStorage.getItem(storageKey());
if (!history) {
history = [];
} else {
history = JSON.parse(history);
}
return history;
};
History.prototype.push = function (record) {
var history = this.store();
history.push(record);
localStorage.setItem(storageKey(), JSON.stringify(history));
this.index = this.count() - 1;
};
History.prototype.count = function () {
return this.store().length;
};
History.prototype.up = function () {
if (this.index > 0) {
this.index--;
}
return this.store()[this.index];
};
History.prototype.down = function () {
if (this.index < this.count() - 1) {
this.index++;
}
return this.store()[this.index];
};
History.prototype.clear = function () {
localStorage.removeItem(storageKey());
};
var history = new History;
var send = function () {
var $input = $('#terminal-query');
$.ajax({
url:location.pathname,
method: 'post',
data: {
c: $input.val(),
_token: LA.token
},
success: function (response) {
history.push($input.val());
$('#terminal-box')
.append('<div class="item"><small class="label label-default"> > artisan '+$input.val()+'<\/small><\/div>')
.append('<div class="item">'+response+'<\/div>')
.slimScroll({ scrollTo: $("#terminal-box")[0].scrollHeight });
$input.val('');
}
});
};
$('#terminal-query').on('keyup', function (e) {
e.preventDefault();
if (e.keyCode == 13) {
send();
}
if (e.keyCode == 38) {
$(this).val(history.up());
}
if (e.keyCode == 40) {
$(this).val(history.down());
}
});
$('#terminal-clear').click(function () {
$('#terminal-box').text('');
//history.clear();
});
$('.loaded-command').click(function () {
$('#terminal-query').val($(this).html() + ' ');
$('#terminal-query').focus();
});
$('#terminal-send').click(function () {
send();
});
});
</script>
<!-- Chat box -->
<div class="box box-primary">
<div class="box-header with-border">
<i class="fa fa-terminal"></i>
</div>
<div class="box-body chat" id="terminal-box">
<!-- chat item -->
<!-- /.item -->
</div>
<!-- /.chat -->
<div class="box-footer with-border">
<div style="margin-bottom: 10px;">
@foreach($commands['groups'] as $group => $command)
<div class="btn-group dropup">
<button type="button" class="btn btn-default btn-flat">{{ $group }}</button>
<button type="button" class="btn btn-default btn-flat dropdown-toggle" data-toggle="dropdown" aria-expanded="false">
<span class="caret"></span>
<span class="sr-only">Toggle Dropdown</span>
</button>
<ul class="dropdown-menu" role="menu">
@foreach($command as $item)
<li><a href="#" class="loaded-command">{{$item}}</a></li>
@endforeach
</ul>
</div>
@endforeach
<div class="btn-group dropup">
<button type="button" class="btn btn-twitter btn-flat">Other</button>
<button type="button" class="btn btn-twitter btn-flat dropdown-toggle" data-toggle="dropdown">
<span class="caret"></span>
<span class="sr-only">Toggle Dropdown</span>
</button>
<ul class="dropdown-menu" role="menu">
@foreach($commands['others'] as $item)
<li><a href="#" class="loaded-command">{{$item}}</a></li>
@endforeach
</ul>
</div>
<button type="button" class="btn btn-success" id="terminal-send"><i class="fa fa-paper-plane"></i> send</button>
<button type="button" class="btn btn-warning" id="terminal-clear"><i class="fa fa-refresh"></i> clear</button>
</div>
<div class="input-group">
<span class="input-group-addon" style="font-size: 18px; line-height: 1.3333333;">artisan</span>
<input class="form-control input-lg" id="terminal-query" placeholder="command" style="border-left: 0px;padding-left: 0px;">
</div>
</div>
</div>
<!-- /.box (chat box) -->

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,301 @@
<div class="box box-primary">
<div class="box-header with-border">
<h3 class="box-title">Scaffold</h3>
</div>
<!-- /.box-header -->
<div class="box-body">
<form method="post" action="{{$action}}" id="scaffold" pjax-container>
<div class="box-body">
<div class="form-horizontal">
<div class="form-group">
<label for="inputTableName" class="col-sm-1 control-label">Table name</label>
<div class="col-sm-4">
<input type="text" name="table_name" class="form-control" id="inputTableName" placeholder="table name" value="{{ old('table_name') }}">
</div>
<span class="help-block hide" id="table-name-help">
<i class="fa fa-info"></i>&nbsp; Table name can't be empty!
</span>
</div>
<div class="form-group">
<label for="inputModelName" class="col-sm-1 control-label">Model</label>
<div class="col-sm-4">
<input type="text" name="model_name" class="form-control" id="inputModelName" placeholder="model" value="{{ old('model_name', "App\\Models\\") }}">
</div>
</div>
<div class="form-group">
<label for="inputControllerName" class="col-sm-1 control-label">Controller</label>
<div class="col-sm-4">
<input type="text" name="controller_name" class="form-control" id="inputControllerName" placeholder="controller" value="{{ old('controller_name', "App\\Admin\\Controllers\\") }}">
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-1 col-sm-11">
<div class="checkbox">
<label>
<input type="checkbox" checked value="migration" name="create[]" /> Create migration
</label>
<label>
<input type="checkbox" checked value="model" name="create[]" /> Create model
</label>
<label>
<input type="checkbox" checked value="controller" name="create[]" /> Create controller
</label>
<label>
<input type="checkbox" checked value="migrate" name="create[]" /> Run migrate
</label>
</div>
</div>
</div>
</div>
<hr />
<h4>Fields</h4>
<table class="table table-hover" id="table-fields">
<tbody>
<tr>
<th style="width: 200px">Field name</th>
<th>Type</th>
<th>Size</th>
<th>Nullable</th>
<th>Key</th>
<th>Default value</th>
<th>Comment</th>
<th>Action</th>
</tr>
@if(old('fields'))
@foreach(old('fields') as $index => $field)
<tr>
<td>
<input type="text" name="fields[{{$index}}][name]" class="form-control" placeholder="field name" value="{{$field['name']}}" />
</td>
<td>
<select style="width: 200px" name="fields[{{$index}}][type]">
@foreach($dbTypes as $type)
<option value="{{ $type }}" {{$field['type'] == $type ? 'selected' : '' }}>{{$type}}</option>
@endforeach
</select>
</td>
<td><input type="number" class="form-control" placeholder="size" name="fields[{{$index}}][size]" value="{{$field['size']}}"/></td>
<td><input type="checkbox" name="fields[{{$index}}][nullable]" {{ array_get($field, 'nullable') == 'on' ? 'checked': '' }}/></td>
<td>
<select style="width: 150px" name="fields[{{$index}}][key]">
{{--<option value="primary">Primary</option>--}}
<option value="" {{$field['key'] == '' ? 'selected' : '' }}>NULL</option>
<option value="unique" {{$field['key'] == 'unique' ? 'selected' : '' }}>Unique</option>
<option value="index" {{$field['key'] == 'index' ? 'selected' : '' }}>Index</option>
</select>
</td>
<td><input type="text" class="form-control" placeholder="default value" name="fields[{{$index}}][default]" value="{{$field['default']}}"/></td>
<td><input type="text" class="form-control" placeholder="comment" name="fields[{{$index}}][comment]" value="{{$field['comment']}}" /></td>
<td><a class="btn btn-sm btn-danger table-field-remove"><i class="fa fa-trash"></i> remove</a></td>
</tr>
@endforeach
@else
<tr>
<td>
<input type="text" name="fields[0][name]" class="form-control" placeholder="field name" />
</td>
<td>
<select style="width: 200px" name="fields[0][type]">
@foreach($dbTypes as $type)
<option value="{{ $type }}">{{$type}}</option>
@endforeach
</select>
</td>
<td><input type="number" class="form-control" placeholder="size" name="fields[0][size]" value="0"></td>
<td><input type="checkbox" name="fields[0][nullable]" /></td>
<td>
<select style="width: 150px" name="fields[0][key]">
{{--<option value="primary">Primary</option>--}}
<option value="" selected>NULL</option>
<option value="unique">Unique</option>
<option value="index">Index</option>
</select>
</td>
<td><input type="text" class="form-control" placeholder="default value" name="fields[0][default]"></td>
<td><input type="text" class="form-control" placeholder="comment" name="fields[0][comment]"></td>
<td><a class="btn btn-sm btn-danger table-field-remove"><i class="fa fa-trash"></i> remove</a></td>
</tr>
@endif
</tbody>
</table>
<hr style="margin-top: 0;"/>
<div class='form-inline margin' style="width: 100%">
<div class='form-group'>
<button type="button" class="btn btn-sm btn-success" id="add-table-field"><i class="fa fa-plus"></i>&nbsp;&nbsp;Add field</button>
</div>
<div class='form-group pull-right' style="margin-right: 20px; margin-top: 5px;">
<div class="checkbox">
<label>
<input type="checkbox" checked name="timestamps"> Created_at & Updated_at
</label>
&nbsp;&nbsp;
<label>
<input type="checkbox" name="soft_deletes"> Soft deletes
</label>
</div>
</div>
<div class="form-group pull-right" style="margin-right: 20px;">
<label for="inputPrimaryKey">Primary key</label>
<input type="text" name="primary_key" class="form-control" id="inputPrimaryKey" placeholder="Primary key" value="id" style="width: 100px;">
</div>
</div>
{{--<hr />--}}
{{--<h4>Relations</h4>--}}
{{--<table class="table table-hover" id="model-relations">--}}
{{--<tbody>--}}
{{--<tr>--}}
{{--<th style="width: 200px">Relation name</th>--}}
{{--<th>Type</th>--}}
{{--<th>Related model</th>--}}
{{--<th>forignKey</th>--}}
{{--<th>OtherKey</th>--}}
{{--<th>With Pivot</th>--}}
{{--<th>Action</th>--}}
{{--</tr>--}}
{{--</tbody>--}}
{{--</table>--}}
{{--<hr style="margin-top: 0;"/>--}}
{{--<div class='form-inline margin' style="width: 100%">--}}
{{--<div class='form-group'>--}}
{{--<button type="button" class="btn btn-sm btn-success" id="add-model-relation"><i class="fa fa-plus"></i>&nbsp;&nbsp;Add relation</button>--}}
{{--</div>--}}
{{--</div>--}}
</div>
<!-- /.box-body -->
<div class="box-footer">
<button type="submit" class="btn btn-info pull-right">submit</button>
</div>
{{ csrf_field() }}
<!-- /.box-footer -->
</form>
</div>
</div>
<template id="table-field-tpl">
<tr>
<td>
<input type="text" name="fields[__index__][name]" class="form-control" placeholder="field name" />
</td>
<td>
<select style="width: 200px" name="fields[__index__][type]">
@foreach($dbTypes as $type)
<option value="{{ $type }}">{{$type}}</option>
@endforeach
</select>
</td>
<td><input type="number" class="form-control" placeholder="size" name="fields[__index__][size]" value="0"></td>
<td><input type="checkbox" name="fields[__index__][nullable]" /></td>
<td>
<select style="width: 150px" name="fields[__index__][key]">
<option value="" selected>NULL</option>
<option value="unique">Unique</option>
<option value="index">Index</option>
</select>
</td>
<td><input type="text" class="form-control" placeholder="default value" name="fields[__index__][default]"></td>
<td><input type="text" class="form-control" placeholder="comment" name="fields[__index__][comment]"></td>
<td><a class="btn btn-sm btn-danger table-field-remove"><i class="fa fa-trash"></i> remove</a></td>
</tr>
</template>
<template id="model-relation-tpl">
<tr>
<td><input type="text" class="form-control" placeholder="relation name" value=""></td>
<td>
<select style="width: 150px">
<option value="HasOne" selected>HasOne</option>
<option value="BelongsTo">BelongsTo</option>
<option value="HasMany">HasMany</option>
<option value="BelongsToMany">BelongsToMany</option>
</select>
</td>
<td><input type="text" class="form-control" placeholder="related model"></td>
<td><input type="text" class="form-control" placeholder="default value"></td>
<td><input type="text" class="form-control" placeholder="default value"></td>
<td><input type="checkbox" /></td>
<td><a class="btn btn-sm btn-danger model-relation-remove"><i class="fa fa-trash"></i> remove</a></td>
</tr>
</template>
<script>
$(function () {
$('input[type=checkbox]').iCheck({checkboxClass:'icheckbox_minimal-blue'});
$('select').select2();
$('#add-table-field').click(function (event) {
$('#table-fields tbody').append($('#table-field-tpl').html().replace(/__index__/g, $('#table-fields tr').length - 1));
$('select').select2();
$('input[type=checkbox]').iCheck({checkboxClass:'icheckbox_minimal-blue'});
});
$('#table-fields').on('click', '.table-field-remove', function(event) {
$(event.target).closest('tr').remove();
});
$('#add-model-relation').click(function (event) {
$('#model-relations tbody').append($('#model-relation-tpl').html().replace(/__index__/g, $('#model-relations tr').length - 1));
$('select').select2();
$('input[type=checkbox]').iCheck({checkboxClass:'icheckbox_minimal-blue'});
relation_count++;
});
$('#model-relations').on('click', '.model-relation-remove', function(event) {
$(event.target).closest('tr').remove();
});
$('#scaffold').on('submit', function (event) {
//event.preventDefault();
if ($('#inputTableName').val() == '') {
$('#inputTableName').closest('.form-group').addClass('has-error');
$('#table-name-help').removeClass('hide');
return false;
}
return true;
});
});
</script>

View File

@ -0,0 +1,171 @@
<?php
namespace Encore\Admin\Helpers\Controllers;
use Encore\Admin\Facades\Admin;
use Encore\Admin\Grid;
use Encore\Admin\Layout\Content;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Routing\Controller;
use Illuminate\Routing\Route;
use Illuminate\Support\Arr;
use Illuminate\Support\Str;
class RouteController extends Controller
{
public function index()
{
return Admin::content(function (Content $content) {
$model = $this->getModel()->setRoutes($this->getRoutes());
$content->body(Admin::grid($model, function (Grid $grid) {
$colors = [
'GET' => 'green',
'HEAD' => 'gray',
'POST' => 'blue',
'PUT' => 'yellow',
'DELETE' => 'red',
'PATCH' => 'aqua',
'OPTIONS'=> 'light-blue',
];
$grid->method()->map(function ($method) use ($colors) {
return "<span class=\"label bg-{$colors[$method]}\">$method</span>";
})->implode('&nbsp;');
$grid->uri()->display(function ($uri) {
return preg_replace('/\{.+?\}/', '<code>$0</span>', $uri);
})->sortable();
$grid->name();
$grid->action()->display(function ($uri) {
return preg_replace('/@.+/', '<code>$0</code>', $uri);
});
$grid->middleware()->badge('yellow');
$grid->disablePagination();
$grid->disableRowSelector();
$grid->disableActions();
$grid->disableCreation();
$grid->disableExport();
$grid->filter(function ($filter) {
$filter->disableIdFilter();
$filter->equal('action');
$filter->equal('uri');
});
}));
});
}
protected function getModel()
{
return new class() extends Model {
protected $routes;
protected $where = [];
public function setRoutes($routes)
{
$this->routes = $routes;
return $this;
}
public function where($column, $condition)
{
$this->where[$column] = trim($condition);
return $this;
}
public function orderBy()
{
return $this;
}
public function get()
{
$this->routes = collect($this->routes)->filter(function ($route) {
foreach ($this->where as $column => $condition) {
if (!Str::contains($route[$column], $condition)) {
return false;
}
}
return true;
})->all();
$instance = $this->newModelInstance();
return $instance->newCollection(array_map(function ($item) use ($instance) {
return $instance->newFromBuilder($item);
}, $this->routes));
}
};
}
public function getRoutes()
{
$routes = app('router')->getRoutes();
$routes = collect($routes)->map(function ($route) {
return $this->getRouteInformation($route);
})->all();
if ($sort = request('_sort')) {
$routes = $this->sortRoutes($sort, $routes);
}
return array_filter($routes);
}
/**
* Get the route information for a given route.
*
* @param \Illuminate\Routing\Route $route
*
* @return array
*/
protected function getRouteInformation(Route $route)
{
return [
'host' => $route->domain(),
'method' => $route->methods(),
'uri' => $route->uri(),
'name' => $route->getName(),
'action' => $route->getActionName(),
'middleware' => $this->getRouteMiddleware($route),
];
}
/**
* Sort the routes by a given element.
*
* @param string $sort
* @param array $routes
*
* @return array
*/
protected function sortRoutes($sort, $routes)
{
return Arr::sort($routes, function ($route) use ($sort) {
return $route[$sort];
});
}
/**
* Get before filters.
*
* @param \Illuminate\Routing\Route $route
*
* @return string
*/
protected function getRouteMiddleware($route)
{
return collect($route->gatherMiddleware())->map(function ($middleware) {
return $middleware instanceof \Closure ? 'Closure' : $middleware;
});
}
}

View File

@ -0,0 +1,116 @@
<?php
namespace Encore\Admin\Helpers\Controllers;
use Encore\Admin\Facades\Admin;
use Encore\Admin\Helpers\Scaffold\ControllerCreator;
use Encore\Admin\Helpers\Scaffold\MigrationCreator;
use Encore\Admin\Helpers\Scaffold\ModelCreator;
use Encore\Admin\Layout\Content;
use Illuminate\Http\Request;
use Illuminate\Routing\Controller;
use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\URL;
use Illuminate\Support\MessageBag;
class ScaffoldController extends Controller
{
public function index()
{
return Admin::content(function (Content $content) {
$content->header('Scaffold');
$dbTypes = [
'string', 'integer', 'text', 'float', 'double', 'decimal', 'boolean', 'date', 'time',
'dateTime', 'timestamp', 'char', 'mediumText', 'longText', 'tinyInteger', 'smallInteger',
'mediumInteger', 'bigInteger', 'unsignedTinyInteger', 'unsignedSmallInteger', 'unsignedMediumInteger',
'unsignedInteger', 'unsignedBigInteger', 'enum', 'json', 'jsonb', 'dateTimeTz', 'timeTz',
'timestampTz', 'nullableTimestamps', 'binary', 'ipAddress', 'macAddress',
];
$action = URL::current();
$content->row(view('laravel-admin-helpers::scaffold', compact('dbTypes', 'action')));
});
}
public function store(Request $request)
{
$paths = [];
$message = '';
try {
// 1. Create model.
if (in_array('model', $request->get('create'))) {
$modelCreator = new ModelCreator($request->get('table_name'), $request->get('model_name'));
$paths['model'] = $modelCreator->create(
$request->get('primary_key'),
$request->get('timestamps') == 'on',
$request->get('soft_deletes') == 'on'
);
}
// 2. Create controller.
if (in_array('controller', $request->get('create'))) {
$paths['controller'] = (new ControllerCreator($request->get('controller_name')))
->create($request->get('model_name'));
}
// 3. Create migration.
if (in_array('migration', $request->get('create'))) {
$migrationName = 'create_'.$request->get('table_name').'_table';
$paths['migration'] = (new MigrationCreator(app('files')))->buildBluePrint(
$request->get('fields'),
$request->get('primary_key', 'id'),
$request->get('timestamps') == 'on',
$request->get('soft_deletes') == 'on'
)->create($migrationName, database_path('migrations'), $request->get('table_name'));
}
// 4. Run migrate.
if (in_array('migrate', $request->get('create'))) {
Artisan::call('migrate');
$message = Artisan::output();
}
} catch (\Exception $exception) {
// Delete generated files if exception thrown.
app('files')->delete($paths);
return $this->backWithException($exception);
}
return $this->backWithSuccess($paths, $message);
}
protected function backWithException(\Exception $exception)
{
$error = new MessageBag([
'title' => 'Error',
'message' => $exception->getMessage(),
]);
return back()->withInput()->with(compact('error'));
}
protected function backWithSuccess($paths, $message)
{
$messages = [];
foreach ($paths as $name => $path) {
$messages[] = ucfirst($name).": $path";
}
$messages[] = "<br />$message";
$success = new MessageBag([
'title' => 'Success',
'message' => implode('<br />', $messages),
]);
return back()->with(compact('success'));
}
}

View File

@ -0,0 +1,265 @@
<?php
namespace Encore\Admin\Helpers\Controllers;
use Encore\Admin\Facades\Admin;
use Encore\Admin\Layout\Content;
use Exception;
use Illuminate\Contracts\Support\Arrayable;
use Illuminate\Routing\Controller;
use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Redis;
use Illuminate\Support\Facades\Request;
use Illuminate\Support\Str;
use MongoDB\Driver\Command;
use MongoDB\Driver\Manager;
use Symfony\Component\Console\Helper\Table;
use Symfony\Component\Console\Input\ArgvInput;
use Symfony\Component\Console\Output\Output;
class TerminalController extends Controller
{
public function artisan()
{
return Admin::content(function (Content $content) {
$content->header('Artisan terminal');
$content->row(view('laravel-admin-helpers::artisan', ['commands' => $this->organizedCommands()]));
});
}
public function runArtisan()
{
$command = Request::get('c', 'list');
// If Exception raised.
if (1 === Artisan::handle(
new ArgvInput(explode(' ', 'artisan '.trim($command))),
$output = new StringOutput()
)) {
return $this->renderException(new Exception($output->getContent()));
}
return sprintf('<pre>%s</pre>', $output->getContent());
}
public function database()
{
return Admin::content(function (Content $content) {
$content->header('Database terminal');
$content->row(view('laravel-admin-helpers::database', ['connections' => $this->connections()]));
});
}
public function runDatabase()
{
$query = Request::get('q');
$connection = Request::get('c', config('database.default'));
return $this->dispatchQuery($connection, $query);
}
protected function getDumpedHtml($var)
{
ob_start();
dump($var);
$content = ob_get_contents();
ob_get_clean();
return substr($content, strpos($content, '<pre '));
}
protected function connections()
{
$dbs = $redis = [];
foreach (config('database.connections') as $name => $_) {
$dbs[] = [
'option' => $name,
'value' => "db:$name",
'selected' => $name == config('database.default'),
];
}
$connections = array_filter(config('database.redis'), function ($config) {
return is_array($config);
});
foreach ($connections as $name => $_) {
$redis[] = [
'value' => "redis:$name",
'option' => $name,
];
}
return compact('dbs', 'redis');
}
protected function table(array $headers, $rows, $style = 'default')
{
$output = new StringOutput();
$table = new Table($output);
if ($rows instanceof Arrayable) {
$rows = $rows->toArray();
}
$table->setHeaders($headers)->setRows($rows)->setStyle($style)->render();
return $output->getContent();
}
protected function dispatchQuery($connection, $query)
{
list($type, $connection) = explode(':', $connection);
if ($type == 'redis') {
return $this->execRedisCommand($connection, $query);
}
$config = config('database.connections.'.$connection);
if ($config['driver'] == 'mongodb') {
return $this->execMongodbQuery($config, $query);
}
/* @var \Illuminate\Database\Connection $connection */
$connection = DB::connection($connection);
$connection->enableQueryLog();
try {
$result = $connection->select(str_replace([';', "\G"], '', $query));
} catch (Exception $exception) {
return $this->renderException($exception);
}
$log = current($connection->getQueryLog());
if (empty($result)) {
return sprintf("<pre>Empty set (%s sec)</pre>\r\n", number_format($log['time'] / 1000, 2));
}
$result = json_decode(json_encode($result), true);
if (Str::contains($query, "\G")) {
return $this->getDumpedHtml($result);
}
return sprintf(
"<pre>%s \n%d %s in set (%s sec)</pre>\r\n",
$this->table(array_keys(current($result)), $result),
count($result),
count($result) == 1 ? 'row' : 'rows',
number_format($log['time'] / 1000, 2)
);
}
protected function execMongodbQuery($config, $query)
{
if (Str::contains($query, '.find(') && !Str::contains($query, '.toArray(')) {
$query .= '.toArray()';
}
$manager = new Manager("mongodb://{$config['host']}:{$config['port']}");
$command = new Command(['eval' => $query]);
try {
$cursor = $manager->executeCommand($config['database'], $command);
} catch (Exception $exception) {
return $this->renderException($exception);
}
$result = $cursor->toArray()[0];
$result = json_decode(json_encode($result), true);
if (isset($result['errmsg'])) {
return $this->renderException(new Exception($result['errmsg']));
}
return $this->getDumpedHtml($result['retval']);
}
protected function execRedisCommand($connection, $command)
{
$command = explode(' ', $command);
try {
$result = Redis::connection($connection)->executeRaw($command);
} catch (Exception $exception) {
return $this->renderException($exception);
}
if (is_string($result) && Str::startsWith($result, ['ERR ', 'WRONGTYPE '])) {
return $this->renderException(new Exception($result));
}
return $this->getDumpedHtml($result);
}
protected function organizedCommands()
{
$commands = array_keys(Artisan::all());
$groups = $others = [];
foreach ($commands as $command) {
$parts = explode(':', $command);
if (isset($parts[1])) {
$groups[$parts[0]][] = $command;
} else {
$others[] = $command;
}
}
foreach ($groups as $key => $group) {
if (count($group) === 1) {
$others[] = $group[0];
unset($groups[$key]);
}
}
ksort($groups);
sort($others);
return compact('groups', 'others');
}
protected function renderException(Exception $exception)
{
return sprintf(
"<div class='callout callout-warning'><i class='icon fa fa-warning'></i>&nbsp;&nbsp;&nbsp;%s</div>",
str_replace("\n", '<br />', $exception->getMessage())
);
}
}
class StringOutput extends Output
{
public $output = '';
public function clear()
{
$this->output = '';
}
protected function doWrite($message, $newline)
{
$this->output .= $message.($newline ? "\n" : '');
}
public function getContent()
{
return trim($this->output);
}
}

88
src/Helpers.php Normal file
View File

@ -0,0 +1,88 @@
<?php
namespace Encore\Admin\Helpers;
use Encore\Admin\Admin;
use Encore\Admin\Auth\Database\Menu;
use Encore\Admin\Extension;
class Helpers extends Extension
{
/**
* Bootstrap this package.
*
* @return void
*/
public static function boot()
{
static::registerRoutes();
Admin::extend('helpers', __CLASS__);
}
/**
* Register routes for laravel-admin.
*
* @return void
*/
public static function registerRoutes()
{
parent::routes(function ($router) {
/* @var \Illuminate\Routing\Router $router */
$router->get('helpers/terminal/database', 'Encore\Admin\Helpers\Controllers\TerminalController@database');
$router->post('helpers/terminal/database', 'Encore\Admin\Helpers\Controllers\TerminalController@runDatabase');
$router->get('helpers/terminal/artisan', 'Encore\Admin\Helpers\Controllers\TerminalController@artisan');
$router->post('helpers/terminal/artisan', 'Encore\Admin\Helpers\Controllers\TerminalController@runArtisan');
$router->get('helpers/scaffold', 'Encore\Admin\Helpers\Controllers\ScaffoldController@index');
$router->post('helpers/scaffold', 'Encore\Admin\Helpers\Controllers\ScaffoldController@store');
$router->get('helpers/routes', 'Encore\Admin\Helpers\Controllers\RouteController@index');
});
}
public static function import()
{
$lastOrder = Menu::max('order');
$root = [
'parent_id' => 0,
'order' => $lastOrder++,
'title' => 'Helpers',
'icon' => 'fa-gears',
'uri' => '',
];
$root = Menu::create($root);
$menus = [
[
'title' => 'Scaffold',
'icon' => 'fa-keyboard-o',
'uri' => 'helpers/scaffold',
],
[
'title' => 'Database terminal',
'icon' => 'fa-database',
'uri' => 'helpers/terminal/database',
],
[
'title' => 'Laravel artisan',
'icon' => 'fa-terminal',
'uri' => 'helpers/terminal/artisan',
],
[
'title' => 'Routes',
'icon' => 'fa-list-alt',
'uri' => 'helpers/routes',
],
];
foreach ($menus as $menu) {
$menu['parent_id'] = $root->id;
$menu['order'] = $lastOrder++;
Menu::create($menu);
}
parent::createPermission('Admin helpers', 'ext.helpers', 'helpers/*');
}
}

View File

@ -0,0 +1,18 @@
<?php
namespace Encore\Admin\Helpers;
use Illuminate\Support\ServiceProvider;
class HelpersServiceProvider extends ServiceProvider
{
/**
* {@inheritdoc}
*/
public function boot()
{
$this->loadViewsFrom(__DIR__.'/../resources/views', 'laravel-admin-helpers');
Helpers::boot();
}
}

View File

@ -0,0 +1,128 @@
<?php
namespace Encore\Admin\Helpers\Scaffold;
class ControllerCreator
{
/**
* Controller full name.
*
* @var string
*/
protected $name;
/**
* The filesystem instance.
*
* @var \Illuminate\Filesystem\Filesystem
*/
protected $files;
/**
* ControllerCreator constructor.
*
* @param string $name
* @param null $files
*/
public function __construct($name, $files = null)
{
$this->name = $name;
$this->files = $files ?: app('files');
}
/**
* Create a controller.
*
* @param string $model
*
* @throws \Exception
*
* @return string
*/
public function create($model)
{
$path = $this->getpath($this->name);
if ($this->files->exists($path)) {
throw new \Exception("Controller [$this->name] already exists!");
}
$stub = $this->files->get($this->getStub());
$this->files->put($path, $this->replace($stub, $this->name, $model));
return $path;
}
/**
* @param string $stub
* @param string $name
* @param string $model
*
* @return string
*/
protected function replace($stub, $name, $model)
{
$stub = $this->replaceClass($stub, $name);
return str_replace(
['DummyModelNamespace', 'DummyModel'],
[$model, class_basename($model)],
$stub
);
}
/**
* Get controller namespace from giving name.
*
* @param string $name
*
* @return string
*/
protected function getNamespace($name)
{
return trim(implode('\\', array_slice(explode('\\', $name), 0, -1)), '\\');
}
/**
* Replace the class name for the given stub.
*
* @param string $stub
* @param string $name
*
* @return string
*/
protected function replaceClass($stub, $name)
{
$class = str_replace($this->getNamespace($name).'\\', '', $name);
return str_replace(['DummyClass', 'DummyNamespace'], [$class, $this->getNamespace($name)], $stub);
}
/**
* Get file path from giving controller name.
*
* @param $name
*
* @return string
*/
public function getPath($name)
{
$segments = explode('\\', $name);
array_shift($segments);
return app_path(implode('/', $segments)).'.php';
}
/**
* Get stub file path.
*
* @return string
*/
public function getStub()
{
return __DIR__.'/stubs/controller.stub';
}
}

View File

@ -0,0 +1,121 @@
<?php
namespace Encore\Admin\Helpers\Scaffold;
use Illuminate\Database\Migrations\MigrationCreator as BaseMigrationCreator;
class MigrationCreator extends BaseMigrationCreator
{
/**
* @var string
*/
protected $bluePrint = '';
/**
* Create a new model.
*
* @param string $name
* @param string $path
* @param null $table
* @param bool|true $create
*
* @return string
* @throws \Illuminate\Contracts\Filesystem\FileNotFoundException
*/
public function create($name, $path, $table = null, $create = true)
{
$this->ensureMigrationDoesntAlreadyExist($name);
$path = $this->getPath($name, $path);
$stub = $this->files->get(__DIR__.'/stubs/create.stub');
$this->files->put($path, $this->populateStub($name, $stub, $table));
$this->firePostCreateHooks($table);
return $path;
}
/**
* Populate stub.
*
* @param string $name
* @param string $stub
* @param string $table
*
* @return mixed
*/
protected function populateStub($name, $stub, $table)
{
return str_replace(
['DummyClass', 'DummyTable', 'DummyStructure'],
[$this->getClassName($name), $table, $this->bluePrint],
$stub
);
}
/**
* Build the table blueprint.
*
* @param array $fields
* @param string $keyName
* @param bool|true $useTimestamps
* @param bool|false $softDeletes
*
* @throws \Exception
*
* @return $this
*/
public function buildBluePrint($fields = [], $keyName = 'id', $useTimestamps = true, $softDeletes = false)
{
$fields = array_filter($fields, function ($field) {
return isset($field['name']) && !empty($field['name']);
});
if (empty($fields)) {
throw new \Exception('Table fields can\'t be empty');
}
$rows[] = "\$table->increments('$keyName');\n";
foreach ($fields as $field) {
$column = "\$table->{$field['type']}('{$field['name']}')";
if ($field['key']) {
$column .= "->{$field['key']}()";
}
if ($field['size'] > 0) {
$column .= "->length({$field['size']})";
}
if (isset($field['default']) && $field['default']) {
$column .= "->default('{$field['default']}')";
}
if (isset($field['comment']) && $field['comment']) {
$column .= "->comment('{$field['comment']}')";
}
if (array_get($field, 'nullable') == 'on') {
$column .= '->nullable()';
}
$rows[] = $column.";\n";
}
if ($useTimestamps) {
$rows[] = "\$table->timestamps();\n";
}
if ($softDeletes) {
$rows[] = "\$table->softDeletes();\n";
}
$this->bluePrint = trim(implode(str_repeat(' ', 12), $rows), "\n");
return $this;
}
}

View File

@ -0,0 +1,238 @@
<?php
namespace Encore\Admin\Helpers\Scaffold;
use Illuminate\Support\Str;
class ModelCreator
{
/**
* Table name.
*
* @var string
*/
protected $tableName;
/**
* Model name.
*
* @var string
*/
protected $name;
/**
* The filesystem instance.
*
* @var \Illuminate\Filesystem\Filesystem
*/
protected $files;
/**
* ModelCreator constructor.
*
* @param string $tableName
* @param string $name
* @param null $files
*/
public function __construct($tableName, $name, $files = null)
{
$this->tableName = $tableName;
$this->name = $name;
$this->files = $files ?: app('files');
}
/**
* Create a new migration file.
*
* @param string $keyName
* @param bool|true $timestamps
* @param bool|false $softDeletes
*
* @throws \Exception
*
* @return string
*/
public function create($keyName = 'id', $timestamps = true, $softDeletes = false)
{
$path = $this->getpath($this->name);
if ($this->files->exists($path)) {
throw new \Exception("Model [$this->name] already exists!");
}
$stub = $this->files->get($this->getStub());
$stub = $this->replaceClass($stub, $this->name)
->replaceNamespace($stub, $this->name)
->replaceSoftDeletes($stub, $softDeletes)
->replaceTable($stub, $this->name)
->replaceTimestamp($stub, $timestamps)
->replacePrimaryKey($stub, $keyName)
->replaceSpace($stub);
$this->files->put($path, $stub);
return $path;
}
/**
* Get path for migration file.
*
* @param string $name
*
* @return string
*/
public function getPath($name)
{
$segments = explode('\\', $name);
array_shift($segments);
return app_path(implode('/', $segments)).'.php';
}
/**
* Get namespace of giving class full name.
*
* @param string $name
*
* @return string
*/
protected function getNamespace($name)
{
return trim(implode('\\', array_slice(explode('\\', $name), 0, -1)), '\\');
}
/**
* Replace class dummy.
*
* @param string $stub
* @param string $name
*
* @return $this
*/
protected function replaceClass(&$stub, $name)
{
$class = str_replace($this->getNamespace($name).'\\', '', $name);
$stub = str_replace('DummyClass', $class, $stub);
return $this;
}
/**
* Replace namespace dummy.
*
* @param string $stub
* @param string $name
*
* @return $this
*/
protected function replaceNamespace(&$stub, $name)
{
$stub = str_replace(
'DummyNamespace', $this->getNamespace($name), $stub
);
return $this;
}
/**
* Replace soft-deletes dummy.
*
* @param string $stub
* @param bool $softDeletes
*
* @return $this
*/
protected function replaceSoftDeletes(&$stub, $softDeletes)
{
$import = $use = '';
if ($softDeletes) {
$import = "use Illuminate\\Database\\Eloquent\\SoftDeletes;\n";
$use = "use SoftDeletes;\n";
}
$stub = str_replace(['DummyImportSoftDeletesTrait', 'DummyUseSoftDeletesTrait'], [$import, $use], $stub);
return $this;
}
/**
* Replace primarykey dummy.
*
* @param string $stub
* @param string $keyName
*
* @return $this
*/
protected function replacePrimaryKey(&$stub, $keyName)
{
$modelKey = $keyName == 'id' ? '' : "protected \$primaryKey = '$keyName';\n";
$stub = str_replace('DummyModelKey', $modelKey, $stub);
return $this;
}
/**
* Replace Table name dummy.
*
* @param string $stub
* @param string $name
*
* @return $this
*/
protected function replaceTable(&$stub, $name)
{
$class = str_replace($this->getNamespace($name).'\\', '', $name);
$table = Str::plural(strtolower($class)) !== $this->tableName ? "protected \$table = '$this->tableName';\n" : '';
$stub = str_replace('DummyModelTable', $table, $stub);
return $this;
}
/**
* Replace timestamps dummy.
*
* @param string $stub
* @param bool $timestamps
*
* @return $this
*/
protected function replaceTimestamp(&$stub, $timestamps)
{
$useTimestamps = $timestamps ? '' : "public \$timestamps = false;\n";
$stub = str_replace('DummyTimestamp', $useTimestamps, $stub);
return $this;
}
/**
* Replace spaces.
*
* @param string $stub
*
* @return mixed
*/
public function replaceSpace($stub)
{
return str_replace(["\n\n\n", "\n \n"], ["\n\n", ''], $stub);
}
/**
* Get stub path of model.
*
* @return string
*/
public function getStub()
{
return __DIR__.'/stubs/model.stub';
}
}

View File

@ -0,0 +1,123 @@
<?php
namespace DummyNamespace;
use DummyModelNamespace;
use App\Http\Controllers\Controller;
use Encore\Admin\Controllers\HasResourceActions;
use Encore\Admin\Form;
use Encore\Admin\Grid;
use Encore\Admin\Layout\Content;
use Encore\Admin\Show;
class DummyClass extends Controller
{
use HasResourceActions;
/**
* Index interface.
*
* @param Content $content
* @return Content
*/
public function index(Content $content)
{
return $content
->header('Index')
->description('description')
->body($this->grid());
}
/**
* Show interface.
*
* @param mixed $id
* @param Content $content
* @return Content
*/
public function show($id, Content $content)
{
return $content
->header('Detail')
->description('description')
->body($this->detail($id));
}
/**
* Edit interface.
*
* @param mixed $id
* @param Content $content
* @return Content
*/
public function edit($id, Content $content)
{
return $content
->header('Edit')
->description('description')
->body($this->form()->edit($id));
}
/**
* Create interface.
*
* @param Content $content
* @return Content
*/
public function create(Content $content)
{
return $content
->header('Create')
->description('description')
->body($this->form());
}
/**
* Make a grid builder.
*
* @return Grid
*/
protected function grid()
{
$grid = new Grid(new DummyModel);
$grid->id('ID');
$grid->created_at('Created at');
$grid->updated_at('Updated at');
return $grid;
}
/**
* Make a show builder.
*
* @param mixed $id
* @return Show
*/
protected function detail($id)
{
$show = new Show(DummyModel::findOrFail($id));
$show->id('ID');
$show->created_at('Created at');
$show->updated_at('Updated at');
return $show;
}
/**
* Make a form builder.
*
* @return Form
*/
protected function form()
{
$form = new Form(new DummyModel);
$form->display('ID');
$form->display('Created at');
$form->display('Updated at');
return $form;
}
}

View File

@ -0,0 +1,30 @@
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class DummyClass extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('DummyTable', function (Blueprint $table) {
DummyStructure
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('DummyTable');
}
}

View File

@ -0,0 +1,17 @@
<?php
namespace DummyNamespace;
use Illuminate\Database\Eloquent\Model;
DummyImportSoftDeletesTrait
class DummyClass extends Model
{
DummyUseSoftDeletesTrait
DummyModelTable
DummyModelKey
DummyTimestamp
}