├── .gitattributes
├── .github
└── workflows
│ └── run-tests.yml
├── .gitignore
├── composer.json
├── config
└── telescope-toolbar.php
├── license.md
├── phpunit.xml.dist
├── readme.md
├── resources
├── css
│ ├── base.css
│ ├── custom.css
│ └── theme_light.css
├── icons
│ ├── LICENSE.txt
│ ├── ajax.svg
│ ├── cache.svg
│ ├── close.svg
│ ├── commands.svg
│ ├── config.svg
│ ├── dumps.svg
│ ├── events.svg
│ ├── exceptions.svg
│ ├── forward.svg
│ ├── gates.svg
│ ├── http-client.svg
│ ├── jobs.svg
│ ├── laravel.svg
│ ├── logs.svg
│ ├── mail.svg
│ ├── memory.svg
│ ├── menu.svg
│ ├── models.svg
│ ├── notifications.svg
│ ├── queries.svg
│ ├── redirect.svg
│ ├── redis.svg
│ ├── refresh.svg
│ ├── requests.svg
│ ├── schedule.svg
│ ├── search.svg
│ ├── session.svg
│ ├── telescope.svg
│ ├── time.svg
│ ├── user.svg
│ ├── validator.svg
│ └── views.svg
└── views
│ ├── base_js.blade.php
│ ├── collectors
│ ├── ajax.blade.php
│ ├── cache.blade.php
│ ├── commands.blade.php
│ ├── config.blade.php
│ ├── dumps.blade.php
│ ├── events.blade.php
│ ├── exceptions.blade.php
│ ├── gates.blade.php
│ ├── jobs.blade.php
│ ├── logs.blade.php
│ ├── mail.blade.php
│ ├── memory.blade.php
│ ├── models.blade.php
│ ├── notifications.blade.php
│ ├── queries.blade.php
│ ├── redis.blade.php
│ ├── request.blade.php
│ ├── session.blade.php
│ ├── time.blade.php
│ ├── user.blade.php
│ └── views.blade.php
│ ├── head.blade.php
│ ├── item.blade.php
│ ├── toolbar.blade.php
│ └── widget.blade.php
├── src
├── Http
│ ├── Controllers
│ │ └── ToolbarController.php
│ ├── assets.php
│ └── routes.php
├── Toolbar.php
├── ToolbarServiceProvider.php
└── helpers.php
└── tests
├── BrowserTestCase.php
├── TelescopeFeatureTestCase.php
├── TestCase.php
├── ToolbarBrowserTest.php
├── ToolbarTest.php
└── resources
└── views
└── dashboard.blade.php
/.gitattributes:
--------------------------------------------------------------------------------
1 | *.blade.php linguist-language=PHP
--------------------------------------------------------------------------------
/.github/workflows/run-tests.yml:
--------------------------------------------------------------------------------
1 | name: Unit Tests
2 |
3 | on:
4 | push:
5 | branches:
6 | - master
7 | pull_request:
8 | branches:
9 | - "*"
10 |
11 | jobs:
12 | php-tests:
13 | runs-on: ubuntu-latest
14 |
15 | timeout-minutes: 15
16 |
17 | env:
18 | COMPOSER_NO_INTERACTION: 1
19 |
20 | strategy:
21 | matrix:
22 | php: [8.4, 8.3, 8.2, 8.1, 8.0]
23 | laravel: ['9.*', '10.*', '11.*', '12.*']
24 | dependency-version: [prefer-stable]
25 | exclude:
26 | - laravel: 10.*
27 | php: 8.0
28 | - laravel: 10.*
29 | php: 8.3
30 | - laravel: 10.*
31 | php: 8.4
32 | - laravel: 11.*
33 | php: 8.1
34 | - laravel: 11.*
35 | php: 8.0
36 | - laravel: 12.*
37 | php: 8.1
38 | - laravel: 12.*
39 | php: 8.0
40 |
41 | name: P${{ matrix.php }} - L${{ matrix.laravel }} - ${{ matrix.dependency-version }}
42 |
43 | steps:
44 | - name: Checkout code
45 | uses: actions/checkout@v2
46 |
47 | - name: Setup PHP
48 | uses: shivammathur/setup-php@v2
49 | with:
50 | php-version: ${{ matrix.php }}
51 | coverage: none
52 | tools: composer:v2
53 |
54 | - name: Install dependencies
55 | run: |
56 | composer require "laravel/framework:${{ matrix.laravel }}" --no-interaction --no-update
57 | composer update --${{ matrix.dependency-version }} --prefer-dist --no-progress
58 |
59 | - name: Update Dusk Chromedriver
60 | run: vendor/bin/dusk-updater detect --auto-update
61 |
62 | - name: Install Sqlite Database
63 | run: vendor/bin/testbench-dusk package:create-sqlite-db
64 |
65 | - name: Execute Unit Tests
66 | run: composer test
67 |
68 | - name: Upload Failed Screenshots
69 | uses: actions/upload-artifact@v4
70 | if: failure()
71 | with:
72 | name: screenshots
73 | path: tests/Browser/screenshots/*
74 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /vendor
2 | composer.phar
3 | composer.lock
4 | .DS_Store
5 | .phpunit.result.cache
6 | /tests/Browser
7 | /build
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "fruitcake/laravel-telescope-toolbar",
3 | "description": "Toolbar for Laravel Telescope based on Symfony Web Profiler",
4 | "keywords": [
5 | "laravel",
6 | "telescope",
7 | "toolbar",
8 | "debugbar",
9 | "profiler",
10 | "debug",
11 | "webprofiler"
12 | ],
13 | "license": "MIT",
14 | "authors": [
15 | {
16 | "name": "Fruitcake",
17 | "email": "info@fruitcake.nl"
18 | },
19 | {
20 | "name": "Barry vd. Heuvel",
21 | "email": "barryvdh@gmail.com"
22 | }
23 | ],
24 | "require": {
25 | "php": "^8",
26 | "ext-json": "*",
27 | "laravel/framework": "^9|^10|^11|^12",
28 | "laravel/telescope": "^4|^5"
29 | },
30 | "require-dev": {
31 | "orchestra/testbench-dusk": "^6|^7|^8|^9|^10"
32 | },
33 | "autoload": {
34 | "psr-4": {
35 | "Fruitcake\\TelescopeToolbar\\": "src/"
36 | },
37 | "files": [
38 | "src/helpers.php"
39 | ]
40 | },
41 | "autoload-dev": {
42 | "psr-4": {
43 | "Fruitcake\\TelescopeToolbar\\Tests\\": "tests"
44 | }
45 | },
46 | "minimum-stability": "dev",
47 | "prefer-stable": true,
48 | "extra": {
49 | "branch-alias": {
50 | "dev-master": "1.4-dev"
51 | },
52 | "laravel": {
53 | "providers": [
54 | "Fruitcake\\TelescopeToolbar\\ToolbarServiceProvider"
55 | ],
56 | "aliases": {
57 | "Toolbar": "Fruitcake\\TelescopeToolbar\\Toolbar"
58 | }
59 | }
60 | },
61 | "scripts": {
62 | "test": "phpunit"
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/config/telescope-toolbar.php:
--------------------------------------------------------------------------------
1 | env('TELESCOPE_TOOLBAR_ENABLED', env('TELESCOPE_ENABLED', true)),
17 |
18 | /*
19 | |--------------------------------------------------------------------------
20 | | Telescope Auto Replace Requests
21 | |--------------------------------------------------------------------------
22 | |
23 | | This options automatically replaces the content of the toolbar with new
24 | | (AJAX) requests.
25 | |
26 | */
27 | 'replace' => env('TELESCOPE_TOOLBAR_REPLACE', true),
28 |
29 | /*
30 | |--------------------------------------------------------------------------
31 | | Use Telescope Theme ('light mode')
32 | |--------------------------------------------------------------------------
33 | |
34 | | This option enabled/disables the Light Theme.
35 | | Laravel Telescope toolbar has two themes; Light and Dark.
36 | | By default is uses dark theme.
37 | | Values be: 'auto', true, or false (default)
38 | |
39 | */
40 | 'light_theme' => env('TELESCOPE_LIGHT_THEME', false),
41 |
42 | /*
43 | |--------------------------------------------------------------------------
44 | | Open links in new tab
45 | |--------------------------------------------------------------------------
46 | |
47 | | This option enabled/disables opening telescope links in a new tab.
48 | | By default, clicking on a tab will open telescope in the same tab.
49 | | Values be: true, false
50 | |
51 | */
52 | 'new_tab' => env('TELESCOPE_TOOLBAR_NEW_TAB', false),
53 |
54 | /*
55 | |--------------------------------------------------------------------------
56 | | Route path of Toolbar
57 | |--------------------------------------------------------------------------
58 | |
59 | | The route path that is being used to collect the toolbar metrics.
60 | |
61 | */
62 | 'path' => '_tt',
63 |
64 | /*
65 | |--------------------------------------------------------------------------
66 | | Middleware of Toolbar
67 | |--------------------------------------------------------------------------
68 | |
69 | | The middleware that is used for the Telescope API routes. By default
70 | | it will use the Telescope middleware.
71 | |
72 | */
73 | 'middleware' => [
74 | 'telescope'
75 | ],
76 |
77 | 'asset_middleware' => [
78 | 'web'
79 | ],
80 |
81 | /*
82 | |--------------------------------------------------------------------------
83 | | Excluded Ajax Paths
84 | |--------------------------------------------------------------------------
85 | |
86 | | This Javascript RegExp excludes Ajax Requests from being collected.
87 | |
88 | */
89 | 'excluded_ajax_paths' => '^/_tt|^/_debugbar|^/horizon',
90 |
91 | /*
92 | |--------------------------------------------------------------------------
93 | | Ignored Paths
94 | |--------------------------------------------------------------------------
95 | |
96 | | This is a list of paths the toolbar will not run on.
97 | |
98 | */
99 | 'ignore_paths' => [
100 | //
101 | ],
102 |
103 | /*
104 | |--------------------------------------------------------------------------
105 | | Store Redirects in Session
106 | |--------------------------------------------------------------------------
107 | |
108 | | This options stores Redirect responses in the Session to show
109 | | them in in the Requests tab on the next 'real' response.
110 | |
111 | */
112 | 'store_redirects' => true,
113 |
114 | /*
115 | |--------------------------------------------------------------------------
116 | | Enable Dump Watcher
117 | |--------------------------------------------------------------------------
118 | |
119 | | This options listens for dumps always, without having the tab open.
120 | | You can specify the number of seconds it listens or disable with `false`
121 | |
122 | */
123 | 'dump_watcher' => false,
124 |
125 | /*
126 | |--------------------------------------------------------------------------
127 | | Collectors
128 | |--------------------------------------------------------------------------
129 | |
130 | | This options configures which collectors are shown
131 | |
132 | */
133 | 'collectors' => [
134 | EntryType::REQUEST => [
135 | 'telescope-toolbar::collectors.request',
136 | 'telescope-toolbar::collectors.time',
137 | 'telescope-toolbar::collectors.memory',
138 | 'telescope-toolbar::collectors.session',
139 | 'telescope-toolbar::collectors.user',
140 | ],
141 | EntryType::EXCEPTION => [
142 | 'telescope-toolbar::collectors.exceptions',
143 | ],
144 | EntryType::VIEW => [
145 | 'telescope-toolbar::collectors.views',
146 | ],
147 | EntryType::QUERY => [
148 | 'telescope-toolbar::collectors.queries',
149 | ],
150 | EntryType::CACHE => [
151 | 'telescope-toolbar::collectors.cache',
152 | ],
153 | EntryType::LOG => [
154 | 'telescope-toolbar::collectors.logs',
155 | ],
156 | EntryType::MAIL => [
157 | 'telescope-toolbar::collectors.mail',
158 | ],
159 | EntryType::NOTIFICATION => [
160 | 'telescope-toolbar::collectors.notifications',
161 | ],
162 | EntryType::GATE => [
163 | 'telescope-toolbar::collectors.gates',
164 | ],
165 | EntryType::JOB => [
166 | 'telescope-toolbar::collectors.jobs',
167 | ],
168 | EntryType::COMMAND => [
169 | 'telescope-toolbar::collectors.commands',
170 | ],
171 | EntryType::DUMP => [
172 | 'telescope-toolbar::collectors.dumps',
173 | ],
174 | EntryType::EVENT => [
175 | 'telescope-toolbar::collectors.events',
176 | ],
177 | EntryType::MODEL => [
178 | 'telescope-toolbar::collectors.models',
179 | ],
180 | EntryType::REDIS => [
181 | 'telescope-toolbar::collectors.redis',
182 | ],
183 | EntryType::SCHEDULED_TASK => [
184 |
185 | ],
186 | ],
187 |
188 | ];
189 |
--------------------------------------------------------------------------------
/license.md:
--------------------------------------------------------------------------------
1 | Copyright (C) 2019-present Fruitcake, Barry vd. Heuvel
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy of
4 | this software and associated documentation files (the "Software"), to deal in
5 | the Software without restriction, including without limitation the rights to
6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
7 | of the Software, and to permit persons to whom the Software is furnished to do
8 | so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in all
11 | copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | SOFTWARE.
--------------------------------------------------------------------------------
/phpunit.xml.dist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | tests
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------
1 | ## Laravel Telescope Toolbar
2 | 
3 | [](http://choosealicense.com/licenses/mit/)
4 | [](https://packagist.org/packages/fruitcake/laravel-telescope-toolbar)
5 | [](https://packagist.org/packages/fruitcake/laravel-telescope-toolbar)
6 | [](https://fruitcake.nl/)
7 |
8 |
9 | ### Extends Laravel Telescope to show a powerful Toolbar
10 | See https://github.com/laravel/telescope
11 |
12 | #### Install
13 |
14 | First install Telescope and check it works (see https://laravel.com/docs/master/telescope)
15 |
16 | ```bash
17 | composer require laravel/telescope
18 | php artisan telescope:install
19 |
20 | # Telescope 5.0 no longer automatically loads migrations from its own migrations directory. Instead, you should run the following command to publish Telescope's migrations to your application:
21 | php artisan vendor:publish --tag=telescope-migrations
22 |
23 | php artisan migrate
24 | ```
25 |
26 | Then just install the package with Composer and it will register automatically:
27 |
28 | ```bash
29 | composer require fruitcake/laravel-telescope-toolbar --dev
30 | ```
31 |
32 | The Toolbar will show by default when Telescope is enabled and APP_DEBUG is true.
33 | It can also enabled or disabled using the `TELESCOPE_TOOLBAR_ENABLED` environment variable.
34 |
35 | 
36 |
37 | > Note: The Toolbar is intended for Development environments, not for production.
38 |
39 | ## Publishing the config
40 |
41 | Run this command to publish the config for this package:
42 |
43 | ```php
44 | php artisan vendor:publish --provider="Fruitcake\\TelescopeToolbar\\ToolbarServiceProvider"
45 | ```
46 |
47 | #### Current Features
48 |
49 | - Inject Toolbar for quick info
50 | - Show redirects and Ajax Requests
51 | - Link to related Telescope Entry page
52 | - Show up to 5 entries for collectors, link to details
53 | - Supported Collectors:
54 | * Request info / timing
55 | * User auth
56 | * Database queries
57 | * Laravel/php version
58 | * Cache hit/miss/set
59 | * Logger entries
60 | * Exceptions
61 | * Mails
62 | * Notifications
63 | * Jobs
64 | * Dumps (when watching the Dump screen, or using `debug(...$args)`)
65 | * Number of entries for: Commands/Models/Events
66 |
67 | #### Screenshots
68 |
69 | Ajax/ Redirects stack:
70 |
71 | 
72 |
73 | Preview for Exceptions/Mail/Notifications/Log entries with link to details:
74 |
75 | 
76 |
77 | Counter for Queries (and Cache etc):
78 |
79 | 
80 |
81 |
82 | Catch `debug()`/`Toolbar::dump()` calls and show them directly in the Toolbar instead of the page:
83 |
84 | 
85 |
86 | ## Running the Test Suite
87 |
88 | - Make sure ChromeDriver is up to date: `vendor/bin/dusk-updater detect --auto-update`
89 | - Create the Sqlite database: `vendor/orchestra/testbench-dusk/create-sqlite-db`
90 | - Run the tests: `composer test`
91 |
92 | ## License and attribution
93 |
94 | Laravel Telescope Toolbar is open-sourced software licensed under the [MIT license](https://opensource.org/licenses/MIT).
95 |
96 | ### Based on Symfony Web Profiler and Laravel Telescope
97 | The styling, javascript, some icons and html of the Toolbar and (part of) its Collectors are based on the Symfony Web Profiler.
98 | JS/CSS is mostly copied and converted to Blade syntax. Collectors are modified to show Laravel data.
99 | See https://github.com/symfony/web-profiler-bundle - © 2004-2019 Fabien Potencier
100 |
101 | Data from collectors is provided by Laravel Telescope. Some styling/icons/logic are alse re-used.
102 | See https://github.com/laravel/telescope - © Taylor Otwell
103 |
104 |
--------------------------------------------------------------------------------
/resources/css/base.css:
--------------------------------------------------------------------------------
1 | .sf-toolbarreset {
2 | --sf-toolbar-font-family-system: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", "Liberation Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
3 | --sf-toolbar-font-family-monospace: "Ubuntu Mono", "JetBrains Mono", ui-monospace, "Roboto Mono", SFMono-Regular, Menlo, Monaco, Consolas,"Liberation Mono", "Courier New", monospace;
4 |
5 | --sf-toolbar-white: #fff;
6 | --sf-toolbar-black: #000;
7 | --sf-toolbar-gray-50: #fafafa;
8 | --sf-toolbar-gray-100: #f5f5f5;
9 | --sf-toolbar-gray-200: #e5e5e5;
10 | --sf-toolbar-gray-300: #d4d4d4;
11 | --sf-toolbar-gray-400: #a3a3a3;
12 | --sf-toolbar-gray-500: #737373;
13 | --sf-toolbar-gray-600: #525252;
14 | --sf-toolbar-gray-700: #404040;
15 | --sf-toolbar-gray-800: #262626;
16 | --sf-toolbar-gray-900: #171717;
17 | --sf-toolbar-red-50: #FEFBFC;
18 | --sf-toolbar-red-100: #FCE9ED;
19 | --sf-toolbar-red-200: #F5B8C5;
20 | --sf-toolbar-red-300: #EF869C;
21 | --sf-toolbar-red-400: #E85574;
22 | --sf-toolbar-red-500: #E1244B;
23 | --sf-toolbar-red-600: #B41939;
24 | --sf-toolbar-red-700: #83122A;
25 | --sf-toolbar-red-800: #510B1A;
26 | --sf-toolbar-red-900: #20040A;
27 | --sf-toolbar-yellow-50: #fef7e1;
28 | --sf-toolbar-yellow-100: #fef2cd;
29 | --sf-toolbar-yellow-200: #fde496;
30 | --sf-toolbar-yellow-300: #fcd55f;
31 | --sf-toolbar-yellow-400: #fbc728;
32 | --sf-toolbar-yellow-500: #e6af05;
33 | --sf-toolbar-yellow-600: #af8503;
34 | --sf-toolbar-yellow-700: #785b02;
35 | --sf-toolbar-yellow-800: #413101;
36 | --sf-toolbar-yellow-900: #0a0800;
37 | --sf-toolbar-green-50: #eff5f5;
38 | --sf-toolbar-green-100: #deeaea;
39 | --sf-toolbar-green-200: #bbd5d5;
40 | --sf-toolbar-green-300: #99bfbf;
41 | --sf-toolbar-green-400: #76a9a9;
42 | --sf-toolbar-green-500: #598e8e;
43 | --sf-toolbar-green-600: #436c6c;
44 | --sf-toolbar-green-700: #2e4949;
45 | --sf-toolbar-green-800: #182727;
46 | --sf-toolbar-green-900: #030404;
47 | }
48 |
49 | .sf-minitoolbar {
50 | background-color: var(--sf-toolbar-gray-800);
51 | border-top-left-radius: 4px;
52 | bottom: 0;
53 | box-sizing: border-box;
54 | display: none;
55 | height: 36px;
56 | padding: 6px;
57 | position: fixed;
58 | right: 0;
59 | z-index: 99999;
60 | }
61 |
62 | .sf-minitoolbar button {
63 | background-color: transparent;
64 | padding: 0;
65 | border: none;
66 | }
67 | .sf-minitoolbar svg,
68 | .sf-minitoolbar img {
69 | color: var(--sf-toolbar-gray-200);
70 | max-height: 24px;
71 | max-width: 24px;
72 | display: inline;
73 | }
74 |
75 | .sf-toolbar-clearer {
76 | clear: both;
77 | height: 36px;
78 | }
79 |
80 | .sf-display-none {
81 | display: none;
82 | }
83 |
84 | .sf-toolbarreset *:not(svg rect) {
85 | box-sizing: content-box;
86 | vertical-align: baseline;
87 | letter-spacing: normal;
88 | width: auto;
89 | }
90 |
91 | .sf-toolbarreset {
92 | background-color: var(--sf-toolbar-gray-800);
93 | bottom: 0;
94 | box-shadow: inset 0 1px 0 var(--sf-toolbar-black), 0 -1px 0 rgba(0, 0, 0, 0.5);
95 | color: var(--sf-toolbar-gray-200);
96 | font: 11px var(--sf-toolbar-font-family-system);
97 | left: 0;
98 | margin: 0;
99 | padding: 0 36px 0 0;
100 | position: fixed;
101 | right: 0;
102 | text-align: left;
103 | text-transform: none;
104 | z-index: 99999;
105 | direction: ltr;
106 |
107 | /* neutralize the aliasing defined by external CSS styles */
108 | -webkit-font-smoothing: subpixel-antialiased;
109 | -moz-osx-font-smoothing: auto;
110 | }
111 | .sf-toolbarreset abbr {
112 | border: dashed var(--sf-toolbar-gray-500);
113 | border-width: 0 0 1px;
114 | }
115 | .sf-toolbarreset svg,
116 | .sf-toolbarreset img {
117 | height: 20px;
118 | width: 20px;
119 | display: inline-block;
120 | }
121 |
122 | .sf-toolbarreset .sf-cancel-button {
123 | color: var(--sf-toolbar-gray-700);
124 | }
125 |
126 | .sf-toolbarreset .hide-button {
127 | background: var(--sf-toolbar-gray-800);
128 | color: var(--sf-toolbar-gray-300);
129 | display: block;
130 | position: absolute;
131 | top: 2px;
132 | right: 0;
133 | width: 36px;
134 | height: 34px;
135 | cursor: pointer;
136 | text-align: center;
137 | border: none;
138 | margin: 0;
139 | padding: 0;
140 | }
141 | .sf-toolbarreset .hide-button:hover {
142 | background: var(--sf-toolbar-gray-700);
143 | }
144 | .sf-toolbarreset .hide-button svg {
145 | max-height: 18px;
146 | margin-top: 1px;
147 | }
148 |
149 | .sf-toolbar-block {
150 | cursor: default;
151 | display: block;
152 | float: left;
153 | height: 36px;
154 | margin-right: 0;
155 | position: relative;
156 | white-space: nowrap;
157 | max-width: 15%;
158 | }
159 | .sf-toolbar-block > a,
160 | .sf-toolbar-block > a:hover {
161 | display: block;
162 | text-decoration: none;
163 | background-color: transparent;
164 | color: inherit;
165 | }
166 |
167 | .sf-toolbar-block span {
168 | display: inline-block;
169 | }
170 | .sf-toolbar-block .sf-toolbar-value {
171 | color: var(--sf-toolbar-gray-100);
172 | font-size: 13px;
173 | line-height: 36px;
174 | padding: 0;
175 | }
176 | .sf-toolbar-block .sf-toolbar-label,
177 | .sf-toolbar-block .sf-toolbar-class-separator {
178 | color: var(--sf-toolbar-gray-400);
179 | font-size: 12px;
180 | margin-left: 2px;
181 | }
182 |
183 | .sf-toolbar-block .sf-toolbar-info {
184 | border-collapse: collapse;
185 | display: table;
186 | z-index: 100000;
187 | }
188 | .sf-toolbar-block hr {
189 | border-top: 1px solid var(--sf-toolbar-gray-500);
190 | margin: 4px 0;
191 | padding-top: 4px;
192 | }
193 | .sf-toolbar-block .sf-toolbar-info-piece {
194 | /* this 'border-bottom' trick is needed because 'margin-bottom' doesn't work for table rows */
195 | border-bottom: solid transparent 3px;
196 | display: table-row;
197 | }
198 | .sf-toolbar-block .sf-toolbar-info-piece-additional,
199 | .sf-toolbar-block .sf-toolbar-info-piece-additional-detail {
200 | display: none;
201 | }
202 | .sf-toolbar-block .sf-toolbar-info-group {
203 | margin-bottom: 4px;
204 | padding-bottom: 2px;
205 | border-bottom: 1px solid #333333;
206 | }
207 | .sf-toolbar-block .sf-toolbar-info-group:last-child {
208 | margin-bottom: 0;
209 | padding-bottom: 0;
210 | border-bottom: none;
211 | }
212 |
213 | .sf-toolbar-block .sf-toolbar-info-piece .sf-toolbar-status {
214 | border-radius: 4px;
215 | padding: 2px 5px;
216 | margin-bottom: 0;
217 | }
218 | .sf-toolbar-block .sf-toolbar-info-piece .sf-toolbar-status + .sf-toolbar-status {
219 | margin-left: 4px;
220 | }
221 |
222 | .sf-toolbar-block .sf-toolbar-info-piece:last-child {
223 | margin-bottom: 0;
224 | }
225 |
226 | div.sf-toolbar .sf-toolbar-block .sf-toolbar-info-piece a {
227 | color: #99CDD8;
228 | text-decoration: underline;
229 | }
230 | div.sf-toolbar .sf-toolbar-block a:hover {
231 | text-decoration: none;
232 | }
233 |
234 | .sf-toolbar-block .sf-toolbar-info-piece b {
235 | color: var(--sf-toolbar-gray-400);
236 | display: table-cell;
237 | font-size: 11px;
238 | padding: 4px 8px 4px 0;
239 | }
240 | .sf-toolbar-block:not(.sf-toolbar-block-dump) .sf-toolbar-info-piece span {
241 | color: var(--sf-toolbar-gray-100);
242 | }
243 | .sf-toolbar-block .sf-toolbar-info-piece span {
244 | font-size: 12px;
245 | }
246 | div.sf-toolbar .sf-toolbar-block .sf-toolbar-info-piece.sf-toolbar-info-php-ext a {
247 | text-decoration: none;
248 | }
249 |
250 | .sf-toolbar-block .sf-toolbar-info {
251 | background-color: var(--sf-toolbar-gray-700);
252 | border-radius: 4px;
253 | border-bottom-left-radius: 0;
254 | bottom: 36px;
255 | color: var(--sf-toolbar-gray-100);
256 | display: none;
257 | padding: 9px 0;
258 | position: absolute;
259 | }
260 |
261 | .sf-toolbar-block.sf-toolbar-block-right .sf-toolbar-info {
262 | border-bottom-left-radius: 4px;
263 | border-bottom-right-radius: 0;
264 | }
265 |
266 | .sf-toolbar-block .sf-toolbar-info:empty {
267 | visibility: hidden;
268 | }
269 |
270 | .sf-toolbar-block .sf-toolbar-status {
271 | display: inline-block;
272 | color: var(--sf-toolbar-white);
273 | background-color: var(--sf-toolbar-gray-600);
274 | padding: 3px 6px;
275 | margin: 0 4px;
276 | min-width: 15px;
277 | min-height: 13px;
278 | text-align: center;
279 | }
280 |
281 | .sf-toolbar-block .sf-toolbar-status.sf-toolbar-status-green,
282 | .sf-toolbar-block .sf-toolbar-info .sf-toolbar-status.sf-toolbar-status-green {
283 | background-color: #059669;
284 | color: var(--white);
285 | }
286 | .sf-toolbar-block .sf-toolbar-status.sf-toolbar-status-red,
287 | .sf-toolbar-block .sf-toolbar-info .sf-toolbar-status.sf-toolbar-status-red {
288 | background-color: var(--sf-toolbar-red-500);
289 | color: var(--sf-toolbar-red-50);
290 | }
291 | .sf-toolbar-block .sf-toolbar-status.sf-toolbar-status-yellow,
292 | .sf-toolbar-block .sf-toolbar-info .sf-toolbar-status.sf-toolbar-status-yellow {
293 | background-color: var(--sf-toolbar-yellow-300);
294 | color: var(--sf-toolbar-yellow-800);
295 | }
296 |
297 | .sf-toolbar-block.sf-toolbar-status-green::before,
298 | .sf-toolbar-block.sf-toolbar-status-red::before,
299 | .sf-toolbar-block.sf-toolbar-status-yellow::before {
300 | background: var(--sf-toolbar-yellow-400);
301 | border-radius: 6px;
302 | content: '';
303 | position: absolute;
304 | bottom: 1px;
305 | left: 0;
306 | width: 98%;
307 | height: 3px;
308 | z-index: 10005;
309 | }
310 | .sf-toolbar-block.sf-toolbar-status-red::before {
311 | background: var(--sf-toolbar-red-400);
312 | }
313 | .sf-toolbar-block.sf-toolbar-status-green::before {
314 | background: var(--sf-toolbar-green-400);
315 | }
316 | .sf-toolbar-block-request.sf-toolbar-block.sf-toolbar-status-green::before,
317 | .sf-toolbar-block-request.sf-toolbar-block.sf-toolbar-status-red::before,
318 | .sf-toolbar-block-request.sf-toolbar-block.sf-toolbar-status-yellow::before {
319 | display: none;
320 | }
321 |
322 | .sf-toolbar-block-request .sf-toolbar-status {
323 | border-radius: 6px;
324 | color: #fff;
325 | display: inline-block;
326 | flex-shrink: 0;
327 | font-size: 13px;
328 | font-weight: 500;
329 | padding: 4px 8px;
330 | }
331 | .sf-toolbar-block-request .sf-toolbar-info-piece a {
332 | background-color: transparent;
333 | text-decoration: none;
334 | }
335 | .sf-toolbar-block-request .sf-toolbar-info-piece a:hover {
336 | text-decoration: underline;
337 | }
338 | .sf-toolbar-block-request .sf-toolbar-redirection-status {
339 | font-weight: normal;
340 | padding: 2px 4px;
341 | line-height: 18px;
342 | }
343 | .sf-toolbar-block.sf-toolbar-block-request .sf-toolbar-redirection-status.sf-toolbar-status-yellow {
344 | background-color: var(--sf-toolbar-yellow-300);
345 | border-radius: 4px;
346 | color: var(--sf-toolbar-yellow-800);
347 | padding: 1px 4px;
348 | }
349 | .sf-toolbar-block.sf-toolbar-block-request .sf-toolbar-info-piece .sf-toolbar-redirection-method {
350 | background: transparent;
351 | color: var(--sf-toolbar-gray-300);
352 | border: 1px solid var(--sf-toolbar-gray-400);
353 | padding: 1px 4px;
354 | }
355 | .sf-toolbar-block-request .sf-toolbar-info-piece span.sf-toolbar-redirection-method {
356 | font-size: 12px;
357 | height: 17px;
358 | line-height: 17px;
359 | margin-right: 5px;
360 | }
361 | .sf-toolbar-block-request .sf-toolbar-request-icon svg {
362 | stroke-width: 3px;
363 | }
364 |
365 | .sf-toolbar-block-ajax .sf-toolbar-icon {
366 | cursor: pointer;
367 | }
368 |
369 | .sf-toolbar-status-green .sf-toolbar-label,
370 | .sf-toolbar-status-yellow .sf-toolbar-label,
371 | .sf-toolbar-status-red .sf-toolbar-label {
372 | color: var(--sf-toolbar-white);
373 | }
374 | .sf-toolbar-block-config svg path,
375 | .sf-toolbar-block-config svg .sf-svg-path {
376 | fill: var(--sf-toolbar-white);
377 | }
378 |
379 | .sf-toolbar-block .sf-toolbar-icon {
380 | color: var(--sf-toolbar-gray-300);
381 | align-items: center;
382 | display: flex;
383 | height: 36px;
384 | padding: 0 7px;
385 | overflow: hidden;
386 | text-overflow: ellipsis;
387 | }
388 | .sf-toolbar-block:hover .sf-toolbar-icon {
389 | border-bottom-left-radius: 4px;
390 | border-bottom-right-radius: 4px;
391 | box-shadow: 1px 0 0 var(--sf-toolbar-black), inset 0 -1px 0 var(--sf-toolbar-black);
392 | }
393 | .sf-toolbar-block.sf-toolbar-block-right:hover .sf-toolbar-icon {
394 | box-shadow: -1px 0 0 var(--sf-toolbar-black), inset 0 -1px 0 var(--sf-toolbar-black);
395 | }
396 |
397 | .sf-toolbar-block-request .sf-toolbar-icon {
398 | padding-left: 0;
399 | padding-right: 0;
400 | }
401 |
402 | .sf-toolbar-block .sf-toolbar-icon img,
403 | .sf-toolbar-block .sf-toolbar-icon svg {
404 | border-width: 0;
405 | }
406 |
407 | .sf-toolbar-block .sf-toolbar-icon img + span,
408 | .sf-toolbar-block .sf-toolbar-icon svg + span {
409 | margin-left: 4px;
410 | }
411 | .sf-toolbar-block-config .sf-toolbar-icon .sf-toolbar-value,
412 | .sf-toolbar-block.sf-toolbar-block-sf-cli .sf-toolbar-value {
413 | margin-left: 5px;
414 | }
415 | .sf-toolbar-block-config .sf-toolbar-icon .sf-toolbar-label,
416 | .sf-toolbar-block.sf-toolbar-block-sf-cli .sf-toolbar-label {
417 | margin-left: 0;
418 | }
419 |
420 | .sf-toolbar-block:hover,
421 | .sf-toolbar-block.hover {
422 | position: relative;
423 | }
424 | .sf-toolbar-block:hover .sf-toolbar-icon,
425 | .sf-toolbar-block.hover .sf-toolbar-icon {
426 | background-color: var(--sf-toolbar-gray-700);
427 | position: relative;
428 | z-index: 10002;
429 | }
430 | .sf-toolbar-block-ajax.hover .sf-toolbar-info {
431 | z-index: 10001;
432 | }
433 | .sf-toolbar-block:hover .sf-toolbar-info,
434 | .sf-toolbar-block.hover .sf-toolbar-info {
435 | display: block;
436 | padding: 10px;
437 | max-width: 525px;
438 | max-height: 480px;
439 | word-wrap: break-word;
440 | overflow: hidden;
441 | overflow-y: auto;
442 | }
443 | .sf-toolbar-info-piece b.sf-toolbar-ajax-info {
444 | color: var(--sf-toolbar-gray-100);
445 | }
446 | .sf-toolbar-ajax-requests {
447 | border: 1px solid var(--sf-toolbar-gray-500);
448 | font-variant: tabular-nums;
449 | margin: 5px 0 0;
450 | width: 100%;
451 | }
452 | .sf-toolbar-ajax-requests td {
453 | background-color: var(--sf-toolbar-gray-700);
454 | border: 1px solid var(--sf-toolbar-gray-500);
455 | color: var(--sf-toolbar-gray-100);
456 | font-size: 12px;
457 | padding: 4px;
458 | vertical-align: middle;
459 | }
460 | .sf-toolbar-ajax-requests thead {
461 | border: 0;
462 | }
463 | .sf-toolbar-ajax-requests th {
464 | background-color: var(--sf-toolbar-gray-800);
465 | border: 1px solid var(--sf-toolbar-gray-500);
466 | color: var(--sf-toolbar-gray-200);
467 | font-size: 11px;
468 | padding: 4px;
469 | }
470 | .sf-ajax-request-url {
471 | max-width: 250px;
472 | line-height: 9px;
473 | overflow: hidden;
474 | text-overflow: ellipsis;
475 | }
476 | .sf-toolbar-ajax-requests .sf-ajax-request-url a {
477 | text-decoration: none;
478 | }
479 | .sf-toolbar-ajax-requests .sf-ajax-request-url a:hover {
480 | text-decoration: underline;
481 | }
482 | .sf-ajax-request-duration {
483 | text-align: right;
484 | }
485 | .sf-toolbar-block .sf-toolbar-info-piece .sf-toolbar-ajax-requests .sf-toolbar-status {
486 | font-size: 11px;
487 | padding: 1px 3px;
488 | }
489 | .sf-ajax-request-loading {
490 | animation: sf-blink .5s ease-in-out infinite;
491 | }
492 | @keyframes sf-blink {
493 | 0% { background: var(--sf-toolbar-gray-800); }
494 | 50% { background: var(--sf-toolbar-gray-700); }
495 | 100% { background: var(--sf-toolbar-gray-800); }
496 | }
497 |
498 | .sf-toolbar-block.sf-toolbar-block-dump .sf-toolbar-info {
499 | max-width: none;
500 | width: 100%;
501 | position: fixed;
502 | box-sizing: border-box;
503 | left: 0;
504 | }
505 |
506 | .sf-toolbar-block-dump pre.sf-dump {
507 | background-color: var(--sf-toolbar-gray-800);
508 | border-color: var(--sf-toolbar-gray-500);
509 | border-radius: 0;
510 | margin: 6px 0 12px 0;
511 | }
512 | .sf-toolbar-block-dump pre.sf-dump:last-child {
513 | margin-bottom: 0;
514 | }
515 | .sf-toolbar-block-dump pre.sf-dump .sf-dump-search-wrapper {
516 | margin-bottom: 5px;
517 | }
518 | .sf-toolbar-block-dump pre.sf-dump span.sf-dump-search-count {
519 | color: #333;
520 | font-size: 12px;
521 | }
522 | .sf-toolbar-block-dump .sf-toolbar-info-piece {
523 | display: block;
524 | }
525 | .sf-toolbar-block-dump .sf-toolbar-info-piece .sf-toolbar-file-line {
526 | color: var(--sf-toolbar-gray-400);
527 | margin-left: 4px;
528 | }
529 | .sf-toolbar-block-dump .sf-toolbar-info img {
530 | display: none;
531 | }
532 |
533 | .sf-toolbar-block-serializer .detailed-metrics {
534 | display: grid;
535 | grid-template-columns: repeat(3, 1fr);
536 | grid-gap: 15px;
537 | margin-top: 15px;
538 | }
539 |
540 | /* Responsive Design */
541 | .sf-toolbar-icon .sf-toolbar-label,
542 | .sf-toolbar-icon .sf-toolbar-value {
543 | display: none;
544 | }
545 | .sf-toolbar-block-config .sf-toolbar-icon .sf-toolbar-label,
546 | .sf-cli .sf-toolbar-icon .sf-toolbar-label {
547 | display: inline-block;
548 | }
549 |
550 | /* Legacy Design - these styles are maintained to make old panels look
551 | a bit better on the new toolbar */
552 | .sf-toolbar-block .sf-toolbar-info-piece-additional-detail {
553 | color: var(--sf-toolbar-gray-400);
554 | font-size: 12px;
555 | }
556 | .sf-toolbar-status-green .sf-toolbar-info-piece-additional-detail,
557 | .sf-toolbar-status-yellow .sf-toolbar-info-piece-additional-detail,
558 | .sf-toolbar-status-red .sf-toolbar-info-piece-additional-detail {
559 | color: var(--sf-toolbar-white);
560 | }
561 |
562 | @media (min-width: 768px) {
563 | .sf-toolbar-icon .sf-toolbar-label,
564 | .sf-toolbar-icon .sf-toolbar-value {
565 | display: inline;
566 | }
567 |
568 | .sf-toolbar-block-time .sf-toolbar-icon svg,
569 | .sf-toolbar-block-memory .sf-toolbar-icon svg {
570 | display: none;
571 | }
572 | .sf-toolbar-block-time .sf-toolbar-icon svg + span,
573 | .sf-toolbar-block-memory .sf-toolbar-icon svg + span {
574 | margin-left: 0;
575 | }
576 |
577 | .sf-toolbar-block .sf-toolbar-icon {
578 | padding: 0 10px;
579 | }
580 | .sf-toolbar-block-time .sf-toolbar-icon {
581 | padding-right: 5px;
582 | }
583 | .sf-toolbar-block-memory .sf-toolbar-icon {
584 | padding-left: 5px;
585 | }
586 | .sf-toolbar-block-request .sf-toolbar-icon {
587 | display: flex;
588 | align-items: center;
589 | padding-left: 0;
590 | padding-right: 0;
591 | }
592 | .sf-toolbar-block-request .sf-toolbar-label {
593 | margin-left: 4px;
594 | margin-right: 1px;
595 | }
596 | .sf-toolbar-block-request .sf-toolbar-status + .sf-toolbar-request-icon {
597 | display: inline-flex;
598 | margin-left: 5px;
599 | }
600 | .sf-toolbar-block-request .sf-toolbar-icon .sf-toolbar-request-icon + .sf-toolbar-label {
601 | margin-left: 0;
602 | }
603 | .sf-toolbar-block-request .sf-toolbar-label + .sf-toolbar-value {
604 | margin-right: 5px;
605 | }
606 |
607 | .sf-toolbar-block-request:hover .sf-toolbar-info {
608 | max-width: none;
609 | }
610 |
611 | .sf-toolbar-block .sf-toolbar-info-piece b {
612 | font-size: 12px;
613 | }
614 | .sf-toolbar-block .sf-toolbar-info-piece span {
615 | font-size: 13px;
616 | }
617 |
618 | .sf-toolbar-block-right {
619 | float: right;
620 | margin-left: 0;
621 | margin-right: 0;
622 | }
623 | .sf-toolbarreset .sf-toolbar-block.sf-toolbar-block-right:not(.sf-toolbar-block-sf-cli) .sf-toolbar-info {
624 | border-bottom-left-radius: 4px;
625 | border-bottom-right-radius: 0;
626 | }
627 | }
628 |
629 | @media (min-width: 1024px) {
630 | .sf-toolbar-block .sf-toolbar-info-piece-additional,
631 | .sf-toolbar-block .sf-toolbar-info-piece-additional-detail {
632 | display: inline;
633 | }
634 |
635 | .sf-toolbar-block .sf-toolbar-info-piece-additional:empty,
636 | .sf-toolbar-block .sf-toolbar-info-piece-additional-detail:empty {
637 | display: none;
638 | }
639 | }
640 |
641 | /***** Error Toolbar *****/
642 | .sf-error-toolbar .sf-toolbarreset {
643 | background: var(--sf-toolbar-gray-800);
644 | color: var(--sf-toolbar-gray-100);
645 | font: 13px/36px var(--sf-toolbar-font-family-system);
646 | height: 36px;
647 | padding: 0 15px;
648 | text-align: left;
649 | }
650 |
651 | .sf-error-toolbar .sf-toolbarreset svg {
652 | height: auto;
653 | }
654 |
655 | .sf-error-toolbar .sf-toolbarreset a {
656 | color: #99cdd8;
657 | margin-left: 5px;
658 | text-decoration: underline;
659 | }
660 |
661 | .sf-error-toolbar .sf-toolbarreset a:hover {
662 | text-decoration: none;
663 | }
664 |
665 | .sf-error-toolbar .sf-toolbarreset .sf-toolbar-icon {
666 | float: left;
667 | padding: 5px 0;
668 | margin-right: 10px;
669 | }
670 |
671 | .sf-full-stack {
672 | left: 0px;
673 | font-size: 12px;
674 | }
675 |
676 | /***** Media query print: Do not print the Toolbar. *****/
677 | @media print {
678 | .sf-toolbar {
679 | display: none !important;
680 | }
681 | }
682 |
--------------------------------------------------------------------------------
/resources/css/custom.css:
--------------------------------------------------------------------------------
1 | .Whoops.container {
2 | z-index: 99998;
3 | }
4 |
5 | .sf-toolbar-block.sf-toolbar-block-fullwidth .sf-toolbar-info {
6 | max-width: none;
7 | width: 100%;
8 | position: fixed;
9 | box-sizing: border-box;
10 | left: 0;
11 | }
12 |
13 | .sf-toolbar-previews {
14 | table-layout: auto;
15 | width: 100%;
16 | }
17 |
18 | .sf-toolbar-previews td {
19 | background-color: #444;
20 | border-bottom: 1px solid #777;
21 | color: #F5F5F5;
22 | font-size: 12px;
23 | padding: 4px;
24 | }
25 |
26 | .sf-toolbar-previews td.monospace {
27 | font-family: SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;
28 | font-size: 10px;
29 | }
30 |
31 | .sf-toolbar-previews td.sf-query {
32 | font-family: SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;
33 | font-size: 11px;
34 | white-space: normal;
35 | vertical-align: middle;
36 | }
37 |
38 | .sf-toolbar-previews tr:last-child td {
39 | border-bottom: 0;
40 | }
41 |
42 | .sf-toolbar-previews th {
43 | background-color: #222;
44 | border-bottom: 0;
45 | color: #AAA;
46 | font-size: 11px;
47 | padding: 4px;
48 | }
49 |
50 | .sf-minitoolbar .open-button svg {
51 | max-height: 18px;
52 | margin-top: 4px;
53 | }
54 | .sf-toolbar-block-config svg path,
55 | .sf-toolbar-block-config svg .sf-svg-path {
56 | fill: #FF2929;
57 | }
58 |
--------------------------------------------------------------------------------
/resources/css/theme_light.css:
--------------------------------------------------------------------------------
1 |
2 | .sf-toolbarreset {
3 | background-color: #EEF1F3;
4 | box-shadow: 0px 1px 5px rgba(0, 0, 0, 0.2);
5 | color: #212529;
6 | }
7 |
8 | .sf-toolbarreset .hide-button, .sf-toolbarreset .hide-button:hover {
9 | background: #EEF1F3;
10 | color: var(--sf-toolbar-gray-800);
11 | }
12 |
13 | .sf-toolbar-block .sf-toolbar-value {
14 | color: #212529;
15 | }
16 |
17 | .sf-toolbar-block .sf-toolbar-label,
18 | .sf-toolbar-block .sf-toolbar-class-separator {
19 | color: #212529;
20 | }
21 |
22 | .sf-toolbar-block hr {
23 | border-top: 1px solid #212529;
24 | }
25 |
26 | .sf-toolbar-block .sf-toolbar-info-group {
27 | border-bottom: 1px solid #212529;
28 | }
29 |
30 | div.sf-toolbar .sf-toolbar-block .sf-toolbar-info-piece a {
31 | color: #3439bc;
32 | }
33 |
34 | .sf-toolbar-block .sf-toolbar-info-piece b {
35 | color: #212529;
36 | }
37 |
38 | .sf-toolbar-block:not(.sf-toolbar-block-dump) .sf-toolbar-info-piece span {
39 | color: #212529;
40 | }
41 |
42 | .sf-toolbar-block:not(.sf-toolbar-block-dump) .sf-toolbar-info-piece span.sf-toolbar-status {
43 | color: #FFF;
44 | }
45 |
46 | .sf-toolbar-block .sf-toolbar-info {
47 | background-color: #fff;
48 | color: #212529;
49 | }
50 |
51 | .sf-toolbar-block .sf-toolbar-status {
52 | background-color: #212529;
53 | }
54 |
55 |
56 | .sf-toolbar-block .sf-toolbar-icon {
57 | color: var(--sf-toolbar-gray-800);
58 | }
59 |
60 | .sf-toolbar-block:hover .sf-toolbar-icon,
61 | .sf-toolbar-block.hover .sf-toolbar-icon {
62 | background-color: #fff;
63 | border-bottom: 2px solid #4040c8;
64 | box-sizing: border-box;
65 | }
66 |
67 | .sf-toolbar-block:hover .sf-toolbar-info,
68 | .sf-toolbar-block.hover .sf-toolbar-info {
69 | box-shadow: 0px -2px 6px rgba(0,0,0,0.1);
70 | border-top-left-radius: 3px;
71 | border-top-right-radius: 3px;
72 | }
73 |
74 | .sf-toolbar-info-piece b.sf-toolbar-ajax-info {
75 | color: #212529;
76 | }
77 |
78 | .sf-toolbar-ajax-requests td, .sf-toolbar-previews td {
79 | background-color: #fff;
80 | border-bottom: 1px solid #212529;
81 | color: #212529;
82 | }
83 |
84 | .sf-toolbar-ajax-requests th, .sf-toolbar-previews th {
85 | background-color: #EEF1F3;
86 | color: #212529;
87 | }
88 |
89 | @keyframes sf-blink {
90 | 0% { background: #EEF1F3; }
91 | 50% { background: #444; }
92 | 100% { background: #EEF1F3; }
93 | }
94 |
95 | .sf-toolbar-block .sf-toolbar-info-piece-additional-detail {
96 | color: #EEF1F3;
97 | }
98 |
99 | .sf-error-toolbar .sf-toolbarreset {
100 | background: #EEF1F3;
101 | color: #212529;
102 | }
103 |
104 | .sf-error-toolbar .sf-toolbarreset a {
105 | color: #3439bc;
106 | }
107 |
--------------------------------------------------------------------------------
/resources/icons/LICENSE.txt:
--------------------------------------------------------------------------------
1 | Icons are from "Tabler Icons" (https://github.com/tabler/tabler-icons), a set of
2 | free MIT-licensed high-quality SVG icons.
3 |
4 | -----
5 |
6 | MIT License
7 |
8 | Copyright (c) 2020-2022 Paweł Kuna
9 |
10 | Permission is hereby granted, free of charge, to any person obtaining a copy
11 | of this software and associated documentation files (the "Software"), to deal
12 | in the Software without restriction, including without limitation the rights
13 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14 | copies of the Software, and to permit persons to whom the Software is
15 | furnished to do so, subject to the following conditions:
16 |
17 | The above copyright notice and this permission notice shall be included in all
18 | copies or substantial portions of the Software.
19 |
20 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26 | SOFTWARE.
27 |
--------------------------------------------------------------------------------
/resources/icons/ajax.svg:
--------------------------------------------------------------------------------
1 |
8 |
--------------------------------------------------------------------------------
/resources/icons/cache.svg:
--------------------------------------------------------------------------------
1 |
8 |
--------------------------------------------------------------------------------
/resources/icons/close.svg:
--------------------------------------------------------------------------------
1 |
6 |
--------------------------------------------------------------------------------
/resources/icons/commands.svg:
--------------------------------------------------------------------------------
1 |
7 |
--------------------------------------------------------------------------------
/resources/icons/config.svg:
--------------------------------------------------------------------------------
1 |
14 |
--------------------------------------------------------------------------------
/resources/icons/dumps.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/resources/icons/events.svg:
--------------------------------------------------------------------------------
1 |
9 |
--------------------------------------------------------------------------------
/resources/icons/exceptions.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/resources/icons/forward.svg:
--------------------------------------------------------------------------------
1 |
7 |
--------------------------------------------------------------------------------
/resources/icons/gates.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/resources/icons/http-client.svg:
--------------------------------------------------------------------------------
1 |
10 |
--------------------------------------------------------------------------------
/resources/icons/jobs.svg:
--------------------------------------------------------------------------------
1 |
9 |
--------------------------------------------------------------------------------
/resources/icons/laravel.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/resources/icons/logs.svg:
--------------------------------------------------------------------------------
1 |
9 |
--------------------------------------------------------------------------------
/resources/icons/mail.svg:
--------------------------------------------------------------------------------
1 |
6 |
7 |
--------------------------------------------------------------------------------
/resources/icons/memory.svg:
--------------------------------------------------------------------------------
1 |
15 |
--------------------------------------------------------------------------------
/resources/icons/menu.svg:
--------------------------------------------------------------------------------
1 |
8 |
--------------------------------------------------------------------------------
/resources/icons/models.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/resources/icons/notifications.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/resources/icons/queries.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/resources/icons/redirect.svg:
--------------------------------------------------------------------------------
1 |
6 |
--------------------------------------------------------------------------------
/resources/icons/redis.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/resources/icons/refresh.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/resources/icons/requests.svg:
--------------------------------------------------------------------------------
1 |
7 |
--------------------------------------------------------------------------------
/resources/icons/schedule.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/resources/icons/search.svg:
--------------------------------------------------------------------------------
1 |
6 |
--------------------------------------------------------------------------------
/resources/icons/session.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/resources/icons/telescope.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/resources/icons/time.svg:
--------------------------------------------------------------------------------
1 |
11 |
--------------------------------------------------------------------------------
/resources/icons/user.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/resources/icons/validator.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/resources/icons/views.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/resources/views/base_js.blade.php:
--------------------------------------------------------------------------------
1 |
677 |
678 |
679 |
--------------------------------------------------------------------------------
/resources/views/collectors/ajax.blade.php:
--------------------------------------------------------------------------------
1 | @component('telescope-toolbar::item', ['name' => 'ajax', 'additional_classes' => 'sf-toolbar-block-right'])
2 |
3 | @slot('icon')
4 |
5 | @ttIcon('ajax')
6 |
7 | 0
8 |
9 | @endslot
10 |
11 | @slot("text")
12 |
13 |
17 |
18 |
34 |
35 | @endslot
36 | @endcomponent
37 |
--------------------------------------------------------------------------------
/resources/views/collectors/cache.blade.php:
--------------------------------------------------------------------------------
1 | content['type']])) {
13 | $types[$entry->content['type']] = 0;
14 | }
15 | $types[$entry->content['type']]++;
16 | }
17 |
18 | ?>
19 | @component('telescope-toolbar::item', ['name' => 'queries', 'link' => true])
20 |
21 | @slot('icon')
22 | @ttIcon('cache')
23 |
24 | {{ $calls }}
25 |
26 | @if (isset($types['missed']))
27 |
28 | ({{ $types['missed'] }} miss)
29 |
30 | @endif
31 | @endslot
32 |
33 | @slot('text')
34 |
35 |
55 |
56 | @endslot
57 |
58 | @endcomponent
--------------------------------------------------------------------------------
/resources/views/collectors/commands.blade.php:
--------------------------------------------------------------------------------
1 |
8 | @component('telescope-toolbar::item', ['name' => 'commands', 'link' => true])
9 |
10 | @slot('icon')
11 | @ttIcon('commands')
12 |
13 | {{ $entries->count() }}
14 |
15 | @endslot
16 |
17 |
18 | @endcomponent
--------------------------------------------------------------------------------
/resources/views/collectors/config.blade.php:
--------------------------------------------------------------------------------
1 | @component('telescope-toolbar::item', ['name' => 'config', 'additional_classes' => 'sf-toolbar-block-right'])
2 |
3 | @slot('icon')
4 |
5 | @ttIcon('laravel')
6 |
7 | {{ app()->version() }}
8 |
9 | @endslot
10 |
11 | @slot("text")
12 |
34 |
35 | @endslot
36 | @endcomponent
37 |
--------------------------------------------------------------------------------
/resources/views/collectors/dumps.blade.php:
--------------------------------------------------------------------------------
1 |
8 | @component('telescope-toolbar::item', ['name' => 'dump', 'link' => route('telescope') . '/dumps', 'status' => 'yellow'])
9 |
10 | @slot('icon')
11 | @ttIcon('dumps')
12 |
13 | {{ $entries->count() }}
14 |
15 | @endslot
16 |
17 | @slot("text")
18 |
19 | @foreach ($entries as $entry)
20 |
26 |
27 | @endforeach
28 |
29 | @endslot
30 |
31 | @endcomponent
--------------------------------------------------------------------------------
/resources/views/collectors/events.blade.php:
--------------------------------------------------------------------------------
1 |
8 | @component('telescope-toolbar::item', ['name' => 'events', 'link' => '#events'])
9 |
10 | @slot('icon')
11 | @ttIcon('events')
12 |
13 | {{ $entries->count() }}
14 |
15 | @endslot
16 |
17 |
18 | @endcomponent
19 |
--------------------------------------------------------------------------------
/resources/views/collectors/exceptions.blade.php:
--------------------------------------------------------------------------------
1 |
8 | @component('telescope-toolbar::item', ['name' => 'exceptions', 'link' => true, 'status' => 'red'])
9 |
10 | @slot('icon')
11 | @ttIcon('exceptions')
12 |
13 | {{ $entries->count() }}
14 |
15 | @endslot
16 |
17 | @slot('text')
18 |
19 |
48 | @endslot
49 |
50 | @endcomponent
--------------------------------------------------------------------------------
/resources/views/collectors/gates.blade.php:
--------------------------------------------------------------------------------
1 |
8 | @component('telescope-toolbar::item', ['name' => 'gates', 'link' => true])
9 |
10 | @slot('icon')
11 | @ttIcon('gates')
12 |
13 | {{ $entries->count() }}
14 |
15 | @endslot
16 |
17 |
18 | @slot('text')
19 |
20 |
40 |
41 | @endslot
42 |
43 | @endcomponent
--------------------------------------------------------------------------------
/resources/views/collectors/jobs.blade.php:
--------------------------------------------------------------------------------
1 |
8 | @component('telescope-toolbar::item', ['name' => 'jobs', 'link' => true])
9 |
10 | @slot('icon')
11 | @ttIcon('jobs')
12 |
13 | {{ $entries->count() }}
14 |
15 | @endslot
16 |
17 | @slot('text')
18 |
19 |
50 | @endslot
51 | @endcomponent
--------------------------------------------------------------------------------
/resources/views/collectors/logs.blade.php:
--------------------------------------------------------------------------------
1 | content['level'];
14 |
15 | if (in_array($level, ['debug', 'info'])) {
16 | $info++;
17 | } elseif (in_array($level, ['notice', 'warning'])) {
18 | $warnings++;
19 | } else {
20 | $errors++;
21 | }
22 |
23 | if (!isset($levels[$entry->content['level']])) {
24 | $levels[$entry->content['level']] = 0;
25 | }
26 | $levels[$entry->content['level']]++;
27 |
28 | $total++;
29 | }
30 |
31 | if ($errors) {
32 | $statusColor = 'red';
33 | } elseif ($warnings) {
34 | $statusColor = 'yellow';
35 | } else {
36 | $statusColor = null;
37 | }
38 |
39 | ?>
40 | @component('telescope-toolbar::item', ['name' => 'logs ', 'link' => true, 'status' => $statusColor])
41 |
42 | @slot('icon')
43 | @ttIcon('logs')
44 |
45 | {{ $total }}
46 |
47 | @endslot
48 |
49 | @slot('text')
50 |
51 |
84 | @endslot
85 |
86 |
87 | @endcomponent
--------------------------------------------------------------------------------
/resources/views/collectors/mail.blade.php:
--------------------------------------------------------------------------------
1 |
8 | @component('telescope-toolbar::item', ['name' => 'mail', 'link' => true])
9 |
10 | @slot('icon')
11 | @ttIcon('mail')
12 |
13 | {{ $entries->count() }}
14 |
15 | @endslot
16 |
17 | @slot('text')
18 |
19 |
53 |
54 | @endslot
55 |
56 | @endcomponent
--------------------------------------------------------------------------------
/resources/views/collectors/memory.blade.php:
--------------------------------------------------------------------------------
1 | first()->content;
4 |
5 | $memory = $data['memory'] ?? null;
6 | if (!$memory) {
7 | return;
8 | }
9 |
10 | $statusColor = null;
11 | if ($memory > 50) {
12 | $statusColor = 'yellow';
13 | } elseif ($memory > 10) {
14 | $memory = round($memory);
15 | }
16 | ?>
17 |
18 | @component('telescope-toolbar::item', ['name' => 'memory', 'link' => true])
19 |
20 | @slot('icon')
21 |
22 | @ttIcon('memory')
23 |
24 | {{ $memory }}
25 | MB
26 | @endslot
27 |
28 | @slot('text')
29 |
30 |
31 | Peak memory usage
32 | {{ $data['memory'] }} MB
33 |
34 |
35 | @endslot
36 |
37 |
38 | @endcomponent
--------------------------------------------------------------------------------
/resources/views/collectors/models.blade.php:
--------------------------------------------------------------------------------
1 |
8 | @component('telescope-toolbar::item', ['name' => 'models', 'link' => true])
9 |
10 | @slot('icon')
11 | @ttIcon('models')
12 |
13 | {{ $entries->count() }}
14 |
15 | @endslot
16 |
17 | @slot('text')
18 |
19 |
47 |
48 | @endslot
49 |
50 | @endcomponent
--------------------------------------------------------------------------------
/resources/views/collectors/notifications.blade.php:
--------------------------------------------------------------------------------
1 |
8 | @component('telescope-toolbar::item', ['name' => 'notifications', 'link' => true])
9 |
10 | @slot('icon')
11 | @ttIcon('notifications')
12 |
13 | {{ $entries->count() }}
14 |
15 | @endslot
16 |
17 | @slot('text')
18 |
19 |
61 |
62 | @endslot
63 |
64 | @endcomponent
--------------------------------------------------------------------------------
/resources/views/collectors/queries.blade.php:
--------------------------------------------------------------------------------
1 | content['slow'] ?? false) {
13 | $num_slow++;
14 | }
15 | $query_time += (float) str_replace(',', '', $query->content['time']) ?? 0;
16 | $queries[$query->content['hash'] ?? $query->content['sql']] = $query->content['sql'];
17 | }
18 |
19 | $num_duplicated = $num_queries - count($queries);
20 | if ($num_queries > 0 && $num_duplicated > $num_queries *.75) {
21 | $statusColor = 'yellow';
22 | } else {
23 | $statusColor = null;
24 | }
25 | ?>
26 | @component('telescope-toolbar::item', ['name' => 'queries', 'link' => '#queries', 'status' => $statusColor, 'additional_classes' => 'sf-toolbar-block-fullwidth'])
27 |
28 | @slot('icon')
29 | @ttIcon('queries')
30 |
31 | {{ $num_queries }}
32 |
33 |
34 | in
35 | {{ round($query_time) }}
36 | ms
37 |
38 |
39 | @endslot
40 |
41 | @slot('text')
42 |
43 |
68 |
69 | @endslot
70 |
71 | @endcomponent
72 |
--------------------------------------------------------------------------------
/resources/views/collectors/redis.blade.php:
--------------------------------------------------------------------------------
1 |
8 | @component('telescope-toolbar::item', ['name' => 'redis', 'link' => true])
9 |
10 | @slot('icon')
11 | @ttIcon('redis')
12 |
13 | {{ $entries->count() }}
14 |
15 | @endslot
16 |
17 |
18 | @endcomponent
--------------------------------------------------------------------------------
/resources/views/collectors/request.blade.php:
--------------------------------------------------------------------------------
1 | first()->content;
4 |
5 | $statusCode = $data['response_status'];
6 | if ($statusCode > 400) {
7 | $statusColor = 'red';
8 | } elseif ($statusCode > 300) {
9 | $statusColor = 'yellow';
10 | } else {
11 | $statusColor = 'green';
12 | }
13 | ?>
14 |
15 | @component('telescope-toolbar::item', ['name' => 'request', 'link' => true])
16 |
17 | @slot('icon')
18 | {{ $statusCode }}
19 | {{ $data['method'] }} {{ $data['uri'] }}
20 | @endslot
21 |
22 | @slot('text')
23 |
72 | @endslot
73 |
74 | @endcomponent
75 |
--------------------------------------------------------------------------------
/resources/views/collectors/session.blade.php:
--------------------------------------------------------------------------------
1 | first()->content;
4 |
5 | $dumper = new \Symfony\Component\VarDumper\Dumper\HtmlDumper();
6 | $varCloner = new \Symfony\Component\VarDumper\Cloner\VarCloner();
7 |
8 | ?>
9 |
10 | @component('telescope-toolbar::item', ['name' => 'dump', 'link' => false])
11 |
12 | @slot('icon')
13 | @ttIcon('session')
14 | Session
15 | @endslot
16 |
17 | @slot('text')
18 |
19 | @if(isset($data['session']))
20 |
27 | @endif
28 |
29 |
30 | @endslot
31 |
32 | @endcomponent
33 |
--------------------------------------------------------------------------------
/resources/views/collectors/time.blade.php:
--------------------------------------------------------------------------------
1 | first()->content;
4 |
5 | ?>
6 |
7 | @component('telescope-toolbar::item', ['name' => 'time', 'link' => true])
8 |
9 | @slot('icon')
10 |
11 | @ttIcon('time')
12 |
13 | {{ $data['duration'] }}
14 | ms
15 | @endslot
16 |
17 | @slot('text')
18 |
19 |
20 | Request Duration
21 | {{ $data['duration'] }} ms
22 |
23 |
24 |
25 | Peak memory usage
26 | {{ $data['memory'] }} MB
27 |
28 |
29 | @endslot
30 |
31 | @endcomponent
32 |
--------------------------------------------------------------------------------
/resources/views/collectors/user.blade.php:
--------------------------------------------------------------------------------
1 | first()->content['user'] ?? [];
4 |
5 | ?>
6 |
7 | @component('telescope-toolbar::item', ['name' => 'user', 'link' => true])
8 |
9 | @slot('icon')
10 |
11 | @ttIcon('user')
12 |
13 | {{ $data['email'] ?? 'n/a' }}
14 | @endslot
15 |
16 | @slot('text')
17 | @if($data)
18 |
28 | @endif
29 | @endslot
30 |
31 | @endcomponent
--------------------------------------------------------------------------------
/resources/views/collectors/views.blade.php:
--------------------------------------------------------------------------------
1 | content['name'];
10 |
11 | if (isset($views[$name])) {
12 | $views[$name]['num']++;
13 | }
14 |
15 | $views[$name] = [
16 | 'id' => $entry->id,
17 | 'name' => $name,
18 | 'path' => $entry->content['path'] ?? '',
19 | 'num' => 1,
20 | ];
21 | $total++;
22 | }
23 |
24 | $views = collect($views)->reverse();
25 |
26 | ?>
27 | @component('telescope-toolbar::item', ['name' => 'views', 'link' => '#views'])
28 |
29 | @slot('icon')
30 | @ttIcon('views')
31 |
32 | {{ $total }}
33 |
34 | @endslot
35 |
36 | @slot('text')
37 |
38 |
71 | @endslot
72 |
73 |
74 | @endcomponent
75 |
--------------------------------------------------------------------------------
/resources/views/head.blade.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | @if ($lightMode)
6 |
7 | @endif
8 |
9 |
16 |
17 |
--------------------------------------------------------------------------------
/resources/views/item.blade.php:
--------------------------------------------------------------------------------
1 |
17 |
--------------------------------------------------------------------------------
/resources/views/toolbar.blade.php:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
10 |
11 |
12 |
13 |
14 |
15 | @foreach (config('telescope-toolbar.collectors') as $type => $templates)
16 | @if(isset($entries[$type]))
17 | @foreach($templates as $template)
18 | @include($template, ['entries' => $entries[$type]])
19 | @endforeach
20 | @endif
21 | @endforeach
22 |
23 | @include("telescope-toolbar::collectors.config")
24 | @include("telescope-toolbar::collectors.ajax")
25 |
26 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/resources/views/widget.blade.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
10 |
--------------------------------------------------------------------------------
/src/Http/Controllers/ToolbarController.php:
--------------------------------------------------------------------------------
1 | entriesRepository = $entriesRepository;
24 |
25 | $this->middleware(function($request, $next) {
26 | Telescope::stopRecording();
27 |
28 | if ($request->hasSession()) {
29 | $request->session()->reflash();
30 | }
31 |
32 | return $next($request);
33 | });
34 | }
35 |
36 | public function render($token)
37 | {
38 | $this->prepareBlade($token);
39 |
40 | $options = $this->findBatchOptions($token);
41 |
42 | $entries = $this->entriesRepository->get(null, $options)->groupBy('type');
43 |
44 | return View::make('telescope-toolbar::toolbar', [
45 | 'entries' => $entries,
46 | ]);
47 | }
48 |
49 | public function show($token)
50 | {
51 | $options = $this->findBatchOptions($token);
52 |
53 | $request = $this->entriesRepository->get('request', $options)->first();
54 |
55 | return redirect(route('telescope') . '/requests/' . $request->id);
56 | }
57 |
58 | public function baseJs()
59 | {
60 | $content = View::make('telescope-toolbar::base_js', [
61 | 'excluded_ajax_paths' => config('telescope-toolbar.excluded_ajax_paths', '^/_tt'),
62 | ])->render();
63 |
64 | $content = $this->stripSurroundingTags($content);
65 |
66 | return response($content, 200, [
67 | 'Content-Type' => 'text/javascript',
68 | ])->setClientTtl(31536000);
69 | }
70 |
71 | public function styling(Request $request)
72 | {
73 | if ($request->get('lightMode')) {
74 | $files = ['theme_light.css'];
75 | } else {
76 | $files = [
77 | 'base.css',
78 | 'custom.css',
79 | ];
80 | }
81 |
82 | $content = '';
83 | foreach ($files as $file) {
84 | $content .= File::get(__DIR__ . '/../../../resources/css/' . $file) . "\n";
85 | }
86 |
87 | return response($content, 200, [
88 | 'Content-Type' => 'text/css',
89 | ])->setClientTtl(31536000);
90 | }
91 |
92 | /**
93 | * Strip