- First commit, routes and database builder are usable

This commit is contained in:
Dave M. 2020-10-20 14:01:34 +00:00
commit 5f6ce82fd2
23 changed files with 787 additions and 0 deletions

21
LICENSE Normal file
View File

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2020 Dave Mc Nicoll
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.

20
composer.json Normal file
View File

@ -0,0 +1,20 @@
{
"name": "mcnd/lean-console",
"description": "A powerful enough debug console for LEAN.",
"type": "library",
"license": "MIT",
"authors": [
{
"name": "Dave Mc Nicoll",
"email": "mcndave@gmail.com"
}
],
"require": {
"mcnd/lean": "dev-master"
},
"autoload": {
"psr-4": {
"Lean\\Console\\": "src/"
}
}
}

View File

@ -0,0 +1,5 @@
{
"title": "Console -- LEAN",
"page-title": "LEAN - Console"
}

View File

@ -0,0 +1,32 @@
{
"lean": {
"label": "Lean",
"dashboard": "Dashboard"
},
"storage": {
"label": "Storage",
"summary": "Summary",
"database": "Database",
"session": "Session"
},
"templating": {
"label": "Templating",
"picea": "Picea"
},
"caching": {
"label": "Caching"
},
"request": {
"label": "Request",
"routes": "Routes"
},
"email": {
"label": "Email",
"routes": "Activity"
}
}

View File

@ -0,0 +1,12 @@
{
"title": "Request -- LEAN",
"page-title": "LEAN - Request",
"route": {
"header": "List of active routes",
"name": "Name",
"uri": "URI",
"methods": "HTTP Methods",
"target": "class::method()"
}
}

View File

@ -0,0 +1,25 @@
{
"title": "Storage -- LEAN",
"page-title": "LEAN - Storage",
"database": {
"header": "List of databases",
"name": "Database",
"adapter": "Adapter",
"host": "Hostname:port",
"username": "Username",
"connection": "Connection",
"table": {
"header": "Tables details",
"fields": "{$count} fields",
"name": "Name",
"actions": "Actions",
"table-fields" : "Fields in table",
"create": "Create table",
"createAll": "Create all tables",
"query": "SQL Query"
}
}
}

View File

@ -0,0 +1,7 @@
{
"title" : "Console -- LEAN",
"page-title": "Console",
"breadcrumb" : {
"index" : "Negundo"
}
}

View File

@ -0,0 +1,7 @@
{
"title" : "Setup -- LEAN",
"breadcrumb" : {
"index" : "Negundo"
}
}

View File

@ -0,0 +1,32 @@
<?php
namespace Lean\Console\Controller;
use Psr\Http\Message\{ ResponseInterface, ServerRequestInterface };
use Notes\Route\Annotation\Object\Route as RouteParam,
Notes\Route\Annotation\Method\Route,
Notes\Security\Annotation\Security,
Notes\Tell\Annotation\Language;
use \Lean\Console\Lib;
/**
* @Language("lean.console")
*/
class Console {
use Lib\ConsoleControllerTrait;
/**
* @Route("/", "name" => "lean.console:home")
* @param ServerRequestInterface $request
* @param array $arguments
* @return ResponseInterface
*/
public function home(ServerRequestInterface $request, array $arguments) : ResponseInterface
{
return $this->renderView("page/dashboard/index", [
]);
}
}

View File

@ -0,0 +1,41 @@
<?php
namespace Lean\Console\Controller;
use League\Route\Router;
use Psr\Http\Message\{ ResponseInterface, ServerRequestInterface };
use Notes\Route\Annotation\Object\Route as RouteParam,
Notes\Route\Annotation\Method\Route,
Notes\Security\Annotation\Security,
Notes\Tell\Annotation\Language;
use \Lean\Console\Lib;
use Picea\Extension\UrlExtension;
/**
* @Language("lean.route")
*/
class Request extends Console {
use Lib\ConsoleControllerTrait;
/**
* @Route("/request/routes", "name" => "lean.console:request.route")
* @param ServerRequestInterface $request
* @param array $arguments
* @return ResponseInterface
*/
public function routes(ServerRequestInterface $request, array $arguments) : ResponseInterface
{
$routes = $this->container->has(UrlExtension::class) ? $this->container->get(UrlExtension::class)->getRouteList(true) : false;
uksort($routes, function($a, $b) {
return $a <=> $b;
});
return $this->renderView("page/request/route", [
'routes' => $routes
]);
}
}

