Create everything required to login and register

This commit is contained in:
Tim 2023-04-02 19:34:00 +02:00
parent 82d5625e60
commit 5be77eeba1
26 changed files with 1664 additions and 55 deletions

View File

@ -13,14 +13,24 @@
"symfony/console": "6.2.*", "symfony/console": "6.2.*",
"symfony/dotenv": "6.2.*", "symfony/dotenv": "6.2.*",
"symfony/flex": "^2", "symfony/flex": "^2",
"symfony/form": "6.2.*",
"symfony/framework-bundle": "6.2.*", "symfony/framework-bundle": "6.2.*",
"symfony/monolog-bundle": "^3.0",
"symfony/runtime": "6.2.*", "symfony/runtime": "6.2.*",
"symfony/security-bundle": "6.2.*", "symfony/security-bundle": "6.2.*",
"symfony/twig-bundle": "6.2.*", "symfony/twig-bundle": "6.2.*",
"symfony/validator": "6.2.*",
"symfony/yaml": "6.2.*", "symfony/yaml": "6.2.*",
"twig/extra-bundle": "^2.12|^3.0", "twig/extra-bundle": "^2.12|^3.0",
"twig/twig": "^2.12|^3.0" "twig/twig": "^2.12|^3.0"
}, },
"require-dev": {
"deployer/deployer": "^7.3",
"symfony/debug-bundle": "6.2.*",
"symfony/maker-bundle": "^1.48",
"symfony/stopwatch": "6.2.*",
"symfony/web-profiler-bundle": "6.2.*"
},
"config": { "config": {
"allow-plugins": { "allow-plugins": {
"php-http/discovery": true, "php-http/discovery": true,
@ -68,8 +78,5 @@
"allow-contrib": false, "allow-contrib": false,
"require": "6.2.*" "require": "6.2.*"
} }
},
"require-dev": {
"symfony/maker-bundle": "^1.48"
} }
} }

887
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -8,4 +8,7 @@ return [
Twig\Extra\TwigExtraBundle\TwigExtraBundle::class => ['all' => true], Twig\Extra\TwigExtraBundle\TwigExtraBundle::class => ['all' => true],
Doctrine\Bundle\DoctrineBundle\DoctrineBundle::class => ['all' => true], Doctrine\Bundle\DoctrineBundle\DoctrineBundle::class => ['all' => true],
Doctrine\Bundle\MigrationsBundle\DoctrineMigrationsBundle::class => ['all' => true], Doctrine\Bundle\MigrationsBundle\DoctrineMigrationsBundle::class => ['all' => true],
Symfony\Bundle\WebProfilerBundle\WebProfilerBundle::class => ['dev' => true, 'test' => true],
Symfony\Bundle\MonologBundle\MonologBundle::class => ['all' => true],
Symfony\Bundle\DebugBundle\DebugBundle::class => ['dev' => true],
]; ];

View File

@ -0,0 +1,5 @@
when@dev:
debug:
# Forwards VarDumper Data clones to a centralized server allowing to inspect dumps on CLI or in your browser.
# See the "server:dump" command to start a new server.
dump_destination: "tcp://%env(VAR_DUMPER_SERVER)%"

View File

@ -0,0 +1,61 @@
monolog:
channels:
- deprecation # Deprecations are logged in the dedicated "deprecation" channel when it exists
when@dev:
monolog:
handlers:
main:
type: stream
path: "%kernel.logs_dir%/%kernel.environment%.log"
level: debug
channels: ["!event"]
# uncomment to get logging in your browser
# you may have to allow bigger header sizes in your Web server configuration
#firephp:
# type: firephp
# level: info
#chromephp:
# type: chromephp
# level: info
console:
type: console
process_psr_3_messages: false
channels: ["!event", "!doctrine", "!console"]
when@test:
monolog:
handlers:
main:
type: fingers_crossed
action_level: error
handler: nested
excluded_http_codes: [404, 405]
channels: ["!event"]
nested:
type: stream
path: "%kernel.logs_dir%/%kernel.environment%.log"
level: debug
when@prod:
monolog:
handlers:
main:
type: fingers_crossed
action_level: error
handler: nested
excluded_http_codes: [404, 405]
buffer_size: 50 # How many messages should be saved? Prevent memory leaks
nested:
type: stream
path: php://stderr
level: debug
formatter: monolog.formatter.json
console:
type: console
process_psr_3_messages: false
channels: ["!event", "!doctrine"]
deprecation:
type: stream
channels: [deprecation]
path: php://stderr

