first commit

This commit is contained in:
2025-04-09 21:43:00 +02:00
commit af5be2794d
39 changed files with 1947 additions and 0 deletions

6
.gitignore vendored Normal file
View File

@@ -0,0 +1,6 @@
/.vscode/
*/.vscode/
/.theia/
*/.theia/
*/*.deb
*.deb

0
README.md Normal file
View File

131
createapp.pl Normal file
View File

@@ -0,0 +1,131 @@
#!/usr/bin/perl -w
use strict;
use warnings;
use Data::Printer;
use File::Find;
use File::Copy;
use Cwd;
use File::Slurp;
my $secret = `openssl rand -base64 48`;
my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime();
$year = $year+1900;
chomp $secret;
my $rc = system('git --version');
if ( $rc < 0) {
print STDOUT "GIT scheint nicht installiert zu sein, Programm wird geschlossen\n";
exit 0;
}
print "Wie soll das neue Programm heißen: ";
chomp( my $name = <ARGV> );
if ( $name !~ /^[a-z]*$/ ) {
print STDOUT "Programmname muss existieren und darf nur kleinbuchstaben beinhalten, Programm wird geschlossen\n";
exit 0;
} elsif ( $name eq 'xxx' ) {
print STDOUT "Programmname darf nicht xxx heißen, Programm wird geschlossen\n";
exit 0;
}
print "Wohin soll das neue Programm kopiert werden ( $ENV{'HOME'}/mojo ): ";
chomp( my $wohin = <ARGV> );
if ( $wohin eq "" ) {
$wohin = "$ENV{'HOME'}/mojo";
};
if ( !-d $wohin ) {
print STDOUT "Ordner existiert nicht, Programm wird geschlossen\n";
exit 0;
} elsif ( $wohin =~ /create_mojo_app/ ) {
print STDOUT "Ordner mit create_mojo_app im Namen sind nicht erlaubt, Programm wird geschlossen\n";
exit 0;
} elsif ( !-w $wohin ) {
print STDOUT "in Ordner $wohin kann nicht geschrieben werden, Programm wird geschlossen\n";
exit 0;
} else {
}
$wohin = $wohin =~ s/\/*$//r; # letztes / entfernen
my $lname = lcfirst $name;
my $uname = ucfirst $name;
print "Wofür ist $lname: ";
chomp( my $wofuer = <ARGV> );
print "auf welchem Port soll $uname laufen (Default 3000): ";
chomp( my $port = <ARGV> );
if ( $port eq "" ) { $port = 3000 }
my $dir = getcwd;
# Ordner und Dateinamen in xxx durchgehen und XXX mit neuen Appnamen ersetzen
find( \&files, 'xxx' );
sub files {
my $str = $File::Find::name;
$str =~ s/Xxx/$uname/g;
$str =~ s/xxx/$lname/g;
if (-d) {
mkdir $wohin . '/' . $str;
}
else {
copy( $dir . '/' . $File::Find::name, $wohin . '/' . $str )
or die "The move operation failed: $!";
}
return;
}
# Dateien in neuen Appordner durchgehen und Werte mit neuen ersetzen
find( \&newfiles, "$wohin/$lname" );
sub newfiles {
if (-f) {
#print "File: $dir/$File::Find::name\n";
if ( $File::Find::name !~ m/.png/g ) {
my @lines = read_file($File::Find::name);
write_file($File::Find::name, '');
for my $line (@lines) {
$line =~ s/mojocreateyear/$year/g;
$line =~ s/Xxx/$uname/g;
$line =~ s/xxx/$lname/g;
$line =~ s/mojoappsecret/$secret/g;
$line =~ s/hypnotoadport/$port/g;
$line =~ s/wofuer/$wofuer/g;
append_file($File::Find::name, $line);
}
}
}
return;
}
# Rechte der Datei ändern
chmod( 0755, "$wohin/$lname/app/morbo.sh" );
chmod( 0755, "$wohin/$lname/app/test.sh" );
chmod( 0755, "$wohin/$lname/deb/DEBIAN/postinst" );
chmod( 0755, "$wohin/$lname/deb/DEBIAN/prerm" );
chmod( 0755, "$wohin/$lname/deb/pbuild.sh" );
print "App: $uname erstellt\n";
chdir "$wohin/$lname/";
system( "git init" );
system( "git add ." );
system( "git commit -m 'initialer Commit'" );
system( "git remote add origin git\@git:WebApps/$uname.git" );
system( "$wohin/$lname/deb/pbuild.sh" ) == 0
or die "Bash Script failed";
1;
system( "$wohin/$lname/app/morbo.sh" ) == 0
or die "Bash Script failed";
1;

116
xxx/app/lib/XxxApp.pm Normal file
View File

@@ -0,0 +1,116 @@
package XxxApp;
use Mojo::Base 'Mojolicious';
use Time::localtime;
# This method will run once at server start
sub startup {
my $self = shift;
# einlesen der lokalen Config
my $config = $self->plugin('Config');
# $self->plugin('Config', 'file' => $config->{zus_config}); # hier und in der konfig zus_config aktivieren
$self->secrets([ $config->{secret}]);
$config->{version} = '__VERSION__';
$config->{prefix} = '';
# Logging mit Log4perl
$self->plugin('steffen::MojoPlug::Syslog' => $config->{logging} );
$self->app->log->info(sprintf('%s v%s (Perl %s, Mojolicious v%s) started', 'Xxx', $config->{version}, $^V, $Mojolicious::VERSION));
# Authentication & Authorization
$self->sessions->cookie_name('Xxx');
$self->sessions->default_expiration(3600);
$self->sessions->samesite('Strict');
delete $self->app->static->extra->{'favicon.ico'};
push @{$self->app->static->paths}, $config->{htlib};
# Kompression
$self->app->renderer->compress(1);
# Authorisation
$self->plugin('steffen::MojoPlug::Authorization' => { verbose => 1 });
# Menu über config
$self->plugin('Config', file => 'navigation.conf');
$self->plugin('steffen::MojoPlug::NavHelper' => { verbose => 1, bs => 5 });
# deaktivierte Plugins
# $self->plugin('RenderFile');
# mysql
# $self->plugin('steffen::MojoPlug::Mysql' => { db => $config->{db} });
# $config->{test} = $self->dbxxx->{dsn} =~ m/dbs-test/gxms;
# Local Helpers
$self->plugin('XxxApp::Helpers' => { verbose => 1 });
# https://editor.swagger.io/?url=https://petstore.swagger.io/v2/swagger.yaml
$self->plugin('OpenAPI' => {
#route => $self->app->routes->under("/api")->to("api#auth"),
url => $self->app->home->rel_file("openapi.json"),
schema => 'v3',
security => {
BasicAuth => sub {
my ($c, $definition, $scopes, $cb) = @_;
my $haspriv;
for my $sc ( @{$scopes} ) {
$haspriv = 1 if $c->has_priv($sc);
}
if ( $haspriv ) {
return $c->$cb()
} else {
return $c->$cb('no permission')
}
}
}
});
$self->plugin( 'steffen::MojoPlug::SwaggerUI' => {
route => $self->app->routes()->any('/rfc'),
url => "/api",
title => "Xxx",
favicon => "/images/favicon.png"
}
);
$self->hook(
'before_dispatch' => sub {
my $c = shift;
$c->req->url->base->path('');
$config->{prefix} = '';
if (!!$c->req->headers->header('Mojo-RP-Location')) {
$c->req->url->base->path( $c->req->headers->header('Mojo-RP-Location') );
$config->{prefix} = '/'.$c->req->headers->header('Mojo-RP-Location');
}
}
);
# Hook für CORS
$self->hook(
'after_dispatch' => sub {
my $c = shift;
$c->res->headers->header('Access-Control-Allow-Origin' => '*');
$c->res->headers->remove('Server');
}
);
# Router
my $r = $self->routes;
# Normal route to controller
$r->get('/') ->to('main#main', title => 'Xxx');
$r->get('/jstreetemplate') ->to('menu#jstreetemplate', title => 'JStree Template'); # Beispiel kann gelöscht werden
$r->get('/xxx') ->to('main#xxx', title => 'Xxx');
$r->get('/about') ->to('main#about', title => 'Über Xxx');
$r->get('/login') ->to('main#loginform');
$r->post('/login') ->to('main#login');
$r->get('/userinfo') ->to('main#userinfo');
$r->get('/getoff') ->to('main#getoff');
# API über OpenAPI Plugin
}
1;

View File

@@ -0,0 +1,376 @@
package XxxApp::Controller::Api::Jstreetemplate;
use Mojo::Base 'Mojolicious::Controller';
use Data::Printer;
my @liste = qw|Anstand
Aufmerksamkeit
Auftrieb
Ausgelassenheit
Ausgewogenheit
Auszeichnung
Bedeutung
Begeisterung
Beglückung
Behaglichkeit
Beherztheit
Belohnung
Bereicherung
Bewunderung
Chance
Charisma
Charme
Dankbarkeit
Daseinsfreude
Ehrlichkeit
Einfallsreichtum
Einigkeit
Empfehlung
Energie
Engagement
Entspannung
Erfolg
Erfüllung
Ermutigung
Faszination
Freiheit
Freude
Freunde
Freundlichkeit
Fröhlichkeit
Fülle
Füreinandereintreten
Gabe
Geduld
Gegenseitigkeit
Gemeinschaftsgeist
Geschenk
Geschlossenheit
Gesundheit
Glück
Glückseligkeit
Glückwunsch
Großzügigkeit
Güte
Harmonie
Hauptgewinn
Heiterkeit
Herzenswärme
Herzensgüte
Herzlichkeit
Hilfsbereitschaft
Hinwendung
Hochgefühl
Hoffnung
Höflichkeit
Idealismus
Intuition
Ideenreichtum
Idylle
Inspiration
Interesse
Jubel
Jugendlichkeit
Kompliment
Köstlichkeit
Kraft
Kreativität
Lächeln
Lachen
Leben
Lebenslust
Lebendigkeit
Lebensfreude
Lebhaftigkeit
Leichtigkeit
Leidenschaft
Liebe
Liebenswürdigkeit
Luxus
Munterkeit
Mut
Nähe
Offenheit
Optimismus
Originalität
Pracht
Prestige
Problemlösung
Pünktlichkeit
Reichhaltigkeit
Reichtum
Respekt
Richtigkeit
Rücksicht
Rücksichtnahme
Schönheit
Schutz
Seelenverwandtschaft
Selbstachtung
Selbstbestimmung
Selbstliebe
Selbstlosigkeit
Selbstsicherheit
Selbstwertgefühl
Selbstzufriedenheit
Seligkeit
Sicherheit
Solidarität
Sorgfalt
Spaß
Spontanität
Stärke
Sympathie
Toleranz
Treue
Überfluss
Überraschung
Überschwang
Überzeugung
Überzeugungskraft
Unbeschwertheit
Unterstützung
Verbesserung
Verbundenheit
Verehrung
Verlockung
Verspieltheit
Verständnis
Vertrauen
Vertrauenswürdigkeit
Vertrautheit
Vertraulichkeit
Vielfalt
Vielseitigkeit
Wahrhaftigkeit
Wärme
Willenskraft
Willkommen
Wohlbefinden
Wohlgefallen
Wohlstand
Wohltat
Wonne
Würde
Wunder
Zauber
Zufriedenheit
Zuneigung
Zusammengehörigkeit
Zusammenhalt
Zuverlässigkeit
Zuwendung|;
sub jstreedata {
my $c = shift->openapi->valid_input or return;
my ($src, $tab ) = _generatetestdata();
my @tree;
push @tree, @{$src};
push @tree, @{$tab};
return $c->render(json => { rc => 0, msg => 'OK', data => \@tree} );
}
sub _generatetestdata {
my ( $liste ) = @_;
my $src;
my $tab;
my $cnt = @liste;
my $first = int(rand( 10));
$first ++;
my $second = int(rand( 10));
$second ++;
my $third = int(rand( 10));
$third ++;
for my $f ( 1..$first ) {
my $word = int(rand( $cnt ));
push @{$src}, { parent => '#', id => $f, text => $liste[$word] };
for my $s ( 1..$second ) {
my $fs = $f * 10 + $s;
my $wordchild = int(rand( $cnt ));
push @{$tab}, { parent => $f, id => $fs, text => $liste[$wordchild] };
for my $t ( 1..$third ) {
my $fst = $fs * 10 + $t;
my $wordchild2 = int(rand( $cnt ));
push @{$tab}, { parent => $fs, id => $fst, text => $liste[$wordchild2] },
}
}
}
return ($src, $tab );
}
sub datatablesdata {
my $c = shift->openapi->valid_input or return;
my $cnt = @liste;
my $rows = int(rand( 1000));
$rows ++;
my @data;
for my $d ( 0..$rows ) {
my $col1 = int(rand( $cnt ));
my $col2 = int(rand( $cnt ));
my $col3 = int(rand( $cnt ));
my $col4 = int(rand( $cnt ));
my $col5 = int(rand( $cnt ));
my $col6 = int(rand( $cnt ));
my $col7 = int(rand( $cnt ));
my $col8 = int(rand( $cnt ));
my $col9 = int(rand( $cnt ));
push @data, { col1 => $liste[$col1], col2 => $liste[$col2], col3 => $liste[$col3], col4 => $liste[$col4], col5 => $liste[$col5], col6 => $liste[$col6], col7 => $liste[$col7], col8 => $liste[$col8], col9 => $liste[$col9] };
}
return $c->render(json => { rc => 0, msg => 'OK', data => \@data} );
}
###
sub listfiles {
my $self = shift;
my $id = $self->stash('todoid');
my $sql = "SELECT idma,
size,
created,
info,
type,
name
FROM todo_master_files
WHERE id = ? and deleted = 0";
my ( $rc, $msg, $data ) = $self->dbxxx->query_hash_array( $sql, $id );
return $self->render( json => { rc => $rc, msg => $msg, data => $data} );
}
sub blob {
my $self = shift;
my $dbh = $self->dbxxx->get;
return (1, "kein DB-Handle bekommen") if !defined $dbh;
my $cursor = $dbh->prepare("SELECT file, type FROM todo_master_files where idma = ?");
$cursor->execute($self->stash('idma'));
my @data = $cursor->fetchrow;
$cursor->finish;
my $blob;
if ( defined $data[0]) {
$blob = encode_base64($data[0]);
} else {
$blob = '';
}
$self->dbxxx->free($dbh);
return $self->render( json => { blob => $blob, type => $data[1] });
}
sub update {
# update eines eintrages der Mastertabelle
my $self = shift;
my $todoid = $self->stash('todoid');
return $self->render( json => { rc => 1, msg => "Die todoid fehlt"} ) if ( $todoid eq "" ) ;
my $daten = $self->req->json;
return $self->render( json => { rc => 1, msg => 'Programmname fehlt'} ) if ( !defined $daten->{name} or $daten->{name} eq "" );
return $self->render( json => { rc => 1, msg => 'Verantwortlichkeit zuweisen'} ) if ( !defined $daten->{who} or $daten->{who} eq "" );
return $self->render( json => { rc => 1, msg => 'Prozesszuweisung fehlt'} ) if ( !defined $daten->{prozess} or $daten->{prozess} eq "" );
return $self->render( json => { rc => 1, msg => 'Festellung definieren'} ) if ( !defined $daten->{todoinfo} or $daten->{todoinfo} eq "" );
return $self->render( json => { rc => 1, msg => 'ext fehlt'} ) if ( !defined $daten->{ext} );
my $sql = "UPDATE todo_master set
name = ?,
who = ?,
prozess = ?,
todoinfo = ?,
status = ?,
finishdate = ?,
closed = ?,
notes_master = ?,
targetdate_master = ?,
ext = ?
WHERE id = ?";
my ($rc, $msg ) = $self->dbxxx->query_res( $sql, $daten->{'name'},
$daten->{'who'},
$daten->{'prozess'},
$daten->{'todoinfo'},
$daten->{'status'},
$daten->{'finishdate'},
$daten->{'closed'},
$daten->{'notes_master'},
$daten->{'targetdate_master'},
$daten->{'ext'},
$todoid);
return $self->render(json => { rc => $rc, msg => $msg });
}
sub insert_file {
my $self = shift;
my $file = $self->param("file");
my $todoid = $self->param("id");
my $info = $self->param("info");
return $self->render(json => { rc => 1, msg => "Die todoid fehlt"} ) if ( $todoid eq "" ) ;
return $self->render(json => { rc => 1, msg => "Die Info fehlt"} ) if ( $info eq "" ) ;
return $self->render(json => { rc => 1, msg => 'file'}) if (!defined $file->asset->slurp or $file->asset->slurp eq "");
return $self->render(json => { rc => 1, msg => 'name'}) if (!defined $file->filename or $file->filename eq "");
return $self->render(json => { rc => 1, msg => 'type'}) if (!defined $file->headers->content_type or $file->headers->content_type eq "");
return $self->render(json => { rc => 1, msg => 'size'}) if (!defined $file->size or $file->size eq "");
my $sql = "INSERT INTO todo_master_files (id, file, type, name, size, info) VALUES (?,?,?,?,?,?)";
my ($rc, $msg ) = $self->dbxxx->query_res( $sql, $todoid, $file->asset->slurp, $file->headers->content_type, $file->filename, $file->size, $info );
return $self->render(template => 'main/todo_main');
}
sub insert_datei {
my $self = shift;
my $daten = $self->req->json;
my $id = $self->stash("todoid");
return $self->render(json => { rc => 1, msg => "Die ID fehlt"} ) if ( $id eq "" ) ;
my $filebase64 = $daten->{file};
$filebase64 =~ s/data:$daten->{type};base64//;
my $file = decode_base64($filebase64);
my $sql = "INSERT INTO todo_master_files (id, file, type, name, size, info) VALUES (?,?,?,?,?,?)";
my ($rc, $msg ) = $self->dbxxx->query_res( $sql, $id, $file, $daten->{type}, $daten->{name}, $daten->{size}, $daten->{info});
# oder mit der zurückgegebenen id
# my ($rc, $msg, $insid ) = $self->dbkoma->query_inserted_id( $sql, $id, $file, $daten->{type}, $daten->{name}, $daten->{size}, $daten->{info});
return $self->render(json => { rc => $rc, msg => $msg });
}
sub deletefile {
my $self = shift;
my $id = $self->stash('idma');
return $self->render(json => { rc => 1, msg => "Die idma fehlt"}) if ( $id eq "" ) ;
my ($rc, $msg) = $self->dbxxx->query_res( "DELETE FROM todo_master_files WHERE idma = ?", $id);
return $self->render( json => { rc => $rc, msg => $msg });
}
1;

View File

@@ -0,0 +1,10 @@
package XxxApp::Controller::Menu;
use Mojo::Base 'Mojolicious::Controller';
sub jstreetemplate {
my $c = shift;
return;
}
1;

View File

@@ -0,0 +1,30 @@
package XxxApp::Helpers;
use Mojo::Base 'Mojolicious::Plugin';
sub register {
my ($self, $app, $args) = @_;
$app->helper('trim' => \&_trim);
$app->helper('debuglog' => \&_debuglog);
$app->log->info(sprintf('REGISTERED: %s', __PACKAGE__)) if !!$args->{verbose};
return;
}
# Führende und nachfolgende Leerzeichen entfernen
sub _trim {
my ($c, $str) = @_;
$str =~ s/^\s+|\s+$//g;
return $str;
};
sub _debuglog {
my ($c, $info) = @_;
if ( $c->config->{logging}->{level} =~ /trace|debug/ ) {
$c->log->debug($info);
}
return 1;
}
1;

View File

@@ -0,0 +1,53 @@
package XxxApp::Main;
use Mojo::Base 'Mojolicious::Controller';
sub main {
my $self = shift;
return $self->render( template => 'main/main', title => 'Xxx', msg => '' );
}
sub login {
my $self = shift;
# Grab the request parameters
my $username = $self->param('username');
my $password = $self->param('password');
my $developer = ($username eq 'developer' and $password eq 'wbdfgmnjke834dshf89w7rsdfsdjf' and $self->app->mode =~ /development/xms ) ? 1 : 0;
if ( $developer or $self->authenticate( $username, $password )) {
$self->session->{msg} = 'Login erfolgreich!';
push @{$self->session->{privs}}, 'user';
} else {
$self->logoff();
$self->session->{url} = '/login';
$self->session->{msg} = 'Fehler, das Login wurde abgewiesen!';
}
return $self->redirect_to( defined $self->session->{url} ? $self->session->{url} : "/" );
}
sub loginform {
my $self = shift;
return $self->render( title => 'xxx: Main', msg => '', sess => $self->session);
}
sub userinfo {
my $self = shift;
return $self->render( title => 'xxx: Userinfo', sess => $self->session, msg => 'Du besitzt folgende Rechte:');
}
sub getoff {
my $self = shift;
$self->logoff();
delete $self->session->{privs};
$self->session->{url} = '/';
$self->session->{msg} = 'bye, bye ...';
return $self->redirect_to( "/");
}
1;

4
xxx/app/morbo.sh Normal file
View File

@@ -0,0 +1,4 @@
#!/bin/bash
cd "${0%/*}"
morbo -l http://*:hypnotoadport script/XxxApp -w templates -w lib -w *.conf -w *.json

39
xxx/app/navigation.conf Normal file
View File

@@ -0,0 +1,39 @@
{
navigation => {
menu => [
# {
# text => 'QS',
# dropdown => [
# { text => 'Arbeitsvorrat', href => '/rech/qs' },
# ]
# },
# {
# text => 'Recherchen',
# dropdown => [
# { text => 'Was steht zum Transport bereit?', href => '/rech/dropd' },
# { text => 'Was wird gerade transportiert?', href => '/rech/taken' },
# { text => 'Was wurde bereits transportiert?', href => '/rech/done' },
# ]
# },
# {
# text => 'Auswertungen',
# dropdown => [
# { text => 'Transportstatistik', href => '/rept/statistik/0' },
# { text => 'QS-Statistik', href => '/rept/statistik/1' },
# ]
# },
# {
# text => 'Konfiguration',
# priv => 'admin',
# dropdown => [
# { text => 'Standort festlegen', href => '/conf/stdort' },
# ]
# },
{
priv => 'user',
text => 'JSTree Template',
href => '/jstreetemplate',
},
],
}
};

209
xxx/app/openapi.json Normal file
View File

@@ -0,0 +1,209 @@
{
"openapi": "3.0.0",
"servers": [
{
"url": "/api"
}
],
"info": {
"version": "1.0",
"title": "Steffens Xxx API"
},
"paths": {
"/jstreedata": {
"get": {
"security": [
{
"BasicAuth": [
"user"
]
}
],
"tags": [
"Jstreetemplate"
],
"operationId": "jstreedata",
"x-mojo-name": "jstreetemplate_jstreedata",
"x-mojo-to": "api-jstreetemplate#jstreedata",
"responses": {
"200": {
"description": "Daten für das JSTree",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/mls_default_response"
}
}
}
}
}
}
},
"/jstreedataupdate": {
"post": {
"summary": "nicht funktionierende UPDATE Route Controller existiert nicht",
"security": [
{
"BasicAuth": [
"user"
]
}
],
"tags": [
"Jstreetemplate"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"required": [
"id"
],
"type": "object",
"properties": {
"id": {
"description": "ident zum update",
"type": "integer",
"minimum": 1
},
"kat_id": {
"description": "Kategorie ident zum update",
"type": "integer",
"minimum": 1
},
"name": {
"description": "Name der Kategorie",
"type": "string",
"minLength": 3,
"maxLength": 20
}
}
}
}
}
},
"operationId": "jstreedataupdate",
"x-mojo-name": "jstreetemplate_jstreedataupdate",
"x-mojo-to": "api-jstreetemplate#jstreedataupdate",
"responses": {
"200": {
"description": "Daten für das JSTree",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/mls_default_response"
}
}
}
}
}
}
},
"/jstree/{id}/{kat_id}": {
"get": {
"summary": "nicht funktionierende GET Path Route Controller existiert nicht",
"security": [
{
"BasicAuth": [
"user"
]
}
],
"tags": [
"Jstreetemplate"
],
"parameters": [
{
"required": true,
"name": "id",
"in": "path",
"schema": {
"type": "integer",
"minimum": 1
}
},
{
"required": true,
"name": "kat_id",
"in": "path",
"schema": {
"type": "integer",
"minimum": 1
}
}
],
"operationId": "jstree",
"x-mojo-name": "jstreetemplate_jstree",
"x-mojo-to": "api-jstreetemplate#jstree",
"responses": {
"200": {
"description": "Daten für das JSTree",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/mls_default_response"
}
}
}
}
}
}
},
"/datatablesdata": {
"get": {
"security": [
{
"BasicAuth": [
"user"
]
}
],
"tags": [
"Jstreetemplate"
],
"operationId": "datatablesdata",
"x-mojo-name": "jstreetemplate_datatablesdata",
"x-mojo-to": "api-jstreetemplate#datatablesdata",
"responses": {
"200": {
"description": "Daten für die Tabelle",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/mls_default_response"
}
}
}
}
}
}
}
},
"components": {
"securitySchemes": {
"BasicAuth": {
"type": "http",
"scheme": "basic"
}
},
"schemas": {
"mls_default_response": {
"type": "object",
"properties": {
"rc": {
"type": "integer",
"enum": [
0,
1
],
"description": "0 - alles gut | 1 - Fehler"
},
"msg": {
"type": "string"
}
}
}
}
}
}

View File

@@ -0,0 +1,34 @@
.jstree-anchor,
.jstree-ocl {
line-height: 20px !important;
}
.jstree-anchor,
.vakata-context {
font-size: 16px;
}
.jstree-ocl {
margin-top: -2px !important;
}
.jstree-icon {
line-height: 16px !important;
transform: scale(1.4) !important;
}
.jstree-node {
min-height: 20px !important;
line-height: 20px !important;
}
.treedetail {
border: 0.5px black solid;
overflow: auto;
box-shadow: 0 0 5px #ccc;
}
.table td,
.table th {
vertical-align: inherit;
}

View File

@@ -0,0 +1 @@
.jstree-anchor,.jstree-ocl{line-height:20px!important}.jstree-anchor,.vakata-context{font-size:16px}.jstree-ocl{margin-top:-2px!important}.jstree-icon{line-height:16px!important;transform:scale(1.4)!important}.jstree-node{min-height:20px!important;line-height:20px!important}.treedetail{border:.5px #000 solid;overflow:auto;box-shadow:0 0 5px #ccc}.table td,.table th{vertical-align:inherit}

View File

@@ -0,0 +1,23 @@
body {
width: 100%;
overflow: hidden;
min-height: 0px;
line-height: 1.1;
}
.navbar {
padding-top: 0px;
padding-bottom: 0px;
height: 50px;
}
.navbar-brand {
padding-top: 0px;
padding-bottom: 0px;
}
.footer {
position: fixed;
bottom: 2px;
right: 0px;
}

1
xxx/app/public/css/xxx.min.css vendored Normal file
View File

@@ -0,0 +1 @@
body{width:100%;overflow:hidden;min-height:0;line-height:1.1}.navbar{padding-top:0;padding-bottom:0;height:50px}.navbar-brand{padding-top:0;padding-bottom:0}.footer{position:fixed;bottom:2px;right:0}

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

View File

@@ -0,0 +1,148 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
sodipodi:docname="test.svg"
inkscape:version="1.2.2 (732a01da63, 2022-12-09)"
id="svg8"
version="1.1"
viewBox="0 0 84.957652 83.999998"
height="84mm"
width="84.957649mm"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/">
<defs
id="defs2">
<linearGradient
id="linearGradient2657"
inkscape:swatch="solid">
<stop
style="stop-color:#1d1c3a;stop-opacity:1;"
offset="0"
id="stop2655" />
</linearGradient>
<linearGradient
id="linearGradient2651"
inkscape:swatch="solid">
<stop
style="stop-color:#ffffff;stop-opacity:1;"
offset="0"
id="stop2649" />
</linearGradient>
<inkscape:perspective
sodipodi:type="inkscape:persp3d"
inkscape:vp_x="141.38559 : 23.558402 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_z="84.423113 : 12.065813 : 1"
inkscape:persp3d-origin="42.478826 : 27.999999 : 1"
id="perspective1152" />
<rect
id="rect1932"
height="157.42708"
width="145.8988"
y="37.230656"
x="21.922619" />
<rect
id="rect1925"
height="154.78125"
width="152.5134"
y="30.994047"
x="18.331844" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient2657"
id="linearGradient2653"
x1="127.3954"
y1="76.994209"
x2="193.25467"
y2="76.994209"
gradientUnits="userSpaceOnUse" />
</defs>
<sodipodi:namedview
inkscape:window-maximized="1"
inkscape:window-y="1076"
inkscape:window-x="-8"
inkscape:window-height="1017"
inkscape:window-width="1920"
showgrid="false"
inkscape:document-rotation="0"
inkscape:current-layer="text1919"
inkscape:document-units="mm"
inkscape:cy="147.98735"
inkscape:cx="168.44294"
inkscape:zoom="1.979899"
inkscape:pageshadow="2"
inkscape:pageopacity="0.0"
borderopacity="1.0"
bordercolor="#666666"
pagecolor="#ffffff"
id="base"
inkscape:showpageshadow="2"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1" />
<metadata
id="metadata5">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>
</metadata>
<g
transform="translate(-32.673336,-72.980634)"
id="layer1"
inkscape:groupmode="layer"
inkscape:label="Ebene 1">
<g
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:50.8px;line-height:1.25;font-family:Gazzarelli;-inkscape-font-specification:Gazzarelli;letter-spacing:0px;word-spacing:0px;stroke-width:0.264583"
id="text1919"
aria-label="Ko Ma">
<text
xml:space="preserve"
style="font-size:44.3654px;line-height:1.65;font-family:'Gill Sans MT';-inkscape-font-specification:'Gill Sans MT';letter-spacing:0px;word-spacing:0px;stroke-width:0.0700042"
x="36.218472"
y="147.49475"
id="text422"
transform="scale(1.0102584,0.98984575)"><tspan
sodipodi:role="line"
id="tspan420"
style="font-size:44.3654px;stroke-width:0.0700042"
x="36.218472"
y="147.49475">SVG</tspan></text>
<text
xml:space="preserve"
style="font-size:19.7556px;line-height:1.65;font-family:'Gill Sans MT';-inkscape-font-specification:'Gill Sans MT';letter-spacing:0px;word-spacing:0px;stroke-width:0.0700042"
x="37.413837"
y="98.903824"
id="text426"><tspan
sodipodi:role="line"
id="tspan424"
style="font-size:19.7556px;stroke-width:0.0700042"
x="37.413837"
y="98.903824">xxx</tspan></text>
<rect
style="opacity:1;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient2653);stroke-width:1.229;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:3.2;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke fill markers"
id="rect1300"
width="83.504608"
height="82.438667"
x="33.447392"
y="73.72715"
rx="7.6999998"
ry="9.1999989" />
</g>
<g
style="font-size:9.87778px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;white-space:pre;shape-inside:url(#rect1925)"
id="text1923" />
<text
style="font-size:9.87778px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;white-space:pre;shape-inside:url(#rect1932)"
id="text1930"
xml:space="preserve" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 5.1 KiB

View File

@@ -0,0 +1,174 @@
var tblmeta, nodeid, nodeselect, tree, table;
var w = {
autoclose: false,
type: 'danger',
top: false,
rc0: {
top: false,
slide: true
}
};
var g = {
autoclose: false,
top: false,
rc0: {
top: false,
slide: true
}
};
var option = '';
function init() {
listtree();
// JQuery Tooltips aktivieren
$('[data-toggle="tooltip"]').tooltip({
delay: {
show: 1000,
hide: 100
}
});
}
function listtree() {
$("#tree").jstree("destroy");
$.get($('#base_url').val() + '/api/jstreedata/', function (res) {
//if ($.popyrc1(res, w)) return;
$('#tree')
.on('changed.jstree', function (e, data) {
// klick auf einen Node und holen der Mitglieder
if (data.node !== undefined) {
nodeselect = data.node;
treecklick(data);
}
})
.jstree({
'core': {
"check_callback": function (operation, node, node_parent, node_position, more) {
// unterbinden des droppen wenn man nicht owner ist und wenn externe sachen gedropt werden
if (more && more.dnd && operation == 'move_node') {
if (treelock || ogsidstochange[node_parent.id] == undefined) {
return false;
}
return true;
}
},
"keyboard": false,
"cache": false,
'data': res.data
},
"plugins": [
"wholerow", "sort", "html_data"
],
}).on('ready.jstree', function (d) {
$('.baum').show();
});
$('.treedetail').css('height', 'calc(100vh - 130px)');
});
}
function treecklick(data) {
$('.words').hide();
if (data.node.parent != '#') {
$('.words').show();
gettabledata(data.node.id);
}
}
function gettabledata() {
table = $('#tbl-worlds').DataTable({
ajax: {
"type": "GET",
"url": $('#base_url').val() + '/api/datatablesdata',
dataSrc: function (json) {
return json.data;
}
},
// fixedHeader: {
// header: true,
// },
// responsive: {
// details: false
// },
// scrollCollapse: true,
searching: true,
destroy: true,
deferRender: true,
// createdRow: function (row, data, index) {
// if (data.hidden == 1) {
// $(row).css('opacity', 0.2)
// }
// },
fixedColumns: {
left: 1,
right: 1
},
select: true,
// "stateSave": true,
// "stateLoadParams": function (settings, data) {
// console.log(settings)
// for (var i in data.columns) {
// data.columns[i].regex = false;
// data.columns[i].search = '';
// }
// },
columns: [
{
data: 'col1',
},
{
data: 'col2',
},
{
data: 'col3',
},
{
data: 'col4',
},
{
data: 'col5',
},
{
data: 'col6',
},
{
data: 'col7',
},
{
data: 'col8',
},
{
data: 'col9',
}
],
info: true,
scrollX: true,
// bLengthChange: false,
scroller: true,
scrollY: 'calc(100vh - 320px)', // Höhe der Tabelle
"sDom": 'Bfrti',
language: {
sLoadingRecords: "Lade Wörter...",
sInfo: '_START_ bis _END_ von _TOTAL_ Wörter',
sInfoEmpty: '',
infoFiltered: '(gefiltert von _MAX_ Wörter)',
searchPlaceholder: 'Suche',
sZeroRecords: 'Keine Wörter vorhanden.',
sSearch: '',
oPaginate: {
sFirst: 'Erste',
sPrevious: '<',
sNext: '>',
sLast: 'Letzte'
}
}
});
}
$(document).ready(function () {
init();
});

View File

@@ -0,0 +1 @@
function init(){listtree(),$('[data-toggle="tooltip"]').tooltip({delay:{show:1e3,hide:100}})}function listtree(){$("#tree").jstree("destroy"),$.get($("#base_url").val()+"/api/jstreedata/",function(e){$("#tree").on("changed.jstree",function(e,t){void 0!==t.node&&(nodeselect=t.node,treecklick(t))}).jstree({core:{check_callback:function(e,t,a,o,r){if(r&&r.dnd&&"move_node"==e)return!treelock&&null!=ogsidstochange[a.id]},keyboard:!1,cache:!1,data:e.data},plugins:["wholerow","sort","html_data"]}).on("ready.jstree",function(e){$(".baum").show()}),$(".treedetail").css("height","calc(100vh - 130px)")})}function treecklick(e){$(".words").hide(),"#"!=e.node.parent&&($(".words").show(),gettabledata(e.node.id))}function gettabledata(){table=$("#tbl-worlds").DataTable({ajax:{type:"GET",url:$("#base_url").val()+"/api/datatablesdata",dataSrc:function(e){return e.data}},searching:!0,destroy:!0,deferRender:!0,fixedColumns:{left:1,right:1},select:!0,columns:[{data:"col1"},{data:"col2"},{data:"col3"},{data:"col4"},{data:"col5"},{data:"col6"},{data:"col7"},{data:"col8"},{data:"col9"}],info:!0,scrollX:!0,scroller:!0,scrollY:"calc(100vh - 320px)",sDom:"Bfrti",language:{sLoadingRecords:"Lade Wörter...",sInfo:"_START_ bis _END_ von _TOTAL_ Wörter",sInfoEmpty:"",infoFiltered:"(gefiltert von _MAX_ Wörter)",searchPlaceholder:"Suche",sZeroRecords:"Keine Wörter vorhanden.",sSearch:"",oPaginate:{sFirst:"Erste",sPrevious:"<",sNext:">",sLast:"Letzte"}}})}var tblmeta,nodeid,nodeselect,tree,table,w={autoclose:!1,type:"danger",top:!1,rc0:{top:!1,slide:!0}},g={autoclose:!1,top:!1,rc0:{top:!1,slide:!0}},option="";$(document).ready(function(){init()});

7
xxx/app/public/js/xxx.js Normal file
View File

@@ -0,0 +1,7 @@
function xxx() {
}
$(document).ready(function () {
new xxx();
});

11
xxx/app/script/XxxApp Normal file
View File

@@ -0,0 +1,11 @@
#!/usr/bin/env perl
use strict;
use warnings;
use FindBin;
BEGIN { unshift @INC, "$FindBin::Bin/../lib" }
use Mojolicious::Commands;
# Start command line interface for application
Mojolicious::Commands->start_app('XxxApp');

108
xxx/app/t/01_routes.t Normal file
View File

@@ -0,0 +1,108 @@
print "Test: $0\n";
use Mojo::Base -strict;
use Mojo::JSON qw(decode_json);
use Modern::Perl;
use Test::More;
use Test::Mojo;
use DBI;
use Data::Printer;
#my $dbh = DBI->connect('DBI:mysql:prod:ares', '', '') or die ('Fehler beim Verbinden mit der Datenbank');
my $t = Test::Mojo->new('XxxApp');
$t->app->log->level('info');
$t->app->ua->max_redirects(1);
subtest 'prod konfig test' => sub {
my $konf = do '../deb/prod/xxx_app.conf';
is exists $konf->{secret}, 1, 'secret = exists';
is $konf->{htlib}, "/var/local/htlib", 'htlib = /var/local/htlib';
is $konf->{hypnotoad}->{pid_file}, "/run/xxx.pid", 'hypnotoad->pid_file = /run/xxx.pid';
is $konf->{hypnotoad}->{workers}, 4, 'hypnotoad->workers = 4';
is $konf->{hypnotoad}->{proxy}, 1, 'hypnotoad->proxy = 1';
is $konf->{logging}->{enable}, 1, 'logging->enable = 1';
is $konf->{logging}->{facility}, 'local7', 'logging->facility = local7';
is $konf->{logging}->{ident}, 'xxx', 'logging->ident = xxx';
is $konf->{logging}->{level}, 'info', 'logging->level = info';
is $konf->{logging}->{only_syslog}, 1, 'logging->only_syslog = 1';
};
subtest 'Routes', sub {
note '/';
$t->get_ok('/')
->status_is(200)
->content_like(qr'<img class="mypic" src="\/images\/xxx.svg">', 'SVG is availble')
->header_isnt('Server' => 'Mojolicious');
note '/login';
$t->get_ok('/login')
->status_is(200)
->content_like(qr'placeholder="Anmeldename"', 'Anmeldename is availble')
->content_like(qr'placeholder="Passwort"', 'Passwort is availble');
note '/api/jstreedata no permission';
$t->get_ok('/api/jstreedata')
->status_is(401);
note '/api/datatablesdata no permission';
$t->get_ok('/api/datatablesdata')
->status_is(401);
# on this point we have a session
note 'with Login no AD';
$t->post_ok(
'/login' => form => { username => 'developer', password => 'wbdfgmnjke834dshf89w7rsdfsdjf' }
);
$t->status_is(302);
note '/api/jstreedata';
$t->get_ok('/api/jstreedata')
->status_is(200)
->json_is( '/msg' => 'OK', 'msg: OK' )
->json_is( '/rc' => 0, 'rc: 0' );
my $res = $t->tx->res->json;
is ref $res->{data}, 'ARRAY', 'data is ARRAY reference';
is exists $res->{data}[0]->{id}, 1, 'data has id';
is exists $res->{data}[0]->{parent}, 1, 'data has firstname';
is exists $res->{data}[0]->{text}, 1, 'data has name';
note '/api/datatablesdata';
$t->get_ok('/api/datatablesdata')
->status_is(200)
->json_is( '/msg' => 'OK', 'msg: OK' )
->json_is( '/rc' => 0, 'rc: 0' );
$res = $t->tx->res->json;
is ref $res->{data}, 'ARRAY', 'data is ARRAY reference';
is exists $res->{data}[0]->{col1}, 1, 'data has col1';
is exists $res->{data}[0]->{col2}, 1, 'data has col2';
is exists $res->{data}[0]->{col3}, 1, 'data has col3';
is exists $res->{data}[0]->{col4}, 1, 'data has col4';
is exists $res->{data}[0]->{col5}, 1, 'data has col5';
is exists $res->{data}[0]->{col6}, 1, 'data has col6';
is exists $res->{data}[0]->{col7}, 1, 'data has col7';
is exists $res->{data}[0]->{col8}, 1, 'data has col8';
is exists $res->{data}[0]->{col9}, 1, 'data has col9';
is exists $res->{data}[0]->{col10}, '', 'data has no col10';
};
subtest 'POST requestBody required must True', sub {
$t->get_ok('/api');
$t->status_is(200);
my $res = $t->tx->res->json;
for my $path ( keys %{$res->{paths}} ) {
my $pa = $res->{paths}->{$path};
if ( $pa->{post}->{requestBody} ) {
is $pa->{post}->{requestBody}->{required}, 1, $path;
}
}
};
done_testing();

View File

@@ -0,0 +1,119 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1">
<title><%= $title %></title>
%= stylesheet '/htlib/bootstrap/5.3.3/css/bootstrap.min.css'
%= stylesheet '/htlib/fontawesome/6.4.0/css/all.min.css'
%= stylesheet '/htlib/jquery-datatables/2.1.8/css/dataTables.bootstrap4.min.css'
%= stylesheet '/htlib/jquery-datatables/extensions/Buttons/3.1.2/css/buttons.dataTables.min.css'
%= stylesheet '/htlib/jquery-datatables/extensions/Scroller/2.4.3/css/scroller.bootstrap4.min.css'
%= stylesheet '/htlib/jquery-datatables/extensions/FixedColumns/5.0.3/css/fixedColumns.dataTables.min.css'
%= stylesheet '/htlib/jquery-ui/1.13.2/jquery-ui.min.css'
%= stylesheet '/css/xxx.min.css'
<style>
% if ( $self->config->{test} ) { # einfärben der Kopfzeile wenn Programm im Developermodus läuft
.navbar {
background-color: rgb(255, 215, 215) !important;
}
% }
</style>
</head>
<body>
<!-- Standard-Navbar -->
<nav class="navbar navbar-expand-sm navbar-light bg-light">
<div class="container-fluid">
<a class="navbar-brand" href="<%= $self->config->{mlandshome} %>">
<img src="<%= url_for('/htlib/mlands/img/mlands.svg') %>">
</a>
<a class="navbar-brand ukraine" href="/" style="margin-right: 0.5rem;">
<img src="<%= url_for('/images/xxx.svg') %>" width="40px">
</a>
<a class="navbar-brand" style="font-size: 1.4rem;" href="<%= url_for('/') %>">Xxx</a>
<div class="collapse navbar-collapse" id="navbarText">
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
<%== nav($self->config->{navigation}->{menu}) %>
</ul>
</ul>
<div class="collapse navbar-collapse justify-content-end" id="navbarNav">
<ul class="navbar-nav nav">
% if ( $self->session->{auth_data}) {
% my $username = $self->session->{auth_data} =~ s/@[^@]*$//r;
<li class="nav-item dropdown">
<a aria-expanded="false" aria-haspopup="true" class="nav-link dropdown-toggle" data-bs-toggle="dropdown" href="#" id="navbarDropdown" role="button">
%== $username
</a>
<ul class="dropdown-menu dropdown-menu-lg-right">
%# Benutzerinformatioen
<li><a href="<%= url_for('/userinfo') %>" class="dropdown-item">
<span class="fas fa-user-circle" style="color:#777;"></span>
&nbsp;Info
</a></li>
<li class="dropdown-divider"></li>
%# Abmelden
<li><a class="dropdown-item"href="<%= url_for('/getoff') %>">
<span class="fas fa-cloud-moon" style="color:#777;"></span>
&nbsp;Abmelden
</a></li>
</ul>
</li>
% } else {
<li class="nav-item">
<a href="<%= url_for('/login') %>" class="nav-link">
Anmelden
<span class="fas fa-lock" style="color:#777;"></span>
</a>
</li>
% }
<li>
<a href="<%= url_for('/about') %>" class="nav-link">
über
<span class="fas fa-info-sign" style="color:#777;"></span>
</a>
</li>
</ul>
</div>
</div>
</nav>
<!-- externe Bibliotheken -->
%= javascript '/htlib/jquery/3.7.1/jquery.min.js'
%= javascript '/htlib/jquery-ui/1.13.2/jquery-ui.min.js'
%= javascript '/htlib/bootstrap/5.3.3/js/bootstrap.bundle.min.js'
%= javascript '/htlib/jquery-datatables/2.1.8/js/dataTables.min.js'
%= javascript '/htlib/jquery-datatables/2.1.8/js/dataTables.bootstrap4.min.js'
%= javascript '/htlib/jquery-datatables/extensions/Select/2.1.0/js/dataTables.select.min.js'
%= javascript '/htlib/jquery-datatables/extensions/Select/2.1.0/js/select.bootstrap4.min.js'
%= javascript '/htlib/jquery-datatables/extensions/Buttons/3.1.2/js/dataTables.buttons.min.js'
%= javascript '/htlib/jquery-datatables/extensions/Buttons/3.1.2/js/buttons.colVis.min.js'
%= javascript '/htlib/jquery-datatables/extensions/JSZip/3.10.1/jszip.min.js'
%= javascript '/htlib/jquery-datatables/extensions/pdfmake/0.2.7/pdfmake.min.js'
%= javascript '/htlib/jquery-datatables/extensions/pdfmake/0.2.7/vfs_fonts.js'
%= javascript '/htlib/jquery-datatables/extensions/Buttons/3.1.2/js/buttons.html5.min.js'
%= javascript "/htlib/jquery-datatables/extensions/FixedColumns/5.0.3/js/dataTables.fixedColumns.min.js"
%= javascript '/htlib/jquery-datatables/extensions/Scroller/2.4.3/js/dataTables.scroller.min.js'
%= javascript '/htlib/jquery-datatables/extensions/Scroller/2.4.3/js/scroller.bootstrap4.min.js'
%= javascript '/htlib/jquery-datatables/date-de/date-de.min.js'
%= javascript "/htlib/mlands/js/popy.min.js"
<%= content %>
<input type="hidden" id="base_url" value="<%= $self->config->{prefix} %>">
<input type="hidden" id="koma-urlbase" value="<%= $self->config->{base}->{koma}->{url} %>">
<!-- FOOTER -->
<footer class="footer">
% use POSIX qw(strftime);
% my $year = strftime('%Y', localtime);
<div class="container-fluid text-muted text-right">
<small>Xxx v<%= $self->config->{version} %> &nbsp; &bull; &nbsp; &copy; 2003-<%= $year %> ml&amp;s Gmbh &amp; Co. KG</small>
</div>
</footer>
</body>
</html>

View File

@@ -0,0 +1,16 @@
% layout 'index';
<div class="container pt-3">
<div class="jumbotron">
<p>Entwickler: <br></p>
<h4><p></p>
<p>Version <%= $self->config->{version} %><p>
<p>powered by</p>
<img src="<%= url_for('/images/powered.png') %>"><br>
<p><a href="https://mojolicious.org/" target="_blank">Mojolicious</a> <%== 'V'.$Mojolicious::VERSION .', <a href="https://perl.org/" target="_blank">Perl</a> '.ucfirst $^V .', ' %><span id="version"></span></p>
</div>
</div>
<script>
$('#version').html('<a href="https://jquery.com/" target="_blank">jQuery</a> V' + $().jquery + ', <a href="https://datatables.net/" target="_blank">Datatables</a> V' + $.fn.dataTable.version + ', <a href="https://getbootstrap.com/docs/3.3/" target="_blank">Bootstrap</a> V' + bootstrap.Tooltip.VERSION );
</script>

View File

@@ -0,0 +1,35 @@
% layout 'index';
<title>Xxx: Login</title>
<div class="container">
%= form_for '/login' => {format => 'txt'} => (method => 'POST', class => 'form-horizontal', id => 'form_login') => begin
<fieldset>
<div class="row" style="margin-top:25vh;">
<div class="col-4" style="margin:0 auto 0 auto;float:none;">
% if ( defined $self->session->{msg} ) {
<h4>
% if ( $self->session->{msg} =~ /Fehler|Sorry/ ) {
<span class="text-danger">
% } else {
<span class="text-info">
% }
<%= $self->session->{msg} %>
</span>
</h4>
% }
<div class="input-group">
<div class="input-group-text"><i class="fas fa-user"></i></div>
<input autocomplete="off" autofocus="" class="form-control" id="id_username" name="username" placeholder="Anmeldename" required="" type="text">
</div>
<div class="input-group pt-3">
<div class="input-group-text"><i class="fas fa-lock"></i></div>
<input class="form-control" id="id_password" name="password" placeholder="Passwort" required="" type="password" value="">
</div>
<div class="pt-3"><input class="btn btn-lg btn-primary btn-block" type="submit" value="Anmelden"></div>
</div>
</fieldset>
% end
</div>

View File

@@ -0,0 +1,16 @@
% layout 'index';
<style>
.mypic {
height: calc(100vh - 180px);
}
</style>
<div class="card m-2">
<div class="row p-3">
<div>Willkommen bei</div>
</div>
<div class="text-center pb-3">
<img class="mypic" src="/images/xxx.svg">
</div>
</div>

View File

@@ -0,0 +1,23 @@
% layout 'index';
<title>*Temp*::Userinfo</title>
<div class="jumbotron" style="margin-top:5vh;">
% if ( defined $msg ) {
<h2>
% if ( $msg =~ /Fehler|Sorry/ ) {
<span class="text-danger">
% } else {
<span class="text-info">
% }
<%= $msg %></span></h2>
% }
% if ( defined $sess->{privs} ) {
% foreach my $s ( @{$sess->{privs} } ) {
<p> Gruppe: <%= $s %></p>
% }
% }
</div>

View File

@@ -0,0 +1,43 @@
% layout 'index';
%= stylesheet '/htlib/jquery-jstree/3.3.17/themes/default/style.min.css'
%= stylesheet '/css/jstreetemplate.min.css'
<div class="container-fluid">
<div class="row p-2">
<div class="col-3">
<label class="col-form-label text-right">Daten:</label>
</div>
</div>
<div class="row pr-3 baum" style="display: none">
<div class="col-3">
<div class="treedetail" id="tree"></div>
</div>
<div class="col-9 treedetail">
<div class="words" style="display: none">
<div class="row p-3">
<label class="col-form-label pb-0">Wörter</label>
</div>
<table class="table table-bordered table-sm" width="100%" id="tbl-worlds">
<thead>
<tr>
<th class="text-center">Spalte1</th>
<th class="text-center">Spalte 2</th>
<th class="text-center">Spalte 3</th>
<th class="text-center">Spalte 4</th>
<th class="text-center">Spalte 5</th>
<th class="text-center">Spalte 6</th>
<th class="text-center">Spalte 7</th>
<th class="text-center">Spalte 8</th>
<th class="text-center">Spalte 9</th>
</tr>
</thead>
<tbody></tbody>
</table>
</div>
</div>
</div>
</div>
%= javascript '/htlib/jquery-jstree/3.3.17/jstree.min.js'
%= javascript "/js/jstreetemplate.min.js?v=". $self->config->{version}

13
xxx/app/test.sh Normal file
View File

@@ -0,0 +1,13 @@
#!/bin/bash
#export PERL5LIB=`pwd`
if [[ $1 == "all" ]]; then
prove -l t/*.t
else
if [[ -f t/$1.t ]]; then
prove -l -v t/$1.t
else
prove -l -v t/*.t
fi
fi

35
xxx/app/xxx_app.conf Normal file
View File

@@ -0,0 +1,35 @@
{
# zus_config => '/etc/mlands/configs/xxx.conf', # um externe konfigs mit Passwörtern zu laden
htlib => '/var/local/htlib',
mlandshome => 'http://intranet.mlands.com:9673/Z/',
komaurl => 'http://intra/koma',
smtpserver => 'mail.mlands.com',
mail => 'Xxx@mlands.com',
secret => 'mojoappsecret',
# DB Pools
# db => {
# xxx => {
# dsn => 'DBI:mysql:test:localhost',
# username => '',
# password => '',
# verbose => 0,
# },
# },
privileges => {
# user => wird bei erfolgreicher Anmeldung den Privs hinzugefügt
},
logging => {
level => 'debug',
facility => 'local7',
enable => 1,
only_syslog => 0,
#access_log => 'v1', #bisher nicht änderbar
ident => 'xxx',
#color => 1, #erst ab Mojolicious 9.01
}
}

1
xxx/deb/DEBIAN/conffiles Normal file
View File

@@ -0,0 +1 @@
/opt/xxx/app/xxx_app.conf

10
xxx/deb/DEBIAN/control Normal file
View File

@@ -0,0 +1,10 @@
Package: steffen-xxx
Version: __VERSION__
Section: unknown
Priority: optional
Architecture: all
Depends: libmojolicious-perl >= (9.39), libev-perl, libsteffen-mojoplug-authorization-perl, libsteffen-mojoplug-navhelper-perl, libsteffen-mojoplug-syslog-perl, libmojolicious-plugin-openapi-perl, libsteffen-mojoplug-swaggerui-perl
Installed-Size: __VERSION__
Maintainer: Steffen Junge <steffen.junge@gmx.de>
Description: wofuer
A HTTP Daemon for the steffen xxx Webservice.

7
xxx/deb/DEBIAN/postinst Normal file
View File

@@ -0,0 +1,7 @@
#!/bin/sh
# Startskript aktivieren
systemctl daemon-reload
/bin/systemd-sysusers
/bin/systemd-tmpfiles --create
systemctl enable --now steffen-xxx.service

6
xxx/deb/DEBIAN/prerm Normal file
View File

@@ -0,0 +1,6 @@
#!/bin/sh
# $Id$
# Daemon stoppen und deaktivieren
systemctl stop steffen-xxx.service || true
systemctl disable steffen-xxx.service || true

52
xxx/deb/pbuild.sh Normal file
View File

@@ -0,0 +1,52 @@
#! /bin/sh
cd "${0%/*}" || exit
SCRIPTPATH=$(pwd)
cd ../app || exit
rc=0
prove -l -v t/*.t
rc=$?
cd "$SCRIPTPATH" || exit
if [ $rc -eq 0 ] ; then
# Jahr in dem das Programm begonnen wurde
createyear=mojocreateyear
currentyear=$(date +'%Y')
major=$((currentyear-createyear))
minor=$(date +'%m')
progpfad="opt/xxx/"
apppfad="app/lib/XxxApp.pm"
workdir=/tmp/$USER_$(pwd | sed 's/^.*\///')
rm -Rf "${workdir:?}/*"
mkdir -p "$workdir"
# Daten in workdir sysnchronisieren
rsync -Ca ./DEBIAN "$workdir/"
rsync -Ca ./usr "$workdir/"
mkdir -p "$workdir/opt"
rsync -Ca ../app "$workdir/$progpfad"
# Test entfernen
rm -r "$workdir/$progpfad/app/t"
rm "$workdir/$progpfad/app/test.sh"
# config aus prod ordner kopieren
cp -ar ./prod/* "$workdir/$progpfad/app"
# Revision holen
REV=$(git rev-list --count --all)
VER="$major.$minor.$REV"
# Version in die startup app.pm und control eintragen
sed -i "s@__VERSION__@$VER@g" "$workdir/DEBIAN/control"
sed -i "s@__VERSION__@$VER@g" "$workdir/$progpfad/$apppfad"
SIZE=$(du -h -k --max-depth=0 "$workdir" | cut -d '/' -f1)
sed -i "s@__SIZE__@$SIZE@g" "$workdir/DEBIAN/control"
echo "Version: $VER, Size: $SIZE"
chmod -R g-s "$workdir"
# paket erstellen
fakeroot dpkg-deb -b "$workdir" ./
else
echo "TESTs failed no package created"
fi

40
xxx/deb/prod/xxx_app.conf Normal file
View File

@@ -0,0 +1,40 @@
{
# zus_config => '/etc/mlands/configs/xxx.conf', # um externe konfigs mit Passwörtern zu laden
htlib => '/var/local/htlib',
secret => 'mojoappsecret',
# dieser Teil wandert in die Datei unter zus_config
# DB Pools
# db => {
# xxx => {
# dsn => 'DBI:mysql:test:ares',
# username => '',
# password => '',
# verbose => 0,
# },
# },
hypnotoad => {
listen => ['http://*:hypnotoadport'],
workers => 4,
pid_file => '/run/xxx.pid',
proxy => 1
},
privileges => {
# user => wird bei erfolgreicher Anmeldung den Privs hinzugefügt
# admin => 'xxx_admin::xgroups',
},
logging => {
level => 'info',
facility => 'local7',
enable => 1,
only_syslog => 1,
#access_log => 'v1', #bisher nicht änderbar
ident => 'xxx',
#color => 1, #erst ab Mojolicious 9.01
}
}

View File

@@ -0,0 +1,41 @@
[Unit]
Description=steffen Xxx/Rfs Webservice
After=network.target syslog.socket
StartLimitBurst=5
StartLimitIntervalSec=10
[Service]
Type=forking
User=steffen-xxx
Group=webapps
WorkingDirectory=/opt/xxx/app
ExecStart=/usr/bin/hypnotoad ./script/XxxApp
ExecReload=/usr/bin/hypnotoad ./script/XxxApp
Restart=on-failure
PIDFile=/run/xxx.pid
KillMode=process
# Optional hardening to improve security
ReadWritePaths=/opt/xxx
NoNewPrivileges=yes
MemoryDenyWriteExecute=true
PrivateDevices=yes
PrivateTmp=yes
ProtectHome=yes
#ProtectSystem=strict
ProtectControlGroups=true
#RestrictSUIDSGID=true
RestrictRealtime=true
LockPersonality=true
#ProtectKernelLogs=true
ProtectKernelTunables=true
#ProtectHostname=true
ProtectKernelModules=true
PrivateUsers=true
#ProtectClock=true
SystemCallArchitectures=native
SystemCallErrorNumber=EPERM
SystemCallFilter=@system-service
[Install]
WantedBy=multi-user.target

View File

@@ -0,0 +1,5 @@
# see https://www.freedesktop.org/software/systemd/man/latest/sysusers.d.html
# Type Name ID GECOS Home Shell
g webapps - -
u steffen-xxx - "steffen Xxx" /opt/xxx -
m steffen-xxx webapps

View File

@@ -0,0 +1,3 @@
# see https://www.freedesktop.org/software/systemd/man/latest/tmpfiles.d.html
# Type Path Mode User Group Age Argument
Z /opt/xxx - steffen-xxx steffen-xxx