View File

@ -0,0 +1,55 @@
<?php
namespace Lean\Console\Controller;
use League\Route\Router;
use Picea\Ui\Method\FormHandler;
use Psr\Http\Message\{ ResponseInterface, ServerRequestInterface };
use Notes\Route\Annotation\Object\Route as RouteParam,
Notes\Route\Annotation\Method\Route,
Notes\Security\Annotation\Security,
Notes\Tell\Annotation\Language;
use \Lean\Console\Lib,
\Lean\Console\Form;
use Picea\Extension\UrlExtension;
use Ulmus\Annotation\Classes\Table,
Ulmus\Container\AdapterProxy;
/**
* @Language("lean.storage")
*/
class Storage extends Console {
use Lib\ConsoleControllerTrait;
/**
* @Route("/storage/database", "name" => "lean.console:request.route")
* @param ServerRequestInterface $request
* @param array $arguments
* @return ResponseInterface
*/
public function database(ServerRequestInterface $request, array $arguments) : ResponseInterface
{
$connections = $this->container->has(AdapterProxy::class) ? $this->container->get(AdapterProxy::class)->connections : false;
$migrations = $this->container->has(Lib\DatabaseMigrations::class) ? $this->container->get(Lib\DatabaseMigrations::class) : false;
$migrations->getEntities();
$form = new FormHandler($request, new Form\Database($migrations), null);
$context = $form->context;
return $this->renderView("page/storage/database", get_defined_vars());
}
public function createTable() : void
{
foreach($migrations->entities as $table) {
// $repository = $table
// $entity::repository()->createTable();
}
}
}

44
src/Entity/Column.php Normal file
View File

@ -0,0 +1,44 @@
<?php
namespace Lean\Console\Entity;
use Ulmus\Common\EntityField;
use Ulmus\Entity\Field\Datetime,
Ulmus\Entity\InformationSchema;
use Ulmus\EntityCollection;
use Ulmus\Migration\FieldDefinition;
/**
* @Table('name' => "columns", 'database' => "information_schema")
*/
class Column extends InformationSchema\Column
{
public function differsFromFieldDefinition(FieldDefinition $definition) : bool
{
switch(true) {
//case $definition->
// return true;
}
return false;
}
/* public static function entityCollection(...$arguments) : EntityCollection
{
return new class(...$arguments) extends EntityCollection
{
public function getEntityData() : array
{
return [
];
}
};
}
*/
}

96
src/Form/Database.php Normal file
View File

@ -0,0 +1,96 @@
<?php
namespace Lean\Console\Form;
use Picea\Ui\Method\{ FormInterface, FormContext, FormContextInterface };
use \Lean\Console\Lib;
use Picea\Compiler\Context;
use Psr\Http\Message\ServerRequestInterface;
use \Ulmus\Entity\InformationSchema\Table;
class Database implements FormInterface
{
protected ? Lib\DatabaseMigrations $migration;
public function __construct(Lib\DatabaseMigrations $migration)
{
$this->migration = $migration;
}
public function initialize(FormContextInterface $context) : void
{
$context->tableExist = [];
foreach($this->migration->entities as $entity => $table) {
$table = Table::repository()->where(Table::field('name'), $table->tableName())
->where(Table::field('schema'), $table->databaseName())
->loadOne();
if ( $table ) {
#$fields = $entiy::resolveEntity()->fieldList();
foreach($table->columns as $col) {
dump( $entity::field($col->name) );
}
# if columns are different
$context->status[$entity] = [
'msg' => "up-to-date",
'query' => "",
];
}
else {
$context->status[$entity] = [
'msg' => "unexisting",
'query' => $entity::repository()->createSqlQuery()->getSqlQuery(true),
];
}
}
if ( $context->formSent() ) {
}
}
public function validate(FormContextInterface $context) : bool
{
return $context->valid();
}
public function execute(FormContextInterface $context) : void
{
try {
if ( $context->create ?? false ) {
$context->create::repository()->createTable();
}
elseif ( $context->createAll ?? false ) {
foreach(explode(',', $context->createAll) as $entity) {
$entity::repository()->createTable();
}
}
$this->initialize($context);
}
catch (\Exception $ex) {
dump($ex);
$context->pushMessage(new Message(
"Une erreur inattendue semble avoir été provoquée lors de la sauvegarde des données. Le programmeur en charge du logiciel en a été avisé par courriel."
));
# @TODO ADD THEBUGS EMAIL SENDING HERE !!!
}
}
public function getContext(ServerRequestInterface $request) : FormContextInterface
{
return new class($request) extends FormContext {
public array $status = [];
public array $actions = [];
};
}
}

