mirror of
https://framagit.org/fiat-tux/hat-softwares/lutim.git
synced 2026-03-28 17:42:54 +01:00
Fix #6
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -3,3 +3,4 @@ lutim.db
|
||||
script/hypnotoad.pid
|
||||
local/*
|
||||
files/*
|
||||
templates/data.html.ep
|
||||
|
||||
@@ -107,7 +107,7 @@ vim de.pm
|
||||
There's just a few sentences, so it will be quick to translate. Please consider to send me you language file in order to help the other users :smile:.
|
||||
|
||||
##Others projects dependancies
|
||||
LUTIm is written in Perl with the Mojolicious framework, uses the Twitter bootstrap framework to look not too ugly, JQuery and JQuery File Uploader (<https://github.com/danielm/uploader/>) to add some modernity.
|
||||
LUTIm is written in Perl with the [Mojolicious](http://mojolicio.us) framework, uses the [Twitter bootstrap](http://getbootstrap.com) framework to look not too ugly, [JQuery](http://jquery.com) and [JQuery File Uploader](https://github.com/danielm/uploader/) (slightly modified) to add some modernity, [Raphaël](http://raphaeljs.com/) and [SimpleGraph](http://benaskins.github.io/simplegraph/) for stats graphs.
|
||||
|
||||
##Official instance
|
||||
You can see it working at http://lut.im.
|
||||
|
||||
@@ -362,6 +362,7 @@
|
||||
"css": [
|
||||
"print.less",
|
||||
"type.less",
|
||||
"tables.less",
|
||||
"forms.less",
|
||||
"buttons.less",
|
||||
"input-groups.less",
|
||||
|
||||
1
cpanfile
1
cpanfile
@@ -4,3 +4,4 @@ requires 'Mojolicious::Plugin::I18N';
|
||||
requires 'ORLite';
|
||||
requires 'File::Type';
|
||||
requires 'Text::Unidecode';
|
||||
requires 'DateTime';
|
||||
|
||||
1264
cpanfile.snapshot
1264
cpanfile.snapshot
File diff suppressed because it is too large
Load Diff
16
lib/Lutim.pm
16
lib/Lutim.pm
@@ -137,6 +137,22 @@ sub startup {
|
||||
}
|
||||
)->name('about');
|
||||
|
||||
$r->get('/stats' => sub {
|
||||
my $c = shift;
|
||||
|
||||
$c->render(
|
||||
template => 'stats',
|
||||
total => LutimModel::Lutim->count('WHERE path IS NOT NULL')
|
||||
);
|
||||
|
||||
# Check provisioning
|
||||
$c->on(finish => sub {
|
||||
shift->provisioning();
|
||||
}
|
||||
);
|
||||
}
|
||||
)->name('stats');
|
||||
|
||||
$r->post('/' => sub {
|
||||
my $c = shift;
|
||||
my $upload = $c->param('file');
|
||||
|
||||
@@ -24,32 +24,35 @@ my $inf_body = <<EOF;
|
||||
EOF
|
||||
|
||||
our %Lexicon = (
|
||||
'license' => 'License:',
|
||||
'fork-me' => 'Fork me on Github !',
|
||||
'share-twitter' => 'Share on Twitter',
|
||||
'informations' => 'Informations',
|
||||
'informations-body' => $inf_body,
|
||||
'view-link' => 'View link:',
|
||||
'download-link' => 'Download link:',
|
||||
'twitter-link' => 'Link for put in a tweet:',
|
||||
'some-bad' => 'Something bad happened',
|
||||
'delete-first' => 'Delete at first view?',
|
||||
'delete-day' => 'Delete after 24 hours?',
|
||||
'upload_image' => 'Send an image',
|
||||
'image-only' => 'Only images are allowed',
|
||||
'go' => 'Let\'s go!',
|
||||
'drag-n-drop' => 'Drag & drop images here',
|
||||
'or' => '-or-',
|
||||
'file-browser' => 'Click to open the file browser',
|
||||
'image_not_found' => 'Unable to find the image',
|
||||
'no_more_short' => 'There is no more available URL. Retry or contact the administrator. [_1]',
|
||||
'no_valid_file' => 'The file [_1] is not an image.',
|
||||
'file_too_big' => 'The file exceed the size limit ([_1])',
|
||||
'no_time_limit' => 'No time limit',
|
||||
'24_hours' => '24 hours',
|
||||
'7_days' => '7 days',
|
||||
'30_days' => '30 days',
|
||||
'1_year' => 'One year',
|
||||
'license' => 'License:',
|
||||
'fork-me' => 'Fork me on Github !',
|
||||
'share-twitter' => 'Share on Twitter',
|
||||
'informations' => 'Informations',
|
||||
'informations-body' => $inf_body,
|
||||
'view-link' => 'View link:',
|
||||
'download-link' => 'Download link:',
|
||||
'twitter-link' => 'Link for put in a tweet:',
|
||||
'some-bad' => 'Something bad happened',
|
||||
'delete-first' => 'Delete at first view?',
|
||||
'delete-day' => 'Delete after 24 hours?',
|
||||
'upload_image' => 'Send an image',
|
||||
'image-only' => 'Only images are allowed',
|
||||
'go' => 'Let\'s go!',
|
||||
'drag-n-drop' => 'Drag & drop images here',
|
||||
'or' => '-or-',
|
||||
'file-browser' => 'Click to open the file browser',
|
||||
'image_not_found' => 'Unable to find the image',
|
||||
'no_more_short' => 'There is no more available URL. Retry or contact the administrator. [_1]',
|
||||
'no_valid_file' => 'The file [_1] is not an image.',
|
||||
'file_too_big' => 'The file exceed the size limit ([_1])',
|
||||
'no_time_limit' => 'No time limit',
|
||||
'24_hours' => '24 hours',
|
||||
'7_days' => '7 days',
|
||||
'30_days' => '30 days',
|
||||
'1_year' => 'One year',
|
||||
'pushed-images' => ' sent images on this instance from beginning.',
|
||||
'graph-data-once-a-day' => 'The graph\'s datas are not updated in real-time.',
|
||||
'lutim-stats' => 'LUTIm\'s statistics',
|
||||
'back-to-index' => 'Back to homepage'
|
||||
);
|
||||
|
||||
|
||||
@@ -24,32 +24,35 @@ my $inf_body = <<EOF;
|
||||
EOF
|
||||
|
||||
our %Lexicon = (
|
||||
'license' => 'Licence :',
|
||||
'fork-me' => 'Fork me on Github',
|
||||
'share-twitter' => 'Partager sur Twitter',
|
||||
'informations' => 'Informations',
|
||||
'informations-body' => $inf_body,
|
||||
'view-link' => 'Lien d\'affichage :',
|
||||
'download-link' => 'Lien de téléchargement :',
|
||||
'twitter-link' => 'Lien pour mettre dans un tweet :',
|
||||
'some-bad' => 'Un problème est survenu',
|
||||
'delete-first' => 'Supprimer au premier accès ?',
|
||||
'delete-day' => 'Supprimer après 24 heures ?',
|
||||
'upload_image' => 'Envoyez une image',
|
||||
'image-only' => 'Seules les images sont acceptées',
|
||||
'go' => 'Allons-y !',
|
||||
'drag-n-drop' => 'Déposez vos images ici',
|
||||
'or' => '-ou-',
|
||||
'file-browser' => 'Cliquez pour utiliser le navigateur de fichier',
|
||||
'image_not_found' => 'Impossible de trouver l\'image',
|
||||
'no_more_short' => 'Il n\'y a plus d\'URL disponible. Veuillez réessayer ou contactez l\'administrateur. [_1].',
|
||||
'no_valid_file' => 'Le fichier [_1] n\'est pas une image.',
|
||||
'file_too_big' => 'Le fichier dépasse la limite de taille ([_1])',
|
||||
'no_time_limit' => 'Pas de limitation de durée',
|
||||
'24_hours' => '24 heures',
|
||||
'7_days' => '7 jours',
|
||||
'30_days' => '30 jours',
|
||||
'1_year' => 'Un an',
|
||||
'license' => 'Licence :',
|
||||
'fork-me' => 'Fork me on Github',
|
||||
'share-twitter' => 'Partager sur Twitter',
|
||||
'informations' => 'Informations',
|
||||
'informations-body' => $inf_body,
|
||||
'view-link' => 'Lien d\'affichage :',
|
||||
'download-link' => 'Lien de téléchargement :',
|
||||
'twitter-link' => 'Lien pour mettre dans un tweet :',
|
||||
'some-bad' => 'Un problème est survenu',
|
||||
'delete-first' => 'Supprimer au premier accès ?',
|
||||
'delete-day' => 'Supprimer après 24 heures ?',
|
||||
'upload_image' => 'Envoyez une image',
|
||||
'image-only' => 'Seules les images sont acceptées',
|
||||
'go' => 'Allons-y !',
|
||||
'drag-n-drop' => 'Déposez vos images ici',
|
||||
'or' => '-ou-',
|
||||
'file-browser' => 'Cliquez pour utiliser le navigateur de fichier',
|
||||
'image_not_found' => 'Impossible de trouver l\'image',
|
||||
'no_more_short' => 'Il n\'y a plus d\'URL disponible. Veuillez réessayer ou contactez l\'administrateur. [_1].',
|
||||
'no_valid_file' => 'Le fichier [_1] n\'est pas une image.',
|
||||
'file_too_big' => 'Le fichier dépasse la limite de taille ([_1])',
|
||||
'no_time_limit' => 'Pas de limitation de durée',
|
||||
'24_hours' => '24 heures',
|
||||
'7_days' => '7 jours',
|
||||
'30_days' => '30 jours',
|
||||
'1_year' => 'Un an',
|
||||
'pushed-images' => ' images envoyées sur cette instance depuis le début.',
|
||||
'graph-data-once-a-day' => 'Les données du graphique ne sont pas mises à jour en temps réél.',
|
||||
'lutim-stats' => 'Statistiques de LUTIm',
|
||||
'back-to-index' => 'Retour à la page d\'accueil'
|
||||
);
|
||||
|
||||
|
||||
26
lib/Mojolicious/Command/cron.pm
Normal file
26
lib/Mojolicious/Command/cron.pm
Normal file
@@ -0,0 +1,26 @@
|
||||
package Mojolicious::Command::cron;
|
||||
use Mojo::Base 'Mojolicious::Commands';
|
||||
|
||||
has description => 'Execute tasks.';
|
||||
has hint => <<EOF;
|
||||
|
||||
See 'script/lutim cron help TASK' for more information on a specific task.
|
||||
EOF
|
||||
has message => sub { shift->extract_usage . "\nCron tasks:\n" };
|
||||
has namespaces => sub { ['Mojolicious::Command::cron'] };
|
||||
|
||||
sub help { shift->run(@_) }
|
||||
|
||||
1;
|
||||
|
||||
=encoding utf8
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Mojolicious::Command::cron - Cron commands
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
Usage: script/lutim cron TASK [OPTIONS]
|
||||
|
||||
=cut
|
||||
64
lib/Mojolicious/Command/cron/stats.pm
Normal file
64
lib/Mojolicious/Command/cron/stats.pm
Normal file
@@ -0,0 +1,64 @@
|
||||
package Mojolicious::Command::cron::stats;
|
||||
use Mojo::Base 'Mojolicious::Command';
|
||||
use LutimModel;
|
||||
use Mojo::DOM;
|
||||
use Mojo::Util qw(slurp spurt decode);
|
||||
use DateTime;
|
||||
use Mojolicious::Plugin::Config;
|
||||
|
||||
has description => 'Generate statistics about LUTIm.';
|
||||
has usage => sub { shift->extract_usage };
|
||||
|
||||
sub run {
|
||||
my $c = shift;
|
||||
|
||||
my $config = Mojolicious::Plugin::Config->parse(decode('UTF-8', slurp 'lutim.conf'), 'lutim.conf');
|
||||
$config->{stats_day_num} = (defined($config->{stats_day_num})) ? $config->{stats_day_num} : 365;
|
||||
|
||||
my $text = slurp('templates/data.html.ep.template');
|
||||
my $dom = Mojo::DOM->new($text);
|
||||
my $thead_tr = $dom->at('table thead tr');
|
||||
my $tbody_tr = $dom->at('table tbody tr');
|
||||
my $tbody_t2 = $tbody_tr->next;
|
||||
|
||||
my $separation = time() - $config->{stats_day_num} * 86400;
|
||||
|
||||
my %data;
|
||||
for my $img (LutimModel::Lutim->select('WHERE path IS NOT NULL AND created_at > = ?', $separation)) {
|
||||
my $time = DateTime->from_epoch(epoch => $img->created_at);
|
||||
my ($year, $month, $day) = ($time->year(), $time->month(), $time->day());
|
||||
|
||||
if (defined($data{$year}->{$month}->{$day})) {
|
||||
$data{$year}->{$month}->{$day} += 1;
|
||||
} else {
|
||||
$data{$year}->{$month}->{$day} = 1;
|
||||
}
|
||||
}
|
||||
|
||||
my $total = LutimModel::Lutim->count('WHERE path IS NOT NULL AND created_at < ?', $separation);
|
||||
for my $year (sort keys %data) {
|
||||
for my $month (sort keys %{$data{$year}}) {
|
||||
for my $day (sort keys %{$data{$year}->{$month}}) {
|
||||
$thead_tr->append_content('<th>'."$day/$month/$year".'</th>'."\n");
|
||||
$tbody_tr->append_content('<td>'.$data{$year}->{$month}->{$day}.'</td>'."\n");
|
||||
$total += $data{$year}->{$month}->{$day};
|
||||
$tbody_t2->append_content('<td>'.$total.'</td>'."\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
spurt $dom, 'templates/data.html.ep';
|
||||
}
|
||||
|
||||
=encoding utf8
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Mojolicious::Command::cron::stats - Stats generator
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
Usage: script/lutim cron stats
|
||||
|
||||
=cut
|
||||
|
||||
1;
|
||||
@@ -12,8 +12,9 @@
|
||||
length => 8, # optional
|
||||
provis_step => 5, # optional
|
||||
provisioning => 100, # optional
|
||||
hosted_by => 'My super hoster <img src="http://hoster.example.com" alt="Hoster logo">', # optional
|
||||
tweet_card_via => '@framasky', # optional
|
||||
max_file_size => 10485760, # optional, size in octets, you can write it 10*1024*1024
|
||||
https => 0, # optional, set to 1 if you use Lutim behind a secure web server
|
||||
#hosted_by => 'My super hoster <img src="http://hoster.example.com" alt="Hoster logo">', # optional
|
||||
#https => 0, # optional, set to 1 if you use Lutim behind a secure web server
|
||||
#stats_day_num => 365, # optional, number of days shown in /stats page
|
||||
};
|
||||
|
||||
229
public/css/bootstrap.css
vendored
229
public/css/bootstrap.css
vendored
@@ -680,6 +680,235 @@ address {
|
||||
font-style: normal;
|
||||
line-height: 1.42857143;
|
||||
}
|
||||
table {
|
||||
max-width: 100%;
|
||||
background-color: transparent;
|
||||
}
|
||||
th {
|
||||
text-align: left;
|
||||
}
|
||||
.table {
|
||||
width: 100%;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.table > thead > tr > th,
|
||||
.table > tbody > tr > th,
|
||||
.table > tfoot > tr > th,
|
||||
.table > thead > tr > td,
|
||||
.table > tbody > tr > td,
|
||||
.table > tfoot > tr > td {
|
||||
padding: 8px;
|
||||
line-height: 1.42857143;
|
||||
vertical-align: top;
|
||||
border-top: 1px solid #dddddd;
|
||||
}
|
||||
.table > thead > tr > th {
|
||||
vertical-align: bottom;
|
||||
border-bottom: 2px solid #dddddd;
|
||||
}
|
||||
.table > caption + thead > tr:first-child > th,
|
||||
.table > colgroup + thead > tr:first-child > th,
|
||||
.table > thead:first-child > tr:first-child > th,
|
||||
.table > caption + thead > tr:first-child > td,
|
||||
.table > colgroup + thead > tr:first-child > td,
|
||||
.table > thead:first-child > tr:first-child > td {
|
||||
border-top: 0;
|
||||
}
|
||||
.table > tbody + tbody {
|
||||
border-top: 2px solid #dddddd;
|
||||
}
|
||||
.table .table {
|
||||
background-color: #ffffff;
|
||||
}
|
||||
.table-condensed > thead > tr > th,
|
||||
.table-condensed > tbody > tr > th,
|
||||
.table-condensed > tfoot > tr > th,
|
||||
.table-condensed > thead > tr > td,
|
||||
.table-condensed > tbody > tr > td,
|
||||
.table-condensed > tfoot > tr > td {
|
||||
padding: 5px;
|
||||
}
|
||||
.table-bordered {
|
||||
border: 1px solid #dddddd;
|
||||
}
|
||||
.table-bordered > thead > tr > th,
|
||||
.table-bordered > tbody > tr > th,
|
||||
.table-bordered > tfoot > tr > th,
|
||||
.table-bordered > thead > tr > td,
|
||||
.table-bordered > tbody > tr > td,
|
||||
.table-bordered > tfoot > tr > td {
|
||||
border: 1px solid #dddddd;
|
||||
}
|
||||
.table-bordered > thead > tr > th,
|
||||
.table-bordered > thead > tr > td {
|
||||
border-bottom-width: 2px;
|
||||
}
|
||||
.table-striped > tbody > tr:nth-child(odd) > td,
|
||||
.table-striped > tbody > tr:nth-child(odd) > th {
|
||||
background-color: #f9f9f9;
|
||||
}
|
||||
.table-hover > tbody > tr:hover > td,
|
||||
.table-hover > tbody > tr:hover > th {
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
table col[class*="col-"] {
|
||||
position: static;
|
||||
float: none;
|
||||
display: table-column;
|
||||
}
|
||||
table td[class*="col-"],
|
||||
table th[class*="col-"] {
|
||||
position: static;
|
||||
float: none;
|
||||
display: table-cell;
|
||||
}
|
||||
.table > thead > tr > td.active,
|
||||
.table > tbody > tr > td.active,
|
||||
.table > tfoot > tr > td.active,
|
||||
.table > thead > tr > th.active,
|
||||
.table > tbody > tr > th.active,
|
||||
.table > tfoot > tr > th.active,
|
||||
.table > thead > tr.active > td,
|
||||
.table > tbody > tr.active > td,
|
||||
.table > tfoot > tr.active > td,
|
||||
.table > thead > tr.active > th,
|
||||
.table > tbody > tr.active > th,
|
||||
.table > tfoot > tr.active > th {
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
.table-hover > tbody > tr > td.active:hover,
|
||||
.table-hover > tbody > tr > th.active:hover,
|
||||
.table-hover > tbody > tr.active:hover > td,
|
||||
.table-hover > tbody > tr.active:hover > th {
|
||||
background-color: #e8e8e8;
|
||||
}
|
||||
.table > thead > tr > td.success,
|
||||
.table > tbody > tr > td.success,
|
||||
.table > tfoot > tr > td.success,
|
||||
.table > thead > tr > th.success,
|
||||
.table > tbody > tr > th.success,
|
||||
.table > tfoot > tr > th.success,
|
||||
.table > thead > tr.success > td,
|
||||
.table > tbody > tr.success > td,
|
||||
.table > tfoot > tr.success > td,
|
||||
.table > thead > tr.success > th,
|
||||
.table > tbody > tr.success > th,
|
||||
.table > tfoot > tr.success > th {
|
||||
background-color: #dff0d8;
|
||||
}
|
||||
.table-hover > tbody > tr > td.success:hover,
|
||||
.table-hover > tbody > tr > th.success:hover,
|
||||
.table-hover > tbody > tr.success:hover > td,
|
||||
.table-hover > tbody > tr.success:hover > th {
|
||||
background-color: #d0e9c6;
|
||||
}
|
||||
.table > thead > tr > td.info,
|
||||
.table > tbody > tr > td.info,
|
||||
.table > tfoot > tr > td.info,
|
||||
.table > thead > tr > th.info,
|
||||
.table > tbody > tr > th.info,
|
||||
.table > tfoot > tr > th.info,
|
||||
.table > thead > tr.info > td,
|
||||
.table > tbody > tr.info > td,
|
||||
.table > tfoot > tr.info > td,
|
||||
.table > thead > tr.info > th,
|
||||
.table > tbody > tr.info > th,
|
||||
.table > tfoot > tr.info > th {
|
||||
background-color: #d9edf7;
|
||||
}
|
||||
.table-hover > tbody > tr > td.info:hover,
|
||||
.table-hover > tbody > tr > th.info:hover,
|
||||
.table-hover > tbody > tr.info:hover > td,
|
||||
.table-hover > tbody > tr.info:hover > th {
|
||||
background-color: #c4e3f3;
|
||||
}
|
||||
.table > thead > tr > td.warning,
|
||||
.table > tbody > tr > td.warning,
|
||||
.table > tfoot > tr > td.warning,
|
||||
.table > thead > tr > th.warning,
|
||||
.table > tbody > tr > th.warning,
|
||||
.table > tfoot > tr > th.warning,
|
||||
.table > thead > tr.warning > td,
|
||||
.table > tbody > tr.warning > td,
|
||||
.table > tfoot > tr.warning > td,
|
||||
.table > thead > tr.warning > th,
|
||||
.table > tbody > tr.warning > th,
|
||||
.table > tfoot > tr.warning > th {
|
||||
background-color: #fcf8e3;
|
||||
}
|
||||
.table-hover > tbody > tr > td.warning:hover,
|
||||
.table-hover > tbody > tr > th.warning:hover,
|
||||
.table-hover > tbody > tr.warning:hover > td,
|
||||
.table-hover > tbody > tr.warning:hover > th {
|
||||
background-color: #faf2cc;
|
||||
}
|
||||
.table > thead > tr > td.danger,
|
||||
.table > tbody > tr > td.danger,
|
||||
.table > tfoot > tr > td.danger,
|
||||
.table > thead > tr > th.danger,
|
||||
.table > tbody > tr > th.danger,
|
||||
.table > tfoot > tr > th.danger,
|
||||
.table > thead > tr.danger > td,
|
||||
.table > tbody > tr.danger > td,
|
||||
.table > tfoot > tr.danger > td,
|
||||
.table > thead > tr.danger > th,
|
||||
.table > tbody > tr.danger > th,
|
||||
.table > tfoot > tr.danger > th {
|
||||
background-color: #f2dede;
|
||||
}
|
||||
.table-hover > tbody > tr > td.danger:hover,
|
||||
.table-hover > tbody > tr > th.danger:hover,
|
||||
.table-hover > tbody > tr.danger:hover > td,
|
||||
.table-hover > tbody > tr.danger:hover > th {
|
||||
background-color: #ebcccc;
|
||||
}
|
||||
@media (max-width: 767px) {
|
||||
.table-responsive {
|
||||
width: 100%;
|
||||
margin-bottom: 15px;
|
||||
overflow-y: hidden;
|
||||
overflow-x: scroll;
|
||||
-ms-overflow-style: -ms-autohiding-scrollbar;
|
||||
border: 1px solid #dddddd;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
.table-responsive > .table {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.table-responsive > .table > thead > tr > th,
|
||||
.table-responsive > .table > tbody > tr > th,
|
||||
.table-responsive > .table > tfoot > tr > th,
|
||||
.table-responsive > .table > thead > tr > td,
|
||||
.table-responsive > .table > tbody > tr > td,
|
||||
.table-responsive > .table > tfoot > tr > td {
|
||||
white-space: nowrap;
|
||||
}
|
||||
.table-responsive > .table-bordered {
|
||||
border: 0;
|
||||
}
|
||||
.table-responsive > .table-bordered > thead > tr > th:first-child,
|
||||
.table-responsive > .table-bordered > tbody > tr > th:first-child,
|
||||
.table-responsive > .table-bordered > tfoot > tr > th:first-child,
|
||||
.table-responsive > .table-bordered > thead > tr > td:first-child,
|
||||
.table-responsive > .table-bordered > tbody > tr > td:first-child,
|
||||
.table-responsive > .table-bordered > tfoot > tr > td:first-child {
|
||||
border-left: 0;
|
||||
}
|
||||
.table-responsive > .table-bordered > thead > tr > th:last-child,
|
||||
.table-responsive > .table-bordered > tbody > tr > th:last-child,
|
||||
.table-responsive > .table-bordered > tfoot > tr > th:last-child,
|
||||
.table-responsive > .table-bordered > thead > tr > td:last-child,
|
||||
.table-responsive > .table-bordered > tbody > tr > td:last-child,
|
||||
.table-responsive > .table-bordered > tfoot > tr > td:last-child {
|
||||
border-right: 0;
|
||||
}
|
||||
.table-responsive > .table-bordered > tbody > tr:last-child > th,
|
||||
.table-responsive > .table-bordered > tfoot > tr:last-child > th,
|
||||
.table-responsive > .table-bordered > tbody > tr:last-child > td,
|
||||
.table-responsive > .table-bordered > tfoot > tr:last-child > td {
|
||||
border-bottom: 0;
|
||||
}
|
||||
}
|
||||
fieldset {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
|
||||
2
public/css/bootstrap.min.css
vendored
2
public/css/bootstrap.min.css
vendored
File diff suppressed because one or more lines are too long
346
public/js/jquery.simplegraph.js
Normal file
346
public/js/jquery.simplegraph.js
Normal file
@@ -0,0 +1,346 @@
|
||||
function SimpleGraph(data, labels, canvas, settings) {
|
||||
|
||||
this.settings = settings;
|
||||
|
||||
setStyleDefaults(settings);
|
||||
|
||||
this.dataSet = new DataSet(data, labels, this.settings);
|
||||
this.grid = new Grid(this.dataSet, this.settings);
|
||||
this.canvas = canvas;
|
||||
|
||||
this.draw = function() {
|
||||
if (this.settings.drawGrid) {
|
||||
this.grid.draw(this.canvas);
|
||||
}
|
||||
if (this.settings.yAxisCaption) {
|
||||
this.dataSet.labelYAxis(this.grid, this.canvas);
|
||||
}
|
||||
this.dataSet.labelXAxis(this.grid, this.canvas);
|
||||
this.dataSet.plot(this.grid, this.canvas);
|
||||
};
|
||||
|
||||
this.replaceDataSet = function(dataSet) {
|
||||
this.dataSet = new DataSet(dataSet, dataSet.labels, this.settings);
|
||||
this.grid = new Grid(this.dataSet, this.settings);
|
||||
};
|
||||
|
||||
this.plotCurrentDataSet = function() {
|
||||
this.dataSet.plot(this.grid, this.canvas);
|
||||
};
|
||||
|
||||
function setStyleDefaults(settings) {
|
||||
var targets = ["xAxisLabel", "yAxisLabel", "yAxisCaption", "hoverLabel", "hoverValue"];
|
||||
var types = ["Color", "Font", "FontSize", "FontWeight"];
|
||||
jQuery.each(targets, function(index, target) {
|
||||
jQuery.each(types, function(index, type) {
|
||||
if (!settings[target + type]) {
|
||||
settings[target + type] = settings["label" + type];
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
settings.labelStyle = {
|
||||
font: settings.labelFontSize + '"' + settings.labelFont + '"',
|
||||
fill: settings.labelColor
|
||||
};
|
||||
|
||||
jQuery.each(targets, function(index, target) {
|
||||
settings[target + "Style"] = {
|
||||
font: settings[target + "FontSize"] + ' ' + settings[target + "Font"],
|
||||
fill: settings[target + "Color"],
|
||||
"font-weight": settings[target + "FontWeight"]
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Holds the data and labels to be plotted, provides methods for labelling the x and y axes,
|
||||
// and for plotting it's own points. Each method requires a grid object for translating values to
|
||||
// x,y pixel coordinates and a canvas object on which to draw.
|
||||
function DataSet(data, labels, settings) {
|
||||
this.data = data;
|
||||
this.labels = labels;
|
||||
this.settings = settings;
|
||||
|
||||
this.labelXAxis = function(grid, canvas) {
|
||||
(function(ds) {
|
||||
jQuery.each(ds.labels, function(i, label) {
|
||||
var x = grid.x(i);
|
||||
canvas.text(x + ds.settings.xAxisLabelOffset, ds.settings.height - 6, label).attr(ds.settings.xAxisLabelStyle);
|
||||
});
|
||||
})(this);
|
||||
};
|
||||
|
||||
this.labelYAxis = function(grid, canvas) {
|
||||
// Legend
|
||||
canvas.rect(
|
||||
grid.leftEdge - (30 + this.settings.yAxisOffset), //TODO PARAM - Label Colum Width
|
||||
grid.topEdge,
|
||||
30, //TODO PARAM - Label Column Width
|
||||
grid.height
|
||||
).attr({stroke: this.settings.lineColor, fill: this.settings.lineColor, opacity: 0.3}); //TODO PARAMS - legend border and fill style
|
||||
|
||||
for (var i = 1, ii = (grid.rows); i < (ii - this.settings.lowerBound/2); i = i + 2) {
|
||||
var value = (ii - i)*2,
|
||||
y = grid.y(value) + 4, // TODO: Value of 4 works for default dimensions, expect will need to scale
|
||||
x = grid.leftEdge - (6 + this.settings.yAxisOffset);
|
||||
canvas.text(x, y, value).attr(this.settings.yAxisLabelStyle);
|
||||
}
|
||||
var caption = canvas.text(
|
||||
grid.leftEdge - (20 + this.settings.yAxisOffset),
|
||||
(grid.height/2) + (this.settings.yAxisCaption.length / 2),
|
||||
this.settings.yAxisCaption + " (" + this.settings.units + ")").attr(this.settings.yAxisCaptionStyle).rotate(270);
|
||||
// Increase the offset for the next caption (if any)
|
||||
this.settings.yAxisOffset = this.settings.yAxisOffset + 30;
|
||||
};
|
||||
|
||||
this.plot = function(grid, canvas) {
|
||||
var line_path = canvas.path({
|
||||
stroke: this.settings.lineColor,
|
||||
"stroke-width": this.settings.lineWidth,
|
||||
"stroke-linejoin": this.settings.lineJoin
|
||||
});
|
||||
|
||||
var fill_path = canvas.path({
|
||||
stroke: "none",
|
||||
fill: this.settings.fillColor,
|
||||
opacity: this.settings.fillOpacity
|
||||
}).moveTo(this.settings.leftGutter, this.settings.height - this.settings.bottomGutter);
|
||||
|
||||
var bars = canvas.group(),
|
||||
dots = canvas.group(),
|
||||
cover = canvas.group();
|
||||
|
||||
var hoverFrame = dots.rect(10, 10, 100, 40, 5).attr({
|
||||
fill: "#fff", stroke: "#474747", "stroke-width": 2}).hide(); //TODO PARAM - fill colour, border colour, border width
|
||||
var hoverText = [];
|
||||
hoverText[0] = canvas.text(60, 25, "").attr(this.settings.hoverValueStyle).hide();
|
||||
hoverText[1] = canvas.text(60, 40, "").attr(this.settings.hoverLabelStyle).hide();
|
||||
|
||||
// Plot the points
|
||||
(function(dataSet) {
|
||||
jQuery.each(dataSet.data, function(i, value) {
|
||||
var y = grid.y(value),
|
||||
x = grid.x(i),
|
||||
label = dataSet.labels ? dataSet.labels[i] : " ";
|
||||
|
||||
if (dataSet.settings.drawPoints) {
|
||||
var dot = dots.circle(x, y, dataSet.settings.pointRadius).attr({fill: dataSet.settings.pointColor, stroke: dataSet.settings.pointColor});
|
||||
}
|
||||
if (dataSet.settings.drawBars) {
|
||||
bars.rect(x + dataSet.settings.barOffset, y, dataSet.settings.barWidth, (dataSet.settings.height - dataSet.settings.bottomGutter) - y).attr({fill: dataSet.settings.barColor, stroke: "none"});
|
||||
}
|
||||
if (dataSet.settings.drawLine) {
|
||||
line_path[i == 0 ? "moveTo" : "cplineTo"](x, y, 5);
|
||||
}
|
||||
if (dataSet.settings.fillUnderLine) {
|
||||
fill_path[i == 0 ? "lineTo" : "cplineTo"](x, y, 5);
|
||||
}
|
||||
if (dataSet.settings.addHover) {
|
||||
var rect = canvas.rect(x - 50, y - 50, 100, 100).attr({stroke: "none", fill: "#fff", opacity: 0}); //TODO PARAM - hover target width / height
|
||||
jQuery(rect[0]).hover( function() {
|
||||
jQuery.fn.simplegraph.hoverIn(canvas, value, label, x, y, hoverFrame, hoverText, dot, dataSet.settings);
|
||||
},
|
||||
function() {
|
||||
jQuery.fn.simplegraph.hoverOut(canvas, hoverFrame, hoverText, dot, dataSet.settings);
|
||||
});
|
||||
}
|
||||
});
|
||||
})(this);
|
||||
|
||||
if (this.settings.fillUnderLine) {
|
||||
fill_path.lineTo(grid.x(this.data.length - 1), this.settings.height - this.settings.bottomGutter).andClose();
|
||||
}
|
||||
hoverFrame.toFront();
|
||||
};
|
||||
}
|
||||
|
||||
// Holds the dimensions of the grid, and provides methods to convert values into x,y
|
||||
// pixel coordinates. Also, provides a method to draw a grid on a supplied canvas.
|
||||
function Grid(dataSet, settings) {
|
||||
this.dataSet = dataSet;
|
||||
this.settings = settings;
|
||||
|
||||
this.calculateMaxYAxis = function() {
|
||||
var max = Math.max.apply(Math, this.dataSet.data),
|
||||
maxOveride = this.settings.minYAxisValue;
|
||||
if (maxOveride && maxOveride > max) {
|
||||
max = maxOveride;
|
||||
}
|
||||
return max;
|
||||
};
|
||||
|
||||
this.setYAxis = function() {
|
||||
this.height = this.settings.height - this.settings.topGutter - this.settings.bottomGutter;
|
||||
this.maxValueYAxis = this.calculateMaxYAxis();
|
||||
this.Y = this.height / (this.maxValueYAxis - this.settings.lowerBound);
|
||||
};
|
||||
|
||||
this.setXAxis = function() {
|
||||
this.X = (this.settings.width - this.settings.leftGutter) / (this.dataSet.data.length - 0.4);
|
||||
};
|
||||
|
||||
this.setDimensions = function() {
|
||||
this.leftEdge = this.settings.leftGutter;
|
||||
this.topEdge = this.settings.topGutter;
|
||||
this.width = this.settings.width - this.settings.leftGutter - this.X;
|
||||
this.columns = this.dataSet.data.length - 1;
|
||||
this.rows = (this.maxValueYAxis - this.settings.lowerBound) / 2; //TODO PARAM - steps per row
|
||||
};
|
||||
|
||||
this.draw = function(canvas) {
|
||||
canvas.drawGrid(
|
||||
this.leftEdge,
|
||||
this.topEdge,
|
||||
this.width,
|
||||
this.height,
|
||||
this.columns,
|
||||
this.rows,
|
||||
this.settings.gridBorderColor
|
||||
);
|
||||
};
|
||||
|
||||
this.x = function(value) {
|
||||
return this.settings.leftGutter + this.X * value;
|
||||
};
|
||||
|
||||
this.y = function(value) {
|
||||
return this.settings.height - this.settings.bottomGutter - this.Y * (value - this.settings.lowerBound);
|
||||
};
|
||||
|
||||
this.setYAxis();
|
||||
this.setXAxis();
|
||||
this.setDimensions();
|
||||
|
||||
};
|
||||
|
||||
(function($) {
|
||||
|
||||
//- required to implement hover function
|
||||
var isLabelVisible;
|
||||
var leaveTimer;
|
||||
|
||||
$.fn.simplegraph = function(data, labels, options) {
|
||||
var settings = $.extend({}, $.fn.simplegraph.defaults, options);
|
||||
setPenColor(settings);
|
||||
|
||||
return this.each( function() {
|
||||
var canvas = Raphael(this, settings.width, settings.height);
|
||||
var simplegraph = new SimpleGraph(data, labels, canvas, settings);
|
||||
|
||||
simplegraph.draw();
|
||||
|
||||
// Stash simplegraph object away for future reference
|
||||
$.data(this, "simplegraph", simplegraph);
|
||||
});
|
||||
};
|
||||
|
||||
// Plot another set of values on an existing graph, use it like this:
|
||||
// $("#target").simplegraph(data, labels).simplegraph_more(moreData);
|
||||
$.fn.simplegraph_more = function(data, options) {
|
||||
return this.each( function() {
|
||||
var sg = $.data(this, "simplegraph");
|
||||
sg.dataSet = new DataSet(data, sg.dataSet.labels, sg.settings);
|
||||
sg.settings.penColor = options.penColor;
|
||||
setPenColor(sg.settings);
|
||||
sg.settings = $.extend(sg.settings, options);
|
||||
sg.grid = new Grid(sg.dataSet, sg.settings);
|
||||
sg.dataSet.labelYAxis(sg.grid, sg.canvas);
|
||||
sg.dataSet.plot(sg.grid, sg.canvas);
|
||||
});
|
||||
};
|
||||
|
||||
// Public
|
||||
|
||||
$.fn.simplegraph.defaults = {
|
||||
drawGrid: false,
|
||||
units: "",
|
||||
// Dimensions
|
||||
width: 600,
|
||||
height: 250,
|
||||
leftGutter: 30,
|
||||
bottomGutter: 20,
|
||||
topGutter: 20,
|
||||
// Label Style
|
||||
labelColor: "#000",
|
||||
labelFont: "Helvetica",
|
||||
labelFontSize: "10px",
|
||||
labelFontWeight: "normal",
|
||||
// Grid Style
|
||||
gridBorderColor: "#ccc",
|
||||
// -- Y Axis Captions
|
||||
yAxisOffset: 0,
|
||||
// -- Y Axis Captions
|
||||
xAxisLabelOffset: 0,
|
||||
// Graph Style
|
||||
// -- Points
|
||||
drawPoints: false,
|
||||
pointColor: "#000",
|
||||
pointRadius: 3,
|
||||
activePointRadius: 5,
|
||||
// -- Line
|
||||
drawLine: true,
|
||||
lineColor: "#000",
|
||||
lineWidth: 3,
|
||||
lineJoin: "round",
|
||||
// -- Bars
|
||||
drawBars: false,
|
||||
barColor: "#000",
|
||||
barWidth: 10,
|
||||
barOffset: 0,
|
||||
// -- Fill
|
||||
fillUnderLine: false,
|
||||
fillColor: "#000",
|
||||
fillOpacity: 0.2,
|
||||
// -- Hover
|
||||
addHover: true,
|
||||
// Calculations
|
||||
lowerBound: 0
|
||||
};
|
||||
|
||||
// Default hoverIn callback, this is public and as such can be overwritten. You can write your
|
||||
// own call back with the same signature if you want different behaviour.
|
||||
$.fn.simplegraph.hoverIn = function(canvas, value, label, x, y, frame, hoverLabel, dot, settings) {
|
||||
clearTimeout(leaveTimer);
|
||||
var newcoord = {x: x * 1 + 7.5, y: y - 19};
|
||||
if (newcoord.x + 100 > settings.width) {
|
||||
newcoord.x -= 114;
|
||||
}
|
||||
hoverLabel[0].attr({text: value}).show().animate({x : newcoord.x + 50, y : newcoord.y + 15}, (isLabelVisible ? 100 : 0));
|
||||
hoverLabel[1].attr({text: label}).show().animate({x : newcoord.x + 50, y : newcoord.y + 30}, (isLabelVisible ? 100 : 0));
|
||||
frame.show().animate({x: newcoord.x, y: newcoord.y}, (isLabelVisible ? 100 : 0));
|
||||
if (settings.drawPoints) {
|
||||
dot.attr("r", settings.activePointRadius);
|
||||
}
|
||||
isLabelVisible = true;
|
||||
canvas.safari();
|
||||
};
|
||||
|
||||
// Default hoverOut callback, this is public and as such can be overwritten. You can write your
|
||||
// own call back with the same signature if you want different behaviour.
|
||||
$.fn.simplegraph.hoverOut = function(canvas, frame, label, dot, settings) {
|
||||
if (settings.drawPoints) {
|
||||
dot.attr("r", settings.pointRadius);
|
||||
}
|
||||
canvas.safari();
|
||||
leaveTimer = setTimeout(function () {
|
||||
isLabelVisible = false;
|
||||
frame.hide();
|
||||
label[0].hide();
|
||||
label[1].hide();
|
||||
canvas.safari();
|
||||
}, 1);
|
||||
};
|
||||
|
||||
// Private
|
||||
|
||||
function setPenColor(settings) {
|
||||
if (settings.penColor) {
|
||||
settings.lineColor = settings.penColor;
|
||||
settings.pointColor = settings.penColor;
|
||||
settings.fillColor = settings.penColor;
|
||||
settings.barColor = settings.penColor;
|
||||
}
|
||||
}
|
||||
|
||||
})(jQuery);
|
||||
2215
public/js/raphael.js
Normal file
2215
public/js/raphael.js
Normal file
File diff suppressed because it is too large
Load Diff
53
public/js/raphael.path.methods.js
Normal file
53
public/js/raphael.path.methods.js
Normal file
@@ -0,0 +1,53 @@
|
||||
Raphael.el.isAbsolute = true;
|
||||
Raphael.el.absolutely = function () {
|
||||
this.isAbsolute = 1;
|
||||
return this;
|
||||
};
|
||||
Raphael.el.relatively = function () {
|
||||
this.isAbsolute = 0;
|
||||
return this;
|
||||
};
|
||||
Raphael.el.moveTo = function (x, y) {
|
||||
this._last = {x: x, y: y};
|
||||
return this.attr({path: this.attrs.path + ["m", "M"][+this.isAbsolute] + parseFloat(x) + " " + parseFloat(y)});
|
||||
};
|
||||
Raphael.el.lineTo = function (x, y) {
|
||||
this._last = {x: x, y: y};
|
||||
return this.attr({path: this.attrs.path + ["l", "L"][+this.isAbsolute] + parseFloat(x) + " " + parseFloat(y)});
|
||||
};
|
||||
Raphael.el.arcTo = function (rx, ry, large_arc_flag, sweep_flag, x, y, angle) {
|
||||
this._last = {x: x, y: y};
|
||||
return this.attr({path: this.attrs.path + ["a", "A"][+this.isAbsolute] + [parseFloat(rx), parseFloat(ry), +angle, large_arc_flag, sweep_flag, parseFloat(x), parseFloat(y)].join(" ")});
|
||||
};
|
||||
Raphael.el.curveTo = function () {
|
||||
var args = Array.prototype.splice.call(arguments, 0, arguments.length),
|
||||
d = [0, 0, 0, 0, "s", 0, "c"][args.length] || "";
|
||||
this.isAbsolute && (d = d.toUpperCase());
|
||||
this._last = {x: args[args.length - 2], y: args[args.length - 1]};
|
||||
return this.attr({path: this.attrs.path + d + args});
|
||||
};
|
||||
Raphael.el.cplineTo = function (x, y, w) {
|
||||
this.attr({path: this.attrs.path + ["C", this._last.x + w, this._last.y, x - w, y, x, y]});
|
||||
this._last = {x: x, y: y};
|
||||
return this;
|
||||
};
|
||||
Raphael.el.qcurveTo = function () {
|
||||
var d = [0, 1, "t", 3, "q"][arguments.length],
|
||||
args = Array.prototype.splice.call(arguments, 0, arguments.length);
|
||||
if (this.isAbsolute) {
|
||||
d = d.toUpperCase();
|
||||
}
|
||||
this._last = {x: args[args.length - 2], y: args[args.length - 1]};
|
||||
return this.attr({path: this.attrs.path + d + args});
|
||||
};
|
||||
Raphael.el.addRoundedCorner = function (r, dir) {
|
||||
var rollback = this.isAbsolute;
|
||||
rollback && this.relatively();
|
||||
this._last = {x: r * (!!(dir.indexOf("r") + 1) * 2 - 1), y: r * (!!(dir.indexOf("d") + 1) * 2 - 1)};
|
||||
this.arcTo(r, r, 0, {"lu": 1, "rd": 1, "ur": 1, "dl": 1}[dir] || 0, this._last.x, this._last.y);
|
||||
rollback && this.absolutely();
|
||||
return this;
|
||||
};
|
||||
Raphael.el.andClose = function () {
|
||||
return this.attr({path: this.attrs.path + "z"});
|
||||
};
|
||||
14
templates/data.html.ep.template
Normal file
14
templates/data.html.ep.template
Normal file
@@ -0,0 +1,14 @@
|
||||
<div id="stats-data">
|
||||
<table class="table table-striped table-bordered" summary="LUTIm stats">
|
||||
<thead>
|
||||
<tr>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
</tr>
|
||||
<tr>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
@@ -83,6 +83,7 @@
|
||||
</div>
|
||||
<!-- /D&D Zone -->
|
||||
|
||||
%= javascript 'js/dmuploader.min.js'
|
||||
%= javascript begin
|
||||
function link(url, dl) {
|
||||
if (dl !== '') {
|
||||
|
||||
@@ -34,7 +34,6 @@
|
||||
% end
|
||||
%= javascript 'js/jquery-2.1.0.min.js'
|
||||
%= javascript 'js/bootstrap.min.js'
|
||||
%= javascript 'js/dmuploader.min.js'
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
|
||||
63
templates/stats.html.ep
Normal file
63
templates/stats.html.ep
Normal file
@@ -0,0 +1,63 @@
|
||||
% # vim:set sts=4 sw=4 ts=4 ft=html.epl expandtab:
|
||||
%= javascript 'js/raphael.js'
|
||||
%= javascript 'js/raphael.path.methods.js'
|
||||
%= javascript 'js/jquery.simplegraph.js'
|
||||
%= include 'data'
|
||||
<div id="evol-holder"></div>
|
||||
<div id="total-holder"></div>
|
||||
<p><small><%=l 'graph-data-once-a-day' %></small></p>
|
||||
|
||||
<h4><%= $total %><%=l 'pushed-images' %></h4>
|
||||
|
||||
%= javascript begin
|
||||
function graph(stats_data, stats_labels, stats_total) {
|
||||
// Plot the data
|
||||
// - Temperature Graph - adds colour, fill, and a minimum value for the y axis
|
||||
$("#evol-holder").simplegraph(
|
||||
stats_data,
|
||||
stats_labels,
|
||||
{
|
||||
addHover: true,
|
||||
penColor: "#f00",
|
||||
fillUnderLine: true,
|
||||
drawPoints: true,
|
||||
width: document.body.offsetWidth - 50
|
||||
}
|
||||
)
|
||||
$("#total-holder").simplegraph(
|
||||
stats_total,
|
||||
stats_labels,
|
||||
{
|
||||
addHover: true,
|
||||
penColor: "#00f",
|
||||
fillUnderLine: true,
|
||||
drawPoints: true,
|
||||
width: document.body.offsetWidth - 50
|
||||
}
|
||||
);
|
||||
}
|
||||
$(document).ready(function() {
|
||||
// Get the data
|
||||
var stats_labels = [], stats_data = [], stats_total = [];
|
||||
$("#stats-data thead th").each(function () {
|
||||
stats_labels.push($(this).html());
|
||||
});
|
||||
$("#stats-data tbody tr:first-child td").each(function () {
|
||||
stats_data.push($(this).html());
|
||||
});
|
||||
$("#stats-data tbody tr:nth-child(2) td").each(function () {
|
||||
stats_total.push($(this).html());
|
||||
});
|
||||
|
||||
// Hide the data
|
||||
$("#stats-data").hide();
|
||||
|
||||
graph(stats_data, stats_labels, stats_total);
|
||||
|
||||
$(window).resize(function() {
|
||||
$("#evol-holder").empty();
|
||||
$("#total-holder").empty();
|
||||
graph(stats_data, stats_labels, stats_total);
|
||||
});
|
||||
});
|
||||
% end
|
||||
Reference in New Issue
Block a user