NEW FEATURE: added translations

This commit is contained in:
Jeroen De Meerleer 2022-05-24 18:09:14 +02:00
parent 1a774f2300
commit ae3788180f
Signed by: JeroenED
GPG Key ID: 28CCCB8F62BFADD6
19 changed files with 293 additions and 41 deletions

View File

@ -71,7 +71,7 @@ function initRunNowButtons() {
tr.classList.remove('norun');
tr.classList.remove('text-danger');
} else if (data.status == 'ran') {
let content = '<p>Cronjob ran in ' + data.runtime.toFixed(3) + ' seconds with exit code ' + data.exitcode +'</p>'
let content = '<p>' + data.message + '</p>'
content += '<pre>' + data.output + '</pre>'
modal.querySelector('.modal-body').innerHTML = content;

View File

@ -25,6 +25,7 @@
"symfony/proxy-manager-bridge": "6.0.*",
"symfony/runtime": "6.0.*",
"symfony/security-bundle": "6.0.*",
"symfony/translation": "6.0.*",
"symfony/twig-bundle": "6.0.*",
"symfony/yaml": "6.0.*"
},

97
composer.lock generated
View File

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "7f812eb1644c07f1064bc949657fc1f5",
"content-hash": "5b6d0f7b3419f7248fa3262cfb27697e",
"packages": [
{
"name": "doctrine/annotations",
@ -5883,6 +5883,101 @@
],
"time": "2022-04-22T08:18:02+00:00"
},
{
"name": "symfony/translation",
"version": "v6.0.8",
"source": {
"type": "git",
"url": "https://github.com/symfony/translation.git",
"reference": "3d38cf8f8834148c4457681d539bc204de701501"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/translation/zipball/3d38cf8f8834148c4457681d539bc204de701501",
"reference": "3d38cf8f8834148c4457681d539bc204de701501",
"shasum": ""
},
"require": {
"php": ">=8.0.2",
"symfony/polyfill-mbstring": "~1.0",
"symfony/translation-contracts": "^2.3|^3.0"
},
"conflict": {
"symfony/config": "<5.4",
"symfony/console": "<5.4",
"symfony/dependency-injection": "<5.4",
"symfony/http-kernel": "<5.4",
"symfony/twig-bundle": "<5.4",
"symfony/yaml": "<5.4"
},
"provide": {
"symfony/translation-implementation": "2.3|3.0"
},
"require-dev": {
"psr/log": "^1|^2|^3",
"symfony/config": "^5.4|^6.0",
"symfony/console": "^5.4|^6.0",
"symfony/dependency-injection": "^5.4|^6.0",
"symfony/finder": "^5.4|^6.0",
"symfony/http-client-contracts": "^1.1|^2.0|^3.0",
"symfony/http-kernel": "^5.4|^6.0",
"symfony/intl": "^5.4|^6.0",
"symfony/polyfill-intl-icu": "^1.21",
"symfony/service-contracts": "^1.1.2|^2|^3",
"symfony/yaml": "^5.4|^6.0"
},
"suggest": {
"psr/log-implementation": "To use logging capability in translator",
"symfony/config": "",
"symfony/yaml": ""
},
"type": "library",
"autoload": {
"files": [
"Resources/functions.php"
],
"psr-4": {
"Symfony\\Component\\Translation\\": ""
},
"exclude-from-classmap": [
"/Tests/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Provides tools to internationalize your application",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/translation/tree/v6.0.8"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2022-04-22T08:18:02+00:00"
},
{
"name": "symfony/translation-contracts",
"version": "v3.0.1",

View File

@ -13,6 +13,9 @@ security:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js|health)/
security: false
login:
pattern: ^/?(%enabled_locales%)/(login|login_check|health)(?=.*)
security: false
main:
pattern: ^\/(.*)
provider: app_user_provider

View File

@ -0,0 +1,13 @@
framework:
default_locale: en
translator:
default_path: '%kernel.project_dir%/translations'
fallbacks:
- en
# providers:
# crowdin:
# dsn: '%env(CROWDIN_DSN)%'
# loco:
# dsn: '%env(LOCO_DSN)%'
# lokalise:
# dsn: '%env(LOKALISE_DSN)%'

View File

@ -1,7 +1,7 @@
default:
path: '/'
defaults:
_controller: App\Controller\JobController::defaultAction
_controller: App\Controller\SecurityController::loginAction
health:
path: '/health'
@ -9,7 +9,7 @@ health:
_controller: App\Controller\SiteController::healthAction
login:
path: '/login'
path: '/{_locale}/login'
defaults:
_controller: App\Controller\SecurityController::loginAction
@ -20,12 +20,12 @@ logout:
path: '/logout'
job_index:
path: '/job'
path: '/{_locale}/job'
defaults:
_controller: App\Controller\JobController::defaultAction
job_view:
path: '/job/{id}/{all}'
path: '/{_locale}/job/{id}/{all}'
methods: [ 'GET' ]
defaults:
_controller: App\Controller\JobController::jobAction
@ -35,7 +35,7 @@ job_view:
all: (all|)
job_delete:
path: '/job/{id}'
path: '/{_locale}/job/{id}'
methods: [ 'DELETE' ]
defaults:
_controller: App\Controller\JobController::jobAction
@ -43,20 +43,20 @@ job_delete:
id: \d+
job_edit:
path: '/job/{id}/edit'
path: '/{_locale}/job/{id}/edit'
defaults:
_controller: App\Controller\JobController::editAction
requirements:
id: \d+
job_runnow:
path: '/job/{id}/runnow'
path: '/{_locale}/job/{id}/runnow'
defaults:
_controller: App\Controller\JobController::runNowAction
requirements:
id: \d+
job_add:
path: '/job/add'
path: '/{_locale}/job/add'
defaults:
_controller: App\Controller\JobController::addAction

View File

@ -4,6 +4,7 @@
# Put parameters here that don't need to change on each machine where the app is deployed
# https://symfony.com/doc/current/best_practices.html#use-parameters-for-application-configuration
parameters:
enabled_locales: 'en|nl'
services:
# default configuration for services in *this* file

View File

@ -64,6 +64,7 @@ class DaemonCommand extends Command
$consolerun = $jobRepo->getTempVar($job, 'consolerun', false);
if($consolerun && !$rebootedself) continue;
}
$manual = ($job->getRunning() == 2);
$jobRepo->setJobRunning($job, true);
$output->writeln('Running Job ' . $job->getId());
if($async) {
@ -75,10 +76,10 @@ class DaemonCommand extends Command
}
if(!$async || $pid == -1) {
$jobRepo->RunJob($job, $job->getRunning() == 2);
$jobRepo->RunJob($job, $manual);
$jobRepo->setJobRunning($job, false);
} elseif ($pid == 0) {
$jobRepo->RunJob($job, $job->getRunning() == 2);
$jobRepo->RunJob($job, $manual);
$jobRepo->setJobRunning($job, false);
exit;
}

View File

@ -11,6 +11,7 @@ use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Contracts\Translation\TranslatorInterface;
class JobController extends AbstractController
{
@ -80,12 +81,33 @@ class JobController extends AbstractController
}
}
public function runNowAction(Request $request, ManagerRegistry $doctrine, int $id): JsonResponse
public function runNowAction(Request $request, ManagerRegistry $doctrine, TranslatorInterface $translator, int $id): JsonResponse
{
if($request->getMethod() == 'GET') {
$jobRepo = $doctrine->getRepository(Job::class);
$job = $jobRepo->find($id);
return new JsonResponse($jobRepo->runNow($job));
$runnowResult = $jobRepo->runNow($job);
if ($runnowResult['success'] === NULL) {
$return = [
'status' => 'deferred',
'success' => NULL,
'title' => $translator->trans('job.runnow.deferred.title'),
'message' => $translator->trans('job.runnow.deferred.message')
];
} else {
$return = [
'status' => 'ran',
'success' => $runnowResult['success'],
'title' => $runnowResult['success'] ? $translator->trans('job.runnow.ran.title.success') : $translator->trans('job.runnow.ran.title.failed'),
'message' => $translator->trans('job.runnow.ran.message', [
'_runtime_' => number_format($runnowResult['runtime'], 3),
'_exitcode_' => $runnowResult['exitcode']
]),
'exitcode' => $runnowResult['exitcode'],
'output' => $runnowResult['output'],
];
}
return new JsonResponse($return);
}
return new JsonResponse(['success'=>false, 'message' => 'Your request is invalid'], Response::HTTP_BAD_REQUEST);
}