View File

@ -0,0 +1,28 @@
<?php
namespace Lean\Console\Lib;
use Psr\Container\ContainerInterface;
use Storage\Session;
use Picea;
/**
* @Language("lean.route")
* @RouteParam("methods" => [ 'GET', 'POST', 'DELETE' ], "base" => "/~")
* @Security('locked' => false)
*/
trait ConsoleControllerTrait
{
use \Lean\Console\ControllerTrait;
protected ContainerInterface $container;
public function __construct(? Picea\Picea $picea, Session $session, ContainerInterface $container) {
$this->picea = $picea;
$this->session = $session;
$this->container = $container;
// $this->mailer = $mailer;
}
}

View File

@ -0,0 +1,46 @@
<?php
namespace Lean\Console\Lib;
class DatabaseMigrations
{
public array $folderList;
public array $entities;
public function __construct(array $folders = [])
{
$this->folderList = $folders;
}
public function getEntities() : void
{
$this->entities = [];
foreach($this->folderList as $folder => $namespace) {
foreach(static::files($folder) as $file) {
$name = $file->getBasename(".".$file->getExtension());
$entity = rtrim($namespace, "\\") . "\\{$name}";
$this->entities[$entity] = $entity::resolveEntity();
}
}
}
protected static function files(string $path, string $fileExtension = "") : \Generator
{
if ( \file_exists($path) ) {
$iterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($path, \RecursiveDirectoryIterator::SKIP_DOTS), \RecursiveIteratorIterator::SELF_FIRST, \RecursiveIteratorIterator::CATCH_GET_CHILD);
foreach ($iterator as $file) {
if ( $file->isFile() || $file->isDir() ) {
if ($fileExtension && ( $file->getExtension() === $fileExtension )) {
yield $file;
}
else {
yield $file;
}
}
}
}
}
}

43
view/base/asset/lean.css Normal file
View File