View File

@ -5,7 +5,7 @@ security:
# https://symfony.com/doc/current/security.html#loading-the-user-the-user-provider # https://symfony.com/doc/current/security.html#loading-the-user-the-user-provider
providers: providers:
# used to reload user from session & other features (e.g. switch_user) # used to reload user from session & other features (e.g. switch_user)
app_user_provider: user_provider:
entity: entity:
class: App\Entity\User class: App\Entity\User
property: username property: username
@ -15,7 +15,19 @@ security:
security: false security: false
main: main:
lazy: true lazy: true
provider: app_user_provider provider: user_provider
logout:
path: logout
form_login:
login_path: login
check_path: login
remember_me:
secret: '%kernel.secret%' # required
lifetime: 2419200 # 4 weeks in seconds
secured_area:
form_login:
enable_csrf: true
# activate different ways to authenticate # activate different ways to authenticate
# https://symfony.com/doc/current/security.html#the-firewall # https://symfony.com/doc/current/security.html#the-firewall
@ -26,8 +38,12 @@ security:
# Easy way to control access for large sections of your site # Easy way to control access for large sections of your site
# Note: Only the *first* access control that matches will be used # Note: Only the *first* access control that matches will be used
access_control: access_control:
# - { path: ^/admin, roles: ROLE_ADMIN } - { path: ^/login$, role: PUBLIC_ACCESS }
# - { path: ^/profile, roles: ROLE_USER } - { path: ^/logout$, role: ROLE_USER }
- { path: ^/register, role: ROLE_ADMIN }
- { path: ^/admin, role: ROLE_ADMIN }
- { path: ^/, role: ROLE_USER }
when@test: when@test:
security: security:

View File

@ -0,0 +1,13 @@
framework:
validation:
email_validation_mode: html5
# Enables validator auto-mapping support.
# For instance, basic validation constraints will be inferred from Doctrine's metadata.
#auto_mapping:
# App\Entity\: []
when@test:
framework:
validation:
not_compromised_password: false

View File

@ -0,0 +1,17 @@
when@dev:
web_profiler:
toolbar: true
intercept_redirects: false
framework:
profiler:
only_exceptions: false
collect_serializer_data: true
when@test:
web_profiler:
toolbar: false
intercept_redirects: false
framework:
profiler: { collect: false }

View File

@ -0,0 +1,8 @@
when@dev:
web_profiler_wdt:
resource: '@WebProfilerBundle/Resources/config/routing/wdt.xml'
prefix: /_wdt
web_profiler_profiler:
resource: '@WebProfilerBundle/Resources/config/routing/profiler.xml'
prefix: /_profiler

View File

@ -20,5 +20,6 @@ services:
- '../src/Entity/' - '../src/Entity/'
- '../src/Kernel.php' - '../src/Kernel.php'
# add more service definitions when explicit configuration is needed App\Service\LastRelease:
# please note that last definitions always *replace* previous ones arguments:
- '%kernel.project_dir%/release.json'

108
deploy.php Normal file
View File