View File

@ -4,13 +4,18 @@
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Security\Http\Authentication\AuthenticationUtils;
class SecurityController extends AbstractController
{
public function loginAction(AuthenticationUtils $authenticationUtils): Response
public function loginAction(Request $request, AuthenticationUtils $authenticationUtils): Response
{
if($this->isGranted('IS_AUTHENTICATED_FULLY')) {
return new RedirectResponse($this->generateUrl('job_index'));
}
// get the login error if there is one
$error = $authenticationUtils->getLastAuthenticationError();

View File

@ -394,11 +394,13 @@ class JobRepository extends EntityRepository
'output' => ($console) ? $output['output'] : htmlentities($output['output']),
'exitcode' => $output['exitcode'],
'runtime' => (float)$output['runtime'],
'title' => !str_contains($output['flags'], RunRepository::FAILED) ? 'Cronjob successfully ran' : 'Cronjob failed. Please check output below',
'success' => !str_contains($output['flags'], RunRepository::FAILED)
];
}
return ['success' => true, 'status' => 'deferred', 'title' => 'Cronjob has been scheduled', 'message' => 'Job was scheduled to be run. You will find the output soon in the job details'];
return [
'success' => NULL,
'status' => 'deferred'
];
}
/**

View File

@ -361,6 +361,19 @@
"symfony/string": {
"version": "v6.0.3"
},
"symfony/translation": {
"version": "6.0",
"recipe": {
"repo": "github.com/symfony/recipes",
"branch": "main",
"version": "5.3",
"ref": "da64f5a2b6d96f5dc24914517c0350a5f91dee43"
},
"files": [
"config/packages/translation.yaml",
"translations/.gitignore"
]
},
"symfony/translation-contracts": {
"version": "v3.0.1"
},

View File

@ -23,9 +23,9 @@
<div class="row py-3">
<div class="col-12 col-xxl-2">
<ul class="nav flex-column">
<li class="nav-item"><a class="nav-link" href="{{ path('job_index') }}">Overview</a></li>
<li class="nav-item"><a class="nav-link" href="{{ path('job_add') }}">Add a new cronjob</a></li>
<li class="nav-item"><a class="nav-link" href="{{ path('logout') }}">Logout</a></li>
<li class="nav-item"><a class="nav-link" href="{{ path('job_index') }}">{{ 'menu.overview' | trans }}</a></li>
<li class="nav-item"><a class="nav-link" href="{{ path('job_add') }}">{{ 'menu.add' | trans }}</a></li>
<li class="nav-item"><a class="nav-link" href="{{ path('logout') }}">{{ 'menu.logout' | trans }}</a></li>
</ul>
</div>
<div class="col-12 col-xxl-10">

View File

@ -1,16 +1,16 @@
{% extends "base.html.twig" %}
{% block title %}Overview{% endblock %}
{% block title %}{{ 'index.title' | trans }}{% endblock %}
{% block content %}
<h2>Overview of your cronjobs</h2>
<h2>{{ 'index.header' | trans }}</h2>
<table class="table table-md-striped">
<thead>
<tr class="d-none d-md-table-row">
<th>&nbsp;</th>
<th>Name</th>
<th>Host</th>
<th>Delay</th>
<th>Next run</th>
<th>{{ 'index.table.headers.name' | trans }}</th>
<th>{{ 'index.table.headers.host' | trans }}</th>
<th>{{ 'index.table.headers.interval' | trans }}</th>
<th>{{ 'index.table.headers.nextrun' | trans }}</th>
<th></th>
</tr>
</thead>

View File

@ -1,11 +1,11 @@
{% extends "base.html.twig" %}
{% block title %}Overview of run for {{ job.name }}{% endblock %}
{% block title %}{{ 'job.title' | trans({ '_jobname_': job.name }) }}{% endblock %}
{% block content %}
<h2>Overview of runs for <span class="job-name">{{ job.name | parsetags | raw }}</span></h2>
<h2>{{ 'job.header' | trans({ '_jobname_': (job.name | parsetags) }) | raw }}</h2>
<p>
<a href="{{ path('job_edit', { id: job.id }) }}">Edit job</a>
{% if allruns %} | <a href="{{ path('job_view', { id: job.id })}}">Only show failed runs</a>
{% elseif not allruns %} | <a href="{{ path('job_view', { id: job.id, all: 'all' })}}">Show all runs</a>
<a href="{{ path('job_edit', { id: job.id }) }}">{{ 'job.edit' | trans }}</a>
{% if allruns %} | <a href="{{ path('job_view', { id: job.id })}}">{{ 'job.show.onlyfailed' | trans }}</a>
{% elseif not allruns %} | <a href="{{ path('job_view', { id: job.id, all: 'all' })}}">{{ 'job.show.all' | trans }}</a>
{% endif %}
</p>
<div id="runs" class="accordion">
@ -15,9 +15,9 @@
<button class="accordion-button{% if loop.index != 1 %} collapsed{% endif %}" type="button" data-bs-toggle="collapse" data-bs-target="#run-{{ run.id }}" aria-expanded="{% if loop.index != 1 %}true{% else %}false{% endif %}" aria-controls="run-{{ run.id }}">
<div>
<div class="d-md-inline d-block text-left">{{ run.timestamp | date("d/m/Y H:i:s") }}</div>
<div class="d-md-inline d-block text-left">(exit code: {{ run.exitcode }} | runtime: {{ run.runtime | interval }})</div>
<div class="d-md-inline d-block text-left">({{ "job.results.exitcode"| trans }}: {{ run.exitcode }} | {{ "job.results.runtime"| trans }}: {{ run.runtime | interval }})</div>
{% if 'M' in run.flags %}
<div class="d-md-inline d-block text-left">Manual Run</div>
<div class="d-md-inline d-block text-left">{{ "job.results.manual"| trans }}</div>
{% endif %}
</div>
</button>
@ -29,8 +29,8 @@
</div>
</div>
{% else %}
<h4>No {% if not allruns %}failed {% endif %}runs found</h4>
<p><a href="{{ path('job_view', { id: job.id, all: 'all' })}}">Show all runs</a></p>
<h4>{% if not allruns %}{{ "job.results.noresults.failed" | trans }}{% else %}{{ "job.results.noresults.all"| trans }}{% endif %}</h4>
<p><a href="{{ path('job_view', { id: job.id, all: 'all' })}}">{{ 'job.show.all' | trans }}</a></p>
{% endfor %}
</div>
{% endblock %}

View File

@ -25,19 +25,19 @@
<form class="form-horizontal" method="post" action="{{ path('login_check') }}">
<div class="mb-3">
<label for="username">Username</label>
<input type="text" name="_username" class="form-control" id="username" placeholder="username">
<label for="username">{{ 'security.login.username.label' | trans }}</label>
<input type="text" name="_username" class="form-control" id="username" placeholder="{{ 'security.login.username.placeholder' | trans }}">
</div>
<div class="mb-3">
<label for="password">Password</label>
<input type="password" name="_password" class="form-control" id="password" placeholder="password">
<label for="password">{{ 'security.login.password.label' | trans }}</label>
<input type="password" name="_password" class="form-control" id="password" placeholder="{{ 'security.login.password.placeholder' | trans }}">
</div>
<div class="form-check mb-3">
<input type="checkbox" name="_remember_me" id="autologin" class="form-check-input">
<label class="from-check-label" for="autologin">Remember, remember</label>
<label class="from-check-label" for="autologin">{{ 'security.login.remember.label' | trans }}</label>
</div>
<input type="hidden" name="_csrf_token" value="{{ csrf_token('authenticate') }}">
<button type="submit" class="btn btn-outline-primary">Submit</button>
<button type="submit" class="btn btn-outline-primary">{{ 'security.login.submit-btn.label' | trans }}</button>
</form>
</div>
</div>

0
translations/.gitignore vendored Normal file
View File

View File

@ -0,0 +1,48 @@
menu:
overview: "Overview"
add: "Add a new cronjob"
logout: "Logout"
security:
login:
username:
label: "Username"
placeholder: "jeroen@example.com"
password:
label: "Password"
placeholder: "abc123"
remember:
label: "Remember, remember"
submit-btn:
label: "Login"
index:
title: "Overview"
header: "Overview of the cronjobs"
table:
headers:
name: "Hame"
host: "Host"
interval: "Interval"
nextrun: "Nextrun"
job:
title: "Overview of runs for _jobname_"
header: "Overview of runs for _jobname_"
edit: "Edit job"
runnow:
deferred:
title: "Cronjob has been scheduled"
message: "Job was scheduled to be run. You will find the output soon in the job details"
ran:
title:
success: "Cronjob succesfully ran"
failed: "Cronjob failed. Please check output below"
message: "Cronjob ran in _runtime_ seconds with exit code _exitcode_"
show:
onlyfailed: "Only show failed runs"
all: "Show all runs"
results:
exitcode: "Exit code"
runtime: "Runtime"
manual: "Manual run"
noresults:
failed: "No failed runs found"
all: "No runs found"

View File

@ -0,0 +1,48 @@
menu:
overview: "Overzicht"
add: "Taak toevoegen"
logout: "Afmelden"
security:
login:
username:
label: "Gebruikersnaam"
placeholder: "jeroen@example.com"
password:
label: "Wachtwoord"
placeholder: "abc123"
remember:
label: "Onthoud mij!"
submit-btn:
label: "Aanmelden"
index:
title: "Overzicht"
header: "Overzicht van de geplande taken"
table:
headers:
name: "Naam"
host: "Host"
interval: "Interval"
nextrun: "Volgende uitvoering"
job:
title: "Overzicht van uitvoeringen van _jobname_"
header: "Overzicht van uitvoeringen van _jobname_"
edit: "Bewerk taak"
runnow:
deferred:
title: "Taak werd doorgestuurd naar de daemon"
message: "Taak werd doorgestuurd naar de daemon. De output vind u binnenkort in de taakdetails"
ran:
title:
success: "Taak werd succesvol uitgevoerd"
failed: "Taak faalde. Hieronder vind u de output ter controle"
message: "Taak werd in _runtime_ seconden uitgevoerd met resultaat _exitcode_"
show:
onlyfailed: "Toon alleen gefaalde uitvoeringen"
all: "Toon alle uitvoeringen"
results:
exitcode: "Resultaat"
runtime: "Duurtijd"
manual: "Manuele uitvoering"
noresults:
failed: "Geen gefaalde uitvoeringen gevonden"
all: "Geen uitvoeringen gevonden"