Merge pull request #1865 from CachetHQ/dashboard-feed

Dashboard feed
This commit is contained in:
James Brooks
2016-05-31 19:25:30 +01:00
6 changed files with 231 additions and 69 deletions

View File

@@ -15,6 +15,7 @@ use AltThree\Bus\Dispatcher;
use CachetHQ\Cachet\Bus\Middleware\UseDatabaseTransactions;
use CachetHQ\Cachet\Dates\DateFactory;
use CachetHQ\Cachet\Integrations\Credits;
use CachetHQ\Cachet\Integrations\Feed;
use CachetHQ\Cachet\Integrations\Releases;
use Illuminate\Support\ServiceProvider;
use Illuminate\Support\Str;
@@ -55,6 +56,7 @@ class AppServiceProvider extends ServiceProvider
{
$this->registerDateFactory();
$this->registerCredits();
$this->registerFeed();
$this->registerReleases();
}
@@ -87,6 +89,20 @@ class AppServiceProvider extends ServiceProvider
});
}
/**
* Register the feed class.
*
* @return void
*/
protected function registerFeed()
{
$this->app->singleton(Feed::class, function ($app) {
$cache = $app['cache.store'];
return new Feed($cache);
});
}
/**
* Register the releases class.
*

View File

@@ -11,6 +11,7 @@
namespace CachetHQ\Cachet\Http\Controllers\Dashboard;
use CachetHQ\Cachet\Integrations\Feed;
use CachetHQ\Cachet\Models\Component;
use CachetHQ\Cachet\Models\Incident;
use CachetHQ\Cachet\Models\Subscriber;
@@ -36,12 +37,22 @@ class DashboardController extends Controller
protected $timeZone;
/**
* Creates a new dashboard controller.
* The feed integration.
*
* @var \CachetHQ\Cachet\Integrations\Feed
*/
protected $feed;
/**
* Creates a new dashboard controller instance.
*
* @param \CachetHQ\Cachet\Integrations\Feed $feed
*
* @return void
*/
public function __construct()
public function __construct(Feed $feed)
{
$this->feed = $feed;
$this->startDate = new Date();
$this->dateTimeZone = Config::get('cachet.timezone');
}
@@ -56,12 +67,16 @@ class DashboardController extends Controller
$components = Component::orderBy('order')->get();
$incidents = $this->getIncidents();
$subscribers = $this->getSubscribers();
$feed = $this->feed->entries();
$entries = array_slice($feed->channel->item, 0, 5);
return View::make('dashboard.index')
->withPageTitle(trans('dashboard.dashboard'))
->withComponents($components)
->withIncidents($incidents)
->withSubscribers($subscribers);
->withSubscribers($subscribers)
->withEntries($entries);
}
/**

74
app/Integrations/Feed.php Normal file
View File

@@ -0,0 +1,74 @@
<?php
/*
* This file is part of Cachet.
*
* (c) Alt Three Services Limited
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace CachetHQ\Cachet\Integrations;
use GuzzleHttp\Client;
use Illuminate\Contracts\Cache\Repository;
/**
* This is the feed class.
*
* @author James Brooks <james@alt-three.com>
*/
class Feed
{
/**
* The default url.
*
* @var string
*/
const URL = 'https://blog.alt-three.com/tag/cachet/rss';
/**
* The cache repository instance.
*
* @var \Illuminate\Contracts\Cache\Repository
*/
protected $cache;
/**
* The url to use.
*
* @var string|null
*/
protected $url;
/**
* Creates a new feed instance.
*
* @param \Illuminate\Contracts\Cache\Repository $cache
* @param string|null $url
*
* @return void
*/
public function __construct(Repository $cache, $url = null)
{
$this->cache = $cache;
$this->url = $url ?: static::URL;
}
/**
* Returns the entries.
*
* @return array
*/
public function entries()
{
return $this->cache->remember('feeds', 2880, function () {
$xml = simplexml_load_string((new Client())->get($this->url, [
'headers' => ['Accept' => 'application/rss+xml', 'User-Agent' => defined('CACHET_VERSION') ? 'cachet/'.constant('CACHET_VERSION') : 'cachet'],
])->getBody()->getContents(), null, LIBXML_NOCDATA);
return json_decode(json_encode($xml));
});
}
}

View File