@ -0,0 +1,108 @@
<?php
namespace Deployer;
require_once 'recipe/common.php';
// Project name
set('application', 'snips');
// Project repository
set('repository', 'git@git.loken.nl:ardent/Snips.git');
// [Optional] Allocate tty for git clone. Default value is false.
set('git_tty', true);
// Shared files/dirs between deploys
set('shared_dirs', ['var/log', 'var/sessions']);
set('shared_files', ['.env.local']);
//set('writable_dirs', ['var']);
set('migrations_config', '');
set('allow_anonymous_stats', false);
// Hosts
host('snips.loken.nl')
->setRemoteUser('www-data')
->set('branch', function () {
return input()->getOption('branch') ?: 'develop';
})
->set('deploy_path', '~/snips.loken.nl');
set('bin/console', function () {
return parse('{{release_path}}/bin/console');
});
set('console_options', function () {
return '--no-interaction';
});
desc('Clear cache');
task('cache:clear', function () {
run('{{bin/php}} {{bin/console}} cache:clear {{console_options}} --no-warmup');
});
desc('Warm up cache');
task('cache:warmup', function () {
run('{{bin/php}} {{bin/console}} cache:warmup {{console_options}}');
});
desc('Migrate database');
task('database:migrate', function () {
// $options = '--allow-no-migration';
// if (get('migrations_config') !== '') {
// $options = sprintf('%s --configuration={{release_path}}/{{migrations_config}}', $options);
// }
//
// run(sprintf('{{bin/php}} {{bin/console}} doctrine:migrations:migrate %s {{console_options}}', $options));
run('{{bin/php}} {{bin/console}} doctrine:schema:update --force');
});
task('deployment:log', function () { //https://stackoverflow.com/questions/59686270/how-to-log-deployments-in-deployer
$branch = parse('{{branch}}');
$date = date('Y-m-d H:i:s');
$commitHashShort = runLocally('git rev-parse --short HEAD');
// $commitHash = runLocally('git rev-parse HEAD');
$commit = explode(PHP_EOL, runLocally('git log -1 --pretty="%H%n%ci"'));
$commitHash = $commit[0];
$commitDate = $commit[1];
// $line = sprintf('%s %s branch="%s" hash="%s"', $date, $commitHashShort, $branch, $commitHash);
$projectUrlBase = 'https://git.loken.nl/ardent/AnimeRSS4';
$array = [
'branch' => $branch,
'branchUrl' => sprintf('%s/src/branch/%s', $projectUrlBase, $branch),
'date' => $date,
'commitHashShort' => $commitHashShort,
'commitHashLong' => $commitHash,
'commitDate' => $commitDate,
'commitUrl' => sprintf('%s/commit/%s', $projectUrlBase, $commitHash),
'projectUrl' => $projectUrlBase,
];
$json = json_encode($array, JSON_PRETTY_PRINT);
runLocally("echo '$json' > release.json");
upload('release.json', '{{release_path}}/release.json');
});
//desc('Deploy project');
//task('deploy', [
// 'deployment:log',
//]);
desc('Deploy project');
task('deploy', [
'deploy:prepare',
'deploy:vendors',
'cache:clear',
'cache:warmup',
'database:migrate',
'deployment:log',
'deploy:symlink',
'deploy:unlock',
'deploy:cleanup',
]);
after('deploy', 'deploy:success');
// [Optional] if deploy fails automatically unlock.
after('deploy:failed', 'deploy:unlock');

View File

View File

@ -0,0 +1,19 @@
<?php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
class HomeController extends AbstractController
{
#[Route('/', name: 'home')]
public function home(): Response
{
return $this->redirectToRoute('task_view');
// return $this->render('simple.html.twig', [
// 'text' => 'Welcome!'
// ]);
}
}

View File

@ -0,0 +1,72 @@
<?php
namespace App\Controller;
use App\Entity\User;
use App\Form\RegistrationFormType;
use Doctrine\ORM\EntityManagerInterface;
use Exception;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Security\Http\Authentication\AuthenticationUtils;
class SecurityController extends AbstractController
{
#[Route('/login', name: 'login')]
public function login(AuthenticationUtils $authenticationUtils): Response
{
$error = $authenticationUtils->getLastAuthenticationError();
$lastUsername = $authenticationUtils->getLastUsername();
return $this->render('security/login.html.twig', [
'last_username' => $lastUsername,
'error' => $error,
]);
}
#[Route('/logout', name: 'logout')]
public function logout(): void
{
// controller can be blank: it will never be called!
throw new Exception('Don\'t forget to activate logout in security.yaml');
}
#[Route('/register', name: 'register')]
public function register(
Request $request,
UserPasswordHasherInterface $userPasswordHasher,
EntityManagerInterface $entityManager,
): Response
{
$user = new User();
$form = $this->createForm(RegistrationFormType::class, $user);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
if ($form->get('plainPassword')->getData() !== $form->get('plainPasswordRepeated')->getData()) {
$this->addFlash('error', 'Password and password repeated must be the same');
} else {
$user->setPassword(
$userPasswordHasher->hashPassword(
$user,
$form->get('plainPassword')->getData()
)
);
$entityManager->persist($user);
$entityManager->flush();
// do anything else you need here, like send an email
$this->addFlash('success', sprintf('Successfully registered user %s', $user->getUsername()));
return $this->redirectToRoute('register');
}
}
return $this->render('security/register.html.twig', [
'registrationForm' => $form->createView(),
]);
}
}

View File

