├── .gitignore ├── .github ├── FUNDING.yml └── workflows │ └── tests.yaml ├── screenshots.jpg ├── resources ├── css │ ├── app.css │ ├── components │ │ ├── output.css │ │ ├── layout.css │ │ ├── codemirror.css │ │ └── github-markdown.css │ └── bootstrap.css ├── js │ ├── components │ │ ├── SoarOutput.vue │ │ ├── Soar.vue │ │ └── SoarInput.vue │ └── app.js └── views │ └── web-soar.blade.php ├── public ├── mix-manifest.json └── github.css ├── postcss.config.js ├── phpstan.neon ├── webpack.mix.js ├── .vscode └── settings.json ├── LICENSE.md ├── src ├── Console │ ├── PublishCommand.php │ ├── InstallCommand.php │ └── Concerns │ │ └── InstallSoar.php ├── Http │ ├── Middleware │ │ └── Authorize.php │ └── Controllers │ │ └── WebSoarController.php └── WebSoarServiceProvider.php ├── config └── web-soar.php ├── package.json ├── composer.json ├── README.md ├── .php-cs-fixer.php └── tailwind.js /.gitignore: -------------------------------------------------------------------------------- 1 | vendor 2 | node_modules 3 | composer.lock 4 | package-lock.json -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: huangdijia 2 | custom: https://hdj.me/sponsors/ 3 | -------------------------------------------------------------------------------- /screenshots.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/huangdijia/laravel-web-soar/HEAD/screenshots.jpg -------------------------------------------------------------------------------- /resources/css/app.css: -------------------------------------------------------------------------------- 1 | @charset 'UTF-8'; 2 | 3 | @import './bootstrap.css'; 4 | @import './components/*'; 5 | 6 | @tailwind utilities; 7 | -------------------------------------------------------------------------------- /public/mix-manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "/app.js": "/app.js?id=20522025b6ec7bcf5abc", 3 | "/app.css": "/app.css?id=6f5e91558c710a495e26" 4 | } 5 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: [ 3 | require('postcss-easy-import')(), 4 | require('tailwindcss')('./tailwind.js'), 5 | require('postcss-nesting'), 6 | ], 7 | }; 8 | -------------------------------------------------------------------------------- /resources/js/components/SoarOutput.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 13 | -------------------------------------------------------------------------------- /resources/js/app.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import axios from 'axios'; 3 | 4 | let token = document.head.querySelector('meta[name="csrf-token"]'); 5 | 6 | if (token) { 7 | axios.defaults.headers.common['X-CSRF-TOKEN'] = token.content; 8 | } 9 | 10 | Vue.component('soar', require('./components/Soar.vue')); 11 | 12 | new Vue({ 13 | el: '#web-soar', 14 | }); 15 | -------------------------------------------------------------------------------- /phpstan.neon: -------------------------------------------------------------------------------- 1 | # Magic behaviour with __get, __set, __call and __callStatic is not exactly static analyser-friendly :) 2 | # Fortunately, You can ingore it by the following config. 3 | # 4 | # vendor/bin/phpstan analyse app --memory-limit 200M -l 0 5 | # 6 | parameters: 7 | reportUnmatchedIgnoredErrors: false 8 | ignoreErrors: 9 | # - '#Unsafe usage of new static\(\)#' 10 | -------------------------------------------------------------------------------- /resources/css/components/output.css: -------------------------------------------------------------------------------- 1 | .output { 2 | @apply bg-gutter; 3 | @apply px-8 py-4; 4 | @apply overflow-scroll; 5 | 6 | & warning { 7 | @apply block; 8 | @apply px-2; 9 | @apply bg-warning; 10 | @apply whitespace-normal; 11 | } 12 | 13 | & error { 14 | @apply block; 15 | @apply px-2; 16 | @apply bg-error; 17 | @apply whitespace-normal; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /webpack.mix.js: -------------------------------------------------------------------------------- 1 | const mix = require('laravel-mix'); 2 | require('laravel-mix-purgecss'); 3 | 4 | mix 5 | .setPublicPath('public') 6 | .postCss('resources/css/app.css', 'public') 7 | .purgeCss({ 8 | whitelistPatterns: [/CodeMirror/, /cm/, /^theme-/], 9 | }) 10 | .js('resources/js/app.js', 'public') 11 | .version() 12 | .options({ 13 | // Our PostCSS plugins are defined in a standard `postcss.config.js` 14 | // file, which we'll read for plugins. 15 | postCss: require('./postcss.config').plugins, 16 | }) 17 | .copy('public', '../web-soar-app/public/vendor/web-soar'); 18 | -------------------------------------------------------------------------------- /resources/css/components/layout.css: -------------------------------------------------------------------------------- 1 | .layout { 2 | @apply fixed; 3 | @apply h-screen w-screen; 4 | display: grid; 5 | grid-template-columns: 100vw; 6 | } 7 | 8 | .layout-gutter { 9 | @apply flex items-center justify-center; 10 | @apply bg-line; 11 | height: auto; 12 | cursor: ns-resize; 13 | 14 | &:after { 15 | @apply text-dimmed; 16 | @apply text-center; 17 | content: '•••'; 18 | font-size: 10px; 19 | line-height: 6px; 20 | word-break: break-all; 21 | } 22 | } 23 | 24 | .layout-columns { 25 | &.layout { 26 | grid-template-rows: 100vh; 27 | } 28 | 29 | & .layout-gutter { 30 | cursor: ew-resize; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "php.suggest.basic": false, 3 | "[php]": { 4 | "editor.formatOnSave": true, 5 | "editor.defaultFormatter": "junstyle.php-cs-fixer", 6 | }, 7 | "php-cs-fixer.executablePath": "php-cs-fixer", 8 | "php-cs-fixer.executablePathWindows": "php-cs-fixer", 9 | "php-cs-fixer.onsave": true, 10 | "php-cs-fixer.rules": "@PSR2", 11 | "php-cs-fixer.config": ".php_cs;.php_cs.dist", 12 | "php-cs-fixer.allowRisky": false, 13 | "php-cs-fixer.pathMode": "override", 14 | "php-cs-fixer.exclude": [], 15 | "php-cs-fixer.autoFixByBracket": true, 16 | "php-cs-fixer.autoFixBySemicolon": false, 17 | "php-cs-fixer.formatHtml": true, 18 | "php-cs-fixer.documentFormattingProvider": true, 19 | } -------------------------------------------------------------------------------- /resources/views/web-soar.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Web Soar 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 |
19 | 20 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /.github/workflows/tests.yaml: -------------------------------------------------------------------------------- 1 | name: tests 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | ci: 7 | name: Test on PHP ${{ matrix.php-version }} 8 | runs-on: "${{ matrix.os }}" 9 | strategy: 10 | matrix: 11 | os: [ubuntu-latest] 12 | php-version: ['8.0', '8.1'] 13 | max-parallel: 4 14 | steps: 15 | - name: Checkout 16 | uses: actions/checkout@v2 17 | - name: Setup PHP 18 | uses: shivammathur/setup-php@v2 19 | with: 20 | php-version: ${{ matrix.php-version }} 21 | extensions: redis, pdo, pdo_mysql, bcmath 22 | tools: phpize 23 | coverage: none 24 | - name: Setup Packages 25 | run: composer update -o 26 | - name: Run Analyse 27 | run: | 28 | composer analyse src 29 | # - name: Run Test Cases 30 | # run: | 31 | # composer test -------------------------------------------------------------------------------- /public/github.css: -------------------------------------------------------------------------------- 1 | .CodeMirror-lines { 2 | padding: 8px 0; 3 | } 4 | 5 | .CodeMirror-gutters { 6 | box-shadow: 1px 0 2px 0 rgba(0, 0, 0, .5); 7 | -webkit-box-shadow: 1px 0 2px 0 rgba(0, 0, 0, .5); 8 | background-color: #f8f8ff; 9 | padding-right: 10px; 10 | z-index: 3; 11 | border: none; 12 | } 13 | 14 | div.CodeMirror-cursor { 15 | border-left: 3px solid #000; 16 | } 17 | 18 | .CodeMirror-activeline-background { 19 | background: #00000012; 20 | } 21 | 22 | .CodeMirror-selected { 23 | background: #bcd5fa; 24 | } 25 | 26 | .cm-comment { 27 | font-style: italic; 28 | color: #998; 29 | } 30 | 31 | .cm-number { 32 | color: null; 33 | } 34 | 35 | .cm-atom { 36 | color: null; 37 | } 38 | 39 | .cm-string { 40 | color: #e020a3; 41 | } 42 | 43 | .cm-variable-2 { 44 | color: #099; 45 | } 46 | 47 | .cm-property { 48 | color: null; 49 | } 50 | 51 | .cm-keyword { 52 | color: null; 53 | } 54 | 55 | .cm-operator { 56 | color: null; 57 | } 58 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) Spatie bvba 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/Console/PublishCommand.php: -------------------------------------------------------------------------------- 1 | callSilent('vendor:publish', [ 30 | '--tag' => 'view', 31 | '--force' => (bool) $this->option('force'), 32 | ]); 33 | 34 | $this->callSilent('vendor:publish', [ 35 | '--tag' => 'web-soar-assets', 36 | '--force' => (bool) $this->option('force'), 37 | ]); 38 | 39 | $this->info('Web soar published successfully.'); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /config/web-soar.php: -------------------------------------------------------------------------------- 1 | '/soar', 13 | 'theme' => 'auto', 14 | 'enabled' => env('SOAR_ENABLED', env('APP_ENV') === 'local'), 15 | 'hint' => [ 16 | 'enabled' => env('SOAR_HINT_ENABLED', true), 17 | 'connection' => env('SOAR_HINT_CONNECTION', 'mysql'), 18 | 'excludes' => [], 19 | ], 20 | 21 | '-soar-path' => env('SOAR_PATH', base_path('vendor/bin/soar')), 22 | '-test-dsn' => [ 23 | 'host' => env('SOAR_TEST_DSN_HOST', '127.0.0.1'), 24 | 'port' => env('SOAR_TEST_DSN_PORT', '3306'), 25 | 'dbname' => env('SOAR_TEST_DSN_DBNAME', 'database'), 26 | 'username' => env('SOAR_TEST_DSN_USER', 'root'), 27 | 'password' => env('SOAR_TEST_DSN_PASSWORD', ''), 28 | ], 29 | '-log-output' => env('SOAR_LOG_OUTPUT', storage_path('logs/soar.log')), 30 | '-report-type' => env('SOAR_REPORT_TYPE', 'markdown'), 31 | ]; 32 | -------------------------------------------------------------------------------- /src/Console/InstallCommand.php: -------------------------------------------------------------------------------- 1 | comment('Publishing Web Soar Assets...'); 33 | 34 | if (! $this->isSoarInstalled()) { 35 | $this->downloadSoarBinary(); 36 | } 37 | 38 | $this->callSilent('vendor:publish', [ 39 | '--tag' => 'config', 40 | '--force' => (bool) $this->option('force'), 41 | ]); 42 | 43 | $this->info('Web soar installed successfully.'); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "scripts": { 4 | "dev": "npm run development", 5 | "development": "cross-env NODE_ENV=development node_modules/webpack/bin/webpack.js --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js", 6 | "watch": "npm run development -- --watch", 7 | "watch-poll": "npm run watch -- --watch-poll", 8 | "hot": "cross-env NODE_ENV=development node_modules/webpack-dev-server/bin/webpack-dev-server.js --inline --hot --config=node_modules/laravel-mix/setup/webpack.config.js", 9 | "prod": "npm run production", 10 | "production": "cross-env NODE_ENV=production node_modules/webpack/bin/webpack.js --no-progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js", 11 | "format": "prettier --write 'resources/**/*.{css,js,vue}'" 12 | }, 13 | "dependencies": { 14 | "axios": ">=0.21.1", 15 | "bootstrap": "^4.0.0", 16 | "codemirror": "^5.42.0", 17 | "cross-env": "^5.1", 18 | "jquery": "^3.2", 19 | "laravel-mix": "^2.0", 20 | "laravel-mix-purgecss": "^3.0.0", 21 | "lodash": "^4.17.4", 22 | "postcss-easy-import": "^2.1.0", 23 | "postcss-nesting": "^7.0.0", 24 | "prettier": "^1.15.3", 25 | "split-grid": "^1.0.9", 26 | "tailwindcss": "^0.7.2", 27 | "vue": "^2.5.7" 28 | } 29 | } -------------------------------------------------------------------------------- /src/Http/Middleware/Authorize.php: -------------------------------------------------------------------------------- 1 | allowedToUseSoar()) { 34 | abort(403); 35 | } 36 | 37 | return $next($request); 38 | } 39 | 40 | /** 41 | * @throws BindingResolutionException 42 | */ 43 | protected function allowedToUseSoar(): bool 44 | { 45 | if (! config('web-soar.enabled')) { 46 | return false; 47 | } 48 | 49 | return Gate::check('viewWebSoar'); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /resources/css/components/codemirror.css: -------------------------------------------------------------------------------- 1 | .CodeMirror.cm-s-tinker { 2 | @apply h-full; 3 | @apply font-code; 4 | @apply leading-normal; 5 | @apply bg-background; 6 | color: inherit; 7 | 8 | & .CodeMirror-cursor { 9 | border-color: var(--color-text); 10 | } 11 | 12 | & .CodeMirror-lines { 13 | @apply py-4; 14 | } 15 | 16 | & .CodeMirror-gutters { 17 | @apply bg-gutter; 18 | @apply pl-2; 19 | @apply border-r-2 border-line; 20 | } 21 | 22 | & .CodeMirror-linenumber { 23 | @apply text-dimmed; 24 | @apply text-base; 25 | } 26 | 27 | & .CodeMirror pre { 28 | @apply font-code; 29 | @apply whitespace-no-wrap; 30 | } 31 | 32 | & .CodeMirror-selected { 33 | background: rgba(150, 120, 200, 0.2); 34 | } 35 | 36 | & .CodeMirror-focused .CodeMirror-selected { 37 | background: rgba(150, 120, 200, 0.3); 38 | } 39 | 40 | & .cm-comment { 41 | @apply text-dimmed; 42 | font-style: italic; 43 | } 44 | 45 | & .cm-number { 46 | @apply text-number; 47 | } 48 | 49 | & .cm-string { 50 | @apply text-string; 51 | } 52 | 53 | & .cm-variable-2 { 54 | @apply text-variable; 55 | } 56 | 57 | & .cm-operator { 58 | @apply text-operator; 59 | } 60 | 61 | & .cm-keyword { 62 | @apply text-keyword; 63 | @apply font-bold; 64 | color: #708; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "huangdijia/laravel-web-soar", 3 | "description": "Artisan soar in your browser", 4 | "keywords": [ 5 | "huangdijia", 6 | "web-soar", 7 | "laravel", 8 | "soar", 9 | "debug", 10 | "development" 11 | ], 12 | "homepage": "https://github.com/huangdijia/laravel-web-soar", 13 | "license": "MIT", 14 | "authors": [{ 15 | "name": "Huangdijia", 16 | "email": "huangdijia@gmail.com", 17 | "homepage": "https://hdj.me", 18 | "role": "Developer" 19 | }], 20 | "require": { 21 | "php": ">=8.0", 22 | "guanguans/soar-php": "^2.0", 23 | "illuminate/cookie": "^9.0", 24 | "illuminate/session": "^9.0", 25 | "illuminate/support": "^9.0", 26 | "symfony/process": "^6.0" 27 | }, 28 | "require-dev": { 29 | "laravel/framework": "^9.0", 30 | "friendsofphp/php-cs-fixer": "^3.0", 31 | "phpstan/phpstan": "^1.0" 32 | }, 33 | "autoload": { 34 | "psr-4": { 35 | "Huangdijia\\WebSoar\\": "src" 36 | } 37 | }, 38 | "config": { 39 | "sort-packages": true 40 | }, 41 | "extra": { 42 | "laravel": { 43 | "providers": [ 44 | "Huangdijia\\WebSoar\\WebSoarServiceProvider" 45 | ] 46 | } 47 | }, 48 | "scripts": { 49 | "cs-fix": "php-cs-fixer fix $1", 50 | "analyse": "phpstan analyse --memory-limit 300M -l 0 -c phpstan.neon ./src ./config" 51 | } 52 | } -------------------------------------------------------------------------------- /src/Console/Concerns/InstallSoar.php: -------------------------------------------------------------------------------- 1 | urlFormat, $this->soarVersion, Str::lower(PHP_OS_FAMILY)); 55 | 56 | tap(new Process(array_filter([ 57 | 'wget', 58 | $url, 59 | '-O', 60 | $soarPath, 61 | ]), base_path(), null, null, null))->mustRun(function ($type, $buffer) { 62 | $this->output->write($buffer); 63 | }); 64 | 65 | chmod(base_path($soarPath), 755); 66 | 67 | $this->line(''); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /resources/css/bootstrap.css: -------------------------------------------------------------------------------- 1 | @tailwind preflight; 2 | 3 | :root { 4 | --color-text: #000; 5 | --color-warning: #ffffcf; 6 | --color-error: #dd1144; 7 | --color-keyword: #000; 8 | --color-operator: #000; 9 | --color-variable: #a09f91; 10 | --color-number: #009999; 11 | --color-string: #dd1144; 12 | --color-dimmed: #a09f91; 13 | --color-background: #fff; 14 | --color-line: #f3f7f9; 15 | --color-gutter: #fafdfc; 16 | } 17 | 18 | :root.theme-dark { 19 | --color-text: #fff; 20 | --color-warning: #eab21c; 21 | --color-error: #dd1144; 22 | --color-keyword: #fff; 23 | --color-operator: #dd1144; 24 | --color-variable: #a6e22e; 25 | --color-number: #ae81ff; 26 | --color-string: #e6db74; 27 | --color-dimmed: #706b5c; 28 | --color-background: #272822; 29 | --color-line: #3b3d34; 30 | --color-gutter: #2f3129; 31 | } 32 | 33 | @media (prefers-color-scheme: dark) { 34 | :root.theme-auto { 35 | --color-text: #fff; 36 | --color-warning: #eab21c; 37 | --color-error: #dd1144; 38 | --color-keyword: #fff; 39 | --color-operator: #dd1144; 40 | --color-variable: #a6e22e; 41 | --color-number: #ae81ff; 42 | --color-string: #e6db74; 43 | --color-dimmed: #706b5c; 44 | --color-background: #272822; 45 | --color-line: #3b3d34; 46 | --color-gutter: #2f3129; 47 | } 48 | } 49 | 50 | * { 51 | position: relative; 52 | box-sizing: inherit; 53 | margin: 0; 54 | padding: 0; 55 | color: inherit; 56 | font: inherit; 57 | &:after, 58 | &:before { 59 | box-sizing: inherit; 60 | } 61 | } 62 | 63 | html { 64 | @apply bg-background; 65 | @apply font-code; 66 | @apply leading-normal; 67 | @apply text-text; 68 | box-sizing: border-box; 69 | font-size: 14px; 70 | } 71 | 72 | h1 { 73 | font-size: inherit; 74 | } 75 | 76 | a { 77 | text-decoration: none; 78 | } 79 | 80 | ol, 81 | ul { 82 | list-style: none; 83 | } 84 | 85 | img, 86 | svg { 87 | display: block; 88 | } 89 | 90 | pre, 91 | code { 92 | @apply font-code; 93 | } 94 | 95 | ::selection { 96 | background-color: rgba(150, 120, 200, 0.3); 97 | } 98 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # laravel-web-soar 2 | 3 | [![Latest Test](https://github.com/huangdijia/laravel-web-soar/workflows/tests/badge.svg)](https://github.com/huangdijia/laravel-web-soar/actions) 4 | [![Latest Stable Version](https://poser.pugx.org/huangdijia/laravel-web-soar/version.png)](https://packagist.org/packages/huangdijia/laravel-web-soar) 5 | [![Total Downloads](https://poser.pugx.org/huangdijia/laravel-web-soar/d/total.png)](https://packagist.org/packages/huangdijia/laravel-web-soar) 6 | [![GitHub license](https://img.shields.io/github/license/huangdijia/laravel-web-soar)](https://github.com/huangdijia/laravel-web-soar) 7 | 8 | ![screenshots](./screenshots.jpg) 9 | 10 | ## Installation 11 | 12 | ~~~bash 13 | composer require huangdijia/laravel-web-soar --dev 14 | ~~~ 15 | 16 | ### Publish 17 | 18 | ~~~bash 19 | # php artisan vendor:publish --provider="Huangdijia\WebSoar\WebSoarServiceProvider" 20 | php artisan web-soar:install 21 | php artisan web-soar:publish 22 | ~~~ 23 | 24 | ### Download soar 25 | 26 | ~~~bash 27 | # macOS 28 | wget https://github.com/XiaoMi/soar/releases/download/0.11.0/soar.darwin-amd64 -O vendor/bin/soar 29 | # linux 30 | wget https://github.com/XiaoMi/soar/releases/download/0.11.0/soar.linux-amd64 -O vendor/bin/soar 31 | # windows 32 | wget https://github.com/XiaoMi/soar/releases/download/0.11.0/soar.windows-amd64 -O vendor/bin/soar 33 | # authorization 34 | chmod +x vendor/bin/soar 35 | ~~~ 36 | 37 | ## Configure 38 | 39 | ### Env 40 | 41 | ~~~env 42 | SOAR_ENABLED=true 43 | SOAR_HINT_ENABLED=true 44 | SOAR_HINT_CONNECTION=mysql 45 | SOAR_PATH=/usr/local/bin/soar # linux 46 | SOAR_TEST_DSN_HOST=127.0.0.1 47 | SOAR_TEST_DSN_PORT=3306 48 | SOAR_TEST_DSN_DBNAME=yourdb 49 | SOAR_TEST_DSN_USER=root 50 | SOAR_TEST_DSN_PASSWORD= 51 | SOAR_LOG_OUTPUT=/tmp/soar.log 52 | SOAR_REPORT_TYPE=markdown 53 | ~~~ 54 | 55 | ### Gate 56 | 57 | ~~~php 58 | // AuthServiceProvider 59 | Gate::define('viewWebSoar', function($user = null) { 60 | return app()->environment('local', 'dev'); 61 | }); 62 | ~~~ 63 | 64 | ## Run 65 | 66 | * [http://youdomain.com/soar](http://youdomain.com/soar) 67 | 68 | ## Thanks 69 | 70 | * [soar](https://github.com/XiaoMi/soar) 71 | * [soar-php](https://github.com/guanguans/soar-php) 72 | * [laravel-web-tinker](https://github.com/spatie/laravel-web-tinker) 73 | -------------------------------------------------------------------------------- /src/Http/Controllers/WebSoarController.php: -------------------------------------------------------------------------------- 1 | select('SHOW TABLES'); 38 | 39 | return collect(array_map('reset', $tables)) 40 | ->reject(function ($table) { 41 | return in_array($table, config('web-soar.hint.excludes', [])); 42 | }) 43 | ->mapWithKeys(function ($table) { 44 | return [$table => Schema::getColumnListing($table)]; 45 | }) 46 | ->all(); 47 | }); 48 | } 49 | 50 | return view('web-soar::web-soar', [ 51 | 'path' => config('web-soar.path'), 52 | 'tables' => $tables, 53 | ]); 54 | } 55 | 56 | /** 57 | * @throws InvalidArgumentException 58 | * @throws RuntimeException 59 | * @return string 60 | */ 61 | public function execute(Request $request, Soar $soar) 62 | { 63 | $validated = $request->validate([ 64 | 'code' => 'required', 65 | ]); 66 | $pattern = '/^\s*explain\s*/i'; 67 | 68 | if (preg_match($pattern, $validated['code'])) { 69 | $validated['code'] = preg_replace($pattern, '', $validated['code']); 70 | $body = $soar->explain($validated['code'], 'md'); 71 | } else { 72 | $body = $soar->score($validated['code']); 73 | } 74 | 75 | return '
' . Markdown::parse($body) . '
'; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /resources/js/components/Soar.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 94 | -------------------------------------------------------------------------------- /resources/js/components/SoarInput.vue: -------------------------------------------------------------------------------- 1 |