diff --git a/.env.docker-compose.example b/.env.docker-compose.example new file mode 100644 index 0000000..4ce825d --- /dev/null +++ b/.env.docker-compose.example @@ -0,0 +1,17 @@ +# Web 映射到宿主机的端口 +PORT=666 + +# MySQL 映射到宿主机的端口 +MYSQL_PORT=3306 + +# MySQL 数据库名 +MYSQL_DATABASE=code6 + +# MySQL 用户名(请使用非 root 用户) +MYSQL_USER= + +# MySQL 密码 +MYSQL_PASSWORD= + +# MySQL 挂载到宿主机的目录 +MYSQL_VOLUME_PATH= diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md new file mode 100644 index 0000000..d3c98c5 --- /dev/null +++ b/.github/ISSUE_TEMPLATE.md @@ -0,0 +1,15 @@ +## 安装方式 +Docker-Compose 安装、Docker 安装 或 源码安装 + +## 问题描述 +请描述遇到的问题.. + +## 运行环境 +``` +请复制 php doctor.php 运行结果 +``` + +## 报错日志 +``` +日志目录:storage/logs +``` diff --git a/.gitignore b/.gitignore index f5b7047..8f5f877 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,5 @@ Homestead.yaml npm-debug.log yarn-error.log .idea +.DS_Store +.env.docker-compose diff --git a/Dockerfile b/Dockerfile index 2ec0286..1e9bdec 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,50 +2,46 @@ FROM php:7.4-apache EXPOSE 80 -ENV MYSQL_HOST="172.17.0.1" +ENV MYSQL_HOST="mysql" ENV MYSQL_PORT="3306" ENV MYSQL_DATABASE="code6" ENV MYSQL_USERNAME="" ENV MYSQL_PASSWORD="" +ENV APACHE_DOCUMENT_ROOT=/var/www/html/public # 复制代码 COPY . /var/www/html +COPY docker-entrypoint.sh docker-entrypoint.sh WORKDIR /var/www/html # 使用阿里镜像并安装包 -RUN mv /etc/apt/sources.list /etc/apt/sources.list.bak -RUN echo 'deb http://mirrors.aliyun.com/debian buster main'>> /etc/apt/sources.list -RUN echo 'deb http://mirrors.aliyun.com/debian buster-updates main'>> /etc/apt/sources.list -RUN apt-get update && apt-get install -y --allow-downgrades zip cron vim zlib1g=1:1.2.11.dfsg-1 zlib1g-dev libpng-dev -RUN rm -rf /var/lib/apt/lists/* && apt-get clean - +RUN mv /etc/apt/sources.list /etc/apt/sources.list.bak;\ +echo 'deb http://mirrors.aliyun.com/debian buster main'>> /etc/apt/sources.list;\ +echo 'deb http://mirrors.aliyun.com/debian buster-updates main'>> /etc/apt/sources.list;\ +apt-get update;\ +apt-get install -y --allow-downgrades zip cron vim zlib1g=1:1.2.11.dfsg-1+deb10u1 zlib1g-dev libpng-dev;\ +rm -rf /var/lib/apt/lists/*;\ # 安装 PHP 扩展 -RUN docker-php-ext-install pdo_mysql -RUN docker-php-ext-install gd - +docker-php-ext-install pdo_mysql;\ +docker-php-ext-install gd;\ # 配置 Web 路径 -ENV APACHE_DOCUMENT_ROOT=/var/www/html/public -RUN sed -ri -e "s!/var/www/html!${APACHE_DOCUMENT_ROOT}!g" /etc/apache2/sites-available/*.conf -RUN sed -ri -e "s!/var/www/!${APACHE_DOCUMENT_ROOT}!g" /etc/apache2/apache2.conf /etc/apache2/conf-available/*.conf - +sed -ri -e "s!/var/www/html!${APACHE_DOCUMENT_ROOT}!g" /etc/apache2/sites-available/*.conf;\ +sed -ri -e "s!/var/www/!${APACHE_DOCUMENT_ROOT}!g" /etc/apache2/apache2.conf /etc/apache2/conf-available/*.conf;\ # 修改时区 -RUN rm -rf /etc/localtime -RUN ln -s /usr/share/zoneinfo/PRC /etc/localtime - +rm -rf /etc/localtime;\ +ln -s /usr/share/zoneinfo/PRC /etc/localtime;\ # 设置别名 -RUN echo "alias ll='ls -l'">> /etc/bash.bashrc - +echo "alias ll='ls -l'">> /etc/bash.bashrc;\ # Vim 编码配置 -RUN echo 'set fileencodings=utf-8'>> /etc/vim/vimrc -RUN echo 'set termencoding=utf-8'>> /etc/vim/vimrc -RUN echo 'set encoding=utf-8'>> /etc/vim/vimrc - +echo 'set fileencodings=utf-8'>> /etc/vim/vimrc;\ +echo 'set termencoding=utf-8'>> /etc/vim/vimrc;\ +echo 'set encoding=utf-8'>> /etc/vim/vimrc;\ # 安装 Composer 及项目依赖包 -RUN curl -sO https://mirrors.aliyun.com/composer/composer.phar -RUN chmod +x composer.phar -RUN mv composer.phar /usr/local/bin/composer -RUN composer config repo.packagist composer https://mirrors.aliyun.com/composer/ -RUN composer install --no-dev --no-progress --optimize-autoloader +curl -sO https://mirrors.aliyun.com/composer/composer.phar;\ +chmod +x composer.phar;\ +mv composer.phar /usr/local/bin/composer;\ +composer config repo.packagist composer https://mirrors.aliyun.com/composer/;\ +composer install --no-dev --no-progress --optimize-autoloader;\ +chmod +x docker-entrypoint.sh; -RUN chmod +x docker-entrypoint.sh ENTRYPOINT /bin/bash docker-entrypoint.sh diff --git a/README.md b/README.md index d54b073..d99ab48 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,10 @@ --- +> 因 GitHub 官方搜索架构调整,码小六即日起暂停维护,详见 https://github.com/4x99/code6/issues/264 ,感谢大家支持,后会有期! 2023年04月27日 + +--- + ## 系统特点 - 全可视化界面,操作简单 - 支持移动端,随时随地解决问题 @@ -36,7 +40,7 @@ --- ## 安装部署 -码小六支持 [Docker 部署](doc/deploy-docker.md) 与 [源码部署](doc/deploy-source.md),请根据情况选择! +码小六支持 [Docker-Compose 部署](doc/deploy-docker-compose.md)(推荐)、[Docker 部署](doc/deploy-docker.md) 与 [源码部署](doc/deploy-source.md),请根据情况选择! --- diff --git a/app/Console/Commands/JobRunCommand.php b/app/Console/Commands/JobRunCommand.php index 25b2c7f..a5997ea 100644 --- a/app/Console/Commands/JobRunCommand.php +++ b/app/Console/Commands/JobRunCommand.php @@ -85,19 +85,24 @@ public function handle() $this->log->info('Get whitelist success'); while ($job = $this->takeJob()) { - $keyword = $job->keyword; - $this->log->info('Get a job from the queue', ['keyword' => $keyword]); - $configJob = ConfigJob::where('keyword', $keyword)->first(); - $configJob->last_scan_at = date('Y-m-d H:i:s'); - $page = 1; - do { - $client = $this->service->getClient(); - $data = $this->searchCode($client, $keyword, $page); - $count = $this->store($data, $configJob); - $this->log->info('Store record', ['count' => $count]); - $lastResponse = ResponseMediator::getPagination($client->getLastResponse()); - } while ($lastResponse['next'] && (++$page <= $configJob->scan_page)); - $configJob->save(); + try { + $keyword = $job->keyword; + $this->log->info('Get a job from the queue', ['keyword' => $keyword]); + $configJob = ConfigJob::where('keyword', $keyword)->first(); + $configJob->last_scan_at = date('Y-m-d H:i:s'); + $page = 1; + do { + $client = $this->service->getClient(); + $data = $this->searchCode($client, $keyword, $page); + $count = $this->store($data, $configJob); + $this->log->info('Store record', ['count' => $count]); + $lastResponse = ResponseMediator::getPagination($client->getLastResponse()); + } while ($lastResponse['next'] && (++$page <= $configJob->scan_page)); + $configJob->save(); + } catch (Exception $exception) { + $this->log->error($exception->getMessage()); + } + $job->delete(); } $this->log->info('Work done'); @@ -124,10 +129,9 @@ private function createGitHubService() */ private function takeJob() { - if (!$job = QueueJob::orderBy('created_at')->first()) { + if (!$job = QueueJob::orderBy('id')->first()) { return false; } - $job->delete(); return $job; } diff --git a/app/Http/Controllers/CodeLeakController.php b/app/Http/Controllers/CodeLeakController.php index 892cd78..5882458 100644 --- a/app/Http/Controllers/CodeLeakController.php +++ b/app/Http/Controllers/CodeLeakController.php @@ -37,15 +37,22 @@ public function index(Request $request) return $query->where('status', $request->input('status')); }); - foreach (['repo_owner', 'repo_name', 'repo_description', 'keyword', 'path'] as $field) { - $query->when($request->input($field), function ($query, $value) use ($field) { - return $query->where($field, 'like', "%$value%"); + $query->when($request->input('keyword'), function ($query) use ($request) { + return $query->where('keyword', $request->input('keyword')); + }); + + $query->when($search = $request->input('search'), function ($query) use ($search) { + $query->where(function ($query) use ($search) { + $query->where('path', 'like', "%$search%"); + foreach (['repo_name', 'repo_owner', 'repo_description', 'handle_user', 'description'] as $column) { + $query->orwhere($column, 'like', "%$search%"); + } }); - } + }); $perPage = $request->input('limit', 100); $data = $query->orderByDesc('id')->paginate($perPage); - foreach ($data->items() as &$item){ + foreach ($data->items() as &$item) { $item->repo_description = htmlspecialchars($item->repo_description); } return $data; diff --git a/app/Http/Controllers/ConfigJobController.php b/app/Http/Controllers/ConfigJobController.php index 2193ad5..6df932d 100644 --- a/app/Http/Controllers/ConfigJobController.php +++ b/app/Http/Controllers/ConfigJobController.php @@ -3,7 +3,7 @@ namespace App\Http\Controllers; use App\Models\ConfigJob; -use Cron\CronExpression; +use App\Models\QueueJob; use Illuminate\Http\Request; class ConfigJobController extends Controller @@ -94,6 +94,37 @@ public function destroy($id) } } + /** + * 批量删除任务 + * + * @param Request $request + * @return array + */ + public function batchDestroy(Request $request) + { + try { + $id = json_decode($request->input('id'), true); + $success = ConfigJob::whereIn('id', $id)->delete(); + return ['success' => $success]; + } catch (\Exception $e) { + return ['success' => false, 'message' => $e->getMessage()]; + } + } + + /** + * 任务队列 + * + * @return array + */ + public function queue() + { + $data = QueueJob::orderBy('id')->get()->toArray(); + foreach ($data as $k => $v) { + $data[$k]['status'] = $k == 0 ? 1 : 0; + } + return $data; + } + /** * 下次扫描时间 * @@ -102,8 +133,7 @@ public function destroy($id) */ private function getNextScanAt($interval) { - $expression = "*/$interval * * * *"; - $cron = CronExpression::factory($expression); - return $cron->getNextRunDate()->format('Y-m-d H:i:s'); + $nextScanAt = floor(LARAVEL_START - LARAVEL_START % ($interval * 60) + ($interval * 60)); + return date('Y-m-d H:i:s', $nextScanAt); } } diff --git a/app/Http/Controllers/ConfigNotifyController.php b/app/Http/Controllers/ConfigNotifyController.php index 6bf869b..553b567 100644 --- a/app/Http/Controllers/ConfigNotifyController.php +++ b/app/Http/Controllers/ConfigNotifyController.php @@ -5,7 +5,6 @@ use App\Models\ConfigNotify; use App\Services\NotifyService; use Illuminate\Http\Request; -use Illuminate\Support\Arr; use Illuminate\Validation\Rule; class ConfigNotifyController extends Controller diff --git a/app/Http/Controllers/MobileController.php b/app/Http/Controllers/MobileController.php index 7bf98c3..d966cf7 100644 --- a/app/Http/Controllers/MobileController.php +++ b/app/Http/Controllers/MobileController.php @@ -2,11 +2,17 @@ namespace App\Http\Controllers; +use App\Models\CodeLeak; +use Illuminate\Support\Facades\Request; + class MobileController extends Controller { public function home() { - $data = ['title' => '码小六']; + $page = Request::query('page', 1); + $tab = Request::query('tab', 'all'); + $count = CodeLeak::query()->count(); + $data = ['title' => '码小六', 'page' => $page, 'tab' => $tab, 'count' => $count]; return view('mobile.home', $data); } diff --git a/app/Services/GitHubService.php b/app/Services/GitHubService.php index 2f6d0ac..02bb3dd 100644 --- a/app/Services/GitHubService.php +++ b/app/Services/GitHubService.php @@ -44,7 +44,7 @@ public function __construct() */ public function init() { - $this->proxy = $this->testProxy($this->proxy) ? $this->proxy : null; + $this->proxy = $this->proxy ? ($this->testProxy($this->proxy) ? $this->proxy : null) : null; $tokens = ConfigToken::inRandomOrder()->get()->pluck('token'); foreach ($tokens as $token) { $client = ['token' => $token]; diff --git a/app/Services/NotifyService.php b/app/Services/NotifyService.php index d5843c4..8d0a2e7 100644 --- a/app/Services/NotifyService.php +++ b/app/Services/NotifyService.php @@ -28,11 +28,11 @@ public function email($title, $content, $config) { Config::set('mail', [ 'driver' => 'smtp', - 'encryption' => 'ssl', 'host' => $config['host'], 'port' => $config['port'] ?? 465, 'username' => $config['username'], 'password' => $config['password'], + 'encryption' => $config['encryption'] ?? 'SSL', ]); try { diff --git a/composer.lock b/composer.lock index b0403fd..353364e 100644 --- a/composer.lock +++ b/composer.lock @@ -883,16 +883,16 @@ }, { "name": "knplabs/github-api", - "version": "v2.14.0", + "version": "v2.20.0", "source": { "type": "git", "url": "https://github.com/KnpLabs/php-github-api.git", - "reference": "953c9b453d3258a97755ec3557d112f271176f74" + "reference": "939869394c6414768547685945fdba4fe3f061b5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/KnpLabs/php-github-api/zipball/953c9b453d3258a97755ec3557d112f271176f74", - "reference": "953c9b453d3258a97755ec3557d112f271176f74", + "url": "https://api.github.com/repos/KnpLabs/php-github-api/zipball/939869394c6414768547685945fdba4fe3f061b5", + "reference": "939869394c6414768547685945fdba4fe3f061b5", "shasum": "", "mirrors": [ { @@ -916,12 +916,14 @@ "guzzlehttp/psr7": "^1.2", "php-http/guzzle6-adapter": "^1.0 || ^2.0", "php-http/mock-client": "^1.2", + "phpstan/phpstan": "^0.12.23", "phpunit/phpunit": "^7.0 || ^8.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.14.x-dev" + "dev-2.x": "2.20.x-dev", + "dev-master": "3.2.x-dev" } }, "autoload": { @@ -952,7 +954,17 @@ "gist", "github" ], - "time": "2020-04-25T20:36:03+00:00" + "support": { + "issues": "https://github.com/KnpLabs/php-github-api/issues", + "source": "https://github.com/KnpLabs/php-github-api/tree/v2.20.0" + }, + "funding": [ + { + "url": "https://github.com/acrobat", + "type": "github" + } + ], + "time": "2021-04-16T09:36:20+00:00" }, { "name": "laravel/framework", @@ -7262,5 +7274,5 @@ "php": "^7.2.5" }, "platform-dev": [], - "plugin-api-version": "1.1.0" + "plugin-api-version": "2.3.0" } diff --git a/doc/deploy-docker-compose.md b/doc/deploy-docker-compose.md new file mode 100644 index 0000000..19975b9 --- /dev/null +++ b/doc/deploy-docker-compose.md @@ -0,0 +1,69 @@ +# Docker-Compose 部署 +## 克隆代码 +``` +git clone https://github.com/4x99/code6.git +``` + +--- + +## 修改配置 +``` +cd code6 +cp .env.docker-compose.example .env.docker-compose +vim .env.docker-compose +``` + +请根据实际情况修改配置,这里 Web 端口以 `666` 为例: +``` +# Web 映射到宿主机的端口 +PORT=666 + +# MySQL 映射到宿主机的端口 +MYSQL_PORT=3306 + +# MySQL 数据库名 +MYSQL_DATABASE=code6 + +# MySQL 用户名(请使用非 root 用户) +MYSQL_USER= + +# MySQL 密码 +MYSQL_PASSWORD= + +# MySQL 挂载到宿主机的目录 +MYSQL_VOLUME_PATH= +``` + +--- + +## 启动容器 +启动容器,码小六将自动连接 MySQL 并导入数据表: +``` +docker-compose --env-file .env.docker-compose up -d --build +``` + +--- + +## 创建用户 +``` +docker exec -it code6-server /bin/bash +php artisan code6:user-add <邮箱> <密码> +``` + +如需查看用户列表或删除用户请执行: +``` +php artisan code6:user-list +php artisan code6:user-delete <邮箱> +``` + +--- + +## 访问系统 +``` +http://<宿主机 IP>:666 +``` + +--- + +## 配置令牌与任务 +进入系统后请前往 `[ 令牌配置 ]` 和 `[ 任务配置 ]` 模块进行配置,配置完毕即可使用! diff --git a/doc/deploy-source.md b/doc/deploy-source.md index e17a50c..e50707c 100644 --- a/doc/deploy-source.md +++ b/doc/deploy-source.md @@ -87,7 +87,6 @@ crontab -e -u <用户> ## 创建用户 ``` -docker exec -it code6-server /bin/bash php artisan code6:user-add <邮箱> <密码> ``` diff --git a/docker-compose.yaml b/docker-compose.yaml new file mode 100644 index 0000000..cc94741 --- /dev/null +++ b/docker-compose.yaml @@ -0,0 +1,45 @@ +version: "3.9" +services: + mysql: + image: mysql/mysql-server:5.7 + container_name: code6-mysql + restart: always + env_file: + - .env.docker-compose + networks: + - code6-network + ports: + - ${MYSQL_PORT}:3306 + volumes: + - /etc/localtime:/etc/localtime:ro + - ${MYSQL_VOLUME_PATH}:/var/lib/mysql + healthcheck: + test: mysql ${MYSQL_DATABASE} -u${MYSQL_USER} -p${MYSQL_PASSWORD} -e 'SELECT 1' + interval: 5s + retries: 10 + start_period: 60s + code6: + image: code6 + build: + context: . + dockerfile: Dockerfile + container_name: code6-server + depends_on: + mysql: + condition: service_healthy + restart: always + env_file: + - .env.docker-compose + environment: + MYSQL_HOST: mysql + MYSQL_PORT: 3306 + MYSQL_USERNAME: ${MYSQL_USER} + ports: + - ${PORT}:80 + networks: + - code6-network + links: + - mysql +networks: + code6-network: + name: code6-network diff --git a/docker-entrypoint.sh b/docker-entrypoint.sh index deb9fec..c125229 100644 --- a/docker-entrypoint.sh +++ b/docker-entrypoint.sh @@ -1,20 +1,22 @@ #!/bin/bash # 项目配置 -cp .env.example .env -chmod -R 755 storage -chown -R www-data:www-data storage -php artisan key:generate -sed -i "s!DB_HOST=127.0.0.1!DB_HOST=$MYSQL_HOST!" .env -sed -i "s!DB_PORT=3306!DB_PORT=$MYSQL_PORT!" .env -sed -i "s!DB_DATABASE=code6!DB_DATABASE=$MYSQL_DATABASE!" .env -sed -i "s!DB_USERNAME=!DB_USERNAME=$MYSQL_USERNAME!" .env -sed -i "s!DB_PASSWORD=!DB_PASSWORD=$MYSQL_PASSWORD!" .env -php artisan migrate --force +if [ ! -e '.env' ];then + cp -i .env.example .env + chmod -R 755 storage + chown -R www-data:www-data storage + php artisan key:generate + sed -i "s!DB_HOST=127.0.0.1!DB_HOST=$MYSQL_HOST!" .env + sed -i "s!DB_PORT=3306!DB_PORT=$MYSQL_PORT!" .env + sed -i "s!DB_DATABASE=code6!DB_DATABASE=$MYSQL_DATABASE!" .env + sed -i "s!DB_USERNAME=!DB_USERNAME=$MYSQL_USERNAME!" .env + sed -i "s!DB_PASSWORD=!DB_PASSWORD=$MYSQL_PASSWORD!" .env + php artisan migrate --force +fi # 配置任务调度 service cron start -echo "* * * * * cd /var/www/html && /usr/local/bin/php artisan schedule:run>> /dev/null 2>&1">> /etc/cron.d/code6 +echo "* * * * * cd /var/www/html && /usr/local/bin/php artisan schedule:run>> /dev/null 2>&1"> /etc/cron.d/code6 crontab /etc/cron.d/code6 # 配置 Apache diff --git a/doctor.php b/doctor.php new file mode 100644 index 0000000..5c61a46 --- /dev/null +++ b/doctor.php @@ -0,0 +1,106 @@ +=') ? '' : 'PHP 版本需>= 7.3.0'; +console('PHP 版本', PHP_VERSION, $err); + +// PDO 扩展 +$pdo = class_exists('pdo'); +$err = $pdo ? '' : '请先安装 PHP PDO 扩展'; +console('PDO 扩展', $pdo ? '已安装' : '未安装', $err); + +// Laravel 密钥 +$err = $env['APP_KEY'] ? '' : '请执行命令 php artisan key:generate 生成密钥'; +console('Laravel 密钥', $env['APP_KEY'] ? '已生成' : '未生成', $err); + +// Storage 目录 +$writable = is_writable(ROOT.'/storage'); +$err = $writable ? '' : '请设置 storage 目录为可读写'; +console('Storage 目录', $writable ? '可读写' : '不可读写', $err); + +// Composer Package +$import = file_exists(ROOT.'/vendor/autoload.php'); +$err = $import ? '' : '请安装 Composer 并执行 composer install 安装包'; +console('Composer Package', $import ? '已导入' : '未导入', $err); + +// MySQL 连接 +try { + $dberr = ''; + $dsn = "mysql:host={$env['DB_HOST']}:{$env['DB_PORT']};dbname={$env['DB_DATABASE']}"; + $db = new PDO($dsn, $env['DB_USERNAME'], $env['DB_PASSWORD'], [PDO::ATTR_TIMEOUT => 3]); +} catch (Exception $e) { + $dberr = $e->getMessage(); +} +console('MySQL 连接', $dberr ? '失败' : '成功', $dberr); + +// MySQL 数据表 +try { + if ($dberr) { + throw new Exception($dberr); + } + $err = false; + $tables = $db->query("show tables like 'code_leak'")->fetchAll(PDO::FETCH_ASSOC)[0]; + if (!count($tables)) { + throw new Exception('请执行 php artisan migrate 导入数据表'); + } +} catch (Exception $e) { + $err = $e->getMessage(); +} +console('MySQL 数据表', $err ? '未导入' : '已导入', $err); + +// GitHub API +try { + $apierr = false; + $url = 'https://api.github.com'; + $ch = curl_init($url); + curl_setopt($ch, CURLOPT_HEADER, 0); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($ch, CURLOPT_TIMEOUT, 5); + curl_setopt($ch, CURLOPT_USERAGENT, 'code6'); + $result = json_decode(curl_exec($ch), true); + if (empty($result)) { + throw new Exception("请求 $url 错误"); + } + curl_close($ch); +} catch (Exception $e) { + $apierr = $e->getMessage(); +} +console('GitHub API', $apierr ? '请求错误' : '请求成功', $apierr); + +// PHP 禁用函数 +$disFuns = get_cfg_var('disable_functions') ?: '无'; +echo "PHP 禁用函数:$disFuns\n"; + +// PHP 已编译模块 +$exts = implode(',', get_loaded_extensions()); +echo "PHP 已编译模块:$exts\n"; + +echo DIVIDER."[ 系统信息 ]\n"; + +// 码小六版本 +$version = trim(file_get_contents(ROOT.'/version')); +echo "码小六版本:$version\n"; + +// 框架运行环境 +$appEnv = $env['APP_ENV'] ?? '无'; +echo "框架运行环境:$appEnv\n"; + +// 框架调试开关 +$appDebug = $env['APP_DEBUG'] ?? '无'; +echo "框架调试开关:$appDebug\n"; + +echo DIVIDER."\n有任何问题和建议请联系-> https://github.com/4x99/code6/issues\n\n"; diff --git a/public/css/style.css b/public/css/style.css index ebaf106..1da105b 100644 --- a/public/css/style.css +++ b/public/css/style.css @@ -189,6 +189,10 @@ textarea::-webkit-input-placeholder, input::-webkit-input-placeholder { background: url(../image/icon/email.png) no-repeat; } +.icon-excel { + background: url(../image/icon/excel.png) no-repeat; +} + .icon-feishu { background: url(../image/icon/feishu.png) no-repeat; } diff --git a/public/image/icon/excel.png b/public/image/icon/excel.png new file mode 100644 index 0000000..3f47759 Binary files /dev/null and b/public/image/icon/excel.png differ diff --git a/public/js/extjs/plugin/export.js b/public/js/extjs/plugin/export.js new file mode 100644 index 0000000..49fd892 --- /dev/null +++ b/public/js/extjs/plugin/export.js @@ -0,0 +1,52 @@ +/** + * 导出数据 + */ +Ext.define('plugin.export', { + extend: 'Ext.Button', + text: '导出数据', + iconCls: 'icon-excel', + onClick: function () { + var data = this.getData(); + this.saveCsv(data); + }, + // 读取数据 + getData: function () { + var header = [], value = []; + var grid = this.up('gridpanel'); + var store = grid.store; + var columns = grid.columns ? grid.columns : grid.columnManager.columns; + Ext.each(store.getData().items, function (record, index) { + var row = []; + if (store.filters && !store.filters.filterFn(record)) { + return true; + } + Ext.each(columns, function (column) { + if (column.hidden || column.xtype === 'widgetcolumn') { + return true; + } + if (index === 0) { + header.push(column.text); + } + var value = record.get(column.dataIndex); + if (column.renderer) { + value = column.renderer(value, {}, record); + } + value = String(value).replace(/\n+|()+/ig, ' '); // 去除换行 + value = Ext.util.Format.stripTags(value); + row.push(`"${value}"`); + }); + value.push(row.join(',')); + }); + return header.join(',') + '\n' + value.join('\n'); + }, + // 保存 CSV 文件 + saveCsv: function (data) { + const blob = new Blob([data], {type: 'text/csv'}); + const url = window.URL.createObjectURL(blob) + const a = document.createElement('a') + a.href = url; + a.download = Ext.Date.format(new Date(), 'YmdHis') + '.csv' + a.click() + a.remove(); + } +}); diff --git a/public/js/extjs/plugin/grid.js b/public/js/extjs/plugin/grid.js index 93c86c7..6bddff7 100644 --- a/public/js/extjs/plugin/grid.js +++ b/public/js/extjs/plugin/grid.js @@ -17,6 +17,14 @@ Ext.define('plugin.grid', { xtype: 'pagingtoolbar', dock: 'bottom', displayInfo: true, + listeners: { + change: function (obj) { + var view = obj.up('grid').getView(); + if (typeof(view.scrollTo) == 'function') { + view.scrollTo(0, 0); + } + } + } } ] }); diff --git a/resources/views/codeLeak/index.blade.php b/resources/views/codeLeak/index.blade.php index 2b02311..7c2fdf5 100644 --- a/resources/views/codeLeak/index.blade.php +++ b/resources/views/codeLeak/index.blade.php @@ -50,7 +50,7 @@ format: 'Y-m-d', maxValue: new Date(), emptyText: '开始日期', - width: 110, + width: 120, }, { xtype: 'datefield', @@ -58,41 +58,19 @@ format: 'Y-m-d', maxValue: new Date(), emptyText: '结束日期', - width: 110, + width: 120, }, { xtype: 'combo', valueField: 'value', - width: 65, + width: 120, name: 'status', emptyText: '状态', store: {data: status} }, - { - xtype: 'textfield', - name: 'repo_owner', - emptyText: '用户名', - }, - { - xtype: 'textfield', - name: 'repo_name', - emptyText: '仓库名', - }, - { - xtype: 'textfield', - name: 'path', - emptyText: '文件路径', - width: 130, - }, - { - xtype: 'textfield', - name: 'repo_description', - emptyText: '仓库描述', - width: 130, - }, { xtype: 'combo', - width: 150, + width: 120, name: 'keyword', displayField: 'keyword', valueField: 'keyword', @@ -110,9 +88,15 @@ }, }, }, + { + xtype: 'textfield', + width: 380, + name: 'search', + emptyText: '用户名 / 仓库名 / 文件路径 / 仓库描述 / 处理人 / 说明', + }, { xtype: 'buttongroup', - baseCls: 'border:0', + baseCls: '', width: 150, items: [ { @@ -202,6 +186,25 @@ } ] }, + dockedItems: [ + { + xtype: 'pagingtoolbar', + dock: 'bottom', + displayInfo: true, + items: [ + '-', + Ext.create('plugin.export'), + ], + listeners: { + change: function (obj) { + let view = obj.up('grid').getView(); + if (typeof (view.scrollTo) == 'function') { + view.scrollTo(0, 0); + } + } + } + } + ], columns: [ { text: 'ID', @@ -289,7 +292,7 @@ flex: 1, align: 'center', renderer: function (value) { - return value ? value : '-'; + return value ? Ext.String.htmlEncode(value) : '-'; } }, { @@ -300,7 +303,7 @@ xtype: 'widgetcolumn', widget: { xtype: 'buttongroup', - baseCls: 'border:0', + baseCls: '', layout: { type: 'hbox', pack: 'center', @@ -461,7 +464,7 @@ function winForm(value, handler, modal) { return Ext.create('Ext.window.Window', { title: '编辑信息', iconCls: 'icon-add', - width: 350, + width: 600, modal: modal, layout: 'fit', items: [ @@ -473,8 +476,9 @@ function winForm(value, handler, modal) { { fieldLabel: '说明', name: 'description', - xtype: 'textfield', + xtype: 'textareafield', value: value, + fieldStyle: 'min-height:150px', } ], buttons: [ diff --git a/resources/views/configJob/index.blade.php b/resources/views/configJob/index.blade.php index a706117..161c92c 100644 --- a/resources/views/configJob/index.blade.php +++ b/resources/views/configJob/index.blade.php @@ -6,8 +6,6 @@ Ext.onReady(function () { Ext.QuickTips.init(true, {dismissDelay: 0}); - var GitHub = 'https://github.com/'; - Ext.create('Ext.data.Store', { storeId: 'store', pageSize: 99999, // 不分页 @@ -24,9 +22,18 @@ {value: 2, text: '一个仓库只记录一次', qtip: '一个仓库只记录一次'}, ]; + var queue = { + config: [ + {text: '待执行', color: 'gray'}, + {text: '执行中', color: 'green'}, + ], + tpl: new Ext.XTemplate('
{text}
'), + } + var grid = Ext.create('plugin.grid', { store: Ext.data.StoreManager.lookup('store'), bufferedRenderer: false, + selType: 'checkboxmodel', tbar: { margin: '5 12 15 18', items: [ @@ -36,6 +43,53 @@ handler: winHelp, }, '->', + { + text: '任务队列', + iconCls: 'icon-page-db', + handler: winQueue, + }, + '-', + { + text: '批量删除', + iconCls: 'icon-cross', + handler: function () { + var records = grid.getSelectionModel().getSelection(); + if (!records.length) { + tool.toast('请先勾选任务!'); + return; + } + + Ext.Msg.show({ + title: '提示', + iconCls: 'icon-page', + message: '确定执行此操作?', + buttons: Ext.Msg.YESNO, + fn: function (btn) { + if (btn !== 'yes') { + return; + } + + + var id = []; + for (var record of records) { + id.push(record.get('id')); + } + + var params = {id: Ext.encode(id)}; + tool.ajax('DELETE', '/api/configJob/batchDestroy', params, function (rsp) { + if (rsp.success) { + tool.toast('操作成功!', 'success'); + grid.store.reload(); + grid.getSelectionModel().clearSelections(); + } else { + tool.toast(rsp.message, 'error'); + } + }); + } + }); + } + }, + '-', { text: '新增任务', iconCls: 'icon-add', @@ -58,6 +112,9 @@ dataIndex: 'keyword', flex: 1, align: 'center', + renderer: function (value) { + return Ext.String.htmlEncode(value); + } }, { text: '扫描页数', @@ -107,7 +164,7 @@ flex: 1, align: 'center', renderer: function (value) { - return value ? value : '-'; + return value ? Ext.String.htmlEncode(value) : '-'; } }, { @@ -118,7 +175,7 @@ xtype: 'widgetcolumn', widget: { xtype: 'buttongroup', - baseCls: 'border:0', + baseCls: '', layout: { type: 'hbox', pack: 'center', @@ -288,6 +345,67 @@ function winHelp() { }).removeCls('x-unselectable'); } + var taskRunner = new Ext.util.TaskRunner(); + + function newTask(interval, run) { + var task = taskRunner.newTask({ + run: run, + interval: interval, + fireOnStart: true, + }); + task.start(); + return task; + } + + function winQueue() { + var gridQueue = Ext.create('plugin.grid', { + disableSelection: true, + viewConfig: { + loadMask: false, + emptyText: '
任务队列为空..
' + }, + store: { + autoLoad: true, + pageSize: 99999, + proxy: { + type: 'ajax', + url: '/api/configJob/queue', + }, + }, + columns: [ + { + text: '关键字', + dataIndex: 'keyword', + align: 'center', + flex: 2, + }, + { + text: '状态', + dataIndex: 'status', + align: 'center', + flex: 1, + renderer: function (value) { + return queue.tpl.apply(queue.config[value]); + }, + }, + ] + }); + + Ext.create('Ext.window.Window', { + title: '任务队列', + iconCls: 'icon-page-db', + width: 800, + height: 500, + layout: 'fit', + items: [gridQueue], + }).show(); + + // 每 5 秒自动刷新 + newTask(5000, function () { + gridQueue.store.reload(); + }); + } + Ext.create('Ext.container.Container', { renderTo: Ext.getBody(), height: '100%', diff --git a/resources/views/configNotify/index.blade.php b/resources/views/configNotify/index.blade.php index 91e24d5..5369e4d 100644 --- a/resources/views/configNotify/index.blade.php +++ b/resources/views/configNotify/index.blade.php @@ -22,10 +22,21 @@ iconCls: 'icon-email', items: [ createEnableField('email'), + { + xtype: 'combo', + name: 'encryption', + fieldLabel: '加密方式', + store: {data: [{text: 'SSL'}, {text: 'TLS'}]}, + queryMode: 'local', + valueField: 'text', + typeAhead: true, + editable: false, + value: getConfig('email.value.encryption', 'SSL'), + }, { xtype: 'combo', name: 'host', - fieldLabel: '服 务 器', + fieldLabel: '服
器', store: { data: [ {text: 'smtp.qq.com'}, @@ -48,14 +59,6 @@ value: getConfig('email.value.port', 465), allowBlank: true, allowDecimals: false, - listeners: { - render: function (c) { - Ext.QuickTips.register({ - target: c.getEl(), - text: '已启用 SSL 协议,通常使用 465 而非 25 端口', - }); - } - } }, { name: 'username', @@ -75,6 +78,7 @@ fieldLabel: '接收邮箱', emptyText: '支持多个邮箱(一行一个)', value: getConfig('email.value.to'), + fieldStyle: 'min-height:50px', }, createIntervalField('email'), createTimeField('email'), @@ -225,7 +229,7 @@ type: 'help', tooltip: '企业微信文档', handler: function () { - tool.winOpen('https://work.weixin.qq.com/api/doc/90000/90136/91770'); + tool.winOpen('https://developer.work.weixin.qq.com/document/path/91770'); } }], items: [ @@ -240,7 +244,7 @@ function createBtn(type) { return { xtype: 'buttongroup', - baseCls: 'border:0', + baseCls: '', layout: { type: 'hbox', pack: 'end', @@ -498,7 +502,7 @@ function winFormTemplate() { layout: 'form', columnWidth: 1 / 3, margin: 10, - height: 450, + height: 470, bodyPadding: 20, bodyStyle: 'background:#FAFAFA', }, diff --git a/resources/views/configToken/index.blade.php b/resources/views/configToken/index.blade.php index 6dc4281..960b6a6 100644 --- a/resources/views/configToken/index.blade.php +++ b/resources/views/configToken/index.blade.php @@ -96,6 +96,9 @@ dataIndex: 'token', width: 380, align: 'center', + renderer: function (value) { + return Ext.String.htmlEncode(value); + } }, { text: '状态', @@ -154,7 +157,7 @@ align: 'center', flex: 1, renderer: function (value) { - return value ? value : '-'; + return value ? Ext.String.htmlEncode(value) : '-'; } }, { @@ -165,7 +168,7 @@ xtype: 'widgetcolumn', widget: { xtype: 'buttongroup', - baseCls: 'border:0', + baseCls: '', layout: { type: 'hbox', pack: 'center', diff --git a/resources/views/configWhitelist/index.blade.php b/resources/views/configWhitelist/index.blade.php index c533269..b7b99b6 100644 --- a/resources/views/configWhitelist/index.blade.php +++ b/resources/views/configWhitelist/index.blade.php @@ -96,7 +96,7 @@ flex: 1, align: 'center', renderer: function (value) { - return value.split('/')[0]; + return Ext.String.htmlEncode(value.split('/')[0]); } }, { @@ -105,7 +105,7 @@ flex: 1, align: 'center', renderer: function (value) { - return value.split('/')[1]; + return Ext.String.htmlEncode(value.split('/')[1]); } }, { @@ -116,7 +116,7 @@ xtype: 'widgetcolumn', widget: { xtype: 'buttongroup', - baseCls: 'border:0', + baseCls: '', layout: { type: 'hbox', pack: 'center', diff --git a/resources/views/mobile/home.blade.php b/resources/views/mobile/home.blade.php index c52f851..878c273 100644 --- a/resources/views/mobile/home.blade.php +++ b/resources/views/mobile/home.blade.php @@ -82,8 +82,8 @@ data: { loading: true, page: { - count: 0, - current: 1, + count: {{ $count }}, + current: {{ $page }}, }, action: { show: false, @@ -100,7 +100,7 @@ selection: [], }, tab: { - current: 'all', + current: '{{ $tab }}', } }, methods: { @@ -117,6 +117,8 @@ me.page.count = rsp.data.last_page ? rsp.data.last_page : 0; me.list.data = rsp.data.data; me.loading = false; + var data = {page: me.page, data: me.list.data, tab: me.tab}; + history.pushState(data, '', '/mobile?page=' + page + '&tab=' + me.tab.current); }).catch(function (rsp) { me.$toast.fail(rsp.message); me.loading = false; @@ -168,7 +170,16 @@ }, }, mounted: function () { - this.load(1); + var me = this; + me.load(me.page.current); + window.addEventListener('popstate', function (e) { + if (e.state && e.state.data) { + me.page = e.state.page; + me.list.data = e.state.data; + me.tab = e.state.tab; + me.loading = false; + } + }) } }); diff --git a/routes/web.php b/routes/web.php index e876bb1..96d8e66 100644 --- a/routes/web.php +++ b/routes/web.php @@ -41,6 +41,8 @@ Route::resource('/api/codeLeak', 'CodeLeakController'); Route::get('/configJob', 'ConfigJobController@view'); + Route::get('/api/configJob/queue', 'ConfigJobController@queue'); + Route::delete('/api/configJob/batchDestroy', 'ConfigJobController@batchDestroy'); Route::resource('/api/configJob', 'ConfigJobController'); Route::get('/configToken', 'ConfigTokenController@view'); diff --git a/version b/version index eac1e0a..9edc58b 100644 --- a/version +++ b/version @@ -1 +1 @@ -1.5.6 +1.6.4

AltStyle によって変換されたページ (->オリジナル) /