@ -0,0 +1,63 @@
<?php
namespace App\Controller;
use App\Entity\User;
use App\Form\ProfileType;
use App\Form\UserSettingsType;
use App\Service\LastRelease;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Uid\Uuid;
#[Route('/user', name: 'user')]
class UserController extends AbstractController
{
public function __construct(
private EntityManagerInterface $em,
)
{
}
#[Route('/profile', name: '_profile')]
public function profile(
Request $request,
UserPasswordHasherInterface $userPasswordHasher,
LastRelease $lastRelease,
): Response
{
$user = $this->getUser();
$form = $this->createForm(ProfileType::class, $user);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
if (!empty($form->get('plainPassword')->getData())) {
if ($form->get('plainPassword')->getData() !== $form->get('plainPasswordRepeated')->getData()) {
$this->addFlash('error', 'Password and password repeated must be the same, password not changed');
} else {
$this->addFlash('success', 'Password updated successfully');
$user
->setPassword(
$userPasswordHasher->hashPassword(
$user,
$form->get('plainPassword')->getData()
)
);
}
}
$this->addFlash('success', 'Profile updated successfully');
$this->em->persist($user);
$this->em->flush();
}
return $this->render('user/profile.html.twig', [
'form' => $form->createView(),
'release' => $lastRelease,
]);
}
}

49
src/Form/ProfileType.php Normal file
View File

@ -0,0 +1,49 @@
<?php
namespace App\Form;
use App\Entity\User;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\PasswordType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Validator\Constraints\Length;
class ProfileType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
->add('email')
->add('plainPassword', PasswordType::class, [
'required' => false,
'mapped' => false,
'attr' => ['autocomplete' => 'new-password'],
'label' => 'Password',
'constraints' => [
new Length([
'min' => 8,
'minMessage' => 'Your password should be at least {{ limit }} characters',
// max length allowed by Symfony for security reasons
'max' => 4096,
]),
],
])
->add('plainPasswordRepeated', PasswordType::class, [
'required' => false,
'mapped' => false,
'attr' => ['autocomplete' => 'new-password'],
'label' => 'Password repeated',
])
->add('save', SubmitType::class)
;
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'data_class' => User::class,
]);
}
}

View File

@ -0,0 +1,64 @@
<?php
namespace App\Form;
use App\Entity\User;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
use Symfony\Component\Form\Extension\Core\Type\EmailType;
use Symfony\Component\Form\Extension\Core\Type\PasswordType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Validator\Constraints\IsTrue;
use Symfony\Component\Validator\Constraints\Length;
use Symfony\Component\Validator\Constraints\NotBlank;
class RegistrationFormType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
->add('username')
->add('name')
->add('plainPassword', PasswordType::class, [
// instead of being set onto the object directly,
// this is read and encoded in the controller
'mapped' => false,
'attr' => ['autocomplete' => 'new-password'],
'label' => 'Password',
'constraints' => [
new NotBlank([
'message' => 'Please enter a password',
]),
new Length([
'min' => 8,
'minMessage' => 'Your password should be at least {{ limit }} characters',
// max length allowed by Symfony for security reasons
'max' => 4096,
]),
],
])
->add('plainPasswordRepeated', PasswordType::class, [
'mapped' => false,
'label' => 'Password repeated',
])
->add('email', EmailType::class)
// ->add('agreeTerms', CheckboxType::class, [
// 'mapped' => false,
// 'constraints' => [
// new IsTrue([
// 'message' => 'You should agree to our terms.',
// ]),
// ],
// ])
->add('register', SubmitType::class);
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'data_class' => User::class,
]);
}
}

View File

@ -0,0 +1,68 @@
<?php
namespace App\Service;
use JetBrains\PhpStorm\ArrayShape;
class LastRelease
{
#[ArrayShape([
"branch" => "string",
"date" => "string",
"commitHashShort" => "string",
"commitHashLong" => "string",
"commitDate" => "string",
"branchUrl" => "string",
"projectUrl" => "string",
"commitUrl" => "string",
])]
private array $lastRelease = [];
public function __construct(string $jsonFile)
{
if (file_exists($jsonFile)) {
$this->lastRelease = json_decode(file_get_contents($jsonFile), true);
}
}
public function getBranch(): string
{
return $this->lastRelease['branch'] ?? '-';
}
public function getBranchUrl(): string
{
return $this->lastRelease['branchUrl'] ?? '#';
}
public function getProjectUrl(): string
{
return $this->lastRelease['projectUrl'] ?? '#';
}
public function getCommitUrl(): string
{
return $this->lastRelease['commitUrl'] ?? '#';
}
public function getDate(): string
{
return $this->lastRelease['date'] ?? '-';
}
public function getCommitHashShort(): string
{
return $this->lastRelease['commitHashShort'] ?? '-';
}
public function getCommitHashLong(): string
{
return $this->lastRelease['commitHashLong'] ?? '-';
}
public function getCommitDate(): string
{
return $this->lastRelease['commitDate'] ?? '-';
}
}