@@ -40,6 +40,28 @@
padding-top: 10px;
}
.stats-body {
margin-top: -20px;
padding-top: 10px;
.list-group {
border: none;
padding-bottom: 0;
margin-bottom: 0;
.list-group-item {
border-right: none;
border-left: none;
border-color: #eee;
&:last-child {
border-bottom: none;
}
}
}
}
.stats-bottom {
border-top: #eee 1px solid;
color: #777;

View File

@@ -249,6 +249,14 @@ return [
'whoops' => 'Whoops.',
],
// Widgets
'widgets' => [
'support' => 'Support Cachet',
'support_subtitle' => 'Check out our <strong><a href="https://patreon.com/jbrooksuk" target="_blank">Patreon</a></strong> page!',
'news' => 'Latest News',
'news_subtitle' => 'Get the latest updates',
],
// Welcome modal
'welcome' => [
'welcome' => 'Welcome to your new status page!',

View File

@@ -1,86 +1,113 @@
@extends('layout.dashboard')
@section('content')
<div class="header">
<div class="sidebar-toggler visible-xs">
<i class="ion ion-navicon"></i>
</div>
<span class="uppercase">
<i class="ion ion-speedometer"></i> {{ trans('dashboard.dashboard') }}
</span>
<div class="header">
<div class="sidebar-toggler visible-xs">
<i class="ion ion-navicon"></i>
</div>
<div class="content-wrapper">
<div class="row">
<div class="col-md-12">
<div class="alert alert-info hidden" id="update-alert">{!! trans('cachet.system.update') !!}</div>
</div>
<span class="uppercase">
<i class="ion ion-speedometer"></i> {{ trans('dashboard.dashboard') }}
</span>
</div>
<div class="content-wrapper">
<div class="row">
<div class="col-md-12">
<div class="alert alert-info hidden" id="update-alert">{!! trans('cachet.system.update') !!}</div>
</div>
<div class="row">
<div class="col-md-12">
<h4 class="sub-header">{{ trans('dashboard.components.component_statuses') }}</h4>
<div class="panel panel-default">
<div class="list-group">
@forelse($components as $component)
<div class="list-group-item">
<form class='component-inline form-vertical' data-messenger="{{trans('dashboard.components.edit.success')}}">
<div class="row striped-list-item">
<div class="col-lg-4 col-md-3 col-sm-12">
<h4>{{ $component->name }}</h4>
</div>
<div class="col-lg-8 col-md-9 col-sm-12 radio-items componet-inline-update">
@foreach(trans('cachet.components.status') as $statusID => $status)
<div class="radio-inline">
<label>
<input type="radio" name="status" value="{{ $statusID }}" {{ (int) $component->status === $statusID ? 'checked' : null }}>
{{ $status }}
</label>
</div>
@endforeach
</div>
</div>
<div class="row">
<div class="col-md-12">
<h4 class="sub-header">{{ trans('dashboard.components.component_statuses') }}</h4>
<div class="panel panel-default">
<div class="list-group">
@forelse($components as $component)
<div class="list-group-item">
<form class='component-inline form-vertical' data-messenger="{{trans('dashboard.components.edit.success')}}">
<div class="row striped-list-item">
<div class="col-lg-4 col-md-3 col-sm-12">
<h4>{{ $component->name }}</h4>
</div>
<input type="hidden" name="component_id" value="{{ $component->id }}">
</form>
</div>
@empty
<div class="list-group-item"><a href="{{ route('dashboard.components.add') }}">{{ trans('dashboard.components.add.message') }}</a></div>
@endforelse
<div class="col-lg-8 col-md-9 col-sm-12 radio-items componet-inline-update">
@foreach(trans('cachet.components.status') as $statusID => $status)
<div class="radio-inline">
<label>
<input type="radio" name="status" value="{{ $statusID }}" {{ (int) $component->status === $statusID ? 'checked' : null }}>
{{ $status }}
</label>
</div>
@endforeach
</div>
</div>
<input type="hidden" name="component_id" value="{{ $component->id }}">
</form>
</div>
@empty
<div class="list-group-item"><a href="{{ route('dashboard.components.add') }}">{{ trans('dashboard.components.add.message') }}</a></div>
@endforelse
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-sm-12 col-lg-6">
<div class="stats-widget">
<div class="stats-top">
<span class="stats-value"><a href="{{ route('dashboard.incidents.index') }}">{{ $incidents->map(function($incident) { return count($incident); })->sum() }}</a></span>
<span class="stats-label">{{ trans('dashboard.incidents.incidents') }}</span>
</div>
<div class="stats-chart">
<div class="sparkline" data-type="line" data-resize="true" data-height="80" data-width="100%" data-line-width="2" data-min-spot-color="#e65100" data-max-spot-color="#ffb300" data-line-color="#3498db" data-spot-color="#00838f" data-fill-color="#3498db" data-highlight-line-color="#00acc1" data-highlight-spot-color="#ff8a65" data-spot-radius="false" data-data="[{{ $incidents->map(function ($incident) { return count($incident); } )->implode(',') }}]"></div>
</div>
</div>
</div>
<div class="row">
<div class="col-sm-12 col-lg-6">
<div class="stats-widget">
<div class="stats-top">
<span class="stats-value"><a href="{{ route('dashboard.incidents.index') }}">{{ $incidents->map(function($incident) { return count($incident); })->sum() }}</a></span>
<span class="stats-label">{{ trans('dashboard.incidents.incidents') }}</span>
</div>
<div class="stats-chart">
<div class="sparkline" data-type="line" data-resize="true" data-height="80" data-width="100%" data-line-width="2" data-min-spot-color="#e65100" data-max-spot-color="#ffb300" data-line-color="#3498db" data-spot-color="#00838f" data-fill-color="#3498db" data-highlight-line-color="#00acc1" data-highlight-spot-color="#ff8a65" data-spot-radius="false" data-data="[{{ $incidents->map(function ($incident) { return count($incident); } )->implode(',') }}]"></div>
</div>
<div class="col-sm-12 col-lg-6">
<div class="stats-widget">
<div class="stats-top">
<span class="stats-value"><a href="{{ route('dashboard.subscribers.index') }}">{{ $subscribers->map(function($subscribers) { return count($subscribers); })->sum() }}</a></span>
<span class="stats-label">{{ trans('dashboard.subscribers.subscribers') }}</span>
</div>
<div class="stats-chart">
<div class="sparkline" data-type="line" data-resize="true" data-height="80" data-width="100%" data-line-width="2" data-min-spot-color="#e65100" data-max-spot-color="#ffb300" data-line-color="#3498db" data-spot-color="#00838f" data-fill-color="#3498db" data-highlight-line-color="#00acc1" data-highlight-spot-color="#ff8a65" data-spot-radius="false" data-data="[{{ $subscribers->map(function ($subscriber) { return count($subscriber); } )->implode(',') }}]"></div>
</div>
</div>
</div>
</div>
<div class="col-sm-12 col-lg-6">
<div class="stats-widget">
<div class="stats-top">
<span class="stats-value"><a href="{{ route('dashboard.subscribers.index') }}">{{ $subscribers->map(function($subscribers) { return count($subscribers); })->sum() }}</a></span>
<span class="stats-label">{{ trans('dashboard.subscribers.subscribers') }}</span>
</div>
<div class="stats-chart">
<div class="sparkline" data-type="line" data-resize="true" data-height="80" data-width="100%" data-line-width="2" data-min-spot-color="#e65100" data-max-spot-color="#ffb300" data-line-color="#3498db" data-spot-color="#00838f" data-fill-color="#3498db" data-highlight-line-color="#00acc1" data-highlight-spot-color="#ff8a65" data-spot-radius="false" data-data="[{{ $subscribers->map(function ($subscriber) { return count($subscriber); } )->implode(',') }}]"></div>
<div class="row">
<div class="col-sm-12 col-lg-6">
<div class="stats-widget">
<div class='stats-top'>
<span class='stats-value'>{{ trans('dashboard.widgets.support') }}</span>
<span class='stats-label'>{!! trans('dashboard.widgets.support_subtitle') !!}</span>
</div>
</div>
</div>
<div class="col-sm-12 col-lg-6">
<div class="stats-widget">
<div class='stats-top'>
<span class='stats-value'>{{ trans('dashboard.widgets.news') }}</span>
<span class='stats-label'>{{ trans('dashboard.widgets.news_subtitle') }}</span>
</div>
<div class='stats-body'>
<div class="list-group">
@foreach($entries as $entry)
<a class="list-group-item" href="{{ $entry->link }}" target="_blank">{{ $entry->title }}, <small>{{ $entry->pubDate }}</small></a>
@endforeach
</div>
</div>
</div>
</div>
</div>
@if(Session::get('setup.done'))
@include('dashboard.partials.welcome-modal')
<script>
$(function() {
$('#welcome-modal').modal('show');
});
</script>
@endif
</div>
@if(Session::get('setup.done'))
@include('dashboard.partials.welcome-modal')
<script>
$(function() {
$('#welcome-modal').modal('show');
});
</script>
@endif
@stop