@ -0,0 +1,43 @@
/*! minireset.css v0.0.6 | MIT License | github.com/jgthms/minireset.css */html,body,p,ol,ul,li,dl,dt,dd,blockquote,figure,fieldset,legend,textarea,pre,iframe,hr,h1,h2,h3,h4,h5,h6{margin:0;padding:0}h1,h2,h3,h4,h5,h6{font-size:100%;font-weight:normal}ul{list-style:none}button,input,select,textarea{margin:0}html{box-sizing:border-box}*,*::before,*::after{box-sizing:inherit}img,video{height:auto;max-width:100%}iframe{border:0}table{border-collapse:collapse;border-spacing:0}td,th{padding:0}td:not([align]),th:not([align]){text-align:left}
.grid{box-sizing:border-box;display:-webkit-flex;display:-ms-flexbox;display:-webkit-box;display:flex;-webkit-flex:0 1 auto;-ms-flex:0 1 auto;-webkit-box-flex:0;flex:0 1 auto;-webkit-flex-direction:row;-ms-flex-direction:row;-webkit-box-orient:horizontal;-webkit-box-direction:normal;flex-direction:row;-webkit-flex-wrap:wrap;-ms-flex-wrap:wrap;flex-wrap:wrap;margin:0 -8px 0 -8px}.grid.grid-nogutter{margin:0}.grid.grid-nogutter>.col{padding:0}.col{box-sizing:border-box;-webkit-flex:0 0 auto;-ms-flex:0 0 auto;flex:0 0 auto;-webkit-flex-grow:1;-ms-flex-positive:1;-webkit-box-flex:1;flex-grow:1;-ms-flex-preferred-size:0;-webkit-flex-basis:0;flex-basis:0;max-width:100%;min-width:0;padding:0 8px 0 8px}.col-align-top{-webkit-align-self:flex-start;-ms-flex-item-align:start;align-self:flex-start}.col-align-bottom{align-self:flex-end}.col-align-middle{-webkit-align-self:center;-ms-flex-item-align:center;align-self:center}.col-top{justify-content:flex-start !important;flex-direction:column;display:flex}.col-bottom{justify-content:flex-end !important;flex-direction:column;display:flex}.col-middle{justify-content:center;flex-direction:column;display:flex}.grid-start{-webkit-box-pack:start;-ms-flex-pack:start;justify-content:flex-start}.grid-center{-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center}.grid-end{-webkit-box-pack:end;-ms-flex-pack:end;justify-content:flex-end}.grid-around{justify-content:space-around}.grid-between{-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between}.col-first{-webkit-box-ordinal-group:0;-ms-flex-order:-1;order:-1}.col-last{-webkit-box-ordinal-group:2;-ms-flex-order:1;order:1}.grid-reverse{-webkit-box-orient:horizontal;-webkit-box-direction:reverse;-ms-flex-direction:row-reverse;flex-direction:row-reverse}.col-fixed{flex:initial}.col-grow-2{flex-grow:2}.col-grow-3{flex-grow:3}.col-grow-4{flex-grow:4}.col-grow-5{flex-grow:5}.col-grow-6{flex-grow:6}.col-grow-7{flex-grow:7}.col-grow-8{flex-grow:8}.col-grow-9{flex-grow:9}.col-grow-10{flex-grow:10}.col-grow-11{flex-grow:11}.col-1{-ms-flex-preferred-size:8.33333%;-webkit-flex-basis:8.33333%;flex-basis:8.33333%;max-width:8.33333%}.col-2{-ms-flex-preferred-size:16.66667%;-webkit-flex-basis:16.66667%;flex-basis:16.66667%;max-width:16.66667%}.col-3{-ms-flex-preferred-size:25%;-webkit-flex-basis:25%;flex-basis:25%;max-width:25%}.col-4{-ms-flex-preferred-size:33.33333%;-webkit-flex-basis:33.33333%;flex-basis:33.33333%;max-width:33.33333%}.col-5{-ms-flex-preferred-size:41.66667%;-webkit-flex-basis:41.66667%;flex-basis:41.66667%;max-width:41.66667%}.col-6{-ms-flex-preferred-size:50%;-webkit-flex-basis:50%;flex-basis:50%;max-width:50%}.col-7{-ms-flex-preferred-size:58.33333%;-webkit-flex-basis:58.33333%;flex-basis:58.33333%;max-width:58.33333%}.col-8{-ms-flex-preferred-size:66.66667%;-webkit-flex-basis:66.66667%;flex-basis:66.66667%;max-width:66.66667%}.col-9{-ms-flex-preferred-size:75%;-webkit-flex-basis:75%;flex-basis:75%;max-width:75%}.col-10{-ms-flex-preferred-size:83.33333%;-webkit-flex-basis:83.33333%;flex-basis:83.33333%;max-width:83.33333%}.col-11{-ms-flex-preferred-size:91.66667%;-webkit-flex-basis:91.66667%;flex-basis:91.66667%;max-width:91.66667%}.col-12{-ms-flex-preferred-size:100%;-webkit-flex-basis:100%;flex-basis:100%;max-width:100%}@media only screen and (max-width: 480px){.col-sm{flex:100%;max-width:100%}}@media only screen and (max-width: 624px){.col-md{flex:100%;max-width:100%}}@media only screen and (max-width: 744px){.col-lg{flex:100%;max-width:100%}}
.align-items-center{align-items:center}.align-items-top{align-items:start}
:root {
--color-red: #d47474
}
.text-center{text-align:center;}.text-right{text-align:right}.text-left{text-align:left;}
[class^="btn-"], [class~=" btn-"] { display: inline-block; font-weight: 400; text-align: center; white-space: nowrap; vertical-align: middle; user-select: none; border: 1px solid transparent; padding: .375rem .75rem; font-size: 1rem; line-height: 1.5; border-radius: .25rem; cursor:pointer; transition: color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out; background:rgba(0,0,0,0.3); color:rgba(255,255,255,0.8); }
[class^="btn-"]:hover, [class~=" btn-"]:hover {border-color: rgba(0, 0, 0, 0.5);text-decoration: underline;}
.btn-red {background: rgba(255,0,0,0.5);color: rgba(255, 255, 255, 0.9);}
code {white-space:pre;font-family:monospace;color: #cacaca;background: #434446;display: block;padding: 1rem;line-height: 1.5em;border: 1px solid #2e2e30;}
h1 {font-size:2rem;color:#525960}
body {font-family:Helvetica Neue, Helvetica, Arial, sans-serif;}
summary {cursor:pointer;}
#body-main {background:#ccc;min-height:calc(100vh - 90px);display:flex}
/* Header */
#body-header {background: #385075; color:#fff; min-height:60px;line-height:60px;padding:0 1rem}
#body-header .page-title {font-size:1.5rem}
#body-header .page-title:before {content:"🔳"; margin-right:10px}
/* Footer */
#body-footer {background: #2e2e30;color: #fff;height: 30px;line-height: 30px;font-size: 0.8rem;padding: 0 0.5rem;text-align: right;border-top:2px solid #434446;}
/* Sidebar */
#main-nav {background:#393a3c;border-top:2px solid #172842;min-width:210px;color:#fff;font-family:consolas, courier new, monospace;font-size:0.9rem}
#main-nav label {background:rgba(0,0,0,0.2);text-transform:uppercase ; display:block;padding:0.8rem 1rem;font-size:1.15em;}
#main-nav label:before {content:"⊙"; margin-right:10px}
#main-nav a {color: #efecec;text-decoration:none;display:block;padding:0rem 1rem;background:rgba(255,255,255,0.05);transition:all 0.3s ease;line-height:3em;font-size:0.9em}
#main-nav a:before {content:""; margin-right:10px;line-height:3em}
#main-nav a:hover {background:rgba(255,255,255,0.1)}
#main-nav li + li {border-top:1px solid #555}
/* Main content */
#main-content {padding:4vh 2vw;border-top:2px solid #172842;width:100%;}
#main-content section {border-radius:4px;box-shadow: 1px 1px 3px rgba(0,0,0,0.5)}
#main-content section article {background:#fff;padding:2vh 1vw;}
#main-content section header {background:#e8e8e8;padding:2vh 1vw;}

View File

@ -0,0 +1,44 @@
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="description" content="LEAN console environment">
<meta name="author" content="Dave Mc Nicoll">
<meta name="msapplication-TileColor" content="#ccc">
<meta name="theme-color" content="#ffffff">
<link rel="icon" href="data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%2016%2016'%3E%3Ctext%20x='0'%20y='14'%3E🔳%3C/text%3E%3C/svg%3E" type="image/svg+xml" />
<title>{{ title() }}</title>
<style>
{% include "lean/base/asset/lean.css" %}
</style>
{% section "head" %}{% endsection %}
</head>
<body>
<header id="body-header">
{% section "header" %}{% endsection %}
</header>
<main id="body-main">
{% section "nav" %}
{% view "lean/base/console/main-nav" %}
{% endsection %}
<section id="main-content">
{% section "content" %}{% endsection %}
</section>
</main>
<footer id="body-footer">
{% section "footer" %}
&copy; {{ date('Y') }} - LEAN
{% endsection %}
</footer>
</body>
</html>

View File

@ -0,0 +1,28 @@
<nav id="main-nav">
<label>{% lang "lean.nav.lean.label" %}</label>
<ul>
<li><a href="{{ url('~') }}">{% lang "lean.nav.lean.dashboard" %}</a></li>
</ul>
<label>{% lang "lean.nav.request.label" %}</label>
<ul>
<li><a href="{{ url('~/request/routes') }}">{% lang "lean.nav.request.routes" %}</a></li>
</ul>
<label>{% lang "lean.nav.storage.label" %}</label>
<ul>
{#<li><a href="{{ url('~/storage') }}">{% lang "lean.nav.storage.summary" %}</a></li>#}
<li><a href="{{ url('~/storage/database') }}">{% lang "lean.nav.storage.database" %}</a></li>
<li><a href="{{ url('~/storage/session') }}">{% lang "lean.nav.storage.session" %}</a></li>
</ul>
<label>{% lang "lean.nav.templating.label" %}</label>
<ul>
<li><a href="{{ url('~/templating/picea') }}">{% lang "lean.nav.templating.picea" %}</a></li>
</ul>
<label>{% lang "lean.nav.caching.label" %}</label>
<ul>
<li><a href="{{ url('~/storage') }}">{% lang "lean.nav.templating.picea" %}</a></li>
</ul>
</nav>

View File

@ -0,0 +1,28 @@
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="description" content="LEAN console environment">
<meta name="author" content="Dave Mc Nicoll">
<meta name="msapplication-TileColor" content="#ccc">
<meta name="theme-color" content="#ffffff">
<link rel="icon" href="data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%2016%2016'%3E%3Ctext%20x='0'%20y='14'%3E🔳%3C/text%3E%3C/svg%3E" type="image/svg+xml" />
<title>{{ title() }}</title>
<style>
{% include "lean/base/asset/lean.css" %}
</style>
{% section "head" %}{% endsection %}
</head>
<body>
<header>{% section "header" %}{% endsection %}</header>
<main>{% section "content" %}{% endsection %}</main>
<footer>{% section "footer" %}{% endsection %}</footer>
</body>
</html>

View File

@ -0,0 +1,13 @@
{% extends "lean/base/console/layout" %}
{% language.set "lean.console" %}
{% title _("title") %}
{% section "header" %}
<div class="page-title">{% _ "page-title" %}</div>
{% endsection %}
{% section "content" %}
{% endsection %}

View File

@ -0,0 +1,49 @@
{% extends "lean/base/console/layout" %}
{% language.set "lean.request" %}
{% title _("title") %}
{% section "header" %}
<div class="page-title">{% _ "page-title" %}</div>
{% endsection %}
{% section "content" %}
<section>
<header>
<h1>{% _ 'route.header' %}</h1>
</header>
<article class="routes">
{% if $routes %}
<strong class="grid">
<div class="col route-name">{% _ 'route.name' %}</div>
<div class="col route-uri">{% _ 'route.uri' %}</div>
<div class="col route-methods">{% _ 'route.methods' %}</div>
<div class="col controller-method">{% _ 'route.target' %}</div>
</strong>
{% foreach $routes as $name => $route %}
<div class="grid">
<strong class="col route-name">{{ $name }}</strong>
<div class="col route-uri">{{ $route['route'] }}</div>
<div class="col route-methods">{{ implode(', ', $route['routeMethods']) }}</div>
<div class="col controller-method">
<span class="class">{{ $route['class'] }}</span>::<span>{{ $route['classMethod'] }}</span>
</div>
</div>
{% endforeach %}
{% else %}
{% _ 'routeless' %}
{% endif %}
</article>
</section>
<style>
.routes strong.grid {background:#172842;color:#eeffee}
.routes .grid {padding:0.66rem 0.3rem}
.routes .grid strong {color:#444}
.routes .grid:nth-child(even) {background: #e3e3e3 }
</style>
{% endsection %}

View File

@ -0,0 +1,7 @@
{% extends "lean/base/layout/console" %}
{% section "content" %}
Welcome to Setup!
{% endsection %}

View File

@ -0,0 +1,104 @@
{% extends "lean/base/console/layout" %}
{% language.set "lean.storage" %}
{% title _("title") %}
{% section "header" %}
<div class="page-title">{% _ "page-title" %}</div>
{% endsection %}
{% section "content" %}
<section>
<header>
<h1>{% _ 'database.header' %}</h1>
</header>
<article class="databases">
{% if $connections %}
<strong class="grid db-grid">
<div class="col db-connection">{% _ 'database.connection' %}</div>
<div class="col db-adapter">{% _ 'database.adapter' %}</div>
<div class="col db-host">{% _ 'database.host' %}</div>
<div class="col db-name">{% _ 'database.name' %}</div>
<div class="col db-username">{% _ 'database.username' %}</div>
</strong>
{% foreach $connections as $connection %}
{% php $conf = $connection->getConfiguration(); %}
<div class="grid db-grid">
<div class="col db-connection">{{ $connection->name }}</div>
<div class="col db-adapter">{{ $conf['adapter'] }}</div>
<div class="col db-host">{{ $conf['host'] }}:{{ $conf['port'] }}</div>
<div class="col db-name">{{ $conf['database'] }}</div>
<div class="col db-username">{{ $conf['username'] }}</div>
</div>
<details>
<summary>
{% _ 'database.table.header' %}
</summary>
<div class="tables">
<strong class="grid table-item">
<div class="col">{% _ 'database.table.name' %}</div>
<div class="col">{% _ 'database.table.table-fields' %}</div>
<div class="col col-grow-2">{% _ 'database.table.query' %}</div>
<div class="col text-right">{% _ 'database.table.actions' %}</div>
</strong>
{% ui.form.post "database" %}
{% foreach array_filter($migrations->entities, fn($e) => $e->databaseAdapter() === $connection) as $entity => $item %}
<div class="grid table-item align-items-center">
<div class="col"><span>📄</span> <span>{{ $item->tableName() }}</span></div>
<div class="col">{% _ "database.table.fields", [ 'count' => count( $item->fieldList() ) ] %}</div>
<div class="col col-grow-2">
{% if $context->status[$entity]['query'] %}
<code>{{ $context->status[$entity]['query'] }}</code>
{% else %}
<div>🗸 This table is up-to-date</div>
{% endif %}
</div>
<div class="col">
{% switch $context->status[$entity]['msg'] %}
{% case 'unexisting' %}
{% php $createAll[] = $entity %}
<div class="text-right">
<button name="create" value="{{$entity}}" class="btn-blue">🗸 {% _ 'database.table.create' %}</button>
</div>
{% break %}
{% endswitch %}
</div>
</div>
{% endforeach %}
{% if ($createAll ?? 0) > 1 %}
<div class="text-right" style="padding:0.5rem 0">
<button name="createAll" value="{{ implode(',', $createAll) }}" class="btn-red">🗸 {% _ 'database.table.createAll' %}</button>
</div>
{% endif %}
{% ui.endform %}
</div>
</details>
{% endforeach %}
{% endif %}
</article>
</section>
<style>
.databases strong.grid {background:#172842;color:#eeffee}
.databases .db-grid {padding:0.66rem 0.3rem}
.databases .db-grid strong {color:#444}
.databases .db-grid:nth-child(even) {background: #e3e3e3 }
.databases .tables .table-item {padding:0.33rem 0;background:rgba(255,255,255,0.1)}
.databases .tables .table-item:nth-child(even) {background:rgba(255,255,255,0.2)}
.databases summary {background: #172841;color:#fff;margin: 0 -8px 0 -8px;padding:7px 10px}
.tables {background: #172841;margin: 0 -8px 0 -8px;color: #fff;padding: 3px 10px;}
.tables label {padding:10px 5px;display:block}
</style>
{% endsection %}