View File

@ -38,6 +38,18 @@
"bin/console" "bin/console"
] ]
}, },
"symfony/debug-bundle": {
"version": "6.2",
"recipe": {
"repo": "github.com/symfony/recipes",
"branch": "main",
"version": "5.3",
"ref": "5aa8aa48234c8eb6dbdd7b3cd5d791485d2cec4b"
},
"files": [
"config/packages/debug.yaml"
]
},
"symfony/flex": { "symfony/flex": {
"version": "2.2", "version": "2.2",
"recipe": { "recipe": {
@ -78,6 +90,18 @@
"ref": "fadbfe33303a76e25cb63401050439aa9b1a9c7f" "ref": "fadbfe33303a76e25cb63401050439aa9b1a9c7f"
} }
}, },
"symfony/monolog-bundle": {
"version": "3.8",
"recipe": {
"repo": "github.com/symfony/recipes",
"branch": "main",
"version": "3.7",
"ref": "213676c4ec929f046dfde5ea8e97625b81bc0578"
},
"files": [
"config/packages/monolog.yaml"
]
},
"symfony/routing": { "symfony/routing": {
"version": "6.2", "version": "6.2",
"recipe": { "recipe": {
@ -116,6 +140,31 @@
"templates/base.html.twig" "templates/base.html.twig"
] ]
}, },
"symfony/validator": {
"version": "6.2",
"recipe": {
"repo": "github.com/symfony/recipes",
"branch": "main",
"version": "5.3",
"ref": "c32cfd98f714894c4f128bb99aa2530c1227603c"
},
"files": [
"config/packages/validator.yaml"
]
},
"symfony/web-profiler-bundle": {
"version": "6.2",
"recipe": {
"repo": "github.com/symfony/recipes",
"branch": "main",
"version": "6.1",
"ref": "e42b3f0177df239add25373083a564e5ead4e13a"
},
"files": [
"config/packages/web_profiler.yaml",
"config/routes/web_profiler.yaml"
]
},
"twig/extra-bundle": { "twig/extra-bundle": {
"version": "v3.5.1" "version": "v3.5.1"
} }

View File

@ -1,19 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>{% block title %}Welcome!{% endblock %}</title>
<link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 128 128%22><text y=%221.2em%22 font-size=%2296%22>⚫️</text></svg>">
{# Run `composer require symfony/webpack-encore-bundle` to start using Symfony UX #}
{% block stylesheets %}
{{ encore_entry_link_tags('app') }}
{% endblock %}
{% block javascripts %}
{{ encore_entry_script_tags('app') }}
{% endblock %}
</head>
<body>
{% block body %}{% endblock %}
</body>
</html>

View File

@ -0,0 +1,70 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{% block title %}BLES{% endblock %}</title>
<link rel="shortcut icon" type="image/jpg" href="/favicon.png">
<script src="https://kit.fontawesome.com/3471b6556e.js" crossorigin="anonymous"></script>
{% if chartjs|default(false) %}
<script src="https://cdn.jsdelivr.net/npm/chart.js@3.9.1/dist/chart.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/chartjs-adapter-date-fns@2.0.0/dist/chartjs-adapter-date-fns.bundle.min.js"></script>
{% endif %}
{% block css %}
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet"
integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3"
crossorigin="anonymous">
{% endblock %}
</head>
<body>
{{ include('base/navbar.html.twig') }}
{# alert block #}
<div class="container">
<br>
<div class="row">
<div class="col-sm mx-auto">
{% block flashes %}
{% for msg in app.session.flashBag.get('error') %}
<div class="alert alert-danger" role="alert">
<strong>Error: </strong> {{ msg|raw }}
</div>
{% endfor %}
{% for msg in app.session.flashBag.get('success') %}
<div class="alert alert-success">
<strong>Success: </strong> {{ msg|raw }}
</div>
{% endfor %}
{% endblock %}
</div>
</div>
</div>
{# body blocks #}
{% block bodyraw %}
<div class="container">
<div class="row">
<div class="col-sm mx-auto">
{% block body %}{% endblock %}
</div>
{% if block('body2') is defined %}
<div class="col-sm mx-auto">
{{ block('body2') }}
</div>
{% endif %}
</div>
</div>
{% endblock %}
{# javascript block #}
{% block js %}
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.min.js"
integrity="sha384-QJHtvGhmr9XOIpI6YVutG+2QOK9T+ZnN4kzFN1RtK3zEFEIsxhlmWl5/YESvpZ13"
crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/jquery@3.6.0/dist/jquery.min.js"
integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4="
crossorigin="anonymous"></script>
{% endblock %}
</body>
</html>

View File

@ -0,0 +1,33 @@
<nav class="navbar navbar-expand-md navbar-dark bg-dark" style="z-index: 1;">
<div class="container-fluid">
<a title="BlueLinked Eco System" class="navbar-brand" href="{{ path('home') }}">BLES</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbar"
aria-controls="navbar" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbar">
<ul class="navbar-nav me-auto mb-2 mb-md-0">
<li class="nav-item">
<a class="nav-link" href="{{ path('home') }}">Home</a>
</li>
</ul>
<ul class="navbar-nav my-2 my-lg-0">
{% if app.environment == 'dev' %}
<li class="nav-item">
<a class="btn navbar-text bg-danger" href="#">dev</a> &nbsp;
</li>
{% endif %}
{% if is_granted("IS_AUTHENTICATED_REMEMBERED") %}
<span class="nav-item navbar-text mr-sm-2">Logged in as</span>
<li class="nav-item">
<a class="nav-link" href="{{ path('user_profile') }}">{{ app.user.username }}</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{{ path('logout') }}">Logout</a>
</li>
{% endif %}
</ul>
</div>
</div>
</nav>

View File

@ -0,0 +1,32 @@
{% extends 'base/base.html.twig' %}
{% block title %}Login{% endblock %}
{% block body %}
<form action="{{ path('login') }}" method="post">
{% if error %}
<div class="alert alert-danger">{{ error.messageKey|trans(error.messageData, 'security') }}</div>
{% endif %}
{% if app.user %}
<div class="mb-3">
You are already logged in as {{ app.user }}, <a href="{{ path('logout') }}">Logout</a>
</div>
{% endif %}
<h1 class="h3 mb-3 font-weight-normal">Please login</h1>
<label for="inputUsername">Username</label>
<input type="text" value="{{ last_username }}" name="_username" id="inputUsername" class="form-control" required
autofocus>
<br/>
<label for="inputPassword">Password</label>
<input type="password" name="_password" id="inputPassword" class="form-control" required>
<input type="hidden" name="_csrf_token" value="{{ csrf_token('authenticate') }}">
<br/>
<div class="checkbox mb-3">
<label>
<input type="checkbox" name="_remember_me"> Remember me
</label>
</div>
<button class="btn btn-primary" type="submit">Login</button>
</form>
{% endblock %}

View File

@ -0,0 +1,6 @@
{% extends 'base/base.html.twig' %}
{% block body %}
<h1 class="h3 mb-3 font-weight-normal">Register new user</h1>
{{ form(registrationForm) }}
{% endblock %}

View File

@ -0,0 +1,5 @@
{% extends 'base/base.html.twig' %}
{% block body %}
{{ text | nl2br }}
{% endblock %}

View File

@ -0,0 +1,26 @@
{% extends "base/base.html.twig" %}
{% block body %}
<div class="row">
<div class="col-sm">
<h4>{{ app.user.name }}</h4>
{% if is_granted('ROLE_API') %}
Your api key: {{ app.user.apiKey }} <a class="btn btn-primary" href="{{ path('user_apikey_generate') }}">Regenerate</a> <br/>
{% endif %}
<br/>
{% if is_granted('ROLE_ADMIN') %}
<br/><br/>
<h4>Latest release stats</h4>
Branch: {{ release.branch }} <br/>
Date: {{ release.date }} <br/>
Hash short: {{ release.commitHashShort }} <br/>
Hash long: {{ release.commitHashLong }} <br/>
Commit date: {{ release.commitDate }} <br/>
{% endif %}
</div>
<div class="col-sm">
<h4>Change profile</h4>
{{ form(form) }}
</div>
</div>
{% endblock %}