#60: Added a basic about page with a link to Github's API to fetch the latest release

This commit is contained in:
Andy Heathershaw 2017-10-01 16:48:50 +01:00
parent 89544437cd
commit dcfcbca530
14 changed files with 365 additions and 3 deletions

View File

@ -15,6 +15,7 @@ use App\Http\Requests\SaveSettingsRequest;
use App\Label;
use App\Mail\TestMailConfig;
use App\Photo;
use App\Services\GithubService;
use App\Services\PhotoService;
use App\Storage;
use App\User;
@ -33,6 +34,48 @@ class DefaultController extends Controller
View::share('is_admin', true);
}
public function about()
{
return Theme::render('admin.about', [
'current_version' => config('app.version'),
'licence_text' => file_get_contents(sprintf('%s/LICENSE', dirname(dirname(dirname(dirname(__DIR__))))))
]);
}
public function aboutLatestRelease()
{
try
{
$githubService = new GithubService();
$releaseInfo = $githubService->checkForLatestRelease();
// Convert the publish date so we can re-format it with the user's settings
$publishDate = \DateTime::createFromFormat('Y-m-d\TH:i:s\Z', $releaseInfo->published_at);
// HTML-ify the body text
$body = nl2br($releaseInfo->body);
$body = preg_replace('/\*\*(.+)\*\*/', '<b>$1</b>', $body);
// Remove the "v" from the release name
$version = substr($releaseInfo->name, 1);
// Determine if we can upgrade
$canUpgrade = version_compare($version, config('app.version')) > 0;
return response()->json([
'can_upgrade' => $canUpgrade,
'body' => $body,
'name' => $version,
'publish_date' => $publishDate->format(UserConfig::get('date_format')),
'url' => $releaseInfo->html_url
]);
}
catch (\Exception $ex)
{
return response()->json(['error' => $ex->getMessage()]);
}
}
public function metadataUpgrade()
{
$albums = DbHelper::getAlbumsForCurrentUser();

View File

@ -0,0 +1,100 @@
<?php
namespace App\Services;
class GithubService
{
private $cacheFile = null;
private $config = [];
public function __construct()
{
$this->config = config('services.github');
$this->cacheFile = storage_path('app/github_cache.txt');
}
public function checkForLatestRelease()
{
$releaseInfo = [];
$etag = '';
if ($this->doesCacheExist())
{
// Get the etag from the cache
$cacheData = $this->getCacheData();
$etag = $cacheData->latest_release->etag;
$releaseInfo = $cacheData->latest_release->release_info;
}
// Lookup and store the version information
$statusCode = -1;
$result = $this->getLatestReleaseFromGithub($etag, $statusCode);
if ($statusCode == 200)
{
// Store the etag (in HTTP headers) for future reference
$matches = [];
$etag = '';
if (preg_match('/^etag: "(.+)"/mi', $result[0], $matches))
{
$etag = $matches[1];
}
$releaseInfo = json_decode($result[1]);
}
if (!empty($etag))
{
$this->setCacheData([
'latest_release' => [
'etag' => $etag,
'release_info' => $releaseInfo
]
]);
}
return $releaseInfo;
}
private function doesCacheExist()
{
return file_exists($this->cacheFile);
}
private function getCacheData()
{
return json_decode(file_get_contents($this->cacheFile));
}
private function getLatestReleaseFromGithub($etag = '', &$statusCode)
{
$httpHeaders = [
sprintf('User-Agent: pandy06269/blue-twilight (v%s)', config('app.version'))
];
if (!empty($etag))
{
$httpHeaders[] = sprintf('If-None-Match: "%s"', $etag);
}
$ch = curl_init($this->config['latest_release_url']);
curl_setopt($ch, CURLOPT_HTTPHEADER, $httpHeaders);
curl_setopt($ch, CURLOPT_HEADER, true);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$result = curl_exec($ch);
if ($result === false)
{
throw new \Exception(sprintf('Error from Github: %s', curl_error($ch)));
}
$statusCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
return explode("\r\n\r\n", $result, 2);
}
private function setCacheData(array $data)
{
file_put_contents($this->cacheFile, json_encode($data));
}
}

View File

@ -14,6 +14,10 @@ return [
|
*/
'github' => [
'latest_release_url' => 'https://api.github.com/repos/pandy06269/blue-twilight/releases/latest'
],
'recaptcha' => [
'verify_url' => 'https://www.google.com/recaptcha/api/siteverify'
]

View File

@ -7,6 +7,11 @@
margin-top: 5px;
}
.meta-label,
.meta-value {
vertical-align: middle !important;
}
.photo .loading {
background-color: #ffffff;
display: none;
@ -23,6 +28,14 @@
.photo .loading img {
margin-top: 40px;
}
.text-red {
color: #ff0000;
}
[v-cloak] {
display: none;
}
.album-slideshow-container #image-preview {
height: 600px;
max-width: 100%;

View File

@ -1,4 +1,4 @@
.admin-sidebar-card{margin-bottom:15px}.album-expand-handle{cursor:pointer;margin-top:5px}.photo .loading{background-color:#fff;display:none;height:100%;left:0;opacity:.8;position:absolute;text-align:center;top:0;width:100%;z-index:1000}.photo .loading img{margin-top:40px}.album-slideshow-container #image-preview{height:600px;max-width:100%;width:800px}.album-slideshow-container #image-preview img{max-width:100%}.album-slideshow-container .thumbnails{overflow-x:scroll;overflow-y:hidden;white-space:nowrap;width:auto}.stats-table .icon-col{font-size:1.4em;width:20%;vertical-align:middle}.stats-table .stat-col{font-size:1.8em;font-weight:bold;width:40%}.stats-table .text-col{font-size:1.2em;vertical-align:middle;width:40%}html{font-size:14px !important}button,input,optgroup,select,textarea{cursor:pointer;font-family:inherit !important}.album-photo-cards .card{margin-bottom:15px}.container,.container-fluid{margin-top:20px}.hidden{display:none}.tab-content{border:solid 1px #ddd;border-top:0;padding:20px}.tether-element,.tether-element:after,.tether-element:before,.tether-element *,.tether-element *:after,.tether-element *:before{box-sizing:border-box}.tether-element{position:absolute;display:none}.tether-element.tether-open{display:block}.tether-element.tether-theme-basic{max-width:100%;max-height:100%}.tether-element.tether-theme-basic .tether-content{border-radius:5px;box-shadow:0 2px 8px rgba(0,0,0,0.2);font-family:inherit;background:#fff;color:inherit;padding:1em;font-size:1.1em;line-height:1.5em}.tether-element,.tether-element:after,.tether-element:before,.tether-element *,.tether-element *:after,.tether-element *:before{box-sizing:border-box}.tether-element{position:absolute;display:none}.tether-element.tether-open{display:block}.tt-query{-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.tt-hint{color:#999}.tt-menu{width:422px;margin-top:4px;padding:4px 0;background-color:#fff;border:1px solid #ccc;border:1px solid rgba(0,0,0,0.2);-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:0 5px 10px rgba(0,0,0,.2);-moz-box-shadow:0 5px 10px rgba(0,0,0,.2);box-shadow:0 5px 10px rgba(0,0,0,.2)}.tt-suggestion{padding:3px 20px;line-height:24px}.tt-suggestion.tt-cursor,.tt-suggestion:hover{color:#fff;background-color:#0097cf}.tt-suggestion p{margin:0}/*!
.admin-sidebar-card{margin-bottom:15px}.album-expand-handle{cursor:pointer;margin-top:5px}.meta-label,.meta-value{vertical-align:middle !important}.photo .loading{background-color:#fff;display:none;height:100%;left:0;opacity:.8;position:absolute;text-align:center;top:0;width:100%;z-index:1000}.photo .loading img{margin-top:40px}.text-red{color:red}[v-cloak]{display:none}.album-slideshow-container #image-preview{height:600px;max-width:100%;width:800px}.album-slideshow-container #image-preview img{max-width:100%}.album-slideshow-container .thumbnails{overflow-x:scroll;overflow-y:hidden;white-space:nowrap;width:auto}.stats-table .icon-col{font-size:1.4em;width:20%;vertical-align:middle}.stats-table .stat-col{font-size:1.8em;font-weight:bold;width:40%}.stats-table .text-col{font-size:1.2em;vertical-align:middle;width:40%}html{font-size:14px !important}button,input,optgroup,select,textarea{cursor:pointer;font-family:inherit !important}.album-photo-cards .card{margin-bottom:15px}.container,.container-fluid{margin-top:20px}.hidden{display:none}.tab-content{border:solid 1px #ddd;border-top:0;padding:20px}.tether-element,.tether-element:after,.tether-element:before,.tether-element *,.tether-element *:after,.tether-element *:before{box-sizing:border-box}.tether-element{position:absolute;display:none}.tether-element.tether-open{display:block}.tether-element.tether-theme-basic{max-width:100%;max-height:100%}.tether-element.tether-theme-basic .tether-content{border-radius:5px;box-shadow:0 2px 8px rgba(0,0,0,0.2);font-family:inherit;background:#fff;color:inherit;padding:1em;font-size:1.1em;line-height:1.5em}.tether-element,.tether-element:after,.tether-element:before,.tether-element *,.tether-element *:after,.tether-element *:before{box-sizing:border-box}.tether-element{position:absolute;display:none}.tether-element.tether-open{display:block}.tt-query{-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.tt-hint{color:#999}.tt-menu{width:422px;margin-top:4px;padding:4px 0;background-color:#fff;border:1px solid #ccc;border:1px solid rgba(0,0,0,0.2);-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:0 5px 10px rgba(0,0,0,.2);-moz-box-shadow:0 5px 10px rgba(0,0,0,.2);box-shadow:0 5px 10px rgba(0,0,0,.2)}.tt-suggestion{padding:3px 20px;line-height:24px}.tt-suggestion.tt-cursor,.tt-suggestion:hover{color:#fff;background-color:#0097cf}.tt-suggestion p{margin:0}/*!
* Bootstrap v4.0.0-beta (https://getbootstrap.com)
* Copyright 2011-2017 The Bootstrap Authors
* Copyright 2011-2017 Twitter, Inc.

View File

@ -22198,6 +22198,53 @@ return Tether;
}));
/**
* This model is used by admin/about.blade.php, to perform a version check against Github.
* @constructor
*/
function AboutViewModel(urls) {
this.el = '#about-app';
this.data = {
can_upgrade: false,
is_loading: true,
version_body: '',
version_date: '',
version_name: '',
version_url: ''
};
this.computed = {
};
this.methods = {
init: function () {
var self = this;
$.ajax(
urls.latest_release_url,
{
complete: function() {
self.is_loading = false;
},
dataType: 'json',
error: function (xhr, textStatus, errorThrown) {
},
method: 'GET',
success: function (data) {
self.version_body = data.body;
self.version_date = data.publish_date;
self.version_name = data.name;
self.version_url = data.url;
// Set this last so any watchers on this property execute after all version data has been set
self.can_upgrade = data.can_upgrade;
}
}
);
}
};
}
/**
* This model is used by admin/analyse_album.blade.php, to analyse all images.
* @constructor

File diff suppressed because one or more lines are too long

View File

@ -7,6 +7,11 @@
margin-top: 5px;
}
.meta-label,
.meta-value {
vertical-align: middle !important;
}
.photo .loading {
background-color: #ffffff;
display: none;
@ -22,4 +27,12 @@
.photo .loading img {
margin-top: 40px;
}
.text-red {
color: #ff0000;
}
[v-cloak] {
display: none;
}

View File

@ -0,0 +1,47 @@
/**
* This model is used by admin/about.blade.php, to perform a version check against Github.
* @constructor
*/
function AboutViewModel(urls) {
this.el = '#about-app';
this.data = {
can_upgrade: false,
is_loading: true,
version_body: '',
version_date: '',
version_name: '',
version_url: ''
};
this.computed = {
};
this.methods = {
init: function () {
var self = this;
$.ajax(
urls.latest_release_url,
{
complete: function() {
self.is_loading = false;
},
dataType: 'json',
error: function (xhr, textStatus, errorThrown) {
},
method: 'GET',
success: function (data) {
self.version_body = data.body;
self.version_date = data.publish_date;
self.version_name = data.name;
self.version_url = data.url;
// Set this last so any watchers on this property execute after all version data has been set
self.can_upgrade = data.can_upgrade;
}
}
);
}
};
}

View File

@ -4,6 +4,19 @@ return [
'create_album_link' => 'Create album',
'panel_header' => 'Actions',
],
'about' => [
'current_version' => 'You are running version',
'github_link' => 'Github Project Website',
'intro' => 'Blue Twilight is an <a href="https://www.andyheathershaw.uk/software/" target="_blank">App by Andy</a>. It is made with <i class="fa fa-heart text-red"></i> by UK software developer <a href="https://www.andyheathershaw.uk" target="_blank">Andy Heathershaw</a>.',
'latest_version_loading' => 'Checking for the latest release on Github...',
'licence_header' => 'Licence',
'links_header' => 'Useful Links',
'title' => 'About Blue Twilight',
'up_to_date' => 'Good job - your installation of Blue Twilight is up-to-date!',
'user_guide_link' => 'User Guide',
'versions_header' => 'About &amp; Version',
'website_link' => 'Main Website'
],
'album_appearance_heading' => 'Appearance',
'album_appearance_intro' => 'The settings shown below control how this album appears to visitors in the gallery.',
'album_basic_info_heading' => 'Album information',

View File

@ -1,6 +1,7 @@
<?php
return [
'breadcrumb' => [
'about' => 'About',
'admin' => 'Admin',
'albums' => 'Albums',
'create_album' => 'Create album',

View File

@ -0,0 +1,79 @@
@extends(Theme::viewName('layout'))
@section('title', trans('admin.about.title'))
@section('breadcrumb')
<li class="breadcrumb-item"><a href="{{ route('home') }}"><i class="fa fa-fw fa-home"></i></a></li>
<li class="breadcrumb-item"><a href="{{ route('admin') }}">@lang('navigation.breadcrumb.admin')</a></li>
<li class="breadcrumb-item active">@lang('navigation.breadcrumb.about')</li>
@endsection
@section('content')
<div class="container">
<div class="row">
<div class="col">
<h1>@lang('admin.about.title')</h1>
<p>@lang('admin.about.intro')</p>
<ul class="nav nav-tabs mt-5">
<li class="nav-item">
<a class="nav-link active" data-toggle="tab" href="#versions-tabpanel" role="tab">@lang('admin.about.versions_header')</a>
</li>
<li class="nav-item">
<a class="nav-link" data-toggle="tab" href="#licence-tabpanel" role="tab">@lang('admin.about.licence_header')</a>
</li>
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" data-toggle="dropdown" href="#" role="button" aria-haspopup="true" aria-expanded="false">@lang('admin.about.links_header')</a>
<div class="dropdown-menu">
<a class="dropdown-item" href="https://showmy.photos" target="_blank">@lang('admin.about.website_link')</a>
<a class="dropdown-item" href="https://www.andyheathershaw.uk/software/blue-twilight-php-photo-gallery/manual/" target="_blank">@lang('admin.about.user_guide_link')</a>
<a class="dropdown-item" href="https://github.com/pandy06269/blue-twilight" target="_blank">@lang('admin.about.github_link')</a>
</div>
</li>
</ul>
<div class="tab-content">
<div class="tab-pane active" id="versions-tabpanel" role="tabpanel">
<div class="row" id="about-app">
<div class="col-md-6 text-center">
<p>@lang('admin.about.current_version')</p>
<p class="m-0" style="font-size: 2.5rem;">{{ $current_version }}</p>
</div>
<div class="col-md-6 text-center pt-2">
<div v-if="is_loading">
<p><img src="{{ asset('ripple.svg') }}"></p>
<p class="m-0">@lang('admin.about.latest_version_loading')</p>
</div>
<p v-cloak v-if="!is_loading && !can_upgrade" class="text-success">
<i class="fa fa-check"></i> @lang('admin.about.up_to_date')
</p>
</div>
</div>
</div>
<div class="tab-pane" id="licence-tabpanel" role="tabpanel">
{!! nl2br($licence_text) !!}
</div>
</div>
</div>
</div>
</div>
@endsection
@push('scripts')
<script type="text/javascript">
$(document).ready(function()
{
var viewModel = new AboutViewModel({
latest_release_url: '{{ route('admin.aboutLatestRelease') }}'
});
var app = new Vue(viewModel);
app.$watch('can_upgrade', function(value)
{
alert('update available');
});
app.init();
});
</script>
@endpush

View File

@ -5,7 +5,7 @@
<tbody>
<tr>
<td class="meta-label">@lang('admin.sysinfo_widget.app_version')</td>
<td class="meta-value">{{ $app_version }}</td>
<td class="meta-value">{{ $app_version }} <a href="{{ route('admin.about') }}" class="btn btn-info ml-2"><i class="fa fa-question"></i></a></td>
</tr>
<tr>
<td class="meta-label">@lang('admin.sysinfo_widget.hostname')</td>

View File

@ -16,6 +16,8 @@ Auth::routes();
// Administration
Route::group(['prefix' => 'admin'], function () {
Route::get('/', 'Admin\DefaultController@index')->name('admin');
Route::get('/about', 'Admin\DefaultController@about')->name('admin.about');
Route::get('/about/latest-release', 'Admin\DefaultController@aboutLatestRelease')->name('admin.aboutLatestRelease');
Route::get('/photo-metadata', 'Admin\DefaultController@metadataUpgrade')->name('admin.metadataUpgrade');
Route::post('quick-upload', 'Admin\DefaultController@quickUpload')->name('admin.quickUpload');
Route::post('settings/save', 'Admin\DefaultController@saveSettings')->name('admin.saveSettings');