[Feature] Public dashboard and Getting Started pages (#822)

This commit is contained in:
Alex Justesen authored and GitHub committed 2023-10-01 18:20:36 -04:00
1 parent 27964058c9
commit 3532c89b9c
12 files changed
+344 -5

No files matched your search

@@ -50,7 +50,7 @@ class RecentSpeedChartWidget extends ChartWidget
return [
'datasets' => [
[
'label' => 'Download (Mbps)',
'label' => 'Download',
'data' => $results->map(fn ($item) => ! blank($item->download) ? toBits(convertSize($item->download), 2) : 0),
'borderColor' => '#0ea5e9',
'backgroundColor' => '#0ea5e9',
@@ -59,7 +59,7 @@ class RecentSpeedChartWidget extends ChartWidget
'tension' => 0.4,
],
[
'label' => 'Upload (Mbps)',
'label' => 'Upload',
'data' => $results->map(fn ($item) => ! blank($item->upload) ? toBits(convertSize($item->upload), 2) : 0),
'borderColor' => '#8b5cf6',
'backgroundColor' => '#8b5cf6',
@@ -72,6 +72,27 @@ class RecentSpeedChartWidget extends ChartWidget
];
}
protected function getOptions(): array
{
return [
'scales' => [
'x' => [
'display' => true,
'title' => [
'display' => true,
],
],
'y' => [
'display' => true,
'title' => [
'display' => true,
'text' => 'Mbps',
],
],
],
];
}
protected function getType(): string
{
return 'line';
+19
View File
@@ -0,0 +1,19 @@
<?php
namespace App\Http\Controllers;
use App\Models\Result;
use Illuminate\Http\Request;
class HomeController extends Controller
{
/**
* Handle the incoming request.
*/
public function __invoke(Request $request)
{
$hasResults = Result::count() > 0;
return view($hasResults ? 'dashboard' : 'get-started');
}
}
+25
View File
@@ -0,0 +1,25 @@
<?php
namespace App\View\Components;
use Closure;
use Illuminate\Contracts\View\View;
use Illuminate\View\Component;
class AppLayout extends Component
{
public $title = 'Page Title';
public function __construct($title)
{
$this->title = $title;
}
/**
* Get the view / contents that represent the component.
*/
public function render(): View|Closure|string
{
return view('layouts.app');
}
}
+1
View File
@@ -0,0 +1 @@
*,:before,:after{box-sizing:border-box;border-width:0;border-style:solid;border-color:rgba(var(--gray-200),1)}:before,:after{--tw-content: ""}html{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:Inter,ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,"Apple Color Emoji","Segoe UI Emoji",Segoe UI Symbol,"Noto Color Emoji";font-feature-settings:normal;font-variation-settings:normal}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,[type=button],[type=reset],[type=submit]{-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dl,dd,h1,h2,h3,h4,h5,h6,hr,figure,p,pre{margin:0}fieldset{margin:0;padding:0}legend{padding:0}ol,ul,menu{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:rgba(var(--gray-400),1)}input::placeholder,textarea::placeholder{opacity:1;color:rgba(var(--gray-400),1)}button,[role=button]{cursor:pointer}:disabled{cursor:default}img,svg,video,canvas,audio,iframe,embed,object{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]{display:none}select{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='rgba(var(--gray-500)%2c var(--tw-stroke-opacity%2c 1))' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='M6 8l4 4 4-4'/%3e%3c/svg%3e");background-position:right .5rem center;background-repeat:no-repeat;background-size:1.5em 1.5em;padding-right:2.5rem;-webkit-print-color-adjust:exact;print-color-adjust:exact}[type=checkbox]:checked{background-image:url("data:image/svg+xml,%3csvg viewBox='0 0 16 16' fill='white' xmlns='http://www.w3.org/2000/svg'%3e%3cpath d='M12.207 4.793a1 1 0 010 1.414l-5 5a1 1 0 01-1.414 0l-2-2a1 1 0 011.414-1.414L6.5 9.086l4.293-4.293a1 1 0 011.414 0z'/%3e%3c/svg%3e")}[type=radio]:checked{background-image:url("data:image/svg+xml,%3csvg viewBox='0 0 16 16' fill='white' xmlns='http://www.w3.org/2000/svg'%3e%3ccircle cx='8' cy='8' r='3'/%3e%3c/svg%3e")}[type=checkbox]:indeterminate{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 16 16'%3e%3cpath stroke='white' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M4 8h8'/%3e%3c/svg%3e");border-color:transparent;background-color:currentColor;background-size:100% 100%;background-position:center;background-repeat:no-repeat}[type=text],input:where(:not([type])),[type=email],[type=url],[type=password],[type=number],[type=date],[type=datetime-local],[type=month],[type=search],[type=tel],[type=time],[type=week],[multiple],textarea,select{-webkit-appearance:none;-moz-appearance:none;appearance:none;background-color:#fff;border-color:rgba(var(--gray-500),var(--tw-border-opacity, 1));border-width:1px;border-radius:0;padding:.5rem .75rem;font-size:1rem;line-height:1.5rem;--tw-shadow: 0 0 #0000}[type=text]:focus,input:where(:not([type])):focus,[type=email]:focus,[type=url]:focus,[type=password]:focus,[type=number]:focus,[type=date]:focus,[type=datetime-local]:focus,[type=month]:focus,[type=search]:focus,[type=tel]:focus,[type=time]:focus,[type=week]:focus,[multiple]:focus,textarea:focus,select:focus{outline:2px solid transparent;outline-offset:2px;--tw-ring-inset: var(--tw-empty, );--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: #2563eb;--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(Line truncated
+5
View File
@@ -1,4 +1,9 @@
{
"resources/css/app.css": {
"file": "assets/app-d8cd240f.css",
"isEntry": true,
"src": "resources/css/app.css"
},
"resources/css/filament/admin/theme.css": {
"file": "assets/theme-182befaf.css",
"isEntry": true,
+9
View File
@@ -0,0 +1,9 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer base {
[x-cloak] {
display: none !important;
}
}
+19
View File
@@ -0,0 +1,19 @@
<x-app-layout title="Dashboard">
<div class="grid gap-4 sm:grid-cols-6 sm:gap-8">
<div class="col-span-full">
@livewire(\App\Filament\Widgets\StatsOverviewWidget::class)
</div>
<div class="col-span-full">
@livewire(\App\Filament\Widgets\RecentSpeedChartWidget::class)
</div>
<div class="col-span-full">
@livewire(\App\Filament\Widgets\RecentPingChartWidget::class)
</div>
<div class="col-span-full">
@livewire(\App\Filament\Widgets\RecentJitterChartWidget::class)
</div>
</div>
</x-app-layout>
+149
View File
@@ -0,0 +1,149 @@
<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}" class="h-full">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Getting Started - {{ config('app.name') }}</title>
{{-- Fonts --}}
<link href="{{ asset('fonts/inter/inter.css') }}" rel="stylesheet" />
{{-- Styles --}}
@filamentStyles
@vite('resources/css/app.css')
<script>
const theme = localStorage.getItem('theme') ?? 'system'
if (
theme === 'dark' ||
(theme === 'system' &&
window.matchMedia('(prefers-color-scheme: dark)')
.matches)
) {
document.documentElement.classList.add('dark')
}
</script>
</head>
<body class="h-full antialiased bg-gray-50 dark:bg-gray-950 text-gray-950 dark:text-white">
<main class="flex flex-col justify-center min-h-full p-4 space-y-4 sm:mx-auto sm:p-6 lg:p-8 sm:max-w-3xl sm:space-y-8">
<header>
<h1 class="text-2xl font-bold tracking-tight text-gray-950 dark:text-white sm:text-3xl">{{ __("Getting Started") }} - {{ config('app.name') }}</h1>
</header>
<div>
<ul role="list" class="divide dark:divide-white/5 divide-white/80">
<li class="relative flex items-center py-4 space-x-4">
<div class="flex-auto min-w-0">
<div class="flex items-center gap-x-3">
<div class="flex-none p-1 rounded-full text-amber-400 bg-gray-100/10">
<div class="w-2 h-2 bg-current rounded-full"></div>
</div>
<h2 class="min-w-0 text-sm font-semibold leading-6 text-white">
<a href="{{ url('/admin/login') }}" class="flex gap-x-2">
<span class="truncate">Sign In</span>
<span class="text-amber-400">/</span>
<span class="whitespace-nowrap">Admin Panel</span>
<span class="absolute inset-0"></span>
</a>
</h2>
</div>
<div class="mt-3 flex items-center gap-x-2.5 text-sm leading-5 dark:text-gray-400 text-gray-950">
<p class="truncate">
Sign in to the admin panel to run your first speedtest or to setup scheduled tests.
</p>
</div>
</div>
<svg class="flex-none w-5 h-5 text-gray-400" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
<path fill-rule="evenodd" d="M7.21 14.77a.75.75 0 01.02-1.06L11.168 10 7.23 6.29a.75.75 0 111.04-1.08l4.5 4.25a.75.75 0 010 1.08l-4.5 4.25a.75.75 0 01-1.06-.02z" clip-rule="evenodd" />
</svg>
</li>
<li class="relative flex items-center py-4 space-x-4">
<div class="flex-auto min-w-0">
<div class="flex items-center gap-x-3">
<div class="flex-none p-1 rounded-full text-amber-400 bg-gray-100/10">
<div class="w-2 h-2 bg-current rounded-full"></div>
</div>
<h2 class="min-w-0 text-sm font-semibold leading-6 text-white">
<a href="https://docs.speedtest-tracker.dev/" target="_blank" rel="nofollow" class="flex gap-x-2">
<span class="truncate">Getting Started</span>
<span class="text-amber-400">/</span>
<span class="whitespace-nowrap">Docs</span>
<span class="absolute inset-0"></span>
</a>
</h2>
</div>
<div class="mt-3 flex items-center gap-x-2.5 text-sm leading-5 dark:text-gray-400 text-gray-950">
<p class="truncate">
Need help getting started with Speedtest Tracker? Check out the docs.
</p>
</div>
</div>
<svg class="flex-none w-5 h-5 text-gray-400" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
<path fill-rule="evenodd" d="M7.21 14.77a.75.75 0 01.02-1.06L11.168 10 7.23 6.29a.75.75 0 111.04-1.08l4.5 4.25a.75.75 0 010 1.08l-4.5 4.25a.75.75 0 01-1.06-.02z" clip-rule="evenodd" />
</svg>
</li>
<li class="relative flex items-center py-4 space-x-4">
<div class="flex-auto min-w-0">
<div class="flex items-center gap-x-3">
<div class="flex-none p-1 rounded-full text-amber-400 bg-gray-100/10">
<div class="w-2 h-2 bg-current rounded-full"></div>
</div>
<h2 class="min-w-0 text-sm font-semibold leading-6 text-white">
<a href="https://github.com/alexjustesen/speedtest-tracker" target="_blank" rel="nofollow" class="flex gap-x-2">
<span class="truncate">GitHub</span>
<span class="text-amber-400">/</span>
<span class="whitespace-nowrap">Repo</span>
<span class="absolute inset-0"></span>
</a>
</h2>
</div>
</div>
<svg class="flex-none w-5 h-5 text-gray-400" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
<path fill-rule="evenodd" d="M7.21 14.77a.75.75 0 01.02-1.06L11.168 10 7.23 6.29a.75.75 0 111.04-1.08l4.5 4.25a.75.75 0 010 1.08l-4.5 4.25a.75.75 0 01-1.06-.02z" clip-rule="evenodd" />
</svg>
</li>
<li class="relative flex items-center py-4 space-x-4">
<div class="flex-auto min-w-0">
<div class="flex items-center gap-x-3">
<div class="flex-none p-1 rounded-full text-amber-400 bg-gray-100/10">
<div class="w-2 h-2 bg-current rounded-full"></div>
</div>
<h2 class="min-w-0 text-sm font-semibold leading-6 text-white">
<a href="https://github.com/alexjustesen/speedtest-tracker/discussions" target="_blank" rel="nofollow" class="flex gap-x-2">
<span class="truncate">Discussions</span>
<span class="text-amber-400">/</span>
<span class="whitespace-nowrap">Community</span>
<span class="absolute inset-0"></span>
</a>
</h2>
</div>
</div>
<svg class="flex-none w-5 h-5 text-gray-400" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
<path fill-rule="evenodd" d="M7.21 14.77a.75.75 0 01.02-1.06L11.168 10 7.23 6.29a.75.75 0 111.04-1.08l4.5 4.25a.75.75 0 010 1.08l-4.5 4.25a.75.75 0 01-1.06-.02z" clip-rule="evenodd" />
</svg>
</li>
<!-- More items... -->
</ul>
</div>
</main>
{{-- Scripts --}}
@filamentScripts
</body>
</html>
+52
View File
@@ -0,0 +1,52 @@
<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{ $title }} - {{ config('app.name') }}</title>
{{-- Fonts --}}
<link href="{{ asset('fonts/inter/inter.css') }}" rel="stylesheet" />
{{-- Styles --}}
@filamentStyles
@vite('resources/css/app.css')
<script>
const theme = localStorage.getItem('theme') ?? 'system'
if (
theme === 'dark' ||
(theme === 'system' &&
window.matchMedia('(prefers-color-scheme: dark)')
.matches)
) {
document.documentElement.classList.add('dark')
}
</script>
</head>
<body class="min-h-screen antialiased bg-gray-50 dark:bg-gray-950 text-gray-950 dark:text-white">
<main class="p-4 sm:p-6 lg:p-8 mx-auto max-w-{{ config('speedtest.content_width') }} space-y-4 sm:space-y-8">
<header class="flex flex-col gap-4 sm:flex-row sm:justify-between sm:items-center">
<div>
<h1 class="text-2xl font-bold tracking-tight text-gray-950 dark:text-white sm:text-3xl">{{ $title ?? 'Page Title' }}</h1>
</div>
<div class="flex-shrink-0">
<x-filament::button
href="{{ url('/admin') }}"
tag="a"
>
Admin Panel
</x-filament::button>
</div>
</header>
{{ $slot }}
</main>
{{-- Scripts --}}
@filamentScripts
</body>
</html>
+2 -1
View File
@@ -1,5 +1,6 @@
<?php
use App\Http\Controllers\HomeController;
use Illuminate\Support\Facades\Route;
/*
@@ -13,7 +14,7 @@ use Illuminate\Support\Facades\Route;
|
*/
Route::redirect('/', '/admin');
Route::get('/', HomeController::class);
Route::get('/login', function () {
return redirect('/admin/login');
+29
View File
@@ -0,0 +1,29 @@
import defaultTheme from 'tailwindcss/defaultTheme';
import forms from '@tailwindcss/forms';
import presets from './vendor/filament/support/tailwind.config.preset';
import typography from '@tailwindcss/typography';
/** @type {import('tailwindcss').Config} */
export default {
presets: [presets],
content: [
'./resources/views/**/*.blade.php',
'./storage/framework/views/*.php',
],
darkMode: 'class',
theme: {
extend: {
fontFamily: {
sans: ['Inter', ...defaultTheme.fontFamily.sans],
},
},
},
plugins: [
forms,
typography,
],
}
+11 -2
View File
@@ -1,13 +1,22 @@
import { defineConfig } from 'vite';
import laravel from 'laravel-vite-plugin';
import laravel, { refreshPaths } from 'laravel-vite-plugin';
export default defineConfig({
plugins: [
laravel({
input: [
'resources/css/app.css',
'resources/css/filament/admin/theme.css',
],
refresh: true,
refresh: [
...refreshPaths,
'app/Livewire/**',
],
}),
],
server: {
hmr: {
host: 'localhost',
},
},
});