Full Code of whyour/qinglong for AI

develop 07bf0c705b13 cached
222 files
1.1 MB
318.6k tokens
1033 symbols
1 requests
Download .txt
Showing preview only (1,228K chars total). Download the full file or copy to clipboard to get everything.
Repository: whyour/qinglong
Branch: develop
Commit: 07bf0c705b13
Files: 222
Total size: 1.1 MB

Directory structure:
gitextract_8pxenqz2/

├── .editorconfig
├── .github/
│   ├── ISSUE_TEMPLATE/
│   │   ├── bug_report.yml
│   │   ├── config.yml
│   │   └── feature_request.yml
│   ├── PULL_REQUEST_TEMPLATE.md
│   ├── agents/
│   │   └── ql.agent.md
│   ├── config.yml
│   └── workflows/
│       └── build-docker-image.yml
├── .gitignore
├── .npmrc
├── .prettierignore
├── .prettierrc
├── .umirc.ts
├── LICENSE
├── README-en.md
├── README.md
├── SECURITY.md
├── back/
│   ├── api/
│   │   ├── config.ts
│   │   ├── cron.ts
│   │   ├── dependence.ts
│   │   ├── env.ts
│   │   ├── health.ts
│   │   ├── index.ts
│   │   ├── log.ts
│   │   ├── open.ts
│   │   ├── script.ts
│   │   ├── subscription.ts
│   │   ├── system.ts
│   │   ├── update.ts
│   │   └── user.ts
│   ├── app.ts
│   ├── config/
│   │   ├── const.ts
│   │   ├── http.ts
│   │   ├── index.ts
│   │   ├── serverEnv.ts
│   │   ├── share.ts
│   │   ├── subscription.ts
│   │   └── util.ts
│   ├── data/
│   │   ├── cron.ts
│   │   ├── cronView.ts
│   │   ├── dependence.ts
│   │   ├── env.ts
│   │   ├── index.ts
│   │   ├── notify.ts
│   │   ├── open.ts
│   │   ├── sock.ts
│   │   ├── subscription.ts
│   │   └── system.ts
│   ├── interface/
│   │   └── schedule.ts
│   ├── loaders/
│   │   ├── app.ts
│   │   ├── bootAfter.ts
│   │   ├── db.ts
│   │   ├── depInjector.ts
│   │   ├── deps.ts
│   │   ├── express.ts
│   │   ├── initData.ts
│   │   ├── initFile.ts
│   │   ├── initTask.ts
│   │   ├── logger.ts
│   │   ├── server.ts
│   │   └── sock.ts
│   ├── middlewares/
│   │   └── monitoring.ts
│   ├── protos/
│   │   ├── api.proto
│   │   ├── api.ts
│   │   ├── cron.proto
│   │   ├── cron.ts
│   │   ├── health.proto
│   │   └── health.ts
│   ├── schedule/
│   │   ├── addCron.ts
│   │   ├── api.ts
│   │   ├── client.ts
│   │   ├── data.ts
│   │   ├── delCron.ts
│   │   └── health.ts
│   ├── services/
│   │   ├── config.ts
│   │   ├── cron.ts
│   │   ├── cronView.ts
│   │   ├── dependence.ts
│   │   ├── env.ts
│   │   ├── grpc.ts
│   │   ├── health.ts
│   │   ├── http.ts
│   │   ├── log.ts
│   │   ├── metrics.ts
│   │   ├── notify.ts
│   │   ├── open.ts
│   │   ├── schedule.ts
│   │   ├── script.ts
│   │   ├── sock.ts
│   │   ├── sshKey.ts
│   │   ├── subscription.ts
│   │   ├── system.ts
│   │   └── user.ts
│   ├── shared/
│   │   ├── auth.ts
│   │   ├── interface.ts
│   │   ├── logStreamManager.ts
│   │   ├── pLimit.ts
│   │   ├── runCron.ts
│   │   ├── store.ts
│   │   └── utils.ts
│   ├── token.ts
│   ├── tsconfig.json
│   ├── types/
│   │   └── express.d.ts
│   └── validation/
│       └── schedule.ts
├── docker/
│   ├── 310.Dockerfile
│   ├── Dockerfile
│   ├── docker-compose.yml
│   └── docker-entrypoint.sh
├── ecosystem.config.js
├── nodemon.json
├── package.json
├── sample/
│   ├── auth.sample.json
│   ├── config.sample.sh
│   ├── extra.sample.sh
│   ├── notify.js
│   ├── notify.py
│   ├── notify.py.save
│   ├── ql_sample.js
│   ├── ql_sample.py
│   ├── task.sample.sh
│   └── tool.ts
├── shell/
│   ├── api.sh
│   ├── bot.sh
│   ├── check.sh
│   ├── env.sh
│   ├── otask.sh
│   ├── preload/
│   │   ├── client.js
│   │   ├── client.py
│   │   ├── sitecustomize.js
│   │   └── sitecustomize.py
│   ├── pub.sh
│   ├── rmlog.sh
│   ├── share.sh
│   ├── task.sh
│   └── update.sh
├── src/
│   ├── app.ts
│   ├── components/
│   │   ├── copy.tsx
│   │   ├── iconfont.tsx
│   │   ├── index.less
│   │   ├── name.tsx
│   │   ├── tag.tsx
│   │   └── terminal.tsx
│   ├── hooks/
│   │   ├── useFilterTreeData.ts
│   │   ├── useScrollHeight.ts
│   │   └── useTableScrollHeight.ts
│   ├── layouts/
│   │   ├── defaultProps.tsx
│   │   ├── index.less
│   │   └── index.tsx
│   ├── loading.tsx
│   ├── locales/
│   │   ├── en-US.json
│   │   └── zh-CN.json
│   ├── pages/
│   │   ├── 404.tsx
│   │   ├── config/
│   │   │   ├── index.less
│   │   │   └── index.tsx
│   │   ├── crontab/
│   │   │   ├── const.ts
│   │   │   ├── detail.tsx
│   │   │   ├── index.less
│   │   │   ├── index.tsx
│   │   │   ├── logModal.tsx
│   │   │   ├── modal.tsx
│   │   │   ├── type.ts
│   │   │   ├── viewCreateModal.tsx
│   │   │   └── viewManageModal.tsx
│   │   ├── dependence/
│   │   │   ├── index.less
│   │   │   ├── index.tsx
│   │   │   ├── logModal.tsx
│   │   │   ├── modal.tsx
│   │   │   └── type.ts
│   │   ├── diff/
│   │   │   ├── index.less
│   │   │   └── index.tsx
│   │   ├── env/
│   │   │   ├── editNameModal.tsx
│   │   │   ├── index.less
│   │   │   ├── index.tsx
│   │   │   └── modal.tsx
│   │   ├── error/
│   │   │   ├── index.less
│   │   │   └── index.tsx
│   │   ├── initialization/
│   │   │   ├── index.less
│   │   │   └── index.tsx
│   │   ├── log/
│   │   │   ├── index.module.less
│   │   │   └── index.tsx
│   │   ├── login/
│   │   │   ├── index.less
│   │   │   └── index.tsx
│   │   ├── script/
│   │   │   ├── components/
│   │   │   │   └── UnsupportedFilePreview/
│   │   │   │       ├── index.module.less
│   │   │   │       └── index.tsx
│   │   │   ├── editModal.tsx
│   │   │   ├── editNameModal.tsx
│   │   │   ├── index.module.less
│   │   │   ├── index.tsx
│   │   │   ├── renameModal.tsx
│   │   │   ├── saveModal.tsx
│   │   │   └── setting.tsx
│   │   ├── setting/
│   │   │   ├── about.tsx
│   │   │   ├── appModal.tsx
│   │   │   ├── checkUpdate.tsx
│   │   │   ├── dependence.tsx
│   │   │   ├── index.less
│   │   │   ├── index.tsx
│   │   │   ├── loginLog.tsx
│   │   │   ├── notification.tsx
│   │   │   ├── other.tsx
│   │   │   ├── progress.tsx
│   │   │   ├── security.tsx
│   │   │   └── systemLog.tsx
│   │   └── subscription/
│   │       ├── index.less
│   │       ├── index.tsx
│   │       ├── logModal.tsx
│   │       └── modal.tsx
│   ├── styles/
│   │   └── variable.less
│   └── utils/
│       ├── codemirror/
│       │   └── systemLog.ts
│       ├── config.ts
│       ├── const.ts
│       ├── date.ts
│       ├── hooks.ts
│       ├── http.tsx
│       ├── index.ts
│       ├── init.ts
│       ├── monaco/
│       │   └── index.ts
│       ├── type.ts
│       └── websocket.ts
├── tsconfig.json
├── typings.d.ts
└── version.yaml

================================================
FILE CONTENTS
================================================

================================================
FILE: .editorconfig
================================================
# http://editorconfig.org
root = true

[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true

[*.md]
trim_trailing_whitespace = false

[Makefile]
indent_style = tab


================================================
FILE: .github/ISSUE_TEMPLATE/bug_report.yml
================================================
name: "\U0001F41E Bug report"
description: Create a report to help us improve
body:
  - type: input
    id: version
    attributes:
      label: Qinglong version
    validations:
      required: true
  - type: textarea
    id: steps-to-reproduce
    attributes:
      label: Steps to reproduce
      description: |
        What do we need to do after opening your repro in order to make the bug happen? Clear and concise reproduction instructions are important for us to be able to triage your issue in a timely manner. Note that you can use [Markdown](https://guides.github.com/features/mastering-markdown/) to format lists and code.
      placeholder: Steps to reproduce
    validations:
      required: true
  - type: textarea
    id: expected
    attributes:
      label: What is expected?
    validations:
      required: true
  - type: textarea
    id: actually-happening
    attributes:
      label: What is actually happening?
    validations:
      required: true
  - type: textarea
    id: system-info
    attributes:
      label: System Info
      description: Output of `npx envinfo --system --binaries --browsers`
      render: shell
      placeholder: System, Binaries, Browsers
  - type: textarea
    id: additional-comments
    attributes:
      label: Any additional comments?
      description: e.g. some background/context of how you ran into this bug.


================================================
FILE: .github/ISSUE_TEMPLATE/config.yml
================================================
blank_issues_enabled: false
contact_links:
  - name: Telegram Chat
    url: https://t.me/jiao_long
    about: Ask questions and discuss with other Qinglong users in real time.
  - name: Questions & Discussions
    url: https://github.com/whyour/qinglong/discussions/new?category=q-a
    about: Use GitHub discussions for message-board style questions and discussions.


================================================
FILE: .github/ISSUE_TEMPLATE/feature_request.yml
================================================
name: "\U0001F680 New feature proposal"
description: Suggest an idea for this project
labels: [":sparkles: feature request"]
body:
  - type: markdown
    attributes:
      value: |
        Thanks for your interest in the project and taking the time to fill out this feature report!
  - type: textarea
    id: feature-description
    attributes:
      label: Clear and concise description of the problem
      description: "Explain your use case, context, and rationale behind this feature request. More importantly, what is the **end user experience** you are trying to build that led to the need for this feature?"
    validations:
      required: true
  - type: textarea
    id: suggested-solution
    attributes:
      label: Suggested solution
      description: "In module [xy] we could provide following implementation..."
    validations:
      required: true
  - type: textarea
    id: alternative
    attributes:
      label: Alternative
      description: Clear and concise description of any alternative solutions or features you've considered.
  - type: textarea
    id: additional-context
    attributes:
      label: Additional context
      description: Any other context or screenshots about the feature request here.
  - type: checkboxes
    id: checkboxes
    attributes:
      label: Validations
      description: Before submitting the issue, please make sure you do the following
      options:
        - label: Check that there isn't already an issue that request the same feature to avoid creating a duplicate.
          required: true

================================================
FILE: .github/PULL_REQUEST_TEMPLATE.md
================================================
## PR Type
What kind of change does this PR introduce?

<!-- Please check the one that applies to this PR using "x". -->

- [ ] Bugfix
- [ ] Feature
- [ ] Code style update (formatting, local variables)
- [ ] Refactoring (no functional changes, no api changes)
- [ ] Other... Please describe:


## What is the current behavior?
<!-- Please describe the current behavior that you are modifying, or link to a relevant issue. -->

Issue Number: N/A


## What is the new behavior?


## Does this PR introduce a breaking change?

- [ ] Yes
- [ ] No


<!-- If this PR contains a breaking change, please describe the impact and migration path for existing applications below. -->


## Other information

================================================
FILE: .github/agents/ql.agent.md
================================================
---
name: Bug Fixer
description: Fix this issue following our error handling pattern.
---


================================================
FILE: .github/config.yml
================================================
# Comment to be posted to on PRs from first time contributors in your repository
newPRWelcomeComment: |
  💖 Thanks for opening this pull request! 💖
  Please be patient and we will get back to you as soon as we can.
# Comment to be posted to on pull requests merged by a first time user
firstPRMergeComment: >
  Congrats on merging your first pull request! 🎉🎉🎉

================================================
FILE: .github/workflows/build-docker-image.yml
================================================
name: Build And Push Docker Image

on:
  push:
    paths-ignore:
      - "*.md"
    branches:
      - "master"
      - "develop"
    tags:
      - "v*"
  workflow_dispatch:

jobs:
  code_gitlab:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6
        with:
          fetch-depth: 0
      - uses: Yikun/hub-mirror-action@master
        with:
          src: github/whyour
          dst: gitlab/whyour
          dst_key: ${{ secrets.GITLAB_SSH_PK }}
          dst_token: ${{ secrets.GITLAB_TOKEN }}
          static_list: "qinglong"
          force_update: true

  code_gitee:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6
        with:
          fetch-depth: 0
      - uses: Yikun/hub-mirror-action@master
        with:
          src: github/whyour
          dst: gitee/whyour
          dst_key: ${{ secrets.GITLAB_SSH_PK }}
          dst_token: ${{ secrets.GITEE_TOKEN }}
          static_list: "qinglong"
          force_update: true

  build-static:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6
      - uses: pnpm/action-setup@v4
        with:
          version: "8.3.1"

      - uses: actions/setup-node@v6
        with:
          cache: "pnpm"

      - name: build front and back
        run: |
          pnpm install --frozen-lockfile
          pnpm build:front
          pnpm build:back

      - name: copy to static repo
        env:
          GITHUB_REPO: github.com/${{ github.repository_owner }}/qinglong-static
          GITHUB_BRANCH: ${{ github.ref_name }}
          REPO_GITEE: git@gitee.com:whyour/qinglong-static.git
          REPO_GITLAB: git@gitlab.com:whyour/qinglong-static.git
          PRIVATE_KEY: ${{ secrets.GITLAB_SSH_PK }}
        run: |
          mkdir -p tmp
          cd ./tmp
          cp -rf ../static/* ./
          git init -b ${GITHUB_BRANCH} && git add .
          git config --local user.name 'github-actions[bot]'
          git config --local user.email 'github-actions[bot]@users.noreply.github.com'
          git commit --allow-empty -m "copy static at $(date +'%Y-%m-%d %H:%M:%S')"
          git push --force --quiet "https://${{ secrets.API_TOKEN }}@${GITHUB_REPO}.git" ${GITHUB_BRANCH}:${GITHUB_BRANCH}

  static_gitlab:
    needs: build-static
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6
        with:
          fetch-depth: 0
      - uses: Yikun/hub-mirror-action@master
        with:
          src: github/whyour
          dst: gitlab/whyour
          dst_key: ${{ secrets.GITLAB_SSH_PK }}
          dst_token: ${{ secrets.GITLAB_TOKEN }}
          static_list: "qinglong-static"
          force_update: true

  static_gitee:
    needs: build-static
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6
        with:
          fetch-depth: 0
      - uses: Yikun/hub-mirror-action@master
        with:
          src: github/whyour
          dst: gitee/whyour
          dst_key: ${{ secrets.GITLAB_SSH_PK }}
          dst_token: ${{ secrets.GITEE_TOKEN }}
          static_list: "qinglong-static"
          force_update: true

  build:
    if: ${{ !startsWith(github.ref, 'refs/tags/') }}
    needs: build-static

    runs-on: ubuntu-22.04

    permissions:
      packages: write
      contents: read

    steps:
      - uses: actions/checkout@v6
      - uses: pnpm/action-setup@v4
        with:
          version: "8.3.1"
      - uses: actions/setup-node@v6
        with:
          cache: "pnpm"

      - name: Read version from version.yaml
        id: version
        run: |
          VERSION=$(grep '^version:' version.yaml | awk '{print $2}')
          echo "version=$VERSION" >> $GITHUB_OUTPUT
          echo "Version: $VERSION"

      - name: Setup timezone
        uses: szenius/set-timezone@v2.0
        with:
          timezoneLinux: Asia/Shanghai

      - name: Login to DockerHub
        uses: docker/login-action@v3
        with:
          username: ${{ secrets.DOCKER_USERNAME }}
          password: ${{ secrets.DOCKER_PASSWORD }}

      - name: Login to GHCR
        uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.repository_owner }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Extract metadata (tags, labels) for Docker
        id: meta
        uses: docker/metadata-action@v5
        with:
          images: |
            ${{ github.repository }}
            ghcr.io/${{ github.repository }}
          flavor: |
            latest=false
          tags: |
            type=ref,event=branch,enable=${{ github.ref == format('refs/heads/{0}', 'develop') }}
            type=raw,value=latest,enable=${{ github.ref == format('refs/heads/{0}', 'master') }}
            type=raw,value=${{ steps.version.outputs.version }},enable=${{ github.ref == format('refs/heads/{0}', 'master') }}
            type=semver,pattern={{version}}

      - name: Set up QEMU
        uses: docker/setup-qemu-action@v3

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3

      - name: Build and push
        id: docker_build
        uses: docker/build-push-action@v6
        with:
          build-args: |
            MAINTAINER=${{ github.repository_owner }}
            QL_BRANCH=${{ github.ref_name }}
            SOURCE_COMMIT=${{ github.sha }}
          network: host
          # linux/s390x npm 暂不可用
          platforms: linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64,linux/ppc64le,linux/386
          context: .
          file: ./docker/Dockerfile
          push: true
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}
          cache-from: type=registry,ref=whyour/qinglong:cache
          cache-to: type=registry,ref=whyour/qinglong:cache,mode=max

      - name: Image digest
        run: |
          echo ${{ steps.docker_build.outputs.digest }}

  build310:
    if: ${{ github.ref_name == 'master' }}
    needs: build-static

    runs-on: ubuntu-22.04

    permissions:
      packages: write
      contents: read

    steps:
      - uses: actions/checkout@v6
      - uses: pnpm/action-setup@v4
        with:
          version: "8.3.1"
      - uses: actions/setup-node@v6
        with:
          cache: "pnpm"

      - name: Read version from version.yaml
        id: version
        run: |
          VERSION=$(grep '^version:' version.yaml | awk '{print $2}')
          echo "version=$VERSION" >> $GITHUB_OUTPUT
          echo "Version: $VERSION"

      - name: Setup timezone
        uses: szenius/set-timezone@v2.0
        with:
          timezoneLinux: Asia/Shanghai

      - name: Login to DockerHub
        uses: docker/login-action@v3
        with:
          username: ${{ secrets.DOCKER_USERNAME }}
          password: ${{ secrets.DOCKER_PASSWORD }}

      - name: Login to GHCR
        uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.repository_owner }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Set up QEMU
        uses: docker/setup-qemu-action@v3

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3

      - name: Build and push python3.10
        id: docker_build_310
        uses: docker/build-push-action@v6
        with:
          build-args: |
            MAINTAINER=${{ github.repository_owner }}
            QL_BRANCH=${{ github.ref_name }}
            SOURCE_COMMIT=${{ github.sha }}
          network: host
          # linux/s390x npm 暂不可用
          platforms: linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64,linux/ppc64le,linux/386
          context: .
          file: ./docker/310.Dockerfile
          push: true
          tags: |
            whyour/qinglong:python3.10
            whyour/qinglong:${{ steps.version.outputs.version }}-python3.10
          cache-from: type=registry,ref=whyour/qinglong:cache-python3.10
          cache-to: type=registry,ref=whyour/qinglong:cache-python3.10,mode=max

      - name: Image digest
        run: |
          echo ${{ steps.docker_build_310.outputs.digest }}


================================================
FILE: .gitignore
================================================
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# dependencies
/node_modules
/npm-debug.log*
/yarn-error.log
/yarn.lock
/package-lock.json

# production
/static
/data

# misc
.DS_Store

# umi
/src/.umi
/src/.umi-production
/src/.umi-test
/.env.local
.env
.history
.version.ts
/.tmp
__pycache__
/shell/preload/env.*
/shell/preload/notify.*
/shell/preload/*-notify.json
/shell/preload/__ql_notify__.*


================================================
FILE: .npmrc
================================================
strict-peer-dependencies=false

================================================
FILE: .prettierignore
================================================
**/*.md
**/*.svg
**/*.ejs
**/*.html
/.umi
/.umi-production
/.umi-test
/.history
/.tmp
/node_modules
npm-debug.log*
yarn-error.log
yarn.lock
package-lock.json
/static
/data
DS_Store
/src/.umi
/src/.umi-production
/src/.umi-test
.env.local
.env
version.ts
/.tmp

================================================
FILE: .prettierrc
================================================
{
  "singleQuote": true,
  "trailingComma": "all",
  "printWidth": 80,
  "overrides": [
    {
      "files": ".prettierrc",
      "options": { "parser": "json" }
    }
  ]
}


================================================
FILE: .umirc.ts
================================================
import { defineConfig } from '@umijs/max';
const CompressionPlugin = require('compression-webpack-plugin');

const baseUrl = process.env.QlBaseUrl || '/';
export default defineConfig({
  hash: true,
  jsMinifier: 'terser',
  antd: {},
  locale: {
    antd: true,
    title: true,
    baseNavigator: true,
  },
  outputPath: 'static/dist',
  fastRefresh: true,
  favicons: [`https://qn.whyour.cn/favicon.svg`],
  publicPath: process.env.NODE_ENV === 'production' ? './' : '/',
  proxy: {
    [`${baseUrl}api`]: {
      target: 'http://127.0.0.1:5700/',
      changeOrigin: true,
      ws: true,
      pathRewrite: { [`^${baseUrl}api`]: '/api' },
    },
  },
  chainWebpack: ((config: any) => {
    config.plugin('compression-webpack-plugin').use(
      new CompressionPlugin({
        algorithm: 'gzip',
        test: new RegExp('\\.(js|css)$'),
        threshold: 10240,
        minRatio: 0.6,
      }),
    );
  }) as any,
  headScripts: [`./api/env.js`],
  copy: [
    {
      from: 'node_modules/monaco-editor/min/vs',
      to: 'static/dist/monaco-editor/min/vs',
    },
  ],
  npmClient: 'pnpm',
});


================================================
FILE: LICENSE
================================================
                              Apache License
                        Version 2.0, January 2004
                    http://www.apache.org/licenses/

TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION

1. Definitions.

  "License" shall mean the terms and conditions for use, reproduction,
  and distribution as defined by Sections 1 through 9 of this document.

  "Licensor" shall mean the copyright owner or entity authorized by
  the copyright owner that is granting the License.

  "Legal Entity" shall mean the union of the acting entity and all
  other entities that control, are controlled by, or are under common
  control with that entity. For the purposes of this definition,
  "control" means (i) the power, direct or indirect, to cause the
  direction or management of such entity, whether by contract or
  otherwise, or (ii) ownership of fifty percent (50%) or more of the
  outstanding shares, or (iii) beneficial ownership of such entity.

  "You" (or "Your") shall mean an individual or Legal Entity
  exercising permissions granted by this License.

  "Source" form shall mean the preferred form for making modifications,
  including but not limited to software source code, documentation
  source, and configuration files.

  "Object" form shall mean any form resulting from mechanical
  transformation or translation of a Source form, including but
  not limited to compiled object code, generated documentation,
  and conversions to other media types.

  "Work" shall mean the work of authorship, whether in Source or
  Object form, made available under the License, as indicated by a
  copyright notice that is included in or attached to the work
  (an example is provided in the Appendix below).

  "Derivative Works" shall mean any work, whether in Source or Object
  form, that is based on (or derived from) the Work and for which the
  editorial revisions, annotations, elaborations, or other modifications
  represent, as a whole, an original work of authorship. For the purposes
  of this License, Derivative Works shall not include works that remain
  separable from, or merely link (or bind by name) to the interfaces of,
  the Work and Derivative Works thereof.

  "Contribution" shall mean any work of authorship, including
  the original version of the Work and any modifications or additions
  to that Work or Derivative Works thereof, that is intentionally
  submitted to Licensor for inclusion in the Work by the copyright owner
  or by an individual or Legal Entity authorized to submit on behalf of
  the copyright owner. For the purposes of this definition, "submitted"
  means any form of electronic, verbal, or written communication sent
  to the Licensor or its representatives, including but not limited to
  communication on electronic mailing lists, source code control systems,
  and issue tracking systems that are managed by, or on behalf of, the
  Licensor for the purpose of discussing and improving the Work, but
  excluding communication that is conspicuously marked or otherwise
  designated in writing by the copyright owner as "Not a Contribution."

  "Contributor" shall mean Licensor and any individual or Legal Entity
  on behalf of whom a Contribution has been received by Licensor and
  subsequently incorporated within the Work.

2. Grant of Copyright License. Subject to the terms and conditions of
  this License, each Contributor hereby grants to You a perpetual,
  worldwide, non-exclusive, no-charge, royalty-free, irrevocable
  copyright license to reproduce, prepare Derivative Works of,
  publicly display, publicly perform, sublicense, and distribute the
  Work and such Derivative Works in Source or Object form.

3. Grant of Patent License. Subject to the terms and conditions of
  this License, each Contributor hereby grants to You a perpetual,
  worldwide, non-exclusive, no-charge, royalty-free, irrevocable
  (except as stated in this section) patent license to make, have made,
  use, offer to sell, sell, import, and otherwise transfer the Work,
  where such license applies only to those patent claims licensable
  by such Contributor that are necessarily infringed by their
  Contribution(s) alone or by combination of their Contribution(s)
  with the Work to which such Contribution(s) was submitted. If You
  institute patent litigation against any entity (including a
  cross-claim or counterclaim in a lawsuit) alleging that the Work
  or a Contribution incorporated within the Work constitutes direct
  or contributory patent infringement, then any patent licenses
  granted to You under this License for that Work shall terminate
  as of the date such litigation is filed.

4. Redistribution. You may reproduce and distribute copies of the
  Work or Derivative Works thereof in any medium, with or without
  modifications, and in Source or Object form, provided that You
  meet the following conditions:

  (a) You must give any other recipients of the Work or
      Derivative Works a copy of this License; and

  (b) You must cause any modified files to carry prominent notices
      stating that You changed the files; and

  (c) You must retain, in the Source form of any Derivative Works
      that You distribute, all copyright, patent, trademark, and
      attribution notices from the Source form of the Work,
      excluding those notices that do not pertain to any part of
      the Derivative Works; and

  (d) If the Work includes a "NOTICE" text file as part of its
      distribution, then any Derivative Works that You distribute must
      include a readable copy of the attribution notices contained
      within such NOTICE file, excluding those notices that do not
      pertain to any part of the Derivative Works, in at least one
      of the following places: within a NOTICE text file distributed
      as part of the Derivative Works; within the Source form or
      documentation, if provided along with the Derivative Works; or,
      within a display generated by the Derivative Works, if and
      wherever such third-party notices normally appear. The contents
      of the NOTICE file are for informational purposes only and
      do not modify the License. You may add Your own attribution
      notices within Derivative Works that You distribute, alongside
      or as an addendum to the NOTICE text from the Work, provided
      that such additional attribution notices cannot be construed
      as modifying the License.

  You may add Your own copyright statement to Your modifications and
  may provide additional or different license terms and conditions
  for use, reproduction, or distribution of Your modifications, or
  for any such Derivative Works as a whole, provided Your use,
  reproduction, and distribution of the Work otherwise complies with
  the conditions stated in this License.

5. Submission of Contributions. Unless You explicitly state otherwise,
  any Contribution intentionally submitted for inclusion in the Work
  by You to the Licensor shall be under the terms and conditions of
  this License, without any additional terms or conditions.
  Notwithstanding the above, nothing herein shall supersede or modify
  the terms of any separate license agreement you may have executed
  with Licensor regarding such Contributions.

6. Trademarks. This License does not grant permission to use the trade
  names, trademarks, service marks, or product names of the Licensor,
  except as required for reasonable and customary use in describing the
  origin of the Work and reproducing the content of the NOTICE file.

7. Disclaimer of Warranty. Unless required by applicable law or
  agreed to in writing, Licensor provides the Work (and each
  Contributor provides its Contributions) on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
  implied, including, without limitation, any warranties or conditions
  of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
  PARTICULAR PURPOSE. You are solely responsible for determining the
  appropriateness of using or redistributing the Work and assume any
  risks associated with Your exercise of permissions under this License.

8. Limitation of Liability. In no event and under no legal theory,
  whether in tort (including negligence), contract, or otherwise,
  unless required by applicable law (such as deliberate and grossly
  negligent acts) or agreed to in writing, shall any Contributor be
  liable to You for damages, including any direct, indirect, special,
  incidental, or consequential damages of any character arising as a
  result of this License or out of the use or inability to use the
  Work (including but not limited to damages for loss of goodwill,
  work stoppage, computer failure or malfunction, or any and all
  other commercial damages or losses), even if such Contributor
  has been advised of the possibility of such damages.

9. Accepting Warranty or Additional Liability. While redistributing
  the Work or Derivative Works thereof, You may choose to offer,
  and charge a fee for, acceptance of support, warranty, indemnity,
  or other liability obligations and/or rights consistent with this
  License. However, in accepting such obligations, You may act only
  on Your own behalf and on Your sole responsibility, not on behalf
  of any other Contributor, and only if You agree to indemnify,
  defend, and hold each Contributor harmless for any liability
  incurred by, or claims asserted against, such Contributor by reason
  of your accepting any such warranty or additional liability.

END OF TERMS AND CONDITIONS

APPENDIX: How to apply the Apache License to your work.

  To apply the Apache License to your work, attach the following
  boilerplate notice, with the fields enclosed by brackets "{}"
  replaced with your own identifying information. (Don't include
  the brackets!)  The text should be enclosed in the appropriate
  comment syntax for the file format. We also recommend that a
  file or class name and description of purpose be included on the
  same "printed page" as the copyright notice for easier
  identification within third-party archives.

Copyright 2021 WHYOUR <https://github.com/whyour>.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

================================================
FILE: README-en.md
================================================
<div align="center">
<img width="100" src="https://user-images.githubusercontent.com/22700758/191449379-f9f56204-0e31-4a16-be5a-331f52696a73.png">

<h1 align="center">Qinglong</h1>

[简体中文](./README.md) | English

Timed task management platform supporting Python3, JavaScript, Shell, Typescript

[![npm version][npm-version-image]][npm-version-url] [![docker pulls][docker-pulls-image]][docker-pulls-url] [![docker stars][docker-stars-image]][docker-stars-url] [![docker image size][docker-image-size-image]][docker-image-size-url]

[npm-version-image]: https://img.shields.io/npm/v/@whyour/qinglong?style=flat
[npm-version-url]: https://www.npmjs.com/package/@whyour/qinglong?activeTab=readme
[docker-pulls-image]: https://img.shields.io/docker/pulls/whyour/qinglong?style=flat
[docker-pulls-url]: https://hub.docker.com/r/whyour/qinglong
[docker-stars-image]: https://img.shields.io/docker/stars/whyour/qinglong?style=flat
[docker-stars-url]: https://hub.docker.com/r/whyour/qinglong
[docker-image-size-image]: https://img.shields.io/docker/image-size/whyour/qinglong?style=flat
[docker-image-size-url]: https://hub.docker.com/r/whyour/qinglong

[Demo](http://demo.qinglong.online:4433/) / [Issues](https://github.com/whyour/qinglong/issues) / [Telegram Channel](https://t.me/jiao_long) / [Buy Me a Coffee](https://www.buymeacoffee.com/qinglong)

[演示](http://demo.qinglong.online:4433/) / [反馈](https://github.com/whyour/qinglong/issues) / [Telegram 频道](https://t.me/jiao_long) / [打赏开发者](https://user-images.githubusercontent.com/22700758/244744295-29cd0cd1-c8bb-4ea1-adf6-29bd390ad4dd.jpg)
</div>

![cover](https://user-images.githubusercontent.com/22700758/244847235-8dc1ca21-e03f-4606-9458-0541fab60413.png)

## Features

- Support for multiple scripting languages (python3, javaScript, shell, typescript)
- Support online management of scripts, environment variables, configuration files
- Support online view task log
- Support second-level task setting
- Support system level notification
- Support dark mode
- Support cell phone operation

## Version

### docker

The `latest` image is built on `alpine` and the `debian` image is built on `debian-slim`. If you need to use a dependency that is not supported by `alpine`, it is recommended that you use the `debian` image.

**⚠️ Important**: If you need to run Docker as a **non-root user**, please use the `debian` image. Alpine's `crond` requires root privileges.

```bash
docker pull whyour/qinglong:latest
docker pull whyour/qinglong:debian
```

### npm

The npm version supports `debian/ubuntu/alpine` systems and requires `node/npm/python3/pip3/pnpm` to be installed.

```bash
npm i @whyour/qinglong
```

## Deployment

[View Documentation](https://qinglong.online/guide/getting-started/installation-guide)

## Built-in API

[View Documentation](https://qinglong.online/guide/user-guide/built-in-api)

## Built-in commands

[View Documentation](https://qinglong.online/guide/user-guide/basic-explanation)

## Development

```bash
git clone https://github.com/whyour/qinglong.git
cd qinglong
cp .env.example .env
# Recommended use pnpm https://pnpm.io/zh/installation
npm install -g pnpm@8.3.1
pnpm install
pnpm start
```

Open your browser and visit <http://127.0.0.1:5700>

## Links

- [nevinee](https://gitee.com/evine)
- [crontab-ui](https://github.com/alseambusher/crontab-ui)
- [Ant Design](https://ant.design)
- [Ant Design Pro](https://pro.ant.design/)
- [Umijs](https://umijs.org)
- [darkreader](https://github.com/darkreader/darkreader)
- [admin-server](https://github.com/sunpu007/admin-server)

## Name Origin

The Green Dragon, also known as the Canglong, is one of the four elephants and one of the [four spirits of the heavens](https://zh.wikipedia.org/wiki/%E5%A4%A9%E4%B9%8B%E5%9B%9B%E7%81%B5) in traditional Chinese culture. According to the Five Elements, it is a spirit animal representing the East as a green dragon, the five elements are wood, and the season represented is spring, with the eight trigrams dominating vibration. Like the Ying Long, the Cang Long has feathered wings. According to the Zhang Guo Xing Zong (Zhang Guo Xing Zong), "a true dragon is one that has complementary wings".

In the Book of the Later Han Dynasty (後漢書-律曆志下), it is written: "The sun is in the sky, a cold and a summer, the four seasons are ready, all things are changed, the regency moves, and the green dragon moves to the star, which is called the year. (The Year of the Star)

Among the [twenty-eight Chinese constellations](https://zh.wikipedia.org/wiki/%E4%BA%8C%E5%8D%81%E5%85%AB%E5%AE%BF), the Green Dragon is the generic name for the seven eastern constellations (Horn, Hyper, Diao, Fang, Heart, Tail and Minchi). It is known in Taoism as "Mengzhang" and in different Taoist scriptures as "Dijun", "Shengjian", "Shenjian" and He is also known in different Daoist scriptures as "Dijun", "Shengjun", "Shenjun" and "Ghost Catcher"[1], and is the guardian deity of Daoism, together with the White Tiger Supervisor of Soldiers.


================================================
FILE: README.md
================================================
<div align="center">
<img width="100" src="https://user-images.githubusercontent.com/22700758/191449379-f9f56204-0e31-4a16-be5a-331f52696a73.png">

<h1 align="center">青龙</h1>

简体中文 | [English](./README-en.md)

支持 Python3、JavaScript、Shell、Typescript 的定时任务管理平台

Timed task management platform supporting Python3, JavaScript, Shell, Typescript

[![npm version][npm-version-image]][npm-version-url] [![docker pulls][docker-pulls-image]][docker-pulls-url] [![docker stars][docker-stars-image]][docker-stars-url] [![docker image size][docker-image-size-image]][docker-image-size-url]

[npm-version-image]: https://img.shields.io/npm/v/@whyour/qinglong?style=flat
[npm-version-url]: https://www.npmjs.com/package/@whyour/qinglong?activeTab=readme
[docker-pulls-image]: https://img.shields.io/docker/pulls/whyour/qinglong?style=flat
[docker-pulls-url]: https://hub.docker.com/r/whyour/qinglong
[docker-stars-image]: https://img.shields.io/docker/stars/whyour/qinglong?style=flat
[docker-stars-url]: https://hub.docker.com/r/whyour/qinglong
[docker-image-size-image]: https://img.shields.io/docker/image-size/whyour/qinglong?style=flat
[docker-image-size-url]: https://hub.docker.com/r/whyour/qinglong

[Demo](http://demo.qinglong.online:4433/) / [Issues](https://github.com/whyour/qinglong/issues) / [Telegram Channel](https://t.me/jiao_long) / [Buy Me a Coffee](https://www.buymeacoffee.com/qinglong)

[演示](http://demo.qinglong.online:4433/) / [反馈](https://github.com/whyour/qinglong/issues) / [Telegram 频道](https://t.me/jiao_long) / [打赏开发者](https://user-images.githubusercontent.com/22700758/244744295-29cd0cd1-c8bb-4ea1-adf6-29bd390ad4dd.jpg)
</div>

![cover](https://user-images.githubusercontent.com/22700758/244847235-8dc1ca21-e03f-4606-9458-0541fab60413.png)

## 功能

- 支持多种脚本语言(python3、javaScript、shell、typescript)
- 支持在线管理脚本、环境变量、配置文件
- 支持在线查看任务日志
- 支持秒级任务设置
- 支持系统级通知
- 支持暗黑模式
- 支持手机端操作

## 版本

### docker

`latest` 镜像是基于 `alpine` 构建,`debian` 镜像是基于 `debian-slim` 构建。如果需要使用 `alpine` 不支持的依赖,建议使用 `debian` 镜像

**⚠️ 重要提示**: 如果您需要以**非 root 用户**运行 Docker,请使用 `debian` 镜像。Alpine 的 `crond` 需要 root 权限。

```bash
docker pull whyour/qinglong:latest
docker pull whyour/qinglong:debian
```

### npm

npm 版本支持 `debian/ubuntu/alpine` 系统,需要自行安装 `node/npm/python3/pip3/pnpm`

```bash
npm i @whyour/qinglong
```

## 部署

[查看文档](https://qinglong.online/guide/getting-started/installation-guide)

## 内置 API

[查看文档](https://qinglong.online/guide/user-guide/built-in-api)

## 内置命令

[查看文档](https://qinglong.online/guide/user-guide/basic-explanation)

## 开发

```bash
git clone https://github.com/whyour/qinglong.git
cd qinglong
cp .env.example .env
# 推荐使用 pnpm https://pnpm.io/zh/installation
npm install -g pnpm@8.3.1
pnpm install
pnpm start
```

打开你的浏览器,访问 <http://127.0.0.1:5700>

## 链接

- [nevinee](https://gitee.com/evine)
- [crontab-ui](https://github.com/alseambusher/crontab-ui)
- [Ant Design](https://ant.design)
- [Ant Design Pro](https://pro.ant.design/)
- [Umijs](https://umijs.org)
- [darkreader](https://github.com/darkreader/darkreader)
- [admin-server](https://github.com/sunpu007/admin-server)

## 名称来源

青龙,又名苍龙,在中国传统文化中是四象之一、[天之四灵](https://zh.wikipedia.org/wiki/%E5%A4%A9%E4%B9%8B%E5%9B%9B%E7%81%B5)之一,根据五行学说,它是代表东方的灵兽,为青色的龙,五行属木,代表的季节是春季,八卦主震。苍龙与应龙一样,都是身具羽翼。《张果星宗》称“又有辅翼,方为真龙”。

《后汉书·律历志下》记载:日周于天,一寒一暑,四时备成,万物毕改,摄提迁次,青龙移辰,谓之岁。

在中国[二十八宿](https://zh.wikipedia.org/wiki/%E4%BA%8C%E5%8D%81%E5%85%AB%E5%AE%BF)中,青龙是东方七宿(角、亢、氐、房、心、尾、箕)的总称。 在早期星宿信仰中,祂是最尊贵的天神。 但被道教信仰吸纳入其神系后,神格大跌,道教将其称为“孟章”,在不同的道经中有“帝君”、“圣将”、“神将”和“捕鬼将”等称呼,与白虎监兵神君一起,是道教的护卫天神。


================================================
FILE: SECURITY.md
================================================
## Reporting a Vulnerability

To report a vulnerability, please open a private vulnerability report at <https://github.com/whyour/qinglong/security>.

While the discovery of new vulnerabilities is rare, we also recommend always using the latest versions of Qinglong to ensure your application remains as secure as possible.


================================================
FILE: back/api/config.ts
================================================
import { Router, Request, Response, NextFunction } from 'express';
import { Container } from 'typedi';
import { Logger } from 'winston';
import config from '../config';
import * as fs from 'fs/promises';
import { celebrate, Joi } from 'celebrate';
import { join } from 'path';
import { SAMPLE_FILES } from '../config/const';
import ConfigService from '../services/config';
import { writeFileWithLock } from '../shared/utils';
const route = Router();

export default (app: Router) => {
  app.use('/configs', route);

  route.get(
    '/sample',
    async (req: Request, res: Response, next: NextFunction) => {
      try {
        res.send({
          code: 200,
          data: SAMPLE_FILES,
        });
      } catch (e) {
        return next(e);
      }
    },
  );

  route.get(
    '/files',
    async (req: Request, res: Response, next: NextFunction) => {
      const logger: Logger = Container.get('logger');
      try {
        const fileList = await fs.readdir(config.configPath, 'utf-8');
        res.send({
          code: 200,
          data: fileList
            .filter((x) => !config.blackFileList.includes(x))
            .map((x) => {
              return { title: x, value: x };
            }),
        });
      } catch (e) {
        return next(e);
      }
    },
  );

  route.get(
    '/detail',
    async (req: Request, res: Response, next: NextFunction) => {
      try {
        const configService = Container.get(ConfigService);
        await configService.getFile(req.query.path as string, res);
      } catch (e) {
        return next(e);
      }
    },
  );

  route.post(
    '/save',
    celebrate({
      body: Joi.object({
        name: Joi.string().required(),
        content: Joi.string().allow('').optional(),
      }),
    }),
    async (req: Request, res: Response, next: NextFunction) => {
      const logger: Logger = Container.get('logger');
      try {
        const { name, content } = req.body;
        if (config.blackFileList.includes(name)) {
          res.send({ code: 403, message: '文件无法访问' });
        }
        let path = join(config.configPath, name);
        if (name.startsWith('data/scripts/')) {
          path = join(config.rootPath, name);
        }
        await writeFileWithLock(path, content);
        res.send({ code: 200, message: '保存成功' });
      } catch (e) {
        return next(e);
      }
    },
  );

  route.get(
    '/:file',
    async (req: Request, res: Response, next: NextFunction) => {
      try {
        const configService = Container.get(ConfigService);
        await configService.getFile(req.params.file, res);
      } catch (e) {
        return next(e);
      }
    },
  );
};


================================================
FILE: back/api/cron.ts
================================================
import { Router, Request, Response, NextFunction } from 'express';
import { Container } from 'typedi';
import { Logger } from 'winston';
import CronService from '../services/cron';
import CronViewService from '../services/cronView';
import { celebrate, Joi } from 'celebrate';
import { commonCronSchema } from '../validation/schedule';

const route = Router();

export default (app: Router) => {
  app.use('/crons', route);

  route.get(
    '/views',
    async (req: Request, res: Response, next: NextFunction) => {
      try {
        const cronViewService = Container.get(CronViewService);
        const data = await cronViewService.list();
        return res.send({ code: 200, data });
      } catch (e) {
        return next(e);
      }
    },
  );

  route.post(
    '/views',
    celebrate({
      body: Joi.object({
        name: Joi.string().required(),
        sorts: Joi.array().optional().allow(null),
        filters: Joi.array().optional(),
        filterRelation: Joi.string().optional(),
      }),
    }),
    async (req: Request, res: Response, next: NextFunction) => {
      try {
        const cronViewService = Container.get(CronViewService);
        const data = await cronViewService.create(req.body);
        return res.send({ code: 200, data });
      } catch (e) {
        return next(e);
      }
    },
  );

  route.put(
    '/views',
    celebrate({
      body: Joi.object({
        name: Joi.string().required(),
        id: Joi.number().required(),
        sorts: Joi.array().optional().allow(null),
        filters: Joi.array().optional(),
        filterRelation: Joi.string().optional(),
      }),
    }),
    async (req: Request, res: Response, next: NextFunction) => {
      try {
        const cronViewService = Container.get(CronViewService);
        if (req.body.type === 1) {
          return res.send({ code: 400, message: '参数错误' });
        } else {
          const data = await cronViewService.update(req.body);
          return res.send({ code: 200, data });
        }
      } catch (e) {
        return next(e);
      }
    },
  );

  route.delete(
    '/views',
    celebrate({
      body: Joi.array().items(Joi.number().required()),
    }),
    async (req: Request, res: Response, next: NextFunction) => {
      try {
        const cronViewService = Container.get(CronViewService);
        const data = await cronViewService.remove(req.body);
        return res.send({ code: 200, data });
      } catch (e) {
        return next(e);
      }
    },
  );

  route.put(
    '/views/move',
    celebrate({
      body: Joi.object({
        fromIndex: Joi.number().required(),
        toIndex: Joi.number().required(),
        id: Joi.number().required(),
      }),
    }),
    async (req: Request<{ id: number }>, res: Response, next: NextFunction) => {
      try {
        const cronViewService = Container.get(CronViewService);
        const data = await cronViewService.move(req.body);
        return res.send({ code: 200, data });
      } catch (e) {
        return next(e);
      }
    },
  );

  route.put(
    '/views/disable',
    celebrate({
      body: Joi.array().items(Joi.number().required()),
    }),
    async (req: Request, res: Response, next: NextFunction) => {
      const logger: Logger = Container.get('logger');
      try {
        const cronViewService = Container.get(CronViewService);
        const data = await cronViewService.disabled(req.body);
        return res.send({ code: 200, data });
      } catch (e) {
        return next(e);
      }
    },
  );

  route.put(
    '/views/enable',
    celebrate({
      body: Joi.array().items(Joi.number().required()),
    }),
    async (req: Request, res: Response, next: NextFunction) => {
      const logger: Logger = Container.get('logger');
      try {
        const cronViewService = Container.get(CronViewService);
        const data = await cronViewService.enabled(req.body);
        return res.send({ code: 200, data });
      } catch (e) {
        return next(e);
      }
    },
  );

  route.get('/', async (req: Request, res: Response, next: NextFunction) => {
    const logger: Logger = Container.get('logger');
    try {
      const cronService = Container.get(CronService);
      const data = await cronService.crontabs(req.query as any);
      return res.send({ code: 200, data });
    } catch (e) {
      logger.error('🔥 error: %o', e);
      return next(e);
    }
  });

  route.get(
    '/detail',
    async (req: Request, res: Response, next: NextFunction) => {
      const logger: Logger = Container.get('logger');
      try {
        const cronService = Container.get(CronService);
        const data = await cronService.find(req.query as any);
        return res.send({ code: 200, data });
      } catch (e) {
        logger.error('🔥 error: %o', e);
        return next(e);
      }
    },
  );

  route.post(
    '/',
    celebrate({
      body: Joi.object(commonCronSchema),
    }),
    async (req: Request, res: Response, next: NextFunction) => {
      const logger: Logger = Container.get('logger');
      try {
        const cronService = Container.get(CronService);
        const data = await cronService.create(req.body);
        return res.send({ code: 200, data });
      } catch (e) {
        return next(e);
      }
    },
  );

  route.put(
    '/run',
    celebrate({
      body: Joi.array().items(Joi.number().required()),
    }),
    async (req: Request, res: Response, next: NextFunction) => {
      const logger: Logger = Container.get('logger');
      try {
        const cronService = Container.get(CronService);
        const data = await cronService.run(req.body);
        return res.send({ code: 200, data });
      } catch (e) {
        return next(e);
      }
    },
  );

  route.put(
    '/stop',
    celebrate({
      body: Joi.array().items(Joi.number().required()),
    }),
    async (req: Request, res: Response, next: NextFunction) => {
      const logger: Logger = Container.get('logger');
      try {
        const cronService = Container.get(CronService);
        const data = await cronService.stop(req.body);
        return res.send({ code: 200, data });
      } catch (e) {
        return next(e);
      }
    },
  );

  route.delete(
    '/labels',
    celebrate({
      body: Joi.object({
        ids: Joi.array().items(Joi.number().required()),
        labels: Joi.array().items(Joi.string().required()),
      }),
    }),
    async (req: Request, res: Response, next: NextFunction) => {
      const logger: Logger = Container.get('logger');
      try {
        const cronService = Container.get(CronService);
        const data = await cronService.removeLabels(
          req.body.ids,
          req.body.labels,
        );
        return res.send({ code: 200, data });
      } catch (e) {
        return next(e);
      }
    },
  );

  route.post(
    '/labels',
    celebrate({
      body: Joi.object({
        ids: Joi.array().items(Joi.number().required()),
        labels: Joi.array().items(Joi.string().required()),
      }),
    }),
    async (req: Request, res: Response, next: NextFunction) => {
      const logger: Logger = Container.get('logger');
      try {
        const cronService = Container.get(CronService);
        const data = await cronService.addLabels(req.body.ids, req.body.labels);
        return res.send({ code: 200, data });
      } catch (e) {
        return next(e);
      }
    },
  );

  route.put(
    '/disable',
    celebrate({
      body: Joi.array().items(Joi.number().required()),
    }),
    async (req: Request, res: Response, next: NextFunction) => {
      const logger: Logger = Container.get('logger');
      try {
        const cronService = Container.get(CronService);
        const data = await cronService.disabled(req.body);
        return res.send({ code: 200, data });
      } catch (e) {
        return next(e);
      }
    },
  );

  route.put(
    '/enable',
    celebrate({
      body: Joi.array().items(Joi.number().required()),
    }),
    async (req: Request, res: Response, next: NextFunction) => {
      const logger: Logger = Container.get('logger');
      try {
        const cronService = Container.get(CronService);
        const data = await cronService.enabled(req.body);
        return res.send({ code: 200, data });
      } catch (e) {
        return next(e);
      }
    },
  );

  route.get(
    '/:id/log',
    celebrate({
      params: Joi.object({
        id: Joi.number().required(),
      }),
    }),
    async (req: Request<{ id: number }>, res: Response, next: NextFunction) => {
      const logger: Logger = Container.get('logger');
      try {
        const cronService = Container.get(CronService);
        const data = await cronService.log(req.params.id);
        return res.send({ code: 200, data });
      } catch (e) {
        return next(e);
      }
    },
  );

  route.put(
    '/',
    celebrate({
      body: Joi.object({
        ...commonCronSchema,
        id: Joi.number().required(),
      }),
    }),
    async (req: Request, res: Response, next: NextFunction) => {
      const logger: Logger = Container.get('logger');
      try {
        const cronService = Container.get(CronService);
        const data = await cronService.update(req.body);
        return res.send({ code: 200, data });
      } catch (e) {
        return next(e);
      }
    },
  );

  route.delete(
    '/',
    celebrate({
      body: Joi.array().items(Joi.number().required()),
    }),
    async (req: Request, res: Response, next: NextFunction) => {
      const logger: Logger = Container.get('logger');
      try {
        const cronService = Container.get(CronService);
        const data = await cronService.remove(req.body);
        return res.send({ code: 200, data });
      } catch (e) {
        return next(e);
      }
    },
  );

  route.put(
    '/pin',
    celebrate({
      body: Joi.array().items(Joi.number().required()),
    }),
    async (req: Request, res: Response, next: NextFunction) => {
      const logger: Logger = Container.get('logger');
      try {
        const cronService = Container.get(CronService);
        const data = await cronService.pin(req.body);
        return res.send({ code: 200, data });
      } catch (e) {
        return next(e);
      }
    },
  );

  route.put(
    '/unpin',
    celebrate({
      body: Joi.array().items(Joi.number().required()),
    }),
    async (req: Request, res: Response, next: NextFunction) => {
      const logger: Logger = Container.get('logger');
      try {
        const cronService = Container.get(CronService);
        const data = await cronService.unPin(req.body);
        return res.send({ code: 200, data });
      } catch (e) {
        return next(e);
      }
    },
  );

  route.get(
    '/import',
    async (req: Request, res: Response, next: NextFunction) => {
      const logger: Logger = Container.get('logger');
      try {
        const cronService = Container.get(CronService);
        const data = await cronService.importCrontab();
        return res.send({ code: 200, data });
      } catch (e) {
        return next(e);
      }
    },
  );

  route.get(
    '/:id',
    celebrate({
      params: Joi.object({
        id: Joi.number().required(),
      }),
    }),
    async (req: Request<{ id: number }>, res: Response, next: NextFunction) => {
      const logger: Logger = Container.get('logger');
      try {
        const cronService = Container.get(CronService);
        const data = await cronService.getDb({ id: req.params.id });
        return res.send({ code: 200, data });
      } catch (e) {
        return next(e);
      }
    },
  );

  route.put(
    '/status',
    celebrate({
      body: Joi.object({
        ids: Joi.array().items(Joi.number().required()),
        status: Joi.string().required(),
        pid: Joi.string().optional().allow(null),
        log_path: Joi.string().optional().allow(null),
        last_running_time: Joi.number().optional().allow(null),
        last_execution_time: Joi.number().optional().allow(null),
      }),
    }),
    async (req: Request, res: Response, next: NextFunction) => {
      try {
        const cronService = Container.get(CronService);
        const data = await cronService.status({
          ...req.body,
          status: req.body.status ? parseInt(req.body.status) : undefined,
          pid: req.body.pid ? parseInt(req.body.pid) : undefined,
        });
        return res.send({ code: 200, data });
      } catch (e) {
        return next(e);
      }
    },
  );

  route.get(
    '/:id/logs',
    celebrate({
      params: Joi.object({
        id: Joi.number().required(),
      }),
    }),
    async (req: Request<{ id: number }>, res: Response, next: NextFunction) => {
      const logger: Logger = Container.get('logger');
      try {
        const cronService = Container.get(CronService);
        const data = await cronService.logs(req.params.id);
        return res.send({ code: 200, data });
      } catch (e) {
        return next(e);
      }
    },
  );
};


================================================
FILE: back/api/dependence.ts
================================================
import { Router, Request, Response, NextFunction } from 'express';
import { Container } from 'typedi';
import DependenceService from '../services/dependence';
import { Logger } from 'winston';
import { celebrate, Joi } from 'celebrate';
const route = Router();

export default (app: Router) => {
  app.use('/dependencies', route);

  route.get(
    '/',
    celebrate({
      query: 
        Joi.object({
          searchValue: Joi.string().optional().allow(''),
          type: Joi.string().optional().allow(''),
          status: Joi.string().optional().allow(''),
        }).unknown(true),
    }),
    async (req: Request, res: Response, next: NextFunction) => {
      const logger: Logger = Container.get('logger');
      try {
        const dependenceService = Container.get(DependenceService);
        const data = await dependenceService.dependencies(req.query as any);
        return res.send({ code: 200, data });
      } catch (e) {
        logger.error('🔥 error: %o', e);
        return next(e);
      }
    },
  );

  route.post(
    '/',
    celebrate({
      body: Joi.array().items(
        Joi.object({
          name: Joi.string().required(),
          type: Joi.number().required(),
          remark: Joi.string().optional().allow(''),
        }),
      ),
    }),
    async (req: Request, res: Response, next: NextFunction) => {
      try {
        const dependenceService = Container.get(DependenceService);
        const data = await dependenceService.create(req.body);
        return res.send({ code: 200, data });
      } catch (e) {
        return next(e);
      }
    },
  );

  route.put(
    '/',
    celebrate({
      body: Joi.object({
        name: Joi.string().required(),
        id: Joi.number().required(),
        type: Joi.number().required(),
        remark: Joi.string().optional().allow(''),
      }),
    }),
    async (req: Request, res: Response, next: NextFunction) => {
      try {
        const dependenceService = Container.get(DependenceService);
        const data = await dependenceService.update(req.body);
        return res.send({ code: 200, data });
      } catch (e) {
        return next(e);
      }
    },
  );

  route.delete(
    '/',
    celebrate({
      body: Joi.array().items(Joi.number().required()),
    }),
    async (req: Request, res: Response, next: NextFunction) => {
      try {
        const dependenceService = Container.get(DependenceService);
        const data = await dependenceService.remove(req.body);
        return res.send({ code: 200, data });
      } catch (e) {
        return next(e);
      }
    },
  );

  route.delete(
    '/force',
    celebrate({
      body: Joi.array().items(Joi.number().required()),
    }),
    async (req: Request, res: Response, next: NextFunction) => {
      try {
        const dependenceService = Container.get(DependenceService);
        const data = await dependenceService.remove(req.body, true);
        return res.send({ code: 200, data });
      } catch (e) {
        return next(e);
      }
    },
  );

  route.get(
    '/:id',
    celebrate({
      params: Joi.object({
        id: Joi.number().required(),
      }),
    }),
    async (req: Request<{ id: number }>, res: Response, next: NextFunction) => {
      try {
        const dependenceService = Container.get(DependenceService);
        const data = await dependenceService.getDb({ id: req.params.id });
        return res.send({ code: 200, data });
      } catch (e) {
        return next(e);
      }
    },
  );

  route.put(
    '/reinstall',
    celebrate({
      body: Joi.array().items(Joi.number().required()),
    }),
    async (req: Request, res: Response, next: NextFunction) => {
      try {
        const dependenceService = Container.get(DependenceService);
        const data = await dependenceService.reInstall(req.body);
        return res.send({ code: 200, data });
      } catch (e) {
        return next(e);
      }
    },
  );

  route.put(
    '/cancel',
    celebrate({
      body: Joi.array().items(Joi.number().required()),
    }),
    async (req: Request, res: Response, next: NextFunction) => {
      try {
        const dependenceService = Container.get(DependenceService);
        await dependenceService.cancel(req.body);
        return res.send({ code: 200 });
      } catch (e) {
        return next(e);
      }
    },
  );
};


================================================
FILE: back/api/env.ts
================================================
import { Joi, celebrate } from 'celebrate';
import { NextFunction, Request, Response, Router } from 'express';
import fs from 'fs';
import multer from 'multer';
import { Container } from 'typedi';
import { Logger } from 'winston';
import config from '../config';
import { safeJSONParse } from '../config/util';
import EnvService from '../services/env';
const route = Router();

const storage = multer.diskStorage({
  destination: function (req, file, cb) {
    cb(null, config.scriptPath);
  },
  filename: function (req, file, cb) {
    cb(null, file.originalname);
  },
});
const upload = multer({ storage: storage });

export default (app: Router) => {
  app.use('/envs', route);

  route.get('/', async (req: Request, res: Response, next: NextFunction) => {
    const logger: Logger = Container.get('logger');
    try {
      const envService = Container.get(EnvService);
      const data = await envService.envs(req.query.searchValue as string);
      return res.send({ code: 200, data });
    } catch (e) {
      logger.error('🔥 error: %o', e);
      return next(e);
    }
  });

  route.post(
    '/',
    celebrate({
      body: Joi.array().items(
        Joi.object({
          value: Joi.string().required(),
          name: Joi.string()
            .required()
            .pattern(/^[a-zA-Z_][0-9a-zA-Z_]*$/),
          remarks: Joi.string().optional().allow(''),
        }),
      ),
    }),
    async (req: Request, res: Response, next: NextFunction) => {
      const logger: Logger = Container.get('logger');
      try {
        const envService = Container.get(EnvService);
        if (!req.body?.length) {
          return res.send({ code: 400, message: '参数不正确' });
        }
        const data = await envService.create(req.body);
        return res.send({ code: 200, data });
      } catch (e) {
        return next(e);
      }
    },
  );

  route.put(
    '/',
    celebrate({
      body: Joi.object({
        value: Joi.string().required(),
        name: Joi.string().required(),
        remarks: Joi.string().optional().allow('').allow(null),
        id: Joi.number().required(),
      }),
    }),
    async (req: Request, res: Response, next: NextFunction) => {
      const logger: Logger = Container.get('logger');
      try {
        const envService = Container.get(EnvService);
        const data = await envService.update(req.body);
        return res.send({ code: 200, data });
      } catch (e) {
        return next(e);
      }
    },
  );

  route.delete(
    '/',
    celebrate({
      body: Joi.array().items(Joi.number().required()),
    }),
    async (req: Request, res: Response, next: NextFunction) => {
      const logger: Logger = Container.get('logger');
      try {
        const envService = Container.get(EnvService);
        const data = await envService.remove(req.body);
        return res.send({ code: 200, data });
      } catch (e) {
        return next(e);
      }
    },
  );

  route.put(
    '/:id/move',
    celebrate({
      params: Joi.object({
        id: Joi.number().required(),
      }),
      body: Joi.object({
        fromIndex: Joi.number().required(),
        toIndex: Joi.number().required(),
      }),
    }),
    async (req: Request<{ id: number }>, res: Response, next: NextFunction) => {
      try {
        const envService = Container.get(EnvService);
        const data = await envService.move(req.params.id, req.body);
        return res.send({ code: 200, data });
      } catch (e) {
        return next(e);
      }
    },
  );

  route.put(
    '/disable',
    celebrate({
      body: Joi.array().items(Joi.number().required()),
    }),
    async (req: Request, res: Response, next: NextFunction) => {
      const logger: Logger = Container.get('logger');
      try {
        const envService = Container.get(EnvService);
        const data = await envService.disabled(req.body);
        return res.send({ code: 200, data });
      } catch (e) {
        return next(e);
      }
    },
  );

  route.put(
    '/enable',
    celebrate({
      body: Joi.array().items(Joi.number().required()),
    }),
    async (req: Request, res: Response, next: NextFunction) => {
      const logger: Logger = Container.get('logger');
      try {
        const envService = Container.get(EnvService);
        const data = await envService.enabled(req.body);
        return res.send({ code: 200, data });
      } catch (e) {
        return next(e);
      }
    },
  );

  route.put(
    '/name',
    celebrate({
      body: Joi.object({
        ids: Joi.array().items(Joi.number().required()),
        name: Joi.string().required(),
      }),
    }),
    async (req: Request, res: Response, next: NextFunction) => {
      const logger: Logger = Container.get('logger');
      try {
        const envService = Container.get(EnvService);
        const data = await envService.updateNames(req.body);
        return res.send({ code: 200, data });
      } catch (e) {
        return next(e);
      }
    },
  );

  route.get(
    '/:id',
    celebrate({
      params: Joi.object({
        id: Joi.number().required(),
      }),
    }),
    async (req: Request<{ id: number }>, res: Response, next: NextFunction) => {
      const logger: Logger = Container.get('logger');
      try {
        const envService = Container.get(EnvService);
        const data = await envService.getDb({ id: req.params.id });
        return res.send({ code: 200, data });
      } catch (e) {
        return next(e);
      }
    },
  );

  route.put(
    '/pin',
    celebrate({
      body: Joi.array().items(Joi.number().required()),
    }),
    async (req: Request, res: Response, next: NextFunction) => {
      const logger: Logger = Container.get('logger');
      try {
        const envService = Container.get(EnvService);
        const data = await envService.pin(req.body);
        return res.send({ code: 200, data });
      } catch (e) {
        return next(e);
      }
    },
  );

  route.put(
    '/unpin',
    celebrate({
      body: Joi.array().items(Joi.number().required()),
    }),
    async (req: Request, res: Response, next: NextFunction) => {
      const logger: Logger = Container.get('logger');
      try {
        const envService = Container.get(EnvService);
        const data = await envService.unPin(req.body);
        return res.send({ code: 200, data });
      } catch (e) {
        return next(e);
      }
    },
  );

  route.post(
    '/upload',
    upload.single('env'),
    async (req: Request, res: Response, next: NextFunction) => {
      const logger: Logger = Container.get('logger');
      try {
        const envService = Container.get(EnvService);
        const fileContent = await fs.promises.readFile(req!.file!.path, 'utf8');
        const parseContent = safeJSONParse(fileContent);
        const data = Array.isArray(parseContent)
          ? parseContent
          : [parseContent];
        if (data.every((x) => x.name && x.value)) {
          const result = await envService.create(
            data.map((x) => ({
              name: x.name,
              value: x.value,
              remarks: x.remarks,
            })),
          );
          return res.send({ code: 200, data: result });
        } else {
          return res.send({
            code: 400,
            message: '每条数据 name 或者 value 字段不能为空,参考导出文件格式',
          });
        }
      } catch (e) {
        return next(e);
      }
    },
  );
};


================================================
FILE: back/api/health.ts
================================================
import { Router } from 'express';
import Logger from '../loaders/logger';
import { HealthService } from '../services/health';
import Container from 'typedi';
const route = Router();

export default (app: Router) => {
  app.use('/', route);

  route.get('/health', async (req, res) => {
    try {
      const healthService = Container.get(HealthService);
      const health = await healthService.check();
      res.status(200).send({
        code: 200,
        data: health,
      });
    } catch (err: any) {
      Logger.error('Health check failed:', err);
      res.status(500).send({
        code: 500,
        message: 'Health check failed',
        error: err.message,
      });
    }
  });
};


================================================
FILE: back/api/index.ts
================================================
import { Router } from 'express';
import user from './user';
import env from './env';
import config from './config';
import log from './log';
import cron from './cron';
import script from './script';
import open from './open';
import dependence from './dependence';
import system from './system';
import subscription from './subscription';
import update from './update';
import health from './health';

export default () => {
  const app = Router();
  user(app);
  env(app);
  config(app);
  log(app);
  cron(app);
  script(app);
  open(app);
  dependence(app);
  system(app);
  subscription(app);
  update(app);
  health(app);

  return app;
};


================================================
FILE: back/api/log.ts
================================================
import { celebrate, Joi } from 'celebrate';
import { NextFunction, Request, Response, Router } from 'express';
import { Container } from 'typedi';
import { Logger } from 'winston';
import config from '../config';
import {
  getFileContentByName,
  readDirs,
  removeAnsi,
  rmPath,
} from '../config/util';
import LogService from '../services/log';
const route = Router();
const blacklist = ['.tmp'];

export default (app: Router) => {
  app.use('/logs', route);

  route.get('/', async (req: Request, res: Response, next: NextFunction) => {
    const logger: Logger = Container.get('logger');
    try {
      const result = await readDirs(config.logPath, config.logPath, blacklist);
      res.send({
        code: 200,
        data: result,
      });
    } catch (e) {
      logger.error('🔥 error: %o', e);
      return next(e);
    }
  });

  route.get(
    '/detail',
    async (req: Request, res: Response, next: NextFunction) => {
      try {
        const logService = Container.get(LogService);
        const finalPath = logService.checkFilePath(
          (req.query.path as string) || '',
          (req.query.file as string) || '',
        );
        if (!finalPath || blacklist.includes(req.query.path as string)) {
          return res.send({
            code: 403,
            message: '暂无权限',
          });
        }
        const content = await getFileContentByName(finalPath);
        res.send({ code: 200, data: removeAnsi(content) });
      } catch (e) {
        return next(e);
      }
    },
  );

  route.get(
    '/:file',
    async (req: Request, res: Response, next: NextFunction) => {
      try {
        const logService = Container.get(LogService);
        const finalPath = logService.checkFilePath(
          (req.query.path as string) || '',
          (req.params.file as string) || '',
        );
        if (!finalPath || blacklist.includes(req.query.path as string)) {
          return res.send({
            code: 403,
            message: '暂无权限',
          });
        }
        const content = await getFileContentByName(finalPath);
        res.send({ code: 200, data: content });
      } catch (e) {
        return next(e);
      }
    },
  );

  route.delete(
    '/',
    celebrate({
      body: Joi.object({
        filename: Joi.string().required(),
        path: Joi.string().allow(''),
        type: Joi.string().optional(),
      }),
    }),
    async (req: Request, res: Response, next: NextFunction) => {
      try {
        let { filename, path } = req.body as {
          filename: string;
          path: string;
        };
        const logService = Container.get(LogService);
        const finalPath = logService.checkFilePath(path, filename);
        if (!finalPath || blacklist.includes(path)) {
          return res.send({
            code: 403,
            message: '暂无权限',
          });
        }
        await rmPath(finalPath);
        res.send({ code: 200 });
      } catch (e) {
        return next(e);
      }
    },
  );

  route.post(
    '/download',
    celebrate({
      body: Joi.object({
        filename: Joi.string().required(),
        path: Joi.string().allow(''),
      }),
    }),
    async (req: Request, res: Response, next: NextFunction) => {
      try {
        let { filename, path } = req.body as {
          filename: string;
          path: string;
        };
        const logService = Container.get(LogService);
        const filePath = logService.checkFilePath(path, filename);
        if (!filePath) {
          return res.send({
            code: 403,
            message: '暂无权限',
          });
        }
        return res.download(filePath, filename, (err) => {
          if (err) {
            return next(err);
          }
        });
      } catch (e) {
        return next(e);
      }
    },
  );
};


================================================
FILE: back/api/open.ts
================================================
import { Router, Request, Response, NextFunction } from 'express';
import { Container } from 'typedi';
import OpenService from '../services/open';
import { Logger } from 'winston';
import { celebrate, Joi } from 'celebrate';
const route = Router();

export default (app: Router) => {
  app.use('/', route);
  route.get(
    '/apps',
    async (req: Request, res: Response, next: NextFunction) => {
      const logger: Logger = Container.get('logger');
      try {
        const openService = Container.get(OpenService);
        const data = await openService.list();
        return res.send({ code: 200, data });
      } catch (e) {
        return next(e);
      }
    },
  );

  route.post(
    '/apps',
    celebrate({
      body: Joi.object({
        name: Joi.string().optional().allow('').disallow('system'),
        scopes: Joi.array().items(Joi.string().required()),
      }),
    }),
    async (req: Request, res: Response, next: NextFunction) => {
      const logger: Logger = Container.get('logger');
      try {
        const openService = Container.get(OpenService);
        const data = await openService.create(req.body);
        return res.send({ code: 200, data });
      } catch (e) {
        return next(e);
      }
    },
  );

  route.put(
    '/apps',
    celebrate({
      body: Joi.object({
        name: Joi.string().optional().allow(''),
        scopes: Joi.array().items(Joi.string()),
        id: Joi.number().required(),
      }),
    }),
    async (req: Request, res: Response, next: NextFunction) => {
      const logger: Logger = Container.get('logger');
      try {
        const openService = Container.get(OpenService);
        const data = await openService.update(req.body);
        return res.send({ code: 200, data });
      } catch (e) {
        return next(e);
      }
    },
  );

  route.delete(
    '/apps',
    celebrate({
      body: Joi.array().items(Joi.number().required()),
    }),
    async (req: Request, res: Response, next: NextFunction) => {
      const logger: Logger = Container.get('logger');
      try {
        const openService = Container.get(OpenService);
        const data = await openService.remove(req.body);
        return res.send({ code: 200, data });
      } catch (e) {
        return next(e);
      }
    },
  );

  route.put(
    '/apps/:id/reset-secret',
    celebrate({
      params: Joi.object({
        id: Joi.number().required(),
      }),
    }),
    async (req: Request<{ id: number }>, res: Response, next: NextFunction) => {
      const logger: Logger = Container.get('logger');
      try {
        const openService = Container.get(OpenService);
        const data = await openService.resetSecret(req.params.id);
        return res.send({ code: 200, data });
      } catch (e) {
        return next(e);
      }
    },
  );

  route.get(
    '/auth/token',
    celebrate({
      query: {
        client_id: Joi.string().required(),
        client_secret: Joi.string().required(),
      },
    }),
    async (req: Request, res: Response, next: NextFunction) => {
      const logger: Logger = Container.get('logger');
      try {
        const openService = Container.get(OpenService);
        const result = await openService.authToken(req.query as any);
        return res.send(result);
      } catch (e) {
        return next(e);
      }
    },
  );
};


================================================
FILE: back/api/script.ts
================================================
import { fileExist, readDirs, readDir, rmPath, IFile } from '../config/util';
import { Router, Request, Response, NextFunction } from 'express';
import { Container } from 'typedi';
import { Logger } from 'winston';
import config from '../config';
import * as fs from 'fs/promises';
import { celebrate, Joi } from 'celebrate';
import path, { join, parse } from 'path';
import ScriptService from '../services/script';
import multer from 'multer';
import { writeFileWithLock } from '../shared/utils';
const route = Router();

const storage = multer.diskStorage({
  destination: function (req, file, cb) {
    cb(null, config.scriptPath);
  },
  filename: function (req, file, cb) {
    cb(null, file.originalname);
  },
});
const upload = multer({ storage: storage });

export default (app: Router) => {
  app.use('/scripts', route);

  route.get(
    '/',
    celebrate({
      query: Joi.object({
        path: Joi.string().optional().allow(''),
      }).unknown(true),
    }),
    async (req: Request, res: Response, next: NextFunction) => {
      const logger: Logger = Container.get('logger');
      try {
        let result: IFile[] = [];
        const blacklist = [
          'node_modules',
          '.git',
          '.pnpm',
          'pnpm-lock.yaml',
          'yarn.lock',
          'package-lock.json',
        ];
        if (req.query.path) {
          result = await readDir(
            req.query.path as string,
            config.scriptPath,
            blacklist,
          );
        } else {
          result = await readDirs(
            config.scriptPath,
            config.scriptPath,
            blacklist,
            (a, b) => {
              if (a.type === b.type) {
                return a.title.localeCompare(b.title);
              } else {
                return a.type === 'directory' ? -1 : 1;
              }
            },
          );
        }
        res.send({
          code: 200,
          data: result,
        });
      } catch (e) {
        logger.error('🔥 error: %o', e);
        return next(e);
      }
    });

  route.get(
    '/detail',
    celebrate({
      query: Joi.object({
        path: Joi.string().optional().allow(''),
        file: Joi.string().required(),
      }).unknown(true),
    }),
    async (req: Request, res: Response, next: NextFunction) => {
      try {
        const scriptService = Container.get(ScriptService);
        const content = await scriptService.getFile(
          req.query?.path as string || '',
          req.query.file as string,
        );
        res.send({ code: 200, data: content });
      } catch (e) {
        return next(e);
      }
    },
  );

  route.get(
    '/:file',
    celebrate({
      params: Joi.object({
        file: Joi.string().required(),
      }),
      query: Joi.object({
        path: Joi.string().optional().allow(''),
      }).unknown(true),
    }),
    async (req: Request, res: Response, next: NextFunction) => {
      try {
        const scriptService = Container.get(ScriptService);
        const content = await scriptService.getFile(
          req.query?.path as string || '',
          req.params.file,
        );
        res.send({ code: 200, data: content });
      } catch (e) {
        return next(e);
      }
    },
  );

  route.post(
    '/',
    upload.single('file'),
    celebrate({
      body: Joi.object({
        filename: Joi.string().required(),
        path: Joi.string().optional().allow(''),
        content: Joi.string().optional().allow(''),
        originFilename: Joi.string().optional().allow(''),
        directory: Joi.string().optional().allow(''),
        file: Joi.string().optional().allow(''),
      }).unknown(true),
    }),
    async (req: Request, res: Response, next: NextFunction) => {
      try {
        let { filename, path, content, originFilename, directory } =
          req.body as {
            filename: string;
            path: string;
            content: string;
            originFilename: string;
            directory: string;
          };

        if (!path) {
          path = config.scriptPath;
        }
        if (!path.endsWith('/')) {
          path += '/';
        }
        if (!path.startsWith('/')) {
          path = join(config.scriptPath, path);
        }
        if (config.writePathList.every((x) => !path.startsWith(x))) {
          return res.send({
            code: 403,
            message: '暂无权限',
          });
        }

        if (req.file) {
          await fs.rename(req.file.path, join(path, filename));
          return res.send({ code: 200 });
        }

        if (directory) {
          await fs.mkdir(join(path, directory), { recursive: true });
          return res.send({ code: 200 });
        }

        if (!originFilename) {
          originFilename = filename;
        }
        const originFilePath = join(
          path,
          `${originFilename.replace(/\//g, '')}`,
        );
        await fs.mkdir(path, { recursive: true });
        const filePath = join(path, `${filename.replace(/\//g, '')}`);
        const fileExists = await fileExist(filePath);
        if (fileExists) {
          await fs.copyFile(
            originFilePath,
            join(config.bakPath, originFilename.replace(/\//g, '')),
          );
          if (filename !== originFilename) {
            await rmPath(originFilePath);
          }
        }
        await writeFileWithLock(filePath, content);
        return res.send({ code: 200 });
      } catch (e) {
        return next(e);
      }
    },
  );

  route.put(
    '/',
    celebrate({
      body: Joi.object({
        filename: Joi.string().required(),
        path: Joi.string().optional().allow(''),
        content: Joi.string().required().allow(''),
      }),
    }),
    async (req: Request, res: Response, next: NextFunction) => {
      try {
        let { filename, content, path } = req.body as {
          filename: string;
          content: string;
          path: string;
        };
        const scriptService = Container.get(ScriptService);
        const filePath = scriptService.checkFilePath(path, filename);
        if (!filePath) {
          return res.send({
            code: 403,
            message: '暂无权限',
          });
        }
        await writeFileWithLock(filePath, content);
        return res.send({ code: 200 });
      } catch (e) {
        return next(e);
      }
    },
  );

  route.delete(
    '/',
    celebrate({
      body: Joi.object({
        filename: Joi.string().required(),
        path: Joi.string().optional().allow(''),
        type: Joi.string().optional(),
      }),
    }),
    async (req: Request, res: Response, next: NextFunction) => {
      try {
        let { filename, path } = req.body as {
          filename: string;
          path: string;
        };
        if (!path) {
          path = '';
        }
        const scriptService = Container.get(ScriptService);
        const filePath = scriptService.checkFilePath(path, filename);
        if (!filePath) {
          return res.send({
            code: 403,
            message: '暂无权限',
          });
        }
        await rmPath(filePath);
        res.send({ code: 200 });
      } catch (e) {
        return next(e);
      }
    },
  );

  route.post(
    '/download',
    celebrate({
      body: Joi.object({
        filename: Joi.string().required(),
        path: Joi.string().optional().allow(''),
      }),
    }),
    async (req: Request, res: Response, next: NextFunction) => {
      try {
        let { filename, path } = req.body as {
          filename: string;
          path: string;
        };
        if (!path) {
          path = '';
        }
        const scriptService = Container.get(ScriptService);
        const filePath = scriptService.checkFilePath(path, filename);
        if (!filePath) {
          return res.send({
            code: 403,
            message: '暂无权限',
          });
        }
        return res.download(filePath, filename, (err) => {
          if (err) {
            return next(err);
          }
        });
      } catch (e) {
        return next(e);
      }
    },
  );

  route.put(
    '/run',
    celebrate({
      body: Joi.object({
        filename: Joi.string().required(),
        content: Joi.string().optional().allow(''),
        path: Joi.string().optional().allow(''),
      }),
    }),
    async (req: Request, res: Response, next: NextFunction) => {
      const logger: Logger = Container.get('logger');
      try {
        let { filename, content, path } = req.body;
        if (!path) {
          path = '';
        }
        const { name, ext } = parse(filename);
        const filePath = join(config.scriptPath, path, `${name}.swap${ext}`);
        await writeFileWithLock(filePath, content || '');

        const scriptService = Container.get(ScriptService);
        const result = await scriptService.runScript(filePath);
        res.send(result);
      } catch (e) {
        return next(e);
      }
    },
  );

  route.put(
    '/stop',
    celebrate({
      body: Joi.object({
        filename: Joi.string().required(),
        path: Joi.string().optional().allow(''),
        pid: Joi.number().optional().allow(''),
      }),
    }),
    async (req: Request, res: Response, next: NextFunction) => {
      try {
        let { filename, path, pid } = req.body;
        if (!path) {
          path = '';
        }
        const { name, ext } = parse(filename);
        const filePath = join(config.scriptPath, path, `${name}.swap${ext}`);
        const logPath = join(config.logPath, path, `${name}.swap`);

        const scriptService = Container.get(ScriptService);
        const result = await scriptService.stopScript(filePath, pid);
        setTimeout(() => {
          rmPath(logPath);
        }, 3000);
        res.send(result);
      } catch (e) {
        return next(e);
      }
    },
  );

  route.put(
    '/rename',
    celebrate({
      body: Joi.object({
        filename: Joi.string().required(),
        path: Joi.string().allow(''),
        newFilename: Joi.string().required(),
      }),
    }),
    async (req: Request, res: Response, next: NextFunction) => {
      try {
        let { filename, path, newFilename } = req.body as {
          filename: string;
          path: string;
          newFilename: string;
        };
        if (!path) {
          path = '';
        }
        const filePath = join(config.scriptPath, path, filename);
        const newPath = join(config.scriptPath, path, newFilename);
        await fs.rename(filePath, newPath);
        res.send({ code: 200 });
      } catch (e) {
        return next(e);
      }
    },
  );
};


================================================
FILE: back/api/subscription.ts
================================================
import { Router, Request, Response, NextFunction } from 'express';
import { Container } from 'typedi';
import { Logger } from 'winston';
import SubscriptionService from '../services/subscription';
import { celebrate, Joi } from 'celebrate';
import CronExpressionParser from 'cron-parser';
const route = Router();

export default (app: Router) => {
  app.use('/subscriptions', route);

  route.get('/', async (req: Request, res: Response, next: NextFunction) => {
    const logger: Logger = Container.get('logger');
    try {
      const subscriptionService = Container.get(SubscriptionService);
      const data = await subscriptionService.list(
        req.query.searchValue as string,
        req.query.ids as string,
      );
      return res.send({ code: 200, data });
    } catch (e) {
      logger.error('🔥 error: %o', e);
      return next(e);
    }
  });

  route.post(
    '/',
    celebrate({
      body: Joi.object({
        type: Joi.string().required(),
        schedule: Joi.string().optional().allow('').allow(null),
        interval_schedule: Joi.object({
          type: Joi.string().required(),
          value: Joi.number().min(1).required(),
        })
          .optional()
          .allow('')
          .allow(null),
        name: Joi.string().optional().allow('').allow(null),
        url: Joi.string().required(),
        whitelist: Joi.string().optional().allow('').allow(null),
        blacklist: Joi.string().optional().allow('').allow(null),
        branch: Joi.string().optional().allow('').allow(null),
        dependences: Joi.string().optional().allow('').allow(null),
        pull_type: Joi.string().optional().allow('').allow(null),
        pull_option: Joi.object().optional().allow('').allow(null),
        extensions: Joi.string().optional().allow('').allow(null),
        sub_before: Joi.string().optional().allow('').allow(null),
        sub_after: Joi.string().optional().allow('').allow(null),
        schedule_type: Joi.string().required(),
        alias: Joi.string().required(),
        proxy: Joi.string().optional().allow('').allow(null),
        autoAddCron: Joi.boolean().optional().allow('').allow(null),
        autoDelCron: Joi.boolean().optional().allow('').allow(null),
      }),
    }),
    async (req: Request, res: Response, next: NextFunction) => {
      const logger: Logger = Container.get('logger');
      try {
        if (
          !req.body.schedule ||
          CronExpressionParser.parse(req.body.schedule).hasNext()
        ) {
          const subscriptionService = Container.get(SubscriptionService);
          const data = await subscriptionService.create(req.body);
          return res.send({ code: 200, data });
        } else {
          return res.send({ code: 400, message: 'param schedule error' });
        }
      } catch (e) {
        return next(e);
      }
    },
  );

  route.put(
    '/run',
    celebrate({
      body: Joi.array().items(Joi.number().required()),
    }),
    async (req: Request, res: Response, next: NextFunction) => {
      const logger: Logger = Container.get('logger');
      try {
        const subscriptionService = Container.get(SubscriptionService);
        const data = await subscriptionService.run(req.body);
        return res.send({ code: 200, data });
      } catch (e) {
        return next(e);
      }
    },
  );

  route.put(
    '/stop',
    celebrate({
      body: Joi.array().items(Joi.number().required()),
    }),
    async (req: Request, res: Response, next: NextFunction) => {
      const logger: Logger = Container.get('logger');
      try {
        const subscriptionService = Container.get(SubscriptionService);
        const data = await subscriptionService.stop(req.body);
        return res.send({ code: 200, data });
      } catch (e) {
        return next(e);
      }
    },
  );

  route.put(
    '/disable',
    celebrate({
      body: Joi.array().items(Joi.number().required()),
    }),
    async (req: Request, res: Response, next: NextFunction) => {
      const logger: Logger = Container.get('logger');
      try {
        const subscriptionService = Container.get(SubscriptionService);
        const data = await subscriptionService.disabled(req.body);
        return res.send({ code: 200, data });
      } catch (e) {
        return next(e);
      }
    },
  );

  route.put(
    '/enable',
    celebrate({
      body: Joi.array().items(Joi.number().required()),
    }),
    async (req: Request, res: Response, next: NextFunction) => {
      const logger: Logger = Container.get('logger');
      try {
        const subscriptionService = Container.get(SubscriptionService);
        const data = await subscriptionService.enabled(req.body);
        return res.send({ code: 200, data });
      } catch (e) {
        return next(e);
      }
    },
  );

  route.get(
    '/:id/log',
    celebrate({
      params: Joi.object({
        id: Joi.number().required(),
      }),
    }),
    async (req: Request<{ id: number }>, res: Response, next: NextFunction) => {
      const logger: Logger = Container.get('logger');
      try {
        const subscriptionService = Container.get(SubscriptionService);
        const data = await subscriptionService.log(req.params.id);
        return res.send({ code: 200, data });
      } catch (e) {
        return next(e);
      }
    },
  );

  route.put(
    '/',
    celebrate({
      body: Joi.object({
        type: Joi.string().required(),
        schedule: Joi.string().optional().allow('').allow(null),
        interval_schedule: Joi.object().optional().allow('').allow(null),
        name: Joi.string().optional().allow('').allow(null),
        url: Joi.string().required(),
        whitelist: Joi.string().optional().allow('').allow(null),
        blacklist: Joi.string().optional().allow('').allow(null),
        branch: Joi.string().optional().allow('').allow(null),
        dependences: Joi.string().optional().allow('').allow(null),
        pull_type: Joi.string().optional().allow('').allow(null),
        pull_option: Joi.object().optional().allow('').allow(null),
        schedule_type: Joi.string().optional().allow('').allow(null),
        extensions: Joi.string().optional().allow('').allow(null),
        sub_before: Joi.string().optional().allow('').allow(null),
        sub_after: Joi.string().optional().allow('').allow(null),
        alias: Joi.string().required(),
        proxy: Joi.string().optional().allow('').allow(null),
        autoAddCron: Joi.boolean().optional().allow('').allow(null),
        autoDelCron: Joi.boolean().optional().allow('').allow(null),
        id: Joi.number().required(),
      }),
    }),
    async (req: Request, res: Response, next: NextFunction) => {
      const logger: Logger = Container.get('logger');
      try {
        if (
          !req.body.schedule ||
          typeof req.body.schedule === 'object' ||
          CronExpressionParser.parse(req.body.schedule).hasNext()
        ) {
          const subscriptionService = Container.get(SubscriptionService);
          const data = await subscriptionService.update(req.body);
          return res.send({ code: 200, data });
        } else {
          return res.send({ code: 400, message: 'param schedule error' });
        }
      } catch (e) {
        return next(e);
      }
    },
  );

  route.delete(
    '/',
    celebrate({
      body: Joi.array().items(Joi.number().required()),
      query: Joi.object({
        force: Joi.boolean().optional(),
        t: Joi.number(),
      }),
    }),
    async (req: Request, res: Response, next: NextFunction) => {
      const logger: Logger = Container.get('logger');
      try {
        const subscriptionService = Container.get(SubscriptionService);
        const data = await subscriptionService.remove(req.body, req.query);
        return res.send({ code: 200, data });
      } catch (e) {
        return next(e);
      }
    },
  );

  route.get(
    '/:id',
    celebrate({
      params: Joi.object({
        id: Joi.number().required(),
      }),
    }),
    async (req: Request<{ id: number }>, res: Response, next: NextFunction) => {
      const logger: Logger = Container.get('logger');
      try {
        const subscriptionService = Container.get(SubscriptionService);
        const data = await subscriptionService.getDb({ id: req.params.id });
        return res.send({ code: 200, data });
      } catch (e) {
        return next(e);
      }
    },
  );

  route.put(
    '/status',
    celebrate({
      body: Joi.object({
        ids: Joi.array().items(Joi.number().required()),
        status: Joi.string().required(),
        pid: Joi.string().optional(),
        log_path: Joi.string().optional(),
      }),
    }),
    async (req: Request, res: Response, next: NextFunction) => {
      const logger: Logger = Container.get('logger');
      try {
        const subscriptionService = Container.get(SubscriptionService);
        const data = await subscriptionService.status({
          ...req.body,
          status: parseInt(req.body.status),
          pid: parseInt(req.body.pid) || '',
        });
        return res.send({ code: 200, data });
      } catch (e) {
        return next(e);
      }
    },
  );

  route.get(
    '/:id/logs',
    celebrate({
      params: Joi.object({
        id: Joi.number().required(),
      }),
    }),
    async (req: Request<{ id: number }>, res: Response, next: NextFunction) => {
      const logger: Logger = Container.get('logger');
      try {
        const subscriptionService = Container.get(SubscriptionService);
        const data = await subscriptionService.logs(req.params.id);
        return res.send({ code: 200, data });
      } catch (e) {
        return next(e);
      }
    },
  );
};


================================================
FILE: back/api/system.ts
================================================
import { Router, Request, Response, NextFunction } from 'express';
import { Container } from 'typedi';
import { Logger } from 'winston';
import * as fs from 'fs/promises';
import config from '../config';
import SystemService from '../services/system';
import { celebrate, Joi } from 'celebrate';
import UserService from '../services/user';
import {
  getUniqPath,
  handleLogPath,
  parseVersion,
  promiseExec,
} from '../config/util';
import dayjs from 'dayjs';
import multer from 'multer';
import { logStreamManager } from '../shared/logStreamManager';

const route = Router();
const storage = multer.diskStorage({
  destination: function (req, file, cb) {
    cb(null, config.tmpPath);
  },
  filename: function (req, file, cb) {
    cb(null, 'data.tgz');
  },
});
const upload = multer({ storage: storage });

export default (app: Router) => {
  app.use('/system', route);

  route.get('/', async (req: Request, res: Response, next: NextFunction) => {
    const logger: Logger = Container.get('logger');
    try {
      const userService = Container.get(UserService);
      const authInfo = await userService.getAuthInfo();
      const { version, changeLog, changeLogLink, publishTime } =
        await parseVersion(config.versionFile);

      let isInitialized = true;
      if (
        Object.keys(authInfo).length === 2 &&
        authInfo.username === 'admin' &&
        authInfo.password === 'admin'
      ) {
        isInitialized = false;
      }
      res.send({
        code: 200,
        data: {
          isInitialized,
          version,
          publishTime: dayjs(publishTime).unix(),
          branch: process.env.QL_BRANCH || 'master',
          changeLog,
          changeLogLink,
        },
      });
    } catch (e) {
      logger.error('🔥 error: %o', e);
      return next(e);
    }
  });

  route.get(
    '/config',
    async (req: Request, res: Response, next: NextFunction) => {
      const logger: Logger = Container.get('logger');
      try {
        const systemService = Container.get(SystemService);
        const data = await systemService.getSystemConfig();
        res.send({ code: 200, data });
      } catch (e) {
        return next(e);
      }
    },
  );

  route.put(
    '/config/log-remove-frequency',
    celebrate({
      body: Joi.object({
        logRemoveFrequency: Joi.number().allow(null),
      }),
    }),
    async (req: Request, res: Response, next: NextFunction) => {
      try {
        const systemService = Container.get(SystemService);
        const result = await systemService.updateLogRemoveFrequency(req.body);
        res.send(result);
      } catch (e) {
        return next(e);
      }
    },
  );

  route.put(
    '/config/cron-concurrency',
    celebrate({
      body: Joi.object({
        cronConcurrency: Joi.number().allow(null),
      }),
    }),
    async (req: Request, res: Response, next: NextFunction) => {
      try {
        const systemService = Container.get(SystemService);
        const result = await systemService.updateCronConcurrency(req.body);
        res.send(result);
      } catch (e) {
        return next(e);
      }
    },
  );

  route.put(
    '/config/dependence-proxy',
    celebrate({
      body: Joi.object({
        dependenceProxy: Joi.string().allow('').allow(null),
      }),
    }),
    async (req: Request, res: Response, next: NextFunction) => {
      try {
        const systemService = Container.get(SystemService);
        const result = await systemService.updateDependenceProxy(req.body);
        res.send(result);
      } catch (e) {
        return next(e);
      }
    },
  );

  route.put(
    '/config/node-mirror',
    celebrate({
      body: Joi.object({
        nodeMirror: Joi.string().allow('').allow(null),
      }),
    }),
    async (req: Request, res: Response, next: NextFunction) => {
      try {
        const systemService = Container.get(SystemService);
        res.setHeader('Content-type', 'application/octet-stream');
        await systemService.updateNodeMirror(req.body, res);
      } catch (e) {
        return next(e);
      }
    },
  );

  route.put(
    '/config/python-mirror',
    celebrate({
      body: Joi.object({
        pythonMirror: Joi.string().allow('').allow(null),
      }),
    }),
    async (req: Request, res: Response, next: NextFunction) => {
      try {
        const systemService = Container.get(SystemService);
        const result = await systemService.updatePythonMirror(req.body);
        res.send(result);
      } catch (e) {
        return next(e);
      }
    },
  );

  route.put(
    '/config/linux-mirror',
    celebrate({
      body: Joi.object({
        linuxMirror: Joi.string().allow('').allow(null),
      }),
    }),
    async (req: Request, res: Response, next: NextFunction) => {
      try {
        const systemService = Container.get(SystemService);
        res.setHeader('Content-type', 'application/octet-stream');
        await systemService.updateLinuxMirror(req.body, res);
      } catch (e) {
        return next(e);
      }
    },
  );

  route.put(
    '/update-check',
    async (req: Request, res: Response, next: NextFunction) => {
      const logger: Logger = Container.get('logger');
      try {
        const systemService = Container.get(SystemService);
        const result = await systemService.checkUpdate();
        res.send(result);
      } catch (e) {
        return next(e);
      }
    },
  );

  route.put(
    '/update',
    async (req: Request, res: Response, next: NextFunction) => {
      const logger: Logger = Container.get('logger');
      try {
        const systemService = Container.get(SystemService);
        const result = await systemService.updateSystem();
        res.send(result);
      } catch (e) {
        return next(e);
      }
    },
  );

  route.put(
    '/reload',
    celebrate({
      body: Joi.object({
        type: Joi.string().optional().allow('').allow(null),
      }),
    }),
    async (req: Request, res: Response, next: NextFunction) => {
      const logger: Logger = Container.get('logger');
      try {
        const systemService = Container.get(SystemService);
        const result = await systemService.reloadSystem(req.body.type);
        res.send(result);
      } catch (e) {
        return next(e);
      }
    },
  );

  route.put(
    '/notify',
    celebrate({
      body: Joi.object({
        title: Joi.string().required(),
        content: Joi.string().required(),
      }),
    }),
    async (req: Request, res: Response, next: NextFunction) => {
      const logger: Logger = Container.get('logger');
      try {
        const systemService = Container.get(SystemService);
        const result = await systemService.notify(req.body);
        res.send(result);
      } catch (e) {
        return next(e);
      }
    },
  );

  route.put(
    '/command-run',
    celebrate({
      body: Joi.object({
        command: Joi.string().required(),
      }),
    }),
    async (req: Request, res: Response, next: NextFunction) => {
      try {
        const systemService = Container.get(SystemService);
        const command = req.body.command;
        const idStr = `cat ${config.crontabFile} | grep -E "${command}" | perl -pe "s|.*ID=(.*) ${command}.*|\\1|" | head -1 | awk -F " " '{print $1}' | xargs echo -n`;
        let id = await promiseExec(idStr);
        const uniqPath = await getUniqPath(command, id);
        const logTime = dayjs().format('YYYY-MM-DD-HH-mm-ss-SSS');
        const logPath = `${uniqPath}/${logTime}.log`;
        res.setHeader('Content-type', 'application/octet-stream');
        await systemService.run(
          { ...req.body, logPath },
          {
            onStart: async (cp, startTime) => {
              res.setHeader('QL-Task-Pid', `${cp.pid}`);
              res.setHeader('QL-Task-Log', `${logPath}`);
            },
            onEnd: async (cp, endTime, diff) => {
              // Close the stream after task completion
              await logStreamManager.closeStream(await handleLogPath(logPath));
              res.end();
            },
            onError: async (message: string) => {
              res.write(message);
              const absolutePath = await handleLogPath(logPath);
              await logStreamManager.write(absolutePath, message);
            },
            onLog: async (message: string) => {
              res.write(message);
              const absolutePath = await handleLogPath(logPath);
              await logStreamManager.write(absolutePath, message);
            },
          },
        );
      } catch (e) {
        return next(e);
      }
    },
  );

  route.put(
    '/command-stop',
    celebrate({
      body: Joi.object({
        command: Joi.string().optional(),
        pid: Joi.number().optional(),
      }),
    }),
    async (req: Request, res: Response, next: NextFunction) => {
      try {
        const systemService = Container.get(SystemService);
        const result = await systemService.stop(req.body);
        res.send(result);
      } catch (e) {
        return next(e);
      }
    },
  );

  route.put(
    '/data/export',
    celebrate({
      body: Joi.object({
        type: Joi.array().items(Joi.string()).optional(),
      }),
    }),
    async (req: Request, res: Response, next: NextFunction) => {
      try {
        const systemService = Container.get(SystemService);
        await systemService.exportData(res, req.body.type);
      } catch (e) {
        return next(e);
      }
    },
  );

  route.put(
    '/data/import',
    upload.single('data'),
    async (req: Request, res: Response, next: NextFunction) => {
      try {
        const systemService = Container.get(SystemService);
        const result = await systemService.importData();
        res.send(result);
      } catch (e) {
        return next(e);
      }
    },
  );

  route.get(
    '/log',
    celebrate({
      query: {
        startTime: Joi.string().allow('').optional(),
        endTime: Joi.string().allow('').optional(),
        t: Joi.string().optional(),
      },
    }),
    async (req: Request, res: Response, next: NextFunction) => {
      try {
        const systemService = Container.get(SystemService);
        await systemService.getSystemLog(
          res,
          req.query as {
            startTime?: string;
            endTime?: string;
          },
        );
      } catch (e) {
        return next(e);
      }
    },
  );

  route.delete(
    '/log',
    async (req: Request, res: Response, next: NextFunction) => {
      try {
        const systemService = Container.get(SystemService);
        await systemService.deleteSystemLog();
        res.send({ code: 200 });
      } catch (e) {
        return next(e);
      }
    },
  );

  route.put(
    '/auth/reset',
    celebrate({
      body: Joi.object({
        retries: Joi.number().optional(),
        twoFactorActivated: Joi.boolean().optional(),
        password: Joi.string().optional(),
        username: Joi.string().optional(),
      }),
    }),
    async (req: Request, res: Response, next: NextFunction) => {
      try {
        const userService = Container.get(UserService);
        await userService.resetAuthInfo(req.body);
        res.send({ code: 200, message: '更新成功' });
      } catch (e) {
        return next(e);
      }
    },
  );

  route.put(
    '/config/timezone',
    celebrate({
      body: Joi.object({
        timezone: Joi.string().allow('').allow(null),
      }),
    }),
    async (req: Request, res: Response, next: NextFunction) => {
      try {
        const systemService = Container.get(SystemService);
        const result = await systemService.updateTimezone(req.body);
        res.send(result);
      } catch (e) {
        return next(e);
      }
    },
  );

  route.put(
    '/config/global-ssh-key',
    celebrate({
      body: Joi.object({
        globalSshKey: Joi.string().allow('').allow(null),
      }),
    }),
    async (req: Request, res: Response, next: NextFunction) => {
      try {
        const systemService = Container.get(SystemService);
        const result = await systemService.updateGlobalSshKey(req.body);
        res.send(result);
      } catch (e) {
        return next(e);
      }
    },
  );

  route.put(
    '/config/dependence-clean',
    celebrate({
      body: Joi.object({
        type: Joi.string().allow(''),
      }),
    }),
    async (req: Request, res: Response, next: NextFunction) => {
      try {
        const systemService = Container.get(SystemService);
        const result = await systemService.cleanDependence(req.body.type);
        res.send(result);
      } catch (e) {
        return next(e);
      }
    },
  );
};


================================================
FILE: back/api/update.ts
================================================
import { NextFunction, Request, Response, Router } from 'express';
import Container from 'typedi';
import Logger from '../loaders/logger';
import SystemService from '../services/system';
const route = Router();

export default (app: Router) => {
  app.use('/update', route);

  route.put(
    '/reload',
    async (req: Request, res: Response, next: NextFunction) => {
      try {
        const systemService = Container.get(SystemService);
        const result = await systemService.reloadSystem();
        res.send(result);
      } catch (e) {
        Logger.error('🔥 error: %o', e);
        return next(e);
      }
    },
  );

  route.put(
    '/system',
    async (req: Request, res: Response, next: NextFunction) => {
      try {
        const systemService = Container.get(SystemService);
        const result = await systemService.reloadSystem('system');
        res.send(result);
      } catch (e) {
        Logger.error('🔥 error: %o', e);
        return next(e);
      }
    },
  );

  route.put(
    '/data',
    async (req: Request, res: Response, next: NextFunction) => {
      try {
        const systemService = Container.get(SystemService);
        const result = await systemService.reloadSystem('data');
        res.send(result);
      } catch (e) {
        Logger.error('🔥 error: %o', e);
        return next(e);
      }
    },
  );
};


================================================
FILE: back/api/user.ts
================================================
import { Router, Request, Response, NextFunction } from 'express';
import { Container } from 'typedi';
import { Logger } from 'winston';
import UserService from '../services/user';
import { celebrate, Joi } from 'celebrate';
import multer from 'multer';
import path from 'path';
import { v4 as uuidV4 } from 'uuid';
import rateLimit from 'express-rate-limit';
import config from '../config';
import { isDemoEnv, getToken } from '../config/util';
const route = Router();

const storage = multer.diskStorage({
  destination: function (req, file, cb) {
    cb(null, config.uploadPath);
  },
  filename: function (req, file, cb) {
    const ext = path.parse(file.originalname).ext;
    const key = uuidV4();
    cb(null, key + ext);
  },
});
const upload = multer({ storage: storage });

export default (app: Router) => {
  app.use('/user', route);

  route.post(
    '/login',
    rateLimit({
      windowMs: 15 * 60 * 1000,
      max: 100,
    }),
    celebrate({
      body: Joi.object({
        username: Joi.string().required(),
        password: Joi.string().required(),
      }),
    }),
    async (req: Request, res: Response, next: NextFunction) => {
      const logger: Logger = Container.get('logger');
      try {
        const userService = Container.get(UserService);
        const data = await userService.login({ ...req.body }, req);
        return res.send(data);
      } catch (e) {
        return next(e);
      }
    },
  );

  route.post(
    '/logout',
    async (req: Request, res: Response, next: NextFunction) => {
      const logger: Logger = Container.get('logger');
      try {
        const userService = Container.get(UserService);
        const token = getToken(req);
        await userService.logout(req.platform, token);
        res.send({ code: 200 });
      } catch (e) {
        return next(e);
      }
    },
  );

  route.put(
    '/',
    celebrate({
      body: Joi.object({
        username: Joi.string().required(),
        password: Joi.string().required(),
      }),
    }),
    async (req: Request, res: Response, next: NextFunction) => {
      try {
        if (isDemoEnv()) {
          return res.send({ code: 450, message: '未知错误' });
        }
        const userService = Container.get(UserService);
        await userService.updateUsernameAndPassword(req.body);
        res.send({ code: 200, message: '更新成功' });
      } catch (e) {
        return next(e);
      }
    },
  );

  route.get('/', async (req: Request, res: Response, next: NextFunction) => {
    const logger: Logger = Container.get('logger');
    try {
      const userService = Container.get(UserService);
      const authInfo = await userService.getAuthInfo();
      res.send({
        code: 200,
        data: {
          username: authInfo.username,
          avatar: authInfo.avatar,
          twoFactorActivated: authInfo.twoFactorActivated,
        },
      });
    } catch (e) {
      logger.error('🔥 error: %o', e);
      return next(e);
    }
  });

  route.get(
    '/two-factor/init',
    async (req: Request, res: Response, next: NextFunction) => {
      const logger: Logger = Container.get('logger');
      try {
        const userService = Container.get(UserService);
        const data = await userService.initTwoFactor();
        res.send({ code: 200, data });
      } catch (e) {
        return next(e);
      }
    },
  );

  route.put(
    '/two-factor/active',
    celebrate({
      body: Joi.object({
        code: Joi.string().required(),
      }),
    }),
    async (req: Request, res: Response, next: NextFunction) => {
      const logger: Logger = Container.get('logger');
      try {
        const userService = Container.get(UserService);
        const data = await userService.activeTwoFactor(req.body.code);
        res.send({ code: 200, data });
      } catch (e) {
        return next(e);
      }
    },
  );

  route.put(
    '/two-factor/deactive',
    async (req: Request, res: Response, next: NextFunction) => {
      const logger: Logger = Container.get('logger');
      try {
        const userService = Container.get(UserService);
        const data = await userService.deactiveTwoFactor();
        res.send({ code: 200, data });
      } catch (e) {
        return next(e);
      }
    },
  );

  route.put(
    '/two-factor/login',
    celebrate({
      body: Joi.object({
        code: Joi.string().required(),
        username: Joi.string().required(),
        password: Joi.string().required(),
      }),
    }),
    async (req: Request, res: Response, next: NextFunction) => {
      const logger: Logger = Container.get('logger');
      try {
        const userService = Container.get(UserService);
        const data = await userService.twoFactorLogin(req.body, req);
        res.send(data);
      } catch (e) {
        return next(e);
      }
    },
  );

  route.get(
    '/login-log',
    async (req: Request, res: Response, next: NextFunction) => {
      const logger: Logger = Container.get('logger');
      try {
        const userService = Container.get(UserService);
        const data = await userService.getLoginLog();
        res.send({ code: 200, data });
      } catch (e) {
        return next(e);
      }
    },
  );

  route.get(
    '/notification',
    async (req: Request, res: Response, next: NextFunction) => {
      const logger: Logger = Container.get('logger');
      try {
        const userService = Container.get(UserService);
        const data = await userService.getNotificationMode();
        res.send({ code: 200, data });
      } catch (e) {
        return next(e);
      }
    },
  );

  route.put(
    '/notification',
    async (req: Request, res: Response, next: NextFunction) => {
      const logger: Logger = Container.get('logger');
      try {
        const userService = Container.get(UserService);
        const result = await userService.updateNotificationMode(req.body);
        res.send(result);
      } catch (e) {
        return next(e);
      }
    },
  );

  route.put(
    '/init',
    celebrate({
      body: Joi.object({
        username: Joi.string().required(),
        password: Joi.string().required(),
      }),
    }),
    async (req: Request, res: Response, next: NextFunction) => {
      const logger: Logger = Container.get('logger');
      try {
        const userService = Container.get(UserService);
        await userService.updateUsernameAndPassword(req.body);
        res.send({ code: 200, message: '更新成功' });
      } catch (e) {
        return next(e);
      }
    },
  );

  route.put(
    '/notification/init',
    async (req: Request, res: Response, next: NextFunction) => {
      const logger: Logger = Container.get('logger');
      try {
        const userService = Container.get(UserService);
        const result = await userService.updateNotificationMode(req.body);
        res.send(result);
      } catch (e) {
        return next(e);
      }
    },
  );

  route.put(
    '/avatar',
    upload.single('avatar'),
    async (req: Request, res: Response, next: NextFunction) => {
      const logger: Logger = Container.get('logger');
      try {
        const userService = Container.get(UserService);
        const result = await userService.updateAvatar(req.file!.filename);
        res.send(result);
      } catch (e) {
        return next(e);
      }
    },
  );
};


================================================
FILE: back/app.ts
================================================
import 'reflect-metadata';
import cluster, { type Worker } from 'cluster';
import compression from 'compression';
import cors from 'cors';
import express from 'express';
import helmet from 'helmet';
import { Container } from 'typedi';
import config from './config';
import Logger from './loaders/logger';
import { monitoringMiddleware } from './middlewares/monitoring';
import { type GrpcServerService } from './services/grpc';
import { type HttpServerService } from './services/http';

interface WorkerMetadata {
  id: number;
  pid: number;
  serviceType: string;
  startTime: Date;
}

class Application {
  private app: express.Application;
  private httpServerService?: HttpServerService;
  private grpcServerService?: GrpcServerService;
  private isShuttingDown = false;
  private workerMetadataMap = new Map<number, WorkerMetadata>();
  private httpWorker?: Worker;

  constructor() {
    this.app = express();
    // 创建一个全局中间件,删除查询参数中的t
    this.app.use((req: express.Request, res: express.Response, next: express.NextFunction) => {
      if (req.query.t) {
        delete req.query.t;
      }
      next();
    });
  }

  async start() {
    try {
      if (cluster.isPrimary) {
        await this.initializeDatabase();
      }
      if (cluster.isPrimary) {
        this.startMasterProcess();
      } else {
        await this.startWorkerProcess();
      }
    } catch (error) {
      Logger.error('Failed to start application:', error);
      process.exit(1);
    }
  }

  private startMasterProcess() {
    // Fork gRPC worker first and wait for it to be ready
    const grpcWorker = this.forkWorker('grpc');
    
    // Wait for gRPC worker to signal it's ready before starting HTTP worker
    this.waitForWorkerReady(grpcWorker, 30000)
      .then(() => {
        Logger.info('✌️ gRPC worker is ready, starting HTTP worker');
        this.httpWorker = this.forkWorker('http');
      })
      .catch((error) => {
        Logger.error('✌️ Failed to wait for gRPC worker:', error);
        process.exit(1);
      });

    cluster.on('exit', (worker, code, signal) => {
      const metadata = this.workerMetadataMap.get(worker.id);
      if (metadata) {
        if (!this.isShuttingDown) {
          Logger.error(
            `✌️ ${metadata.serviceType} worker ${worker.process.pid} died (${signal || code
            }). Restarting...`,
          );
          // If gRPC worker died, restart it and wait for it to be ready
          if (metadata.serviceType === 'grpc') {
            const newGrpcWorker = this.forkWorker('grpc');
            this.waitForWorkerReady(newGrpcWorker, 30000)
              .then(() => {
                Logger.info('✌️ gRPC worker restarted and ready');
                // Re-register cron jobs by notifying the HTTP worker
                if (this.httpWorker) {
                  try {
                    this.httpWorker.send('reregister-crons');
                    Logger.info('✌️ Sent reregister-crons message to HTTP worker');
                  } catch (error) {
                    Logger.error('✌️ Failed to send reregister-crons message:', error);
                  }
                }
              })
              .catch((error) => {
                Logger.error('✌️ Failed to restart gRPC worker:', error);
                process.exit(1);
              });
          } else {
            // For HTTP worker, just restart it
            const newWorker = this.forkWorker(metadata.serviceType);
            this.httpWorker = newWorker;
            Logger.info(`✌️ Restarted ${metadata.serviceType} worker (PID: ${newWorker.process.pid})`);
          }
        }

        this.workerMetadataMap.delete(worker.id);
      }
    });

    this.setupMasterShutdown();
  }

  private waitForWorkerReady(worker: Worker, timeoutMs: number): Promise<void> {
    return new Promise<void>((resolve, reject) => {
      const messageHandler = (msg: any) => {
        if (msg === 'ready') {
          worker.removeListener('message', messageHandler);
          clearTimeout(timeoutId);
          resolve();
        }
      };
      worker.on('message', messageHandler);
      
      // Timeout after specified milliseconds
      const timeoutId = setTimeout(() => {
        worker.removeListener('message', messageHandler);
        reject(new Error(`Worker failed to start within ${timeoutMs / 1000} seconds`));
      }, timeoutMs);
    });
  }

  private forkWorker(serviceType: string): Worker {
    const worker = cluster.fork({ SERVICE_TYPE: serviceType });

    this.workerMetadataMap.set(worker.id, {
      id: worker.id,
      pid: worker.process.pid!,
      serviceType,
      startTime: new Date(),
    });

    return worker;
  }

  private async initializeDatabase() {
    const dbLoader = await import('./loaders/db');
    await dbLoader.default();
  }

  private setupMiddlewares() {
    this.app.use(helmet({
      contentSecurityPolicy: false,
    }));
    this.app.use(cors(config.cors));
    this.app.use(compression());
    this.app.use(monitoringMiddleware);
  }

  private setupMasterShutdown() {
    const shutdown = async () => {
      if (this.isShuttingDown) return;
      this.isShuttingDown = true;

      const workers = Object.values(cluster.workers || {});
      const workerPromises: Promise<void>[] = [];

      workers.forEach((worker) => {
        if (worker) {
          const exitPromise = new Promise<void>((resolve) => {
            worker.once('exit', () => {
              Logger.info(`✌️ Worker ${worker.process.pid} exited`);
              resolve();
            });

            try {
              worker.send('shutdown');
            } catch (error) {
              Logger.warn(
                `✌️ Failed to send shutdown to worker ${worker.process.pid}:`,
                error,
              );
            }
          });

          workerPromises.push(exitPromise);
        }
      });

      try {
        await Promise.race([
          Promise.all(workerPromises),
          new Promise<void>((resolve) => {
            setTimeout(() => {
              Logger.warn('✌️ Worker shutdown timeout reached');
              resolve();
            }, 10000);
          }),
        ]);
        process.exit(0);
      } catch (error) {
        Logger.error('✌️ Error during worker shutdown:', error);
        process.exit(1);
      }
    };

    process.on('SIGTERM', shutdown);
    process.on('SIGINT', shutdown);
  }

  private async startWorkerProcess() {
    const serviceType = process.env.SERVICE_TYPE;
    if (!serviceType || !['http', 'grpc'].includes(serviceType)) {
      Logger.error('✌️ Invalid SERVICE_TYPE:', serviceType);
      process.exit(1);
    }

    Logger.info(`✌️ ${serviceType} worker started (PID: ${process.pid})`);

    try {
      if (serviceType === 'http') {
        await this.startHttpService();
      } else {
        await this.startGrpcService();
      }

      process.send?.('ready');
    } catch (error) {
      Logger.error(`✌️ ${serviceType} worker failed:`, error);
      process.exit(1);
    }
  }

  private async startHttpService() {
    this.setupMiddlewares();

    const { HttpServerService } = await import('./services/http');
    this.httpServerService = Container.get(HttpServerService);

    const appLoader = await import('./loaders/app');
    await appLoader.default({ app: this.app });

    const server = await this.httpServerService.initialize(
      this.app,
      config.port,
    );

    const serverLoader = await import('./loaders/server');
    await (serverLoader.default as any)({ server });
    this.setupWorkerShutdown('http');
  }

  private async startGrpcService() {
    const { GrpcServerService } = await import('./services/grpc');
    this.grpcServerService = Container.get(GrpcServerService);

    await this.grpcServerService.initialize();
    this.setupWorkerShutdown('grpc');
  }

  private setupWorkerShutdown(serviceType: string) {
    process.on('message', async (msg) => {
      if (msg === 'shutdown') {
        this.gracefulShutdown(serviceType);
      } else if (msg === 'reregister-crons' && serviceType === 'http') {
        // Re-register cron jobs when gRPC worker restarts
        try {
          Logger.info('✌️ Received reregister-crons message, re-registering cron jobs...');
          const CronService = (await import('./services/cron')).default;
          const cronService = Container.get(CronService);
          await cronService.autosave_crontab();
          Logger.info('✌️ Cron jobs re-registered successfully');
        } catch (error) {
          Logger.error('✌️ Failed to re-register cron jobs:', error);
        }
      }
    });

    const shutdown = () => this.gracefulShutdown(serviceType);
    process.on('SIGTERM', shutdown);
    process.on('SIGINT', shutdown);
  }

  private async gracefulShutdown(serviceType: string) {
    if (this.isShuttingDown) return;
    this.isShuttingDown = true;

    try {
      if (serviceType === 'http') {
        await this.httpServerService?.shutdown();
      } else {
        await this.grpcServerService?.shutdown();
      }
      process.exit(0);
    } catch (error) {
      Logger.error(`✌️ [${serviceType}] Error during shutdown:`, error);
      process.exit(1);
    }
  }
}

const app = new Application();
app.start().catch((error) => {
  Logger.error('🙅‍♀️ Application failed to start:', error);
  process.exit(1);
});


================================================
FILE: back/config/const.ts
================================================
export const LOG_END_SYMBOL = '     ';

export const TASK_COMMAND = 'task';
export const QL_COMMAND = 'ql';

export const TASK_PREFIX = `${TASK_COMMAND} `;
export const QL_PREFIX = `${QL_COMMAND} `;

export const SAMPLE_FILES = [
  {
    title: 'config.sample.sh',
    value: 'sample/config.sample.sh',
    target: 'config.sh',
  },
  {
    title: 'notify.js',
    value: 'sample/notify.js',
    target: 'data/scripts/sendNotify.js',
  },
  {
    title: 'notify.py',
    value: 'sample/notify.py',
    target: 'data/scripts/notify.py',
  },
];

export const PYTHON_INSTALL_DIR = process.env.PYTHON_HOME;

export const NotificationModeStringMap = {
  0: 'gotify',
  1: 'goCqHttpBot',
  2: 'serverChan',
  3: 'pushDeer',
  4: 'bark',
  5: 'chat',
  6: 'telegramBot',
  7: 'dingtalkBot',
  8: 'weWorkBot',
  9: 'weWorkApp',
  10: 'aibotk',
  11: 'iGot',
  12: 'pushPlus',
  13: 'wePlusBot',
  14: 'email',
  15: 'pushMe',
  16: 'feishu',
  17: 'webhook',
  18: 'chronocat',
  19: 'ntfy',
  20: 'wxPusherBot',
} as const;


================================================
FILE: back/config/http.ts
================================================
import { request as undiciRequest, Dispatcher } from 'undici';

type RequestBaseOptions = {
  dispatcher?: Dispatcher;
  json?: Record<string, any>;
  form?: string;
  headers?: Record<string, string>;
} & Omit<Dispatcher.RequestOptions<null>, 'origin' | 'path' | 'method'>;

type RequestOptionsWithOptions = RequestBaseOptions &
  Partial<Pick<Dispatcher.RequestOptions, 'method'>>;

type ResponseTypeMap = {
  json: Record<string, any>;
  text: string;
};

type ResponseTypeKey = keyof ResponseTypeMap;

async function request(
  url: string,
  options?: RequestOptionsWithOptions,
): Promise<Dispatcher.ResponseData<null>> {
  const { json, form, body, headers = {}, ...rest } = options || {};
  const finalHeaders = { ...headers } as Record<string, string>;
  let finalBody = body;

  if (json) {
    finalHeaders['content-type'] = 'application/json';
    finalBody = JSON.stringify(json);
  } else if (form) {
    finalBody = form;
    delete finalHeaders['content-type'];
  }

  const res = await undiciRequest(url, {
    method: 'POST',
    headers: finalHeaders,
    body: finalBody,
    ...rest,
  });

  return res;
}

async function post<T extends ResponseTypeKey = 'json'>(
  url: string,
  options?: RequestBaseOptions & { responseType?: T },
): Promise<ResponseTypeMap[T]> {
  const resp = await request(url, { ...options, method: 'POST' });

  const rawText = await resp.body.text();

  if (options?.responseType === 'text') {
    return rawText as ResponseTypeMap[T];
  }

  try {
    return JSON.parse(rawText) as ResponseTypeMap[T];
  } catch {
    return rawText as ResponseTypeMap[T];
  }
}

export const httpClient = {
  post,
  request,
};


================================================
FILE: back/config/index.ts
================================================
import dotenv from 'dotenv';
import path from 'path';
import { createRandomString } from './share';

dotenv.config({
  path: path.join(__dirname, '../../.env'),
});

interface Config {
  port: number;
  grpcPort: number;
  nodeEnv: string;
  isDevelopment: boolean;
  isProduction: boolean;
  jwt: {
    secret: string;
    expiresIn?: string;
  };
  cors: {
    origin: string[];
    methods: string[];
  };
  logs: {
    level: string;
  };
  api: {
    prefix: string;
  };
}

const config: Config = {
  port: parseInt(process.env.BACK_PORT || '5700', 10),
  grpcPort: parseInt(process.env.GRPC_PORT || '5500', 10),
  nodeEnv: process.env.NODE_ENV || 'development',
  isDevelopment: process.env.NODE_ENV === 'development',
  isProduction: process.env.NODE_ENV === 'production',
  logs: {
    level: process.env.LOG_LEVEL || 'silly',
  },
  api: {
    prefix: '/api',
  },
  jwt: {
    secret: process.env.JWT_SECRET || 'whyour-secret',
    expiresIn: process.env.JWT_EXPIRES_IN,
  },
  cors: {
    origin: process.env.CORS_ORIGIN
      ? process.env.CORS_ORIGIN.split(',')
      : ['*'],
    methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
  },
};

process.env.NODE_ENV = process.env.NODE_ENV || 'development';

if (!process.env.QL_DIR) {
  let qlHomePath = path.join(__dirname, '../../');
  if (qlHomePath.endsWith('/static/')) {
    qlHomePath = path.join(qlHomePath, '../');
  }
  process.env.QL_DIR = qlHomePath.replace(/\/$/g, '');
}

const lastVersionFile = `https://qn.whyour.cn/version.yaml`;

// Get and normalize QlBaseUrl
let baseUrl = process.env.QlBaseUrl || '';
if (baseUrl) {
  // Ensure it starts with /
  if (!baseUrl.startsWith('/')) {
    baseUrl = `/${baseUrl}`;
  }
  // Remove trailing slash for consistency in route definitions
  if (baseUrl.endsWith('/')) {
    baseUrl = baseUrl.slice(0, -1);
  }
}

const rootPath = process.env.QL_DIR as string;
const envFound = dotenv.config({ path: path.join(rootPath, '.env') });

let dataPath = path.join(rootPath, 'data/');

if (process.env.QL_DATA_DIR) {
  dataPath = process.env.QL_DATA_DIR.replace(/\/$/g, '');
}

const shellPath = path.join(rootPath, 'shell/');
const preloadPath = path.join(shellPath, 'preload/');
const tmpPath = path.join(rootPath, '.tmp/');
const samplePath = path.join(rootPath, 'sample/');
const configPath = path.join(dataPath, 'config/');
const scriptPath = path.join(dataPath, 'scripts/');
const repoPath = path.join(dataPath, 'repo/');
const bakPath = path.join(dataPath, 'bak/');
const logPath = path.join(dataPath, 'log/');
const dbPath = path.join(dataPath, 'db/');
const uploadPath = path.join(dataPath, 'upload/');
const sshdPath = path.join(dataPath, 'ssh.d/');
const systemLogPath = path.join(dataPath, 'syslog/');
const dependenceCachePath = path.join(dataPath, 'dep_cache/');

const envFile = path.join(preloadPath, 'env.sh');
const jsEnvFile = path.join(preloadPath, 'env.js');
const pyEnvFile = path.join(preloadPath, 'env.py');
const jsNotifyFile = path.join(preloadPath, '__ql_notify__.js');
const pyNotifyFile = path.join(preloadPath, '__ql_notify__.py');
const confFile = path.join(configPath, 'config.sh');
const crontabFile = path.join(configPath, 'crontab.list');
const authConfigFile = path.join(configPath, 'auth.json');
const extraFile = path.join(configPath, 'extra.sh');
const confBakDir = path.join(dataPath, 'config/bak/');
const sampleFile = path.join(samplePath, 'config.sample.sh');
const sqliteFile = path.join(samplePath, 'database.sqlite');

const authError = '错误的用户名密码,请重试';
const loginFaild = '请先登录!';
const configString = 'config sample crontab shareCode diy';
const versionFile = path.join(rootPath, 'version.yaml');
const dataTgzFile = path.join(tmpPath, 'data.tgz');
const shareShellFile = path.join(shellPath, 'share.sh');
const dependenceProxyFile = path.join(configPath, 'dependence-proxy.sh');

if (envFound.error) {
  throw new Error("⚠️  Couldn't find .env file  ⚠️");
}

export default {
  ...config,
  jwt: config.jwt,
  baseUrl,
  rootPath,
  tmpPath,
  dataPath,
  dataTgzFile,
  shareShellFile,
  dependenceProxyFile,
  configString,
  loginFaild,
  authError,
  logPath,
  extraFile,
  authConfigFile,
  confBakDir,
  crontabFile,
  sampleFile,
  confFile,
  envFile,
  jsEnvFile,
  pyEnvFile,
  jsNotifyFile,
  pyNotifyFile,
  dbPath,
  uploadPath,
  configPath,
  scriptPath,
  repoPath,
  samplePath,
  blackFileList: [
    'auth.json',
    'config.sh.sample',
    'cookie.sh',
    'crontab.list',
    'dependence-proxy.sh',
    'env.sh',
    'env.js',
    'env.py',
    'token.json',
  ],
  writePathList: [configPath, scriptPath],
  bakPath,
  apiWhiteList: [
    '/api/user/login',
    '/api/health',
    '/open/auth/token',
    '/api/user/two-factor/login',
    '/api/system',
    '/api/user/init',
    '/api/user/notification/init',
    '/open/user/login',
    '/open/user/two-factor/login',
    '/open/system',
    '/open/user/init',
    '/open/user/notification/init',
  ],
  versionFile,
  lastVersionFile,
  sqliteFile,
  sshdPath,
  systemLogPath,
  dependenceCachePath,
  maxTokensPerPlatform: 10, // Maximum number of concurrent sessions per platform
};


================================================
FILE: back/config/serverEnv.ts
================================================
import { Request, Response } from 'express';
import pick from 'lodash/pick';

let pickedEnv: Record<string, string>;

function getPickedEnv() {
  if (pickedEnv) return pickedEnv;
  const picked = pick(process.env, ['QlBaseUrl', 'DeployEnv', 'QL_DIR']);
  if (picked.QlBaseUrl) {
    if (!picked.QlBaseUrl.startsWith('/')) {
      picked.QlBaseUrl = `/${picked.QlBaseUrl}`;
    }
    if (!picked.QlBaseUrl.endsWith('/')) {
      picked.QlBaseUrl = `${picked.QlBaseUrl}/`;
    }
  }
  pickedEnv = picked as Record<string, string>;
  return picked;
}

export function serveEnv(_req: Request, res: Response) {
  res.type('.js');
  res.send(
    Object.entries(getPickedEnv())
      .map(([k, v]) => `window.__ENV__${k}=${JSON.stringify(v)};`)
      .join('\n'),
  );
}


================================================
FILE: back/config/share.ts
================================================
export function createRandomString(min: number, max: number): string {
  const num = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'];
  const english = [
    'a',
    'b',
    'c',
    'd',
    'e',
    'f',
    'g',
    'h',
    'i',
    'j',
    'k',
    'l',
    'm',
    'n',
    'o',
    'p',
    'q',
    'r',
    's',
    't',
    'u',
    'v',
    'w',
    'x',
    'y',
    'z',
  ];
  const ENGLISH = [
    'A',
    'B',
    'C',
    'D',
    'E',
    'F',
    'G',
    'H',
    'I',
    'J',
    'K',
    'L',
    'M',
    'N',
    'O',
    'P',
    'Q',
    'R',
    'S',
    'T',
    'U',
    'V',
    'W',
    'X',
    'Y',
    'Z',
  ];
  const special = ['-', '_'];
  const config = num.concat(english).concat(ENGLISH).concat(special);

  const arr = [];
  arr.push(getOne(num));
  arr.push(getOne(english));
  arr.push(getOne(ENGLISH));
  arr.push(getOne(special));

  const len = min + Math.floor(Math.random() * (max - min + 1));

  for (let i = 4; i < len; i++) {
    arr.push(config[Math.floor(Math.random() * config.length)]);
  }

  const newArr = [];
  for (let j = 0; j < len; j++) {
    newArr.push(arr.splice(Math.random() * arr.length, 1)[0]);
  }

  function getOne(arr: any[]) {
    return arr[Math.floor(Math.random() * arr.length)];
  }

  return newArr.join('');
}

================================================
FILE: back/config/subscription.ts
================================================
import { Subscription } from '../data/subscription';
import isNil from 'lodash/isNil';

export function formatUrl(doc: Subscription) {
  let url = doc.url;
  let host = '';
  if (doc.type === 'private-repo') {
    if (doc.pull_type === 'ssh-key') {
      host = doc.url!.replace(/.*\@([^\:]+)\:.*/, '$1');
      url = doc.url!.replace(host, doc.alias);
    } else {
      host = doc.url!.replace(/.*\:\/\/([^\/]+)\/.*/, '$1');
      const { username, password } = doc.pull_option as any;
      url = doc.url!.replace(host, `${username}:${password}@${host}`);
    }
  }
  return { url, host };
}

export function formatCommand(doc: Subscription, url?: string) {
  let command = `SUB_ID=${doc.id} ql `;
  let _url = url || formatUrl(doc).url;
  const {
    type,
    whitelist,
    blacklist,
    dependences,
    branch,
    extensions,
    proxy,
    autoAddCron,
    autoDelCron,
  } = doc;
  if (type === 'file') {
    command += `raw "${_url}" "${proxy || ''}" "${
      isNil(autoAddCron) ? true : Boolean(autoAddCron)
    }" "${isNil(autoDelCron) ? true : Boolean(autoDelCron)}"`;
  } else {
    command += `repo "${_url}" "${whitelist || ''}" "${blacklist || ''}" "${
      dependences || ''
    }" "${branch || ''}" "${extensions || ''}" "${proxy || ''}" "${
      isNil(autoAddCron) ? true : Boolean(autoAddCron)
    }" "${isNil(autoDelCron) ? true : Boolean(autoDelCron)}"`;
  }
  return command;
}


================================================
FILE: back/config/util.ts
================================================
import * as fs from 'fs/promises';
import * as path from 'path';
import { exec } from 'child_process';
import psTreeFun from 'ps-tree';
import { promisify } from 'util';
import { load } from 'js-yaml';
import config from './index';
import { PYTHON_INSTALL_DIR, TASK_COMMAND } from './const';
import Logger from '../loaders/logger';
import { writeFileWithLock } from '../shared/utils';
import { DependenceTypes } from '../data/dependence';
import { FormData } from 'undici';

export * from './share';

export async function getFileContentByName(fileName: string) {
  const _exsit = await fileExist(fileName);
  if (_exsit) {
    return await fs.readFile(fileName, 'utf8');
  }
  return '';
}

export function removeAnsi(text: string) {
  return text.replace(/\x1b\[\d+m/g, '');
}

export async function getLastModifyFilePath(dir: string) {
  let filePath = '';

  const _exsit = await fileExist(dir);
  if (_exsit) {
    const arr = await fs.readdir(dir);

    arr.forEach(async (item) => {
      const fullpath = path.join(dir, item);
      const stats = await fs.lstat(fullpath);
      if (stats.isFile()) {
        if (stats.mtimeMs >= 0) {
          filePath = fullpath;
        }
      }
    });
  }
  return filePath;
}

export function getToken(req: any) {
  const { authorization = '' } = req.headers;
  if (authorization && authorization.split(' ')[0] === 'Bearer') {
    return (authorization as string)
      .replace('Bearer ', '')
      .replace('mobile-', '')
      .replace('desktop-', '');
  }
  return '';
}

export function getPlatform(userAgent: string): 'mobile' | 'desktop' {
  const ua = userAgent.toLowerCase();
  const testUa = (regexp: RegExp) => regexp.test(ua);
  const testVs = (regexp: RegExp) =>
    (ua.match(regexp) || [])
      .toString()
      .replace(/[^0-9|_.]/g, '')
      .replace(/_/g, '.');

  // 系统
  let system = 'unknow';
  if (testUa(/windows|win32|win64|wow32|wow64/g)) {
    system = 'windows'; // windows系统
  } else if (testUa(/macintosh|macintel/g)) {
    system = 'macos'; // macos系统
  } else if (testUa(/x11/g)) {
    system = 'linux'; // linux系统
  } else if (testUa(/android|adr/g)) {
    system = 'android'; // android系统
  } else if (testUa(/ios|iphone|ipad|ipod|iwatch/g)) {
    system = 'ios'; // ios系统
  } else if (testUa(/openharmony/g)) {
    system = 'openharmony'; // openharmony系统
  }

  let platform = 'desktop';
  if (system === 'windows' || system === 'macos' || system === 'linux') {
    platform = 'desktop';
  } else if (
    system === 'android' ||
    system === 'ios' ||
    system === 'openharmony' ||
    testUa(/mobile/g)
  ) {
    platform = 'mobile';
  }

  return platform as 'mobile' | 'desktop';
}

export async function fileExist(file: any) {
  try {
    await fs.access(file);
    return true;
  } catch (error) {
    return false;
  }
}

export async function createFile(file: string, data: string = '') {
  await fs.mkdir(path.dirname(file), { recursive: true });
  await writeFileWithLock(file, data);
}

export async function handleLogPath(
  logPath: string,
  data: string = '',
): Promise<string> {
  const absolutePath = path.resolve(config.logPath, logPath);
  const logFileExist = await fileExist(absolutePath);
  if (!logFileExist) {
    await createFile(absolutePath, data);
  }
  return absolutePath;
}

export async function concurrentRun(
  fnList: Array<() => Promise<any>> = [],
  max = 5,
) {
  if (!fnList.length) return;

  const replyList: any[] = []; // 收集任务执行结果
  const startTime = new Date().getTime(); // 记录任务执行开始时间

  // 任务执行程序
  const schedule = async (index: number) => {
    return new Promise(async (resolve) => {
      const fn = fnList[index];
      if (!fn) return resolve(null);

      // 执行当前异步任务
      const reply = await fn();
      replyList[index] = reply;

      // 执行完当前任务后,继续执行任务池的剩余任务
      await schedule(index + max);
      resolve(null);
    });
  };

  // 任务池执行程序
  const scheduleList = new Array(max)
    .fill(0)
    .map((_, index) => schedule(index));

  // 使用 Promise.all 批量执行
  const r = await Promise.all(scheduleList);
  const cost = (new Date().getTime() - startTime) / 1000;

  return replyList;
}

enum FileType {
  'directory',
  'file',
}

export interface IFile {
  title: string;
  key: string;
  type: 'directory' | 'file';
  parent: string;
  createTime: number;
  size?: number;
  children?: IFile[];
}

export function dirSort(a: IFile, b: IFile): number {
  if (a.type === 'file' && b.type === 'file') {
    return b.createTime - a.createTime;
  } else if (a.type === 'directory' && b.type === 'directory') {
    return a.title.localeCompare(b.title);
  } else {
    return a.type === 'directory' ? -1 : 1;
  }
}

export async function readDirs(
  dir: string,
  baseDir: string = '',
  blacklist: string[] = [],
  sort: (a: IFile, b: IFile) => number = dirSort,
): Promise<IFile[]> {
  const relativePath = path.relative(baseDir, dir);
  const files = await fs.readdir(dir);
  const result: IFile[] = [];

  for (const file of files) {
    const subPath = path.join(dir, file);
    const stats = await fs.lstat(subPath);
    const key = path.join(relativePath, file);

    if (blacklist.includes(file) || stats.isSymbolicLink()) {
      continue;
    }

    if (stats.isDirectory()) {
      const children = await readDirs(subPath, baseDir, blacklist, sort);
      result.push({
        title: file,
        key,
        type: 'directory',
        parent: relativePath,
        createTime: stats.birthtime.getTime(),
        children: children.sort(sort),
      });
    } else {
      result.push({
        title: file,
        type: 'file',
        key,
        parent: relativePath,
        size: stats.size,
        createTime: stats.birthtime.getTime(),
      });
    }
  }

  return result.sort(sort);
}

export async function readDir(
  dir: string,
  baseDir: string = '',
  blacklist: string[] = [],
): Promise<IFile[]> {
  const absoluteDir = path.join(baseDir, dir);
  const relativePath = path.relative(baseDir, absoluteDir);

  try {
    const files = await fs.readdir(absoluteDir);
    const result: IFile[] = [];

    for (const file of files) {
      const subPath = path.join(absoluteDir, file);
      const stats = await fs.lstat(subPath);
      const key = path.join(relativePath, file);

      if (blacklist.includes(file) || stats.isSymbolicLink()) {
        continue;
      }

      if (stats.isDirectory()) {
        result.push({
          title: file,
          type: 'directory',
          key,
          parent: relativePath,
          createTime: stats.birthtime.getTime(),
          children: [],
        });
      } else {
        result.push({
          title: file,
          type: 'file',
          key,
          parent: relativePath,
          size: stats.size,
          createTime: stats.birthtime.getTime(),
        });
      }
    }

    return result;
  } catch (error: any) {
    if (error.code === 'ENOENT') {
      return [];
    }
    throw error;
  }
}

export async function promiseExec(command: string): Promise<string> {
  try {
    const { stderr, stdout } = await promisify(exec)(command, {
      maxBuffer: 200 * 1024 * 1024,
      encoding: 'utf8',
    });
    return stdout || stderr;
  } catch (error) {
    return JSON.stringify(error);
  }
}

export async function promiseExecSuccess(command: string): Promise<string> {
  try {
    const { stdout } = await promisify(exec)(command, {
      maxBuffer: 200 * 1024 * 1024,
      encoding: 'utf8',
    });
    return stdout || '';
  } catch (error) {
    return '';
  }
}

export function parseHeaders(headers: string) {
  if (!headers) return {};

  const parsed: any = {};
  let key: string;
  let val: string;
  let i: number;

  headers &&
    headers.split('\n').forEach(function parser(line) {
      i = line.indexOf(':');
      key = line.substring(0, i).trim().toLowerCase();
      val = line.substring(i + 1).trim();

      if (!key) {
        return;
      }

      parsed[key] = parsed[key] ? parsed[key] + ', ' + val : val;
    });

  return parsed;
}

function parseString(
  input: string,
  valueFormatFn?: (v: string) => string,
): Record<string, string> {
  const regex = /(\w+):\s*((?:(?!\n\w+:).)*)/g;
  const matches: Record<string, string> = {};

  let match;
  while ((match = regex.exec(input)) !== null) {
    const [, key, value] = match;
    const _key = key.trim();
    if (!_key || matches[_key]) {
      continue;
    }

    let _value = value.trim();

    try {
      _value = valueFormatFn ? valueFormatFn(_value) : _value;
      const jsonValue = JSON.parse(_value);
      matches[_key] = jsonValue;
    } catch (error) {
      matches[_key] = _value;
    }
  }

  return matches;
}

export function parseBody(
  body: string,
  contentType:
    | 'application/json'
    | 'multipart/form-data'
    | 'application/x-www-form-urlencoded'
    | 'text/plain',
  valueFormatFn?: (v: string) => string,
) {
  if (contentType === 'text/plain' || !body) {
    return valueFormatFn && body ? valueFormatFn(body) : body;
  }

  const parsed = parseString(body, valueFormatFn);

  switch (contentType) {
    case 'multipart/form-data':
      return Object.keys(parsed).reduce((p, c) => {
        p.append(c, parsed[c]);
        return p;
      }, new FormData());
    case 'application/x-www-form-urlencoded':
      return Object.keys(parsed).reduce((p, c) => {
        return p ? `${p}&${c}=${parsed[c]}` : `${c}=${parsed[c]}`;
      });
  }

  return parsed;
}

export function psTree(pid: number): Promise<number[]> {
  return new Promise((resolve, reject) => {
    psTreeFun(pid, (err: any, children) => {
      if (err) {
        reject(err);
      }
      resolve(children.map((x) => Number(x.PID)).filter((x) => !isNaN(x)));
    });
  });
}

export async function killTask(pid: number) {
  const pids = await psTree(pid);

  if (pids.length) {
    try {
      [pid, ...pids].reverse().forEach((x) => {
        process.kill(x, 15);
      });
    } catch (error) {}
  } else {
    process.kill(pid, 2);
  }
}

export async function getPid(cmd: string) {
  const taskCommand = `ps -eo pid,command | grep "${cmd}" | grep -v grep | awk '{print $1}' | head -1 | xargs echo -n`;
  const pid = await promiseExec(taskCommand);
  return pid ? Number(pid) : undefined;
}

export async function getAllPids(cmd: string): Promise<number[]> {
  const taskCommand = `ps -eo pid,command | grep "${cmd}" | grep -v grep | awk '{print $1}'`;
  const pidsStr = await promiseExec(taskCommand);
  if (!pidsStr) return [];
  return pidsStr
    .split('\n')
    .map((p) => Number(p.trim()))
    .filter((p) => !isNaN(p) && p > 0);
}

export async function killAllTasks(cmd: string): Promise<void> {
  const pids = await getAllPids(cmd);
  for (const pid of pids) {
    try {
      await killTask(pid);
    } catch (error) {
      // Ignore errors if process already terminated
    }
  }
}

interface IVersion {
  version: string;
  changeLogLink: string;
  changeLog: string;
  publishTime: string;
}

export async function parseVersion(path: string): Promise<IVersion> {
  return load(await fs.readFile(path, 'utf8')) as IVersion;
}

export function parseContentVersion(content: string): IVersion {
  return load(content) as IVersion;
}

export async function getUniqPath(
  command: string,
  id: string,
): Promise<string> {
  let suffix = '';
  if (/^\d+$/.test(id)) {
    suffix = `_${id}`;
  }

  let items = command.split(/ +/);

  const maxTimeCommandIndex = items.findIndex((x) => x === '-m');
  if (maxTimeCommandIndex !== -1) {
    items = items.slice(maxTimeCommandIndex + 2);
  }

  let str = items[0];
  if (items[0] === TASK_COMMAND) {
    str = items[1];
  }

  const dotIndex = str.lastIndexOf('.');

  if (dotIndex !== -1) {
    str = str.slice(0, dotIndex);
  }

  const slashIndex = str.lastIndexOf('/');

  let tempStr = '';
  if (slashIndex !== -1) {
    tempStr = str.slice(0, slashIndex);
    const _slashIndex = tempStr.lastIndexOf('/');
    if (_slashIndex !== -1) {
      tempStr = tempStr.slice(_slashIndex + 1);
    }
    str = `${tempStr}_${str.slice(slashIndex + 1)}`;
  }

  return `${str}${suffix}`;
}

export function safeJSONParse(value?: string) {
  if (!value) {
    return {};
  }

  try {
    return JSON.parse(value);
  } catch (error) {
    Logger.error('[safeJSONParse失败]', error);
    return {};
  }
}

export async function rmPath(path: string) {
  try {
    const _exsit = await fileExist(path);
    if (_exsit) {
      await fs.rm(path, { force: true, recursive: true, maxRetries: 5 });
    }
  } catch (error) {
    Logger.error('[rmPath失败]', error);
  }
}

export async function setSystemTimezone(timezone: string): Promise<boolean> {
  try {
    if (!(await fileExist(`/usr/share/zoneinfo/${timezone}`))) {
      throw new Error('Invalid timezone');
    }

    await promiseExec(`ln -sf /usr/share/zoneinfo/${timezone} /etc/localtime`);
    await promiseExec(`echo "${timezone}" > /etc/timezone`);

    return true;
  } catch (error) {
    Logger.error('[setSystemTimezone失败]', error);
    return false;
  }
}

export function getGetCommand(type: DependenceTypes, name: string): string {
  const baseCommands = {
    [DependenceTypes.nodejs]: `pnpm ls -g  | grep "${name}" | head -1`,
    [DependenceTypes.python3]: `
    python3 -c "exec('''
name='${name}'
try:
    from importlib.metadata import version
    print(version(name))
except:
    import importlib.util as u
    import importlib.metadata as m
    spec=u.find_spec(name)
    print(name if spec else '')
''')"`,
    [DependenceTypes.linux]: `apk info -es ${name}`,
  };

  return baseCommands[type];
}

export function getInstallCommand(type: DependenceTypes, name: string): string {
  const baseCommands = {
    [DependenceTypes.nodejs]: 'pnpm add -g',
    [DependenceTypes.python3]:
      'pip3 install --disable-pip-version-check --root-user-action=ignore',
    [DependenceTypes.linux]: 'apk add --no-check-certificate',
  };

  let command = baseCommands[type];

  if (type === DependenceTypes.python3 && PYTHON_INSTALL_DIR) {
    command = `${command} --prefix=${PYTHON_INSTALL_DIR}`;
  }

  return `${command} ${name.trim()}`;
}

export function getUninstallCommand(
  type: DependenceTypes,
  name: string,
): string {
  const baseCommands = {
    [DependenceTypes.nodejs]: 'pnpm remove -g',
    [DependenceTypes.python3]:
      'pip3 uninstall --disable-pip-version-check --root-user-action=ignore -y',
    [DependenceTypes.linux]: 'apk del',
  };

  return `${baseCommands[type]} ${name.trim()}`;
}

export function isDemoEnv() {
  return process.env.DeployEnv === 'demo';
}


================================================
FILE: back/data/cron.ts
================================================
import { sequelize } from '.';
import { DataTypes, Model, ModelDefined } from 'sequelize';

export class Crontab {
  name?: string;
  command: string;
  schedule?: string;
  timestamp?: string;
  saved?: boolean;
  id?: number;
  status?: CrontabStatus;
  isSystem?: 1 | 0;
  pid?: number;
  isDisabled?: 1 | 0;
  log_path?: string;
  isPinned?: 1 | 0;
  labels?: string[];
  last_running_time?: number;
  last_execution_time?: number;
  sub_id?: number;
  extra_schedules?: Array<{ schedule: string }>;
  task_before?: string;
  task_after?: string;
  log_name?: string;
  allow_multiple_instances?: 1 | 0;

  constructor(options: Crontab) {
    this.name = options.name;
    this.command = options.command.trim();
    this.schedule = options.schedule;
    this.saved = options.saved;
    this.id = options.id;
    this.status =
      typeof options.status === 'number' && CrontabStatus[options.status]
        ? options.status
        : CrontabStatus.idle;
    this.timestamp = new Date().toString();
    this.isSystem = options.isSystem || 0;
    this.pid = options.pid;
    this.isDisabled = options.isDisabled || 0;
    this.log_path = options.log_path || '';
    this.isPinned = options.isPinned || 0;
    this.labels = options.labels || [];
    this.last_running_time = options.last_running_time || 0;
    this.last_execution_time = options.last_execution_time || 0;
    this.sub_id = options.sub_id;
    this.extra_schedules = options.extra_schedules;
    this.task_before = options.task_before;
    this.task_after = options.task_after;
    this.log_name = options.log_name;
    this.allow_multiple_instances = options.allow_multiple_instances || 0;
  }
}

export enum CrontabStatus {
  'running' = 0,
  'queued' = 0.5,
  'idle' = 1,
  'disabled',
}

export interface CronInstance extends Model<Crontab, Crontab>, Crontab {}
export const CrontabModel = sequelize.define<CronInstance>('Crontab', {
  name: {
    unique: 'compositeIndex',
    type: DataTypes.STRING,
  },
  command: {
    unique: 'compositeIndex',
    type: DataTypes.STRING,
  },
  schedule: {
    unique: 'compositeIndex',
    type: DataTypes.STRING,
  },
  timestamp: DataTypes.STRING,
  saved: DataTypes.BOOLEAN,
  status: DataTypes.NUMBER,
  isSystem: DataTypes.NUMBER,
  pid: DataTypes.NUMBER,
  isDisabled: DataTypes.NUMBER,
  isPinned: DataTypes.NUMBER,
  log_path: DataTypes.STRING,
  labels: DataTypes.JSON,
  last_running_time: DataTypes.NUMBER,
  last_execution_time: DataTypes.NUMBER,
  sub_id: { type: DataTypes.NUMBER, allowNull: true },
  extra_schedules: DataTypes.JSON,
  task_before: DataTypes.STRING,
  task_after: DataTypes.STRING,
  log_name: DataTypes.STRING,
  allow_multiple_instances: DataTypes.NUMBER,
});


================================================
FILE: back/data/cronView.ts
================================================
import { sequelize } from '.';
import { DataTypes, Model } from 'sequelize';

export enum CronViewType {
  '系统' = 1,
  '个人',
}

interface SortType {
  type: 'ASC' | 'DESC';
  value: string;
}

interface FilterType {
  property: string;
  operation: string;
  value: string;
}

export class CrontabView {
  name?: string;
  id?: number;
  position?: number;
  isDisabled?: 1 | 0;
  filters?: FilterType[];
  sorts?: SortType[];
  filterRelation?: 'and' | 'or';
  type?: CronViewType;

  constructor(options: CrontabView) {
    this.name = options.name;
    this.id = options.id;
    this.position = options.position;
    this.isDisabled = options.isDisabled || 0;
    this.filters = options.filters;
    this.sorts = options.sorts;
    this.filterRelation = options.filterRelation;
    this.type = options.type || CronViewType.个人;
  }
}

export interface CronViewInstance
  extends Model<CrontabView, CrontabView>,
    CrontabView {}
export const CrontabViewModel = sequelize.define<CronViewInstance>(
  'CrontabView',
  {
    name: {
      unique: 'name',
      type: DataTypes.STRING,
    },
    position: DataTypes.NUMBER,
    isDisabled: DataTypes.NUMBER,
    filters: DataTypes.JSON,
    sorts: DataTypes.JSON,
    filterRelation: {
      type: DataTypes.STRING,
      allowNull: true,
    },
    type: DataTypes.NUMBER,
  },
);


================================================
FILE: back/data/dependence.ts
================================================
import { sequelize } from '.';
import { DataTypes, Model, ModelDefined } from 'sequelize';

export class Dependence {
  timestamp?: string;
  id?: number;
  status: DependenceStatus;
  type: DependenceTypes;
  name: string;
  log?: string[];
  remark?: string;

  constructor(options: Dependence) {
    this.id = options.id;
    this.status =
      typeof options.status === 'number' && DependenceStatus[options.status]
        ? options.status
        : DependenceStatus.queued;
    this.type = options.type || DependenceTypes.nodejs;
    this.timestamp = new Date().toString();
    this.name = options.name.trim();
    this.log = options.log || [];
    this.remark = options.remark || '';
  }
}

export enum DependenceStatus {
  'installing',
  'installed',
  'installFailed',
  'removing',
  'removed',
  'removeFailed',
  'queued',
  'cancelled',
}

export enum DependenceTypes {
  'nodejs',
  'python3',
  'linux',
}

export enum versionDependenceCommandTypes {
  '@',
  '==',
  '=',
}

export interface DependenceInstance
  extends Model<Dependence, Dependence>,
    Dependence {}
export const DependenceModel = sequelize.define<DependenceInstance>(
  'Dependence',
  {
    name: DataTypes.STRING,
    type: DataTypes.NUMBER,
    timestamp: DataTypes.STRING,
    status: DataTypes.NUMBER,
    log: DataTypes.JSON,
    remark: DataTypes.STRING,
  },
);


================================================
FILE: back/data/env.ts
================================================
import { DataTypes, Model } from 'sequelize';
import { sequelize } from '.';

export class Env {
  value?: string;
  timestamp?: string;
  id?: number;
  status?: EnvStatus;
  position?: number;
  name?: string;
  remarks?: string;
  isPinned?: 1 | 0;

  constructor(options: Env) {
    this.value = options.value;
    this.id = options.id;
    this.status =
      typeof options.status === 'number' && EnvStatus[options.status]
        ? options.status
        : EnvStatus.normal;
    this.timestamp = new Date().toString();
    this.position = options.position;
    this.name = options.name;
    this.remarks = options.remarks || '';
    this.isPinned = options.isPinned || 0;
  }
}

export enum EnvStatus {
  'normal',
  'disabled',
}

export const maxPosition = 9000000000000000;
export const initPosition = 4500000000000000;
export const stepPosition = 10000000000;
export const minPosition = 100;

export interface EnvInstance extends Model<Env, Env>, Env {}
export const EnvModel = sequelize.define<EnvInstance>('Env', {
  value: { type: DataTypes.STRING, unique: 'compositeIndex' },
  timestamp: DataTypes.STRING,
  status: DataTypes.NUMBER,
  position: DataTypes.NUMBER,
  name: { type: DataTypes.STRING, unique: 'compositeIndex' },
  remarks: DataTypes.STRING,
  isPinned: DataTypes.NUMBER,
});


================================================
FILE: back/data/index.ts
================================================
import { Sequelize, Transaction } from 'sequelize';
import config from '../config/index';
import { join } from 'path';

export const sequelize = new Sequelize({
  dialect: 'sqlite',
  storage: join(config.dbPath, 'database.sqlite'),
  logging: false,
  retry: {
    max: 10,
    match: ['SQLITE_BUSY: database is locked'],
  },
  pool: {
    max: 5,
    min: 2,
    idle: 30000,
    acquire: 30000,
    evict: 10000,
  },
  transactionType: Transaction.TYPES.IMMEDIATE,
});

export type ResponseType<T> = { code: number; data?: T; message?: string };


================================================
FILE: back/data/notify.ts
================================================
export enum NotificationMode {
  'gotify' = 'gotify',
  'goCqHttpBot' = 'goCqHttpBot',
  'serverChan' = 'serverChan',
  'pushDeer' = 'pushDeer',
  'bark' = 'bark',
  'chat' = 'chat',
  'telegramBot' = 'telegramBot',
  'dingtalkBot' = 'dingtalkBot',
  'weWorkBot' = 'weWorkBot',
  'weWorkApp' = 'weWorkApp',
  'aibotk' = 'aibotk',
  'iGot' = 'iGot',
  'pushPlus' = 'pushPlus',
  'wePlusBot' = 'wePlusBot',
  'email' = 'email',
  'pushMe' = 'pushMe',
  'feishu' = 'feishu',
  'webhook' = 'webhook',
  'chronocat' = 'Chronocat',
  'ntfy' = 'ntfy',
  'wxPusherBot' = 'wxPusherBot',
}

abstract class NotificationBaseInfo {
  public type!: NotificationMode;
}

export class GotifyNotification extends NotificationBaseInfo {
  public gotifyUrl = '';
  public gotifyToken = '';
  public gotifyPriority = 0;
}

export class GoCqHttpBotNotification extends NotificationBaseInfo {
  public goCqHttpBotUrl = '';
  public goCqHttpBotToken = '';
  public goCqHttpBotQq = '';
}

export class ServerChanNotification extends NotificationBaseInfo {
  public serverChanKey = '';
}

export class PushDeerNotification extends NotificationBaseInfo {
  public pushDeerKey = '';
  public pushDeerUrl = '';
}

export class synologyChatNotification extends NotificationBaseInfo {
  public synologyChatUrl = '';
}

export class BarkNotification extends NotificationBaseInfo {
  public barkPush = '';
  public barkIcon = 'https://qn.whyour.cn/logo.png';
  public barkSound = '';
  public barkGroup = 'qinglong';
  public barkLevel = 'active';
  public barkUrl = '';
  public barkArchive = '';
}

export class TelegramBotNotification extends NotificationBaseInfo {
  public telegramBotToken = '';
  public telegramBotUserId = '';
  public telegramBotProxyHost = '';
  public telegramBotProxyPort = '';
  public telegramBotProxyAuth = '';
  public telegramBotApiHost = 'https://api.telegram.org';
}

export class DingtalkBotNotification extends NotificationBaseInfo {
  public dingtalkBotToken = '';
  public dingtalkBotSecret = '';
}

export class WeWorkBotNotification extends NotificationBaseInfo {
  public weWorkBotKey = '';
  public weWorkOrigin = '';
}

export class WeWorkAppNotification extends NotificationBaseInfo {
  public weWorkAppKey = '';
  public weWorkOrigin = '';
}

export class AibotkNotification extends NotificationBaseInfo {
  public aibotkKey: string = '';
  public aibotkType: 'room' | 'contact' = 'room';
  public aibotkName: string = '';
}

export class IGotNotification extends NotificationBaseInfo {
  public iGotPushKey = '';
}

export class PushPlusNotification extends NotificationBaseInfo {
  public pushPlusToken = '';
  public pushPlusUser = '';
  public pushPlusTemplate = '';
  public pushplusChannel = '';
  public pushplusWebhook = '';
  public pushplusCallbackUrl = '';
  public pushplusTo = '';
}

export class WePlusBotNotification extends NotificationBaseInfo {
  public wePlusBotToken = '';
  public wePlusBotReceiver = '';
  public wePlusBotVersion = '';
}

export class EmailNotification extends NotificationBaseInfo {
  public emailService: string = '';
  public emailUser: string = '';
  public emailPass: string = '';
  public emailTo: string = '';
}

export class PushMeNotification extends NotificationBaseInfo {
  public pushMeKey: string = '';
  public pushMeUrl: string = '';
}

export class ChronocatNotification extends NotificationBaseInfo {
  public chronocatURL: string = '';
  public chronocatQQ: string = '';
  public chronocatToken: string = '';
}

export class WebhookNotification extends NotificationBaseInfo {
  public webhookHeaders: string = '';
  public webhookBody: string = '';
  public webhookUrl: string = '';
  public webhookMethod: 'GET' | 'POST' | 'PUT' = 'GET';
  public webhookContentType:
    | 'application/json'
    | 'multipart/form-data'
    | 'application/x-www-form-urlencoded' = 'application/json';
}

export class LarkNotification extends NotificationBaseInfo {
  public larkKey = '';
  public larkSecret = '';
}

export class NtfyNotification extends NotificationBaseInfo {
  public ntfyUrl = '';
  public ntfyTopic = '';
  public ntfyPriority = '';
  public ntfyToken = '';
  public ntfyUsername = '';
  public ntfyPassword = '';
  public ntfyActions = '';
}

export class WxPusherBotNotification extends NotificationBaseInfo {
  public wxPusherBotAppToken = '';
  public wxPusherBotTopicIds = '';
  public wxPusherBotUids = '';
}

export interface NotificationInfo
  extends GoCqHttpBotNotification,
    GotifyNotification,
    ServerChanNotification,
    PushDeerNotification,
    synologyChatNotification,
    BarkNotification,
    TelegramBotNotification,
    DingtalkBotNotification,
    WeWorkBotNotification,
    WeWorkAppNotification,
    AibotkNotification,
    IGotNotification,
    PushPlusNotification,
    WePlusBotNotification,
    EmailNotification,
    PushMeNotification,
    WebhookNotification,
    ChronocatNotification,
    LarkNotification,
    NtfyNotification,
    WxPusherBotNotification {}


================================================
FILE: back/data/open.ts
================================================
import { sequelize } from '.';
import { DataTypes, Model, ModelDefined } from 'sequelize';

export class App {
  name: string;
  scopes: AppScope[];
  client_id: string;
  client_secret: string;
  tokens?: AppToken[];
  id?: number;

  constructor(options: App) {
    this.name = options.name;
    this.scopes = options.scopes;
    this.client_id = options.client_id;
    this.client_secret = options.client_secret;
    this.id = options.id;
  }
}

export interface AppToken {
  value: string;
  type?: 'Bearer';
  expiration: number;
}

export type AppScope = 'envs' | 'crons' | 'configs' | 'scripts' | 'logs' | 'system';

export interface AppInstance extends Model<App, App>, App {}
export const AppModel = sequelize.define<AppInstance>('App', {
  name: { type: DataTypes.STRING, unique: 'name' },
  scopes: DataTypes.JSON,
  client_id: DataTypes.STRING,
  client_secret: DataTypes.STRING,
  tokens: DataTypes.JSON,
});


================================================
FILE: back/data/sock.ts
================================================
export class SockMessage {
  message?: string;
  type?: SockMessageType;
  references?: number[];

  constructor(options: SockMessage) {
    this.type = options.type;
    this.message = options.message;
    this.references = options.references;
  }
}

export type SockMessageType =
  | 'ping'
  | 'installDependence'
  | 'uninstallDependence'
  | 'updateSystemVersion'
  | 'manuallyRunScript'
  | 'runSubscriptionEnd'
  | 'reloadSystem'
  | 'updateNodeMirror'
  | 'updateLinuxMirror';


================================================
FILE: back/data/subscription.ts
================================================
import { sequelize } from '.';
import { DataTypes, Model, ModelDefined } from 'sequelize';
import { SimpleIntervalSchedule } from 'toad-scheduler';

type SimpleIntervalScheduleUnit = keyof SimpleIntervalSchedule;
export class Subscription {
  id?: number;
  name?: string;
  type?: 'public-repo' | 'private-repo' | 'file';
  schedule_type?: 'crontab' | 'interval';
  schedule?: string;
  interval_schedule?: { type: SimpleIntervalScheduleUnit; value: number };
  url?: string;
  whitelist?: string;
  blacklist?: string;
  dependences?: string;
  branch?: string;
  status?: SubscriptionStatus;
  pull_type?: 'ssh-key' | 'user-pwd';
  pull_option?:
    | { private_key: string }
    | { username: string; password: string };
  pid?: number;
  is_disabled?: 1 | 0;
  log_path?: string;
  alias: string;
  command?: string;
  extensions?: string;
  sub_before?: string;
  sub_after?: string;
  proxy?: string;
  autoAddCron?: 1 | 0;
  autoDelCron?: 1 | 0;

  constructor(options: Subscription) {
    this.id = options.id;
    this.name = options.name || options.alias;
    this.type = options.type;
    this.schedule = options.schedule;
    this.status = this.status =
      typeof options.status === 'number' && SubscriptionStatus[options.status]
        ? options.status
        : SubscriptionStatus.idle;
    this.url = options.url;
    this.whitelist = options.whitelist;
    this.blacklist = options.blacklist;
    this.dependences = options.dependences;
    this.branch = options.branch;
    this.pull_type = options.pull_type;
    this.pull_option = options.pull_option;
    this.pid = options.pid;
    this.is_disabled = options.is_disabled;
    this.log_path = options.log_path;
    this.schedule_type = options.schedule_type;
    this.alias = options.alias;
    this.interval_schedule = options.interval_schedule;
    this.extensions = options.extensions;
    this.sub_before = options.sub_before;
    this.sub_after = options.sub_after;
    this.proxy = options.proxy;
    this.autoAddCron = options.autoAddCron ? 1 : 0;
    this.autoDelCron = options.autoDelCron ? 1 : 0;
  }
}

export enum SubscriptionStatus {
  'running',
  'idle',
  'disabled',
  'queued',
}

export interface SubscriptionInstance
  extends Model<Subscription, Subscription>,
    Subscription {}
export const SubscriptionModel = sequelize.define<SubscriptionInstance>(
  'Subscription',
  {
    name: {
      unique: 'compositeIndex',
      type: DataTypes.STRING,
    },
    url: {
      unique: 'compositeIndex',
      type: DataTypes.STRING,
    },
    schedule: {
      unique: 'compositeIndex',
      type: DataTypes.STRING,
    },
    interval_schedule: {
      unique: 'compositeIndex',
      type: DataTypes.JSON,
    },
    type: DataTypes.STRING,
    whitelist: DataTypes.STRING,
    blacklist: DataTypes.STRING,
    status: DataTypes.NUMBER,
    dependences: DataTypes.STRING,
    extensions: DataTypes.STRING,
    sub_before: DataTypes.STRING,
    sub_after: DataTypes.STRING,
    branch: DataTypes.STRING,
    pull_type: DataTypes.STRING,
    pull_option: DataTypes.JSON,
    pid: DataTypes.NUMBER,
    is_disabled: DataTypes.NUMBER,
    log_path: DataTypes.STRING,
    schedule_type: DataTypes.STRING,
    alias: { type: DataTypes.STRING, unique: 'alias' },
    proxy: { type: DataTypes.STRING, allowNull: true },
    autoAddCron: { type: DataTypes.NUMBER, allowNull: true },
    autoDelCron: { type: DataTypes.NUMBER, allowNull: true },
  },
);


================================================
FILE: back/data/system.ts
================================================
import { sequelize } from '.';
import { DataTypes, Model, ModelDefined } from 'sequelize';
import { NotificationInfo } from './notify';

export class SystemInfo {
  ip?: string;
  type: AuthDataType;
  info?: SystemModelInfo;
  id?: number;

  constructor(options: SystemInfo) {
    this.ip = options.ip;
    this.info = options.info;
    this.type = options.type;
    this.id = options.id;
  }
}

export enum LoginStatus {
  'success',
  'fail',
}

export enum AuthDataType {
  'loginLog' = 'loginLog',
  'authToken' = 'authToken',
  'notification' = 'notification',
  'removeLogFrequency' = 'removeLogFrequency',
  'systemConfig' = 'systemConfig',
  'authConfig' = 'authConfig',
}

export interface SystemConfigInfo {
  logRemoveFrequency?: number;
  cronConcurrency?: number;
  dependenceProxy?: string;
  nodeMirror?: string;
  pythonMirror?: string;
  linuxMirror?: string;
  timezone?: string;
  globalSshKey?: string;
}

export interface LoginLogInfo {
  timestamp?: number;
  address?: string;
  ip?: string;
  platform?: string;
  status?: LoginStatus;
}

export interface TokenInfo {
  value: string;
  timestamp: number;
  ip: string;
  address: string;
  platform: string;
  /**
   * Token expiration time in seconds since Unix epoch.
   * If undefined, the token uses JWT's built-in expiration.
   */
  expiration?: number;
}

export interface AuthInfo {
  username: string;
  password: string;
  retries: number;
  lastlogon: number;
  lastip: string;
  lastaddr: string;
  platform: string;
  isTwoFactorChecking: boolean;
  token: string;
  tokens: Record<string, string | TokenInfo[]>;
  twoFactorActivated: boolean;
  twoFactorSecret: string;
  avatar: string;
}

export type SystemModelInfo = SystemConfigInfo &
  Partial<NotificationInfo> &
  LoginLogInfo &
  Partial<AuthInfo>;

export interface SystemInstance
  extends Model<SystemInfo, SystemInfo>,
    SystemInfo {}
export const SystemModel = sequelize.define<SystemInstance>('Auth', {
  ip: DataTypes.STRING,
  type: DataTypes.STRING,
  info: {
    type: DataTypes.JSON,
    allowNull: true,
  },
});


================================================
FILE: back/interface/schedule.ts
================================================
export enum ScheduleType {
  BOOT = '@boot',
  ONCE = '@once',
}

export type ScheduleValidator = (schedule?: string) => boolean;
export type CronSchedulerPayload = {
  name: string;
  id: string;
  schedule: string;
  command: string;
  extra_schedules: Array<{ schedule: string }>;
};


================================================
FILE: back/loaders/app.ts
================================================
import expressLoader from './express';
import depInjectorLoader from './depInjector';
import Logger from './logger';
import initData from './initData';
import { Application } from 'express';
import linkDeps from './deps';
import initTask from './initTask';
import initFile from './initFile';

export default async ({ app }: { app: Application }) => {
  depInjectorLoader();
  Logger.info('✌️ Dependency loaded');

  await linkDeps();
  Logger.info('✌️ Link deps loaded');

  initFile();
  Logger.info('✌️ Init file loaded');

  await initData();
  Logger.info('✌️ Init data loaded');

  initTask();
  Logger.info('✌️ Init task loaded');

  expressLoader({ app });
  Logger.info('✌️ Express loaded');
};


================================================
FILE: back/loaders/bootAfter.ts
================================================
import Container from 'typedi';
import CronService from '../services/cron';

export default async () => {
  const cronService = Container.get(CronService);

  await cronService.bootTask();
};


================================================
FILE: back/loaders/db.ts
================================================
import Logger from './logger';
import { EnvModel } from '../data/env';
import { CrontabModel } from '../data/cron';
import { DependenceModel } from '../data/dependence';
import { AppModel } from '../data/open';
import { SystemModel } from '../data/system';
import { SubscriptionModel } from '../data/subscription';
import { CrontabViewModel } from '../data/cronView';
import { sequelize } from '../data';

export default async () => {
  try {
    await CrontabModel.sync();
    await DependenceModel.sync();
    await AppModel.sync();
    await SystemModel.sync();
    await EnvModel.sync();
    await SubscriptionModel.sync();
    await CrontabViewModel.sync();

    // 初始化新增字段
    const migrations = [
      {
        table: 'CrontabViews',
        column: 'filterRelation',
        type: 'VARCHAR(255)',
      },
      { table: 'Subscriptions', column: 'proxy', type: 'VARCHAR(255)' },
      { table: 'CrontabViews', column: 'type', type: 'NUMBER' },
      { table: 'Subscriptions', column: 'autoAddCron', type: 'NUMBER' },
      { table: 'Subscriptions', column: 'autoDelCron', type: 'NUMBER' },
      { table: 'Crontabs', column: 'sub_id', type: 'NUMBER' },
      { table: 'Crontabs', column: 'extra_schedules', type: 'JSON' },
      { table: 'Crontabs', column: 'task_before', type: 'TEXT' },
      { table: 'Crontabs', column: 'task_after', type: 'TEXT' },
      { table: 'Crontabs', column: 'log_name', type: 'VARCHAR(255)' },
      {
        table: 'Crontabs',
        column: 'allow_multiple_instances',
        type: 'NUMBER',
      },
      { table: 'Envs', column: 'isPinned', type: 'NUMBER' },
    ];

    for (const migration of migrations) {
      try {
        await sequelize.query(
          `alter table ${migration.table} add column ${migration.column} ${migration.type}`,
        );
      } catch (error) {
        // Column already exists or other error, continue
      }
    }

    Logger.info('✌️ DB loaded');
  } catch (error) {
    Logger.error('✌️ DB load failed', error);
  }
};


================================================
FILE: back/loaders/depInjector.ts
================================================
import { Container } from 'typedi';
import LoggerInstance from './logger';

export default () => {
  try {
    Container.set('logger', LoggerInstance);
  } catch (e) {
    LoggerInstance.error('🔥 Error on dependency injector loader: %o', e);
    throw e;
  }
};


================================================
FILE: back/loaders/deps.ts
================================================
import path from 'path';
import fs from 'fs/promises';
import os from 'os';
import chokidar from 'chokidar';
import config from '../config/index';
import Logger from './logger';

async function linkToNodeModule(src: string, dst?: string) {
  const target = path.join(config.rootPath, 'node_modules', dst || src);
  const source = path.join(config.rootPath, src);

  try {
    const stats = await fs.lstat(target);
    if (!stats) {
      await fs.symlink(source, target, 'dir');
    }
  } catch (error) { }
}

async function linkCommand() {
  const homeDir = os.homedir();
  let userBinDir = path.join(homeDir, 'bin');

  try {
    await fs.mkdir(userBinDir, { recursive: true });
    await linkCommandToDir(userBinDir);
  } catch (error) {
    Logger.error('Linking command failed:', error);
  }
}

async function linkCommandToDir(commandDir: string) {
  const linkShell = [
    {
      src: 'update.sh',
      dest: 'ql',
      tmp: 'ql_tmp',
    },
    {
      src: 'task.sh',
      dest: 'task',
      tmp: 'task_tmp',
    },
  ];

  for (const link of linkShell) {
    const source = path.join(config.rootPath, 'shell', link.src);
    const target = path.join(commandDir, link.dest);
    const tmpTarget = path.join(commandDir, link.tmp);
    try {
      const stats = await fs.lstat(tmpTarget);
      if (stats) {
        await fs.unlink(tmpTarget);
      }
    } catch (error) { }

    await fs.symlink(source, tmpTarget);
    await fs.rename(tmpTarget, target);
  }
}

export default async (src: string = 'deps') => {
  await linkCommand();
  await linkToNodeModule(src);

  const source = path.join(config.rootPath, src);
  const watcher = chokidar.watch(source, {
    ignored: /(^|[\/\\])\../, // ignore dotfiles
    persistent: true,
  });

  watcher
    .on('add', () => linkToNodeModule(src))
    .on('change', () => linkToNodeModule(src));
};


================================================
FILE: back/loaders/express.ts
================================================
import express, { Request, Response, NextFunction, Application } from 'express';
import bodyParser from 'body-parser';
import cors from 'cors';
import routes from '../api';
import config from '../config';
import { UnauthorizedError, expressjwt } from 'express-jwt';
import { getPlatform, getToken } from '../config/util';
import rewrite from 'express-urlrewrite';
import { errors } from 'celebrate';
import { serveEnv } from '../config/serverEnv';
import { IKeyvStore, shareStore } from '../shared/store';
import { isValidToken } from '../shared/auth';
import path from 'path';

export default ({ app }: { app: Application }) => {
  // Security: Enable strict routing to prevent case-insensitive path bypass
  app.set('case sensitive routing', true);
  app.set('strict routing', true);
  app.set('trust proxy', 'loopback');
  app.use(cors());
  
  // Security: Path normalization middleware to prevent case variation attacks
  app.use((req, res, next) => {
    const originalPath = req.path;
    const normalizedPath = originalPath.toLowerCase();
    
    // Block requests with case variations on protected paths
    if (originalPath !== normalizedPath && 
        (normalizedPath.startsWith('/api/') || normalizedPath.startsWith('/open/'))) {
      return res.status(400).json({
        code: 400,
        message: 'Invalid path format'
      });
    }
    
    next();
  });
  
  // Rewrite URLs to strip baseUrl prefix if configured
  // This allows the rest of the app to work without baseUrl awareness
  if (config.baseUrl) {
    app.use(rewrite(`${config.baseUrl}/*`, '/$1'));
  }
  
  app.get(`${config.api.prefix}/env.js`, serveEnv);
  app.use(`${config.api.prefix}/static`, express.static(config.uploadPath));

  app.use(bodyParser.json({ limit: '50mb' }));
  app.use(bodyParser.urlencoded({ limit: '50mb', extended: true }));

  const frontendPath = path.join(config.rootPath, 'static/dist');
  app.use(express.static(frontendPath));

  app.use(
    expressjwt({
      secret: config.jwt.secret,
      algorithms: ['HS384'],
    }).unless({
      path: [...config.apiWhiteList, /^(\/(?!api\/).*)$/i],
    }),
  );

  app.use((req: Request, res, next) => {
    if (!req.headers) {
      req.platform = 'desktop';
    } else {
      const platform = getPlatform(req.headers['user-agent'] || '');
      req.platform = platform;
    }
    return next();
  });

  app.use(async (req: Request, res, next) => {
    const pathLower = req.path.toLowerCase();
    if (!['/open/', '/api/'].some((x) => pathLower.startsWith(x))) {
      return next();
    }

    const headerToken = getToken(req);
    if (pathLower.startsWith('/open/')) {
      const apps = await shareStore.getApps();
      const doc = apps?.filter((x) =>
        x.tokens?.find((y) => y.value === headerToken),
      )?.[0];
      if (doc && doc.tokens && doc.tokens.length > 0) {
        const currentToken = doc.tokens.find((x) => x.value === headerToken);
        const keyMatch = pathLower.match(/\/open\/([a-z]+)\/*/);
        const key = keyMatch && keyMatch[1];
        if (
          doc.scopes.includes(key as any) &&
          currentToken &&
          currentToken.expiration >= Math.round(Date.now() / 1000)
        ) {
          return next();
        }
      }
    }

    const originPath = `${req.baseUrl}${req.path === '/' ? '' : req.path}`;
    if (
      !headerToken &&
      originPath &&
      config.apiWhiteList.includes(originPath)
    ) {
      return next();
    }

    const authInfo = await shareStore.getAuthInfo();
    if (isValidToken(authInfo, headerToken, req.platform)) {
      return next();
    }

    const errorCode = headerToken ? 'invalid_token' : 'credentials_required';
    const errorMessage = headerToken
      ? 'jwt malformed'
      : 'No authorization token was found';
    const err = new UnauthorizedError(errorCode, { message: errorMessage });
    next(err);
  });

  app.use(async (req, res, next) => {
    const pathLower = req.path.toLowerCase();
    if (
      ![
        '/api/user/init',
        '/api/user/notification/init',
        '/open/user/init',
        '/open/user/notification/init',
      ].includes(req.path)
    ) {
      return next();
    }
    const authInfo =
      (await shareStore.getAuthInfo()) || ({} as IKeyvStore['authInfo']);

    let isInitialized = true;
    if (
      Object.keys(authInfo).length === 2 &&
      authInfo.username === 'admin' &&
      authInfo.password === 'admin'
    ) {
      isInitialized = false;
    }

    if (isInitialized) {
      return res.send({ code: 450, message: '未知错误' });
    } else {
      return next();
    }
  });

  app.use(rewrite('/open/*', '/api/$1'));
  app.use(config.api.prefix, routes());

  app.get('*', (_, res, next) => {
    const indexPath = path.join(frontendPath, 'index.html');
    res.sendFile(indexPath, (err) => {
      if (err) {
        const err: any = new Error('Not Found');
        err['status'] = 404;
        next(err);
      }
    });
  });

  app.use(errors());

  app.use(
    (
      err: Error & { status: number },
      req: Request,
      res: Response,
      next: NextFunction,
    ) => {
      if (err.name === 'UnauthorizedError') {
        return res
          .status(err.status)
          .send({ code: 401, message: err.message })
          .end();
      }
      return next(err);
    },
  );

  app.use(
    (
      err: Error & { errors: any[] },
      req: Request,
      res: Response,
      next: NextFunction,
    ) => {
      if (err.name.includes('Sequelize')) {
        return res
          .status(500)
          .send({
            code: 400,
            message: `${err.message}`,
            errors: err.errors,
          })
          .end();
      }
      return next(err);
    },
  );

  app.use(
    (
      err: Error & { status: number },
      req: Request,
      res: Response,
      next: NextFunction,
    ) => {
      res.status(err.status || 500);
      res.json({
        code: err.status || 500,
        message: err.message,
      });
    },
  );
};


================================================
FILE: back/loaders/initData.ts
================================================
import DependenceService from '../services/dependence';
import { exec } from 'child_process';
import { Container } from 'typedi';
import { Crontab, CrontabModel, CrontabStatus } from '../data/cron';
import CronService from '../services/cron';
import EnvService from '../services/env';
import { DependenceModel, DependenceStatus } from '../data/dependence';
import { Op } from 'sequelize';
import config from '../config';
import { CrontabViewModel, CronViewType } from '../data/cronView';
import { initPosition } from '../data/env';
import { AuthDataType, SystemModel } from '../data/system';
import SystemService from '../services/system';
import UserService from '../services/user';
import { writeFile, readFile } from 'fs/promises';
import { createRandomString, fileExist, isDemoEnv, safeJSONParse } from '../config/util';
import OpenService from '../services/open';
import { shareStore } from '../shared/store';
import Logger from './logger';
import { AppModel } from '../data/open';

export default async () => {
  const cronService = Container.get(CronService);
  const envService = Container.get(EnvService);
  const dependenceService = Container.get(DependenceService);
  const systemService = Container.get(SystemService);
  const userService = Container.get(UserService);
  const openService = Container.get(OpenService);

  // 初始化增加系统配置
  let systemApp = (
    await AppModel.findOne({
      where: { name: 'system' },
    })
  )?.get({ plain: true });
  if (!systemApp) {
    systemApp = await AppModel.create({
      name: 'system',
      scopes: ['crons', 'system'],
      client_id: createRandomString(12, 12),
      client_secret: createRandomString(24, 24),
    });
  }
  const [systemConfig] = await SystemModel.findOrCreate({
    where: { type: AuthDataType.systemConfig },
  });
  await SystemModel.findOrCreate({
    where: { type: AuthDataType.notification },
  });
  const [authConfig] = await SystemModel.findOrCreate({
    where: { type: AuthDataType.authConfig },
  });
  if (!authConfig?.info || isDemoEnv()) {
    let authInfo = {
      username: 'admin',
      password: 'admin',
    };
    try {
      const authFileExist = await fileExist(config.authConfigFile);
      if (authFileExist) {
        const content = await readFile(config.authConfigFile, 'utf8');
        authInfo = safeJSONParse(content);
      }
    } catch (error) {
      Logger.warn('Failed to read auth config file, using default credentials');
    }
    await SystemModel.upsert({
      id: authConfig?.id,
      info: authInfo,
      type: AuthDataType.authConfig,
    });
  }

  const installDependencies = async () => {
    const docs = await DependenceModel.findAll({
      where: {},
      order: [
        ['type', 'DESC'],
        ['createdAt', 'DESC'],
      ],
      raw: true,
    });

    await DependenceModel.update(
      { status: DependenceStatus.queued, log: [] },
      { where: { id: docs.map((x) => x.id!) } },
    );

    setTimeout(async () => {
      await dependenceService.installDependenceOneByOne(docs);

      const bootAfterLoader = await import('./bootAfter');
      bootAfterLoader.default();
    }, 5000);
  };

  // 初始化更新 linux/python/nodejs 镜像源配置
  if (systemConfig.info?.pythonMirror) {
    systemService.updatePythonMirror({
      pythonMirror: systemConfig.info?.pythonMirror,
    });
  }
  if (systemConfig.info?.linuxMirror) {
    systemService.updateLinuxMirror(
      {
        linuxMirror: systemConfig.info?.linuxMirror,
      },
      undefined,
      () => installDependencies(),
    );
  } else {
    installDependencies();
  }
  if (systemConfig.info?.nodeMirror) {
    systemService.updateNodeMirror({
      nodeMirror: systemConfig.info?.nodeMirror,
    });
  }

  // 初始化新增默认全部任务视图
  CrontabViewModel.findAll({
    where: { type: CronViewType.系统, name: '全部任务' },
    raw: true,
  }).then((docs) => {
    if (docs.length === 0) {
      CrontabViewModel.create({
        name: '全部任务',
        type: CronViewType.系统,
        position: initPosition / 2,
      });
    }
  });

  // 初始化更新所有任务状态为空闲
  await CrontabModel.update({ status: CrontabStatus.idle }, { where: {} });

  // 初始化时执行一次所有的 ql repo 任务
  CrontabModel.findAll({
    where: {
      isDisabled: { [Op.ne]: 1 },
      command: {
        [Op.or]: [{ [Op.like]: `%ql repo%` }, { [Op.like]: `%ql raw%` }],
      },
    },
  }).then((docs) => {
    for (let i = 0; i < docs.length; i++) {
      const doc = docs[i];
      if (doc) {
        exec(doc.command);
      }
    }
  });

  // 更新2.11.3以前的脚本路径
  CrontabModel.findAll({
    where: {
      command: {
        [Op.or]: [
          { [Op.like]: `%\/${config.rootPath}\/scripts\/%` },
          { [Op.like]: `%\/${config.rootPath}\/config\/%` },
          { [Op.like]: `%\/${config.rootPath}\/log\/%` },
          { [Op.like]: `%\/${config.rootPath}\/db\/%` },
        ],
      },
    },
  }).then(async (docs) => {
    for (let i = 0; i < docs.length; i++) {
      const doc = docs[i];
      if (doc) {
        if (doc.command.includes(`${config.rootPath}/scripts/`)) {
          await CrontabModel.update(
            { command: doc.command.replace(`${config.rootPath}/scripts/`, '') },
            { where: { id: doc.id } },
          );
        }
        if (doc.command.includes(`${config.rootPath}/log/`)) {
          await CrontabModel.update(
            {
              command: `${config.dataPath}/log/${doc.command.replace(
                `${config.rootPath}/log/`,
                '',
              )}`,
            },
            { where: { id: doc.id } },
          );
        }
        if (doc.command.includes(`${config.rootPath}/config/`)) {
          await CrontabModel.update(
            {
              command: `${config.dataPath}/config/${doc.command.replace(
                `${config.rootPath}/config/`,
                '',
              )}`,
            },
            { where: { id: doc.id } },
          );
        }
        if (doc.command.includes(`${config.rootPath}/db/`)) {
          await CrontabModel.update(
            {
              command: `${config.dataPath}/db/${doc.command.replace(
                `${config.rootPath}/db/`,
                '',
              )}`,
            },
            { where: { id: doc.id } },
          );
        }
      }
    }
  });

  // 初始化保存一次ck和定时任务数据
  await cronService.autosave_crontab();
  await envService.set_envs();

  const authInfo = await userService.getAuthInfo();
  const apps = await openService.findApps();
  await shareStore.updateAuthInfo(authInfo);
  if (apps?.length) {
    await shareStore.updateApps(apps);
  }
};


================================================
FILE: back/loaders/initFile.ts
================================================
import fs from 'fs/promises';
import path from 'path';
import os from 'os';
import Logger from './logger';
import { fileExist } from '../config/util';
import { writeFileWithLock } from '../shared/utils';

const rootPath = process.env.QL_DIR as string;
let dataPath = path.join(rootPath, 'data/');

if (process.env.QL_DATA_DIR) {
  dataPath = process.env.QL_DATA_DIR.replace(/\/$/g, '');
}

const preloadPath = path.join(rootPath, 'shell/preload/');
const configPath = path.join(dataPath, 'config/');
const scriptPath = path.join(dataPath, 'scripts/');
const logPath = path.join(dataPath, 'log/');
const uploadPath = path.join(dataPath, 'upload/');
const bakPath = path.join(dataPath, 'bak/');
const samplePath = path.join(rootPath, 'sample/');
const tmpPath = path.join(logPath, '.tmp/');
const confFile = path.join(configPath, 'config.sh');
const sampleConfigFile = path.join(samplePath, 'config.sample.sh');
const sampleTaskShellFile = path.join(samplePath, 'task.sample.sh');
const sampleNotifyJsFile = path.join(samplePath, 'notify.js');
const sampleNotifyPyFile = path.join(samplePath, 'notify.py');
const scriptNotifyJsFile = path.join(scriptPath, 'sendNotify.js');
const scriptNotifyPyFile = path.join(scriptPath, 'notify.py');
const jsNotifyFile = path.join(preloadPath, '__ql_notify__.js');
const pyNotifyFile = path.join(preloadPath, '__ql_notify__.py');
const TaskBeforeFile = path.join(configPath, 'task_before.sh');
const TaskBeforeJsFile = path.join(configPath, 'task_before.js');
const TaskBeforePyFile = path.join(configPath, 'task_before.py');
const TaskAfterFile = path.join(configPath, 'task_after.sh');
const homedir = os.homedir();
const sshPath = path.resolve(homedir, '.ssh');
const sshdPath = path.join(dataPath, 'ssh.d');
const systemLogPath = path.join(dataPath, 'syslog');

const directories = [
  configPath,
  scriptPath,
  preloadPath,
  logPath,
  tmpPath,
  uploadPath,
  sshPath,
  bakPath,
  sshdPath,
  systemLogPath,
];

const files = [
  {
    target: confFile,
    source: sampleConfigFile,
    checkExistence: true,
  },
  {
    target: jsNotifyFile,
    source: sampleNotifyJsFile,
    checkExistence: false,
  },
  {
    target: pyNotifyFile,
    source: sampleNotifyPyFile,
    checkExistence: false,
  },
  {
    target: scriptNotifyJsFile,
    source: sampleNotifyJsFile,
    checkExistence: true,
  },
  {
    target: scriptNotifyPyFile,
    source: sampleNotifyPyFile,
    checkExistence: true,
  },
  {
    target: TaskBeforeFile,
    source: sampleTaskShellFile,
    checkExistence: true,
  },
  {
    target: TaskBeforeJsFile,
    content:
      '// The JavaScript code that executes before the JavaScript task execution will execute.',
    checkExistence: true,
  },
  {
    target: TaskBeforePyFile,
    content:
      '# The Python code that executes before the Python task execution will execute.',
    checkExistence: true,
  },
  {
    target: TaskAfterFile,
    source: sampleTaskShellFile,
    checkExistence: true,
  },
];

export default async () => {
  for (const dirPath of directories) {
    if (!(await fileExist(dirPath))) {
      await fs.mkdir(dirPath);
    }
  }

  for (const item of files) {
    const exists = await fileExist(item.target);
    if (!item.checkExistence || !exists) {
      if (!item.content && !item.source) {
        throw new Error(
          `Neither content nor source specified for ${item.target}`,
        );
      }
      const content =
        item.content ||
        (await fs.readFile(item.source!, { encoding: 'utf-8' }));
      await writeFileWithLock(item.target, content);
    }
  }

  Logger.info('✌️ Init file down');
};


================================================
FILE: back/loaders/initTask.ts
================================================
import { Container } from 'typedi';
import SystemService from '../services/system';
import ScheduleService, { ScheduleTaskType } from '../services/schedule';
import SubscriptionService from '../services/subscription';
import SshKeyService from '../services/sshKey';
import config from '../config';
import { fileExist } from '../config/util';
import { join } from 'path';

export default async () => {
  const systemService = Container.get(SystemService);
  const scheduleService = Container.get(ScheduleService);
  const subscriptionService = Container.get(SubscriptionService);
  const sshKeyService = Container.get(SshKeyService);

  // 生成内置token
  let tokenCommand = `ts-node-transpile-only ${join(
    config.rootPath,
    'back/token.ts',
  )}`;
  const tokenFile = join(config.rootPath, 'static/build/token.js');

  if (await fileExist(tokenFile)) {
    tokenCommand = `node ${tokenFile}`;
  }
  const cron = {
    id: NaN,
    name: '生成token',
    command: tokenCommand,
    runOrigin: 'system',
  } as ScheduleTaskType;
  await scheduleService.cancelIntervalTask(cron);
  scheduleService.createIntervalTask(
    cron,
    {
      days: 28,
    },
    true,
  );

  // 运行删除日志任务
  const data = await systemService.getSystemConfig();
  if (data && data.info) {
    if (data.info.logRemoveFrequency) {
      const rmlogCron = {
        id: data.id as number,
        name: '删除日志',
        command: `ql rmlog ${data.info.logRemoveFrequency}`,
        runOrigin: 'system' as const,
      };
      await scheduleService.cancelIntervalTask(rmlogCron);
      scheduleService.createIntervalTask(
        rmlogCron,
        {
          days: data.info.logRemoveFrequency,
        },
        true,
      );
    }

    systemService.updateTimezone(data.info);
    
    // Apply global SSH key if configured
    if (data.info.globalSshKey) {
      await sshKeyService.addGlobalSSHKey(data.info.globalSshKey, 'global');
    }
  }

  await subscriptionService.setSshConfig();
  const subs = await subscriptionService.list();
  for (const sub of subs) {
    subscriptionService.handleTask(sub.get({ plain: true }), !sub.is_disabled);
  }
};


================================================
FILE: back/loaders/logger.ts
================================================
import winston from 'winston';
import 'winston-daily-rotate-file';
import config from '../config';
import path from 'path';

const levelMap: Record<string, string> = {
  info: 'ℹ️', // info图标
  warn: '⚠️', // 警告图标
  error: '❌', // 错误图标
  debug: '🐛', // debug调试图标
};

const baseFormat = [
  winston.format.splat(),
  winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }),
  winston.format.align(),
];

const consoleFormat = winston.format.combine(
  winston.format.colorize({ level: true }),
  ...baseFormat,
  winston.format.printf((info) => {
    return `[${info.level} ${info.timestamp}]:${info.message}`;
  }),
);

const plainFormat = winston.format.combine(
  winston.format.uncolorize(),
  ...baseFormat,
  winston.format.printf((info) => {
    return `[${levelMap[info.level] || ''}${info.level} ${info.timestamp}]:${
      info.message
    }`;
  }),
);

const consoleTransport = new winston.transports.Console({
  format: consoleFormat,
  level: 'debug',
});

const fileTransport = new winston.transports.DailyRotateFile({
  filename: path.join(config.systemLogPath, '%DATE%.log'),
  datePattern: 'YYYY-MM-DD',
  maxSize: '20m',
  maxFiles: '7d',
  format: plainFormat,
  level: config.logs.level || 'info',
});

const LoggerInstance = winston.createLogger({
  level: 'debug',
  levels: winston.config.npm.levels,
  transports: [consoleTransport, fileTransport],
  exceptionHandlers: [consoleTransport, fileTransport],
  rejectionHandlers: [consoleTransport, fileTransport],
});

LoggerInstance.on('error', (error) => {
  console.error('Logger error:', error);
});

export default LoggerInstance;


================================================
FILE: back/loaders/server.ts
================================================
import { Server } from 'http';
import Logger from './logger';
import Sock from './sock';

export default async ({ server }: { server: Server }) => {
  await Sock({ server });
  Logger.info('✌️ Sock loaded');

  process.on('uncaughtException', (error) => {
    Logger.error('Uncaught exception:', error);
  });

  process.on('unhandledRejection', (reason, promise) => {
    Logger.error('Unhandled rejection:', reason, promise);
  });
};


================================================
FILE: back/loaders/sock.ts
================================================
import sockJs from 'sockjs';
import { Server } from 'http';
import { Container } from 'typedi';
import SockService from '../services/sock';
import { getPlatform } from '../config/util';
import { shareStore } from '../shared/store';
import { isValidToken } from '../shared/auth';
import config from '../config';

export default async ({ server }: { server: Server }) => {
  const echo = sockJs.createServer({ prefix: `${config.baseUrl}/api/ws`, log: () => { } });
  const sockService 
Download .txt
gitextract_8pxenqz2/

├── .editorconfig
├── .github/
│   ├── ISSUE_TEMPLATE/
│   │   ├── bug_report.yml
│   │   ├── config.yml
│   │   └── feature_request.yml
│   ├── PULL_REQUEST_TEMPLATE.md
│   ├── agents/
│   │   └── ql.agent.md
│   ├── config.yml
│   └── workflows/
│       └── build-docker-image.yml
├── .gitignore
├── .npmrc
├── .prettierignore
├── .prettierrc
├── .umirc.ts
├── LICENSE
├── README-en.md
├── README.md
├── SECURITY.md
├── back/
│   ├── api/
│   │   ├── config.ts
│   │   ├── cron.ts
│   │   ├── dependence.ts
│   │   ├── env.ts
│   │   ├── health.ts
│   │   ├── index.ts
│   │   ├── log.ts
│   │   ├── open.ts
│   │   ├── script.ts
│   │   ├── subscription.ts
│   │   ├── system.ts
│   │   ├── update.ts
│   │   └── user.ts
│   ├── app.ts
│   ├── config/
│   │   ├── const.ts
│   │   ├── http.ts
│   │   ├── index.ts
│   │   ├── serverEnv.ts
│   │   ├── share.ts
│   │   ├── subscription.ts
│   │   └── util.ts
│   ├── data/
│   │   ├── cron.ts
│   │   ├── cronView.ts
│   │   ├── dependence.ts
│   │   ├── env.ts
│   │   ├── index.ts
│   │   ├── notify.ts
│   │   ├── open.ts
│   │   ├── sock.ts
│   │   ├── subscription.ts
│   │   └── system.ts
│   ├── interface/
│   │   └── schedule.ts
│   ├── loaders/
│   │   ├── app.ts
│   │   ├── bootAfter.ts
│   │   ├── db.ts
│   │   ├── depInjector.ts
│   │   ├── deps.ts
│   │   ├── express.ts
│   │   ├── initData.ts
│   │   ├── initFile.ts
│   │   ├── initTask.ts
│   │   ├── logger.ts
│   │   ├── server.ts
│   │   └── sock.ts
│   ├── middlewares/
│   │   └── monitoring.ts
│   ├── protos/
│   │   ├── api.proto
│   │   ├── api.ts
│   │   ├── cron.proto
│   │   ├── cron.ts
│   │   ├── health.proto
│   │   └── health.ts
│   ├── schedule/
│   │   ├── addCron.ts
│   │   ├── api.ts
│   │   ├── client.ts
│   │   ├── data.ts
│   │   ├── delCron.ts
│   │   └── health.ts
│   ├── services/
│   │   ├── config.ts
│   │   ├── cron.ts
│   │   ├── cronView.ts
│   │   ├── dependence.ts
│   │   ├── env.ts
│   │   ├── grpc.ts
│   │   ├── health.ts
│   │   ├── http.ts
│   │   ├── log.ts
│   │   ├── metrics.ts
│   │   ├── notify.ts
│   │   ├── open.ts
│   │   ├── schedule.ts
│   │   ├── script.ts
│   │   ├── sock.ts
│   │   ├── sshKey.ts
│   │   ├── subscription.ts
│   │   ├── system.ts
│   │   └── user.ts
│   ├── shared/
│   │   ├── auth.ts
│   │   ├── interface.ts
│   │   ├── logStreamManager.ts
│   │   ├── pLimit.ts
│   │   ├── runCron.ts
│   │   ├── store.ts
│   │   └── utils.ts
│   ├── token.ts
│   ├── tsconfig.json
│   ├── types/
│   │   └── express.d.ts
│   └── validation/
│       └── schedule.ts
├── docker/
│   ├── 310.Dockerfile
│   ├── Dockerfile
│   ├── docker-compose.yml
│   └── docker-entrypoint.sh
├── ecosystem.config.js
├── nodemon.json
├── package.json
├── sample/
│   ├── auth.sample.json
│   ├── config.sample.sh
│   ├── extra.sample.sh
│   ├── notify.js
│   ├── notify.py
│   ├── notify.py.save
│   ├── ql_sample.js
│   ├── ql_sample.py
│   ├── task.sample.sh
│   └── tool.ts
├── shell/
│   ├── api.sh
│   ├── bot.sh
│   ├── check.sh
│   ├── env.sh
│   ├── otask.sh
│   ├── preload/
│   │   ├── client.js
│   │   ├── client.py
│   │   ├── sitecustomize.js
│   │   └── sitecustomize.py
│   ├── pub.sh
│   ├── rmlog.sh
│   ├── share.sh
│   ├── task.sh
│   └── update.sh
├── src/
│   ├── app.ts
│   ├── components/
│   │   ├── copy.tsx
│   │   ├── iconfont.tsx
│   │   ├── index.less
│   │   ├── name.tsx
│   │   ├── tag.tsx
│   │   └── terminal.tsx
│   ├── hooks/
│   │   ├── useFilterTreeData.ts
│   │   ├── useScrollHeight.ts
│   │   └── useTableScrollHeight.ts
│   ├── layouts/
│   │   ├── defaultProps.tsx
│   │   ├── index.less
│   │   └── index.tsx
│   ├── loading.tsx
│   ├── locales/
│   │   ├── en-US.json
│   │   └── zh-CN.json
│   ├── pages/
│   │   ├── 404.tsx
│   │   ├── config/
│   │   │   ├── index.less
│   │   │   └── index.tsx
│   │   ├── crontab/
│   │   │   ├── const.ts
│   │   │   ├── detail.tsx
│   │   │   ├── index.less
│   │   │   ├── index.tsx
│   │   │   ├── logModal.tsx
│   │   │   ├── modal.tsx
│   │   │   ├── type.ts
│   │   │   ├── viewCreateModal.tsx
│   │   │   └── viewManageModal.tsx
│   │   ├── dependence/
│   │   │   ├── index.less
│   │   │   ├── index.tsx
│   │   │   ├── logModal.tsx
│   │   │   ├── modal.tsx
│   │   │   └── type.ts
│   │   ├── diff/
│   │   │   ├── index.less
│   │   │   └── index.tsx
│   │   ├── env/
│   │   │   ├── editNameModal.tsx
│   │   │   ├── index.less
│   │   │   ├── index.tsx
│   │   │   └── modal.tsx
│   │   ├── error/
│   │   │   ├── index.less
│   │   │   └── index.tsx
│   │   ├── initialization/
│   │   │   ├── index.less
│   │   │   └── index.tsx
│   │   ├── log/
│   │   │   ├── index.module.less
│   │   │   └── index.tsx
│   │   ├── login/
│   │   │   ├── index.less
│   │   │   └── index.tsx
│   │   ├── script/
│   │   │   ├── components/
│   │   │   │   └── UnsupportedFilePreview/
│   │   │   │       ├── index.module.less
│   │   │   │       └── index.tsx
│   │   │   ├── editModal.tsx
│   │   │   ├── editNameModal.tsx
│   │   │   ├── index.module.less
│   │   │   ├── index.tsx
│   │   │   ├── renameModal.tsx
│   │   │   ├── saveModal.tsx
│   │   │   └── setting.tsx
│   │   ├── setting/
│   │   │   ├── about.tsx
│   │   │   ├── appModal.tsx
│   │   │   ├── checkUpdate.tsx
│   │   │   ├── dependence.tsx
│   │   │   ├── index.less
│   │   │   ├── index.tsx
│   │   │   ├── loginLog.tsx
│   │   │   ├── notification.tsx
│   │   │   ├── other.tsx
│   │   │   ├── progress.tsx
│   │   │   ├── security.tsx
│   │   │   └── systemLog.tsx
│   │   └── subscription/
│   │       ├── index.less
│   │       ├── index.tsx
│   │       ├── logModal.tsx
│   │       └── modal.tsx
│   ├── styles/
│   │   └── variable.less
│   └── utils/
│       ├── codemirror/
│       │   └── systemLog.ts
│       ├── config.ts
│       ├── const.ts
│       ├── date.ts
│       ├── hooks.ts
│       ├── http.tsx
│       ├── index.ts
│       ├── init.ts
│       ├── monaco/
│       │   └── index.ts
│       ├── type.ts
│       └── websocket.ts
├── tsconfig.json
├── typings.d.ts
└── version.yaml
Download .txt
SYMBOL INDEX (1033 symbols across 95 files)

FILE: back/app.ts
  type WorkerMetadata (line 14) | interface WorkerMetadata {
  class Application (line 21) | class Application {
    method constructor (line 29) | constructor() {
    method start (line 40) | async start() {
    method startMasterProcess (line 56) | private startMasterProcess() {
    method waitForWorkerReady (line 114) | private waitForWorkerReady(worker: Worker, timeoutMs: number): Promise...
    method forkWorker (line 133) | private forkWorker(serviceType: string): Worker {
    method initializeDatabase (line 146) | private async initializeDatabase() {
    method setupMiddlewares (line 151) | private setupMiddlewares() {
    method setupMasterShutdown (line 160) | private setupMasterShutdown() {
    method startWorkerProcess (line 211) | private async startWorkerProcess() {
    method startHttpService (line 234) | private async startHttpService() {
    method startGrpcService (line 253) | private async startGrpcService() {
    method setupWorkerShutdown (line 261) | private setupWorkerShutdown(serviceType: string) {
    method gracefulShutdown (line 284) | private async gracefulShutdown(serviceType: string) {

FILE: back/config/const.ts
  constant LOG_END_SYMBOL (line 1) | const LOG_END_SYMBOL = '     ';
  constant TASK_COMMAND (line 3) | const TASK_COMMAND = 'task';
  constant QL_COMMAND (line 4) | const QL_COMMAND = 'ql';
  constant TASK_PREFIX (line 6) | const TASK_PREFIX = `${TASK_COMMAND} `;
  constant QL_PREFIX (line 7) | const QL_PREFIX = `${QL_COMMAND} `;
  constant SAMPLE_FILES (line 9) | const SAMPLE_FILES = [
  constant PYTHON_INSTALL_DIR (line 27) | const PYTHON_INSTALL_DIR = process.env.PYTHON_HOME;

FILE: back/config/http.ts
  type RequestBaseOptions (line 3) | type RequestBaseOptions = {
  type RequestOptionsWithOptions (line 10) | type RequestOptionsWithOptions = RequestBaseOptions &
  type ResponseTypeMap (line 13) | type ResponseTypeMap = {
  type ResponseTypeKey (line 18) | type ResponseTypeKey = keyof ResponseTypeMap;
  function request (line 20) | async function request(
  function post (line 46) | async function post<T extends ResponseTypeKey = 'json'>(

FILE: back/config/index.ts
  type Config (line 9) | interface Config {

FILE: back/config/serverEnv.ts
  function getPickedEnv (line 6) | function getPickedEnv() {
  function serveEnv (line 21) | function serveEnv(_req: Request, res: Response) {

FILE: back/config/share.ts
  function createRandomString (line 1) | function createRandomString(min: number, max: number): string {

FILE: back/config/subscription.ts
  function formatUrl (line 4) | function formatUrl(doc: Subscription) {
  function formatCommand (line 20) | function formatCommand(doc: Subscription, url?: string) {

FILE: back/config/util.ts
  function getFileContentByName (line 16) | async function getFileContentByName(fileName: string) {
  function removeAnsi (line 24) | function removeAnsi(text: string) {
  function getLastModifyFilePath (line 28) | async function getLastModifyFilePath(dir: string) {
  function getToken (line 48) | function getToken(req: any) {
  function getPlatform (line 59) | function getPlatform(userAgent: string): 'mobile' | 'desktop' {
  function fileExist (line 99) | async function fileExist(file: any) {
  function createFile (line 108) | async function createFile(file: string, data: string = '') {
  function handleLogPath (line 113) | async function handleLogPath(
  function concurrentRun (line 125) | async function concurrentRun(
  type FileType (line 162) | enum FileType {
  type IFile (line 167) | interface IFile {
  function dirSort (line 177) | function dirSort(a: IFile, b: IFile): number {
  function readDirs (line 187) | async function readDirs(
  function readDir (line 231) | async function readDir(
  function promiseExec (line 282) | async function promiseExec(command: string): Promise<string> {
  function promiseExecSuccess (line 294) | async function promiseExecSuccess(command: string): Promise<string> {
  function parseHeaders (line 306) | function parseHeaders(headers: string) {
  function parseString (line 330) | function parseString(
  function parseBody (line 359) | function parseBody(
  function psTree (line 389) | function psTree(pid: number): Promise<number[]> {
  function killTask (line 400) | async function killTask(pid: number) {
  function getPid (line 414) | async function getPid(cmd: string) {
  function getAllPids (line 420) | async function getAllPids(cmd: string): Promise<number[]> {
  function killAllTasks (line 430) | async function killAllTasks(cmd: string): Promise<void> {
  type IVersion (line 441) | interface IVersion {
  function parseVersion (line 448) | async function parseVersion(path: string): Promise<IVersion> {
  function parseContentVersion (line 452) | function parseContentVersion(content: string): IVersion {
  function getUniqPath (line 456) | async function getUniqPath(
  function safeJSONParse (line 498) | function safeJSONParse(value?: string) {
  function rmPath (line 511) | async function rmPath(path: string) {
  function setSystemTimezone (line 522) | async function setSystemTimezone(timezone: string): Promise<boolean> {
  function getGetCommand (line 538) | function getGetCommand(type: DependenceTypes, name: string): string {
  function getInstallCommand (line 559) | function getInstallCommand(type: DependenceTypes, name: string): string {
  function getUninstallCommand (line 576) | function getUninstallCommand(
  function isDemoEnv (line 590) | function isDemoEnv() {

FILE: back/data/cron.ts
  class Crontab (line 4) | class Crontab {
    method constructor (line 27) | constructor(options: Crontab) {
  type CrontabStatus (line 55) | enum CrontabStatus {
  type CronInstance (line 62) | interface CronInstance extends Model<Crontab, Crontab>, Crontab {}

FILE: back/data/cronView.ts
  type CronViewType (line 4) | enum CronViewType {
  type SortType (line 9) | interface SortType {
  type FilterType (line 14) | interface FilterType {
  class CrontabView (line 20) | class CrontabView {
    method constructor (line 30) | constructor(options: CrontabView) {
  type CronViewInstance (line 42) | interface CronViewInstance

FILE: back/data/dependence.ts
  class Dependence (line 4) | class Dependence {
    method constructor (line 13) | constructor(options: Dependence) {
  type DependenceStatus (line 27) | enum DependenceStatus {
  type DependenceTypes (line 38) | enum DependenceTypes {
  type versionDependenceCommandTypes (line 44) | enum versionDependenceCommandTypes {
  type DependenceInstance (line 50) | interface DependenceInstance

FILE: back/data/env.ts
  class Env (line 4) | class Env {
    method constructor (line 14) | constructor(options: Env) {
  type EnvStatus (line 29) | enum EnvStatus {
  type EnvInstance (line 39) | interface EnvInstance extends Model<Env, Env>, Env {}

FILE: back/data/index.ts
  type ResponseType (line 23) | type ResponseType<T> = { code: number; data?: T; message?: string };

FILE: back/data/notify.ts
  type NotificationMode (line 1) | enum NotificationMode {
  class GotifyNotification (line 29) | class GotifyNotification extends NotificationBaseInfo {
  class GoCqHttpBotNotification (line 35) | class GoCqHttpBotNotification extends NotificationBaseInfo {
  class ServerChanNotification (line 41) | class ServerChanNotification extends NotificationBaseInfo {
  class PushDeerNotification (line 45) | class PushDeerNotification extends NotificationBaseInfo {
  class synologyChatNotification (line 50) | class synologyChatNotification extends NotificationBaseInfo {
  class BarkNotification (line 54) | class BarkNotification extends NotificationBaseInfo {
  class TelegramBotNotification (line 64) | class TelegramBotNotification extends NotificationBaseInfo {
  class DingtalkBotNotification (line 73) | class DingtalkBotNotification extends NotificationBaseInfo {
  class WeWorkBotNotification (line 78) | class WeWorkBotNotification extends NotificationBaseInfo {
  class WeWorkAppNotification (line 83) | class WeWorkAppNotification extends NotificationBaseInfo {
  class AibotkNotification (line 88) | class AibotkNotification extends NotificationBaseInfo {
  class IGotNotification (line 94) | class IGotNotification extends NotificationBaseInfo {
  class PushPlusNotification (line 98) | class PushPlusNotification extends NotificationBaseInfo {
  class WePlusBotNotification (line 108) | class WePlusBotNotification extends NotificationBaseInfo {
  class EmailNotification (line 114) | class EmailNotification extends NotificationBaseInfo {
  class PushMeNotification (line 121) | class PushMeNotification extends NotificationBaseInfo {
  class ChronocatNotification (line 126) | class ChronocatNotification extends NotificationBaseInfo {
  class WebhookNotification (line 132) | class WebhookNotification extends NotificationBaseInfo {
  class LarkNotification (line 143) | class LarkNotification extends NotificationBaseInfo {
  class NtfyNotification (line 148) | class NtfyNotification extends NotificationBaseInfo {
  class WxPusherBotNotification (line 158) | class WxPusherBotNotification extends NotificationBaseInfo {
  type NotificationInfo (line 164) | interface NotificationInfo

FILE: back/data/open.ts
  class App (line 4) | class App {
    method constructor (line 12) | constructor(options: App) {
  type AppToken (line 21) | interface AppToken {
  type AppScope (line 27) | type AppScope = 'envs' | 'crons' | 'configs' | 'scripts' | 'logs' | 'sys...
  type AppInstance (line 29) | interface AppInstance extends Model<App, App>, App {}

FILE: back/data/sock.ts
  class SockMessage (line 1) | class SockMessage {
    method constructor (line 6) | constructor(options: SockMessage) {
  type SockMessageType (line 13) | type SockMessageType =

FILE: back/data/subscription.ts
  type SimpleIntervalScheduleUnit (line 5) | type SimpleIntervalScheduleUnit = keyof SimpleIntervalSchedule;
  class Subscription (line 6) | class Subscription {
    method constructor (line 35) | constructor(options: Subscription) {
  type SubscriptionStatus (line 66) | enum SubscriptionStatus {
  type SubscriptionInstance (line 73) | interface SubscriptionInstance

FILE: back/data/system.ts
  class SystemInfo (line 5) | class SystemInfo {
    method constructor (line 11) | constructor(options: SystemInfo) {
  type LoginStatus (line 19) | enum LoginStatus {
  type AuthDataType (line 24) | enum AuthDataType {
  type SystemConfigInfo (line 33) | interface SystemConfigInfo {
  type LoginLogInfo (line 44) | interface LoginLogInfo {
  type TokenInfo (line 52) | interface TokenInfo {
  type AuthInfo (line 65) | interface AuthInfo {
  type SystemModelInfo (line 81) | type SystemModelInfo = SystemConfigInfo &
  type SystemInstance (line 86) | interface SystemInstance

FILE: back/interface/schedule.ts
  type ScheduleType (line 1) | enum ScheduleType {
  type ScheduleValidator (line 6) | type ScheduleValidator = (schedule?: string) => boolean;
  type CronSchedulerPayload (line 7) | type CronSchedulerPayload = {

FILE: back/loaders/deps.ts
  function linkToNodeModule (line 8) | async function linkToNodeModule(src: string, dst?: string) {
  function linkCommand (line 20) | async function linkCommand() {
  function linkCommandToDir (line 32) | async function linkCommandToDir(commandDir: string) {

FILE: back/middlewares/monitoring.ts
  type RequestMetrics (line 6) | interface RequestMetrics {

FILE: back/protos/api.ts
  type NotificationMode (line 24) | enum NotificationMode {
  function notificationModeFromJSON (line 49) | function notificationModeFromJSON(object: any): NotificationMode {
  function notificationModeToJSON (line 121) | function notificationModeToJSON(object: NotificationMode): string {
  type EnvItem (line 171) | interface EnvItem {
  type GetEnvsRequest (line 180) | interface GetEnvsRequest {
  type CreateEnvRequest (line 184) | interface CreateEnvRequest {
  type UpdateEnvRequest (line 188) | interface UpdateEnvRequest {
  type DeleteEnvsRequest (line 192) | interface DeleteEnvsRequest {
  type MoveEnvRequest (line 196) | interface MoveEnvRequest {
  type DisableEnvsRequest (line 202) | interface DisableEnvsRequest {
  type EnableEnvsRequest (line 206) | interface EnableEnvsRequest {
  type UpdateEnvNamesRequest (line 210) | interface UpdateEnvNamesRequest {
  type GetEnvByIdRequest (line 215) | interface GetEnvByIdRequest {
  type EnvsResponse (line 219) | interface EnvsResponse {
  type EnvResponse (line 225) | interface EnvResponse {
  type Response (line 231) | interface Response {
  type ExtraScheduleItem (line 236) | interface ExtraScheduleItem {
  type CronItem (line 240) | interface CronItem {
  type CreateCronRequest (line 257) | interface CreateCronRequest {
  type UpdateCronRequest (line 268) | interface UpdateCronRequest {
  type DeleteCronsRequest (line 280) | interface DeleteCronsRequest {
  type GetCronsRequest (line 284) | interface GetCronsRequest {
  type GetCronByIdRequest (line 288) | interface GetCronByIdRequest {
  type EnableCronsRequest (line 292) | interface EnableCronsRequest {
  type DisableCronsRequest (line 296) | interface DisableCronsRequest {
  type RunCronsRequest (line 300) | interface RunCronsRequest {
  type CronsResponse (line 304) | interface CronsResponse {
  type CronResponse (line 310) | interface CronResponse {
  type CronDetailRequest (line 316) | interface CronDetailRequest {
  type CronDetailResponse (line 320) | interface CronDetailResponse {
  type NotificationInfo (line 326) | interface NotificationInfo {
  type SystemNotifyRequest (line 398) | interface SystemNotifyRequest {
  function createBaseEnvItem (line 404) | function createBaseEnvItem(): EnvItem {
  method encode (line 416) | encode(message: EnvItem, writer: BinaryWriter = new BinaryWriter()): Bin...
  method decode (line 438) | decode(input: BinaryReader | Uint8Array, length?: number): EnvItem {
  method fromJSON (line 502) | fromJSON(object: any): EnvItem {
  method toJSON (line 513) | toJSON(message: EnvItem): unknown {
  method create (line 536) | create<I extends Exact<DeepPartial<EnvItem>, I>>(base?: I): EnvItem {
  method fromPartial (line 539) | fromPartial<I extends Exact<DeepPartial<EnvItem>, I>>(object: I): EnvItem {
  function createBaseGetEnvsRequest (line 551) | function createBaseGetEnvsRequest(): GetEnvsRequest {
  method encode (line 556) | encode(message: GetEnvsRequest, writer: BinaryWriter = new BinaryWriter(...
  method decode (line 563) | decode(input: BinaryReader | Uint8Array, length?: number): GetEnvsRequest {
  method fromJSON (line 587) | fromJSON(object: any): GetEnvsRequest {
  method toJSON (line 591) | toJSON(message: GetEnvsRequest): unknown {
  method create (line 599) | create<I extends Exact<DeepPartial<GetEnvsRequest>, I>>(base?: I): GetEn...
  method fromPartial (line 602) | fromPartial<I extends Exact<DeepPartial<GetEnvsRequest>, I>>(object: I):...
  function createBaseCreateEnvRequest (line 609) | function createBaseCreateEnvRequest(): CreateEnvRequest {
  method encode (line 614) | encode(message: CreateEnvRequest, writer: BinaryWriter = new BinaryWrite...
  method decode (line 621) | decode(input: BinaryReader | Uint8Array, length?: number): CreateEnvRequ...
  method fromJSON (line 645) | fromJSON(object: any): CreateEnvRequest {
  method toJSON (line 649) | toJSON(message: CreateEnvRequest): unknown {
  method create (line 657) | create<I extends Exact<DeepPartial<CreateEnvRequest>, I>>(base?: I): Cre...
  method fromPartial (line 660) | fromPartial<I extends Exact<DeepPartial<CreateEnvRequest>, I>>(object: I...
  function createBaseUpdateEnvRequest (line 667) | function createBaseUpdateEnvRequest(): UpdateEnvRequest {
  method encode (line 672) | encode(message: UpdateEnvRequest, writer: BinaryWriter = new BinaryWrite...
  method decode (line 679) | decode(input: BinaryReader | Uint8Array, length?: number): UpdateEnvRequ...
  method fromJSON (line 703) | fromJSON(object: any): UpdateEnvRequest {
  method toJSON (line 707) | toJSON(message: UpdateEnvRequest): unknown {
  method create (line 715) | create<I extends Exact<DeepPartial<UpdateEnvRequest>, I>>(base?: I): Upd...
  method fromPartial (line 718) | fromPartial<I extends Exact<DeepPartial<UpdateEnvRequest>, I>>(object: I...
  function createBaseDeleteEnvsRequest (line 725) | function createBaseDeleteEnvsRequest(): DeleteEnvsRequest {
  method encode (line 730) | encode(message: DeleteEnvsRequest, writer: BinaryWriter = new BinaryWrit...
  method decode (line 739) | decode(input: BinaryReader | Uint8Array, length?: number): DeleteEnvsReq...
  method fromJSON (line 773) | fromJSON(object: any): DeleteEnvsRequest {
  method toJSON (line 777) | toJSON(message: DeleteEnvsRequest): unknown {
  method create (line 785) | create<I extends Exact<DeepPartial<DeleteEnvsRequest>, I>>(base?: I): De...
  method fromPartial (line 788) | fromPartial<I extends Exact<DeepPartial<DeleteEnvsRequest>, I>>(object: ...
  function createBaseMoveEnvRequest (line 795) | function createBaseMoveEnvRequest(): MoveEnvRequest {
  method encode (line 800) | encode(message: MoveEnvRequest, writer: BinaryWriter = new BinaryWriter(...
  method decode (line 813) | decode(input: BinaryReader | Uint8Array, length?: number): MoveEnvRequest {
  method fromJSON (line 853) | fromJSON(object: any): MoveEnvRequest {
  method toJSON (line 861) | toJSON(message: MoveEnvRequest): unknown {
  method create (line 875) | create<I extends Exact<DeepPartial<MoveEnvRequest>, I>>(base?: I): MoveE...
  method fromPartial (line 878) | fromPartial<I extends Exact<DeepPartial<MoveEnvRequest>, I>>(object: I):...
  function createBaseDisableEnvsRequest (line 887) | function createBaseDisableEnvsRequest(): DisableEnvsRequest {
  method encode (line 892) | encode(message: DisableEnvsRequest, writer: BinaryWriter = new BinaryWri...
  method decode (line 901) | decode(input: BinaryReader | Uint8Array, length?: number): DisableEnvsRe...
  method fromJSON (line 935) | fromJSON(object: any): DisableEnvsRequest {
  method toJSON (line 939) | toJSON(message: DisableEnvsRequest): unknown {
  method create (line 947) | create<I extends Exact<DeepPartial<DisableEnvsRequest>, I>>(base?: I): D...
  method fromPartial (line 950) | fromPartial<I extends Exact<DeepPartial<DisableEnvsRequest>, I>>(object:...
  function createBaseEnableEnvsRequest (line 957) | function createBaseEnableEnvsRequest(): EnableEnvsRequest {
  method encode (line 962) | encode(message: EnableEnvsRequest, writer: BinaryWriter = new BinaryWrit...
  method decode (line 971) | decode(input: BinaryReader | Uint8Array, length?: number): EnableEnvsReq...
  method fromJSON (line 1005) | fromJSON(object: any): EnableEnvsRequest {
  method toJSON (line 1009) | toJSON(message: EnableEnvsRequest): unknown {
  method create (line 1017) | create<I extends Exact<DeepPartial<EnableEnvsRequest>, I>>(base?: I): En...
  method fromPartial (line 1020) | fromPartial<I extends Exact<DeepPartial<EnableEnvsRequest>, I>>(object: ...
  function createBaseUpdateEnvNamesRequest (line 1027) | function createBaseUpdateEnvNamesRequest(): UpdateEnvNamesRequest {
  method encode (line 1032) | encode(message: UpdateEnvNamesRequest, writer: BinaryWriter = new Binary...
  method decode (line 1044) | decode(input: BinaryReader | Uint8Array, length?: number): UpdateEnvName...
  method fromJSON (line 1086) | fromJSON(object: any): UpdateEnvNamesRequest {
  method toJSON (line 1093) | toJSON(message: UpdateEnvNamesRequest): unknown {
  method create (line 1104) | create<I extends Exact<DeepPartial<UpdateEnvNamesRequest>, I>>(base?: I)...
  method fromPartial (line 1107) | fromPartial<I extends Exact<DeepPartial<UpdateEnvNamesRequest>, I>>(obje...
  function createBaseGetEnvByIdRequest (line 1115) | function createBaseGetEnvByIdRequest(): GetEnvByIdRequest {
  method encode (line 1120) | encode(message: GetEnvByIdRequest, writer: BinaryWriter = new BinaryWrit...
  method decode (line 1127) | decode(input: BinaryReader | Uint8Array, length?: number): GetEnvByIdReq...
  method fromJSON (line 1151) | fromJSON(object: any): GetEnvByIdRequest {
  method toJSON (line 1155) | toJSON(message: GetEnvByIdRequest): unknown {
  method create (line 1163) | create<I extends Exact<DeepPartial<GetEnvByIdRequest>, I>>(base?: I): Ge...
  method fromPartial (line 1166) | fromPartial<I extends Exact<DeepPartial<GetEnvByIdRequest>, I>>(object: ...
  function createBaseEnvsResponse (line 1173) | function createBaseEnvsResponse(): EnvsResponse {
  method encode (line 1178) | encode(message: EnvsResponse, writer: BinaryWriter = new BinaryWriter())...
  method decode (line 1191) | decode(input: BinaryReader | Uint8Array, length?: number): EnvsResponse {
  method fromJSON (line 1231) | fromJSON(object: any): EnvsResponse {
  method toJSON (line 1239) | toJSON(message: EnvsResponse): unknown {
  method create (line 1253) | create<I extends Exact<DeepPartial<EnvsResponse>, I>>(base?: I): EnvsRes...
  method fromPartial (line 1256) | fromPartial<I extends Exact<DeepPartial<EnvsResponse>, I>>(object: I): E...
  function createBaseEnvResponse (line 1265) | function createBaseEnvResponse(): EnvResponse {
  method encode (line 1270) | encode(message: EnvResponse, writer: BinaryWriter = new BinaryWriter()):...
  method decode (line 1283) | decode(input: BinaryReader | Uint8Array, length?: number): EnvResponse {
  method fromJSON (line 1323) | fromJSON(object: any): EnvResponse {
  method toJSON (line 1331) | toJSON(message: EnvResponse): unknown {
  method create (line 1345) | create<I extends Exact<DeepPartial<EnvResponse>, I>>(base?: I): EnvRespo...
  method fromPartial (line 1348) | fromPartial<I extends Exact<DeepPartial<EnvResponse>, I>>(object: I): En...
  function createBaseResponse (line 1357) | function createBaseResponse(): Response {
  method encode (line 1362) | encode(message: Response, writer: BinaryWriter = new BinaryWriter()): Bi...
  method decode (line 1372) | decode(input: BinaryReader | Uint8Array, length?: number): Response {
  method fromJSON (line 1404) | fromJSON(object: any): Response {
  method toJSON (line 1411) | toJSON(message: Response): unknown {
  method create (line 1422) | create<I extends Exact<DeepPartial<Response>, I>>(base?: I): Response {
  method fromPartial (line 1425) | fromPartial<I extends Exact<DeepPartial<Response>, I>>(object: I): Respo...
  function createBaseExtraScheduleItem (line 1433) | function createBaseExtraScheduleItem(): ExtraScheduleItem {
  method encode (line 1438) | encode(message: ExtraScheduleItem, writer: BinaryWriter = new BinaryWrit...
  method decode (line 1445) | decode(input: BinaryReader | Uint8Array, length?: number): ExtraSchedule...
  method fromJSON (line 1469) | fromJSON(object: any): ExtraScheduleItem {
  method toJSON (line 1473) | toJSON(message: ExtraScheduleItem): unknown {
  method create (line 1481) | create<I extends Exact<DeepPartial<ExtraScheduleItem>, I>>(base?: I): Ex...
  method fromPartial (line 1484) | fromPartial<I extends Exact<DeepPartial<ExtraScheduleItem>, I>>(object: ...
  function createBaseCronItem (line 1491) | function createBaseCronItem(): CronItem {
  method encode (line 1511) | encode(message: CronItem, writer: BinaryWriter = new BinaryWriter()): Bi...
  method decode (line 1557) | decode(input: BinaryReader | Uint8Array, length?: number): CronItem {
  method fromJSON (line 1685) | fromJSON(object: any): CronItem {
  method toJSON (line 1708) | toJSON(message: CronItem): unknown {
  method create (line 1755) | create<I extends Exact<DeepPartial<CronItem>, I>>(base?: I): CronItem {
  method fromPartial (line 1758) | fromPartial<I extends Exact<DeepPartial<CronItem>, I>>(object: I): CronI...
  function createBaseCreateCronRequest (line 1778) | function createBaseCreateCronRequest(): CreateCronRequest {
  method encode (line 1792) | encode(message: CreateCronRequest, writer: BinaryWriter = new BinaryWrit...
  method decode (line 1820) | decode(input: BinaryReader | Uint8Array, length?: number): CreateCronReq...
  method fromJSON (line 1900) | fromJSON(object: any): CreateCronRequest {
  method toJSON (line 1915) | toJSON(message: CreateCronRequest): unknown {
  method create (line 1944) | create<I extends Exact<DeepPartial<CreateCronRequest>, I>>(base?: I): Cr...
  method fromPartial (line 1947) | fromPartial<I extends Exact<DeepPartial<CreateCronRequest>, I>>(object: ...
  function createBaseUpdateCronRequest (line 1961) | function createBaseUpdateCronRequest(): UpdateCronRequest {
  method encode (line 1976) | encode(message: UpdateCronRequest, writer: BinaryWriter = new BinaryWrit...
  method decode (line 2007) | decode(input: BinaryReader | Uint8Array, length?: number): UpdateCronReq...
  method fromJSON (line 2095) | fromJSON(object: any): UpdateCronRequest {
  method toJSON (line 2111) | toJSON(message: UpdateCronRequest): unknown {
  method create (line 2143) | create<I extends Exact<DeepPartial<UpdateCronRequest>, I>>(base?: I): Up...
  method fromPartial (line 2146) | fromPartial<I extends Exact<DeepPartial<UpdateCronRequest>, I>>(object: ...
  function createBaseDeleteCronsRequest (line 2161) | function createBaseDeleteCronsRequest(): DeleteCronsRequest {
  method encode (line 2166) | encode(message: DeleteCronsRequest, writer: BinaryWriter = new BinaryWri...
  method decode (line 2175) | decode(input: BinaryReader | Uint8Array, length?: number): DeleteCronsRe...
  method fromJSON (line 2209) | fromJSON(object: any): DeleteCronsRequest {
  method toJSON (line 2213) | toJSON(message: DeleteCronsRequest): unknown {
  method create (line 2221) | create<I extends Exact<DeepPartial<DeleteCronsRequest>, I>>(base?: I): D...
  method fromPartial (line 2224) | fromPartial<I extends Exact<DeepPartial<DeleteCronsRequest>, I>>(object:...
  function createBaseGetCronsRequest (line 2231) | function createBaseGetCronsRequest(): GetCronsRequest {
  method encode (line 2236) | encode(message: GetCronsRequest, writer: BinaryWriter = new BinaryWriter...
  method decode (line 2243) | decode(input: BinaryReader | Uint8Array, length?: number): GetCronsReque...
  method fromJSON (line 2267) | fromJSON(object: any): GetCronsRequest {
  method toJSON (line 2271) | toJSON(message: GetCronsRequest): unknown {
  method create (line 2279) | create<I extends Exact<DeepPartial<GetCronsRequest>, I>>(base?: I): GetC...
  method fromPartial (line 2282) | fromPartial<I extends Exact<DeepPartial<GetCronsRequest>, I>>(object: I)...
  function createBaseGetCronByIdRequest (line 2289) | function createBaseGetCronByIdRequest(): GetCronByIdRequest {
  method encode (line 2294) | encode(message: GetCronByIdRequest, writer: BinaryWriter = new BinaryWri...
  method decode (line 2301) | decode(input: BinaryReader | Uint8Array, length?: number): GetCronByIdRe...
  method fromJSON (line 2325) | fromJSON(object: any): GetCronByIdRequest {
  method toJSON (line 2329) | toJSON(message: GetCronByIdRequest): unknown {
  method create (line 2337) | create<I extends Exact<DeepPartial<GetCronByIdRequest>, I>>(base?: I): G...
  method fromPartial (line 2340) | fromPartial<I extends Exact<DeepPartial<GetCronByIdRequest>, I>>(object:...
  function createBaseEnableCronsRequest (line 2347) | function createBaseEnableCronsRequest(): EnableCronsRequest {
  method encode (line 2352) | encode(message: EnableCronsRequest, writer: BinaryWriter = new BinaryWri...
  method decode (line 2361) | decode(input: BinaryReader | Uint8Array, length?: number): EnableCronsRe...
  method fromJSON (line 2395) | fromJSON(object: any): EnableCronsRequest {
  method toJSON (line 2399) | toJSON(message: EnableCronsRequest): unknown {
  method create (line 2407) | create<I extends Exact<DeepPartial<EnableCronsRequest>, I>>(base?: I): E...
  method fromPartial (line 2410) | fromPartial<I extends Exact<DeepPartial<EnableCronsRequest>, I>>(object:...
  function createBaseDisableCronsRequest (line 2417) | function createBaseDisableCronsRequest(): DisableCronsRequest {
  method encode (line 2422) | encode(message: DisableCronsRequest, writer: BinaryWriter = new BinaryWr...
  method decode (line 2431) | decode(input: BinaryReader | Uint8Array, length?: number): DisableCronsR...
  method fromJSON (line 2465) | fromJSON(object: any): DisableCronsRequest {
  method toJSON (line 2469) | toJSON(message: DisableCronsRequest): unknown {
  method create (line 2477) | create<I extends Exact<DeepPartial<DisableCronsRequest>, I>>(base?: I): ...
  method fromPartial (line 2480) | fromPartial<I extends Exact<DeepPartial<DisableCronsRequest>, I>>(object...
  function createBaseRunCronsRequest (line 2487) | function createBaseRunCronsRequest(): RunCronsRequest {
  method encode (line 2492) | encode(message: RunCronsRequest, writer: BinaryWriter = new BinaryWriter...
  method decode (line 2501) | decode(input: BinaryReader | Uint8Array, length?: number): RunCronsReque...
  method fromJSON (line 2535) | fromJSON(object: any): RunCronsRequest {
  method toJSON (line 2539) | toJSON(message: RunCronsRequest): unknown {
  method create (line 2547) | create<I extends Exact<DeepPartial<RunCronsRequest>, I>>(base?: I): RunC...
  method fromPartial (line 2550) | fromPartial<I extends Exact<DeepPartial<RunCronsRequest>, I>>(object: I)...
  function createBaseCronsResponse (line 2557) | function createBaseCronsResponse(): CronsResponse {
  method encode (line 2562) | encode(message: CronsResponse, writer: BinaryWriter = new BinaryWriter()...
  method decode (line 2575) | decode(input: BinaryReader | Uint8Array, length?: number): CronsResponse {
  method fromJSON (line 2615) | fromJSON(object: any): CronsResponse {
  method toJSON (line 2623) | toJSON(message: CronsResponse): unknown {
  method create (line 2637) | create<I extends Exact<DeepPartial<CronsResponse>, I>>(base?: I): CronsR...
  method fromPartial (line 2640) | fromPartial<I extends Exact<DeepPartial<CronsResponse>, I>>(object: I): ...
  function createBaseCronResponse (line 2649) | function createBaseCronResponse(): CronResponse {
  method encode (line 2654) | encode(message: CronResponse, writer: BinaryWriter = new BinaryWriter())...
  method decode (line 2667) | decode(input: BinaryReader | Uint8Array, length?: number): CronResponse {
  method fromJSON (line 2707) | fromJSON(object: any): CronResponse {
  method toJSON (line 2715) | toJSON(message: CronResponse): unknown {
  method create (line 2729) | create<I extends Exact<DeepPartial<CronResponse>, I>>(base?: I): CronRes...
  method fromPartial (line 2732) | fromPartial<I extends Exact<DeepPartial<CronResponse>, I>>(object: I): C...
  function createBaseCronDetailRequest (line 2741) | function createBaseCronDetailRequest(): CronDetailRequest {
  method encode (line 2746) | encode(message: CronDetailRequest, writer: BinaryWriter = new BinaryWrit...
  method decode (line 2753) | decode(input: BinaryReader | Uint8Array, length?: number): CronDetailReq...
  method fromJSON (line 2777) | fromJSON(object: any): CronDetailRequest {
  method toJSON (line 2781) | toJSON(message: CronDetailRequest): unknown {
  method create (line 2789) | create<I extends Exact<DeepPartial<CronDetailRequest>, I>>(base?: I): Cr...
  method fromPartial (line 2792) | fromPartial<I extends Exact<DeepPartial<CronDetailRequest>, I>>(object: ...
  function createBaseCronDetailResponse (line 2799) | function createBaseCronDetailResponse(): CronDetailResponse {
  method encode (line 2804) | encode(message: CronDetailResponse, writer: BinaryWriter = new BinaryWri...
  method decode (line 2817) | decode(input: BinaryReader | Uint8Array, length?: number): CronDetailRes...
  method fromJSON (line 2857) | fromJSON(object: any): CronDetailResponse {
  method toJSON (line 2865) | toJSON(message: CronDetailResponse): unknown {
  method create (line 2879) | create<I extends Exact<DeepPartial<CronDetailResponse>, I>>(base?: I): C...
  method fromPartial (line 2882) | fromPartial<I extends Exact<DeepPartial<CronDetailResponse>, I>>(object:...
  function createBaseNotificationInfo (line 2891) | function createBaseNotificationInfo(): NotificationInfo {
  method encode (line 2966) | encode(message: NotificationInfo, writer: BinaryWriter = new BinaryWrite...
  method decode (line 3177) | decode(input: BinaryReader | Uint8Array, length?: number): NotificationI...
  method fromJSON (line 3745) | fromJSON(object: any): NotificationInfo {
  method toJSON (line 3831) | toJSON(message: NotificationInfo): unknown {
  method create (line 4043) | create<I extends Exact<DeepPartial<NotificationInfo>, I>>(base?: I): Not...
  method fromPartial (line 4046) | fromPartial<I extends Exact<DeepPartial<NotificationInfo>, I>>(object: I...
  function createBaseSystemNotifyRequest (line 4121) | function createBaseSystemNotifyRequest(): SystemNotifyRequest {
  method encode (line 4126) | encode(message: SystemNotifyRequest, writer: BinaryWriter = new BinaryWr...
  method decode (line 4139) | decode(input: BinaryReader | Uint8Array, length?: number): SystemNotifyR...
  method fromJSON (line 4179) | fromJSON(object: any): SystemNotifyRequest {
  method toJSON (line 4187) | toJSON(message: SystemNotifyRequest): unknown {
  method create (line 4201) | create<I extends Exact<DeepPartial<SystemNotifyRequest>, I>>(base?: I): ...
  method fromPartial (line 4204) | fromPartial<I extends Exact<DeepPartial<SystemNotifyRequest>, I>>(object...
  type ApiService (line 4215) | type ApiService = typeof ApiService;
  type ApiServer (line 4390) | interface ApiServer extends UntypedServiceImplementation {
  type ApiClient (line 4412) | interface ApiClient extends Client {
  type Builtin (line 4706) | type Builtin = Date | Function | Uint8Array | string | number | boolean ...
  type DeepPartial (line 4708) | type DeepPartial<T> = T extends Builtin ? T
  type KeysOfUnion (line 4714) | type KeysOfUnion<T> = T extends T ? keyof T : never;
  type Exact (line 4715) | type Exact<P, I extends P> = P extends Builtin ? P
  function longToNumber (line 4718) | function longToNumber(int64: { toString(): string }): number {
  function isSet (line 4729) | function isSet(value: any): boolean {
  type MessageFns (line 4733) | interface MessageFns<T> {

FILE: back/protos/cron.ts
  type ISchedule (line 24) | interface ISchedule {
  type ICron (line 28) | interface ICron {
  type AddCronRequest (line 36) | interface AddCronRequest {
  type AddCronResponse (line 40) | interface AddCronResponse {
  type DeleteCronRequest (line 43) | interface DeleteCronRequest {
  type DeleteCronResponse (line 47) | interface DeleteCronResponse {
  function createBaseISchedule (line 50) | function createBaseISchedule(): ISchedule {
  method encode (line 55) | encode(message: ISchedule, writer: BinaryWriter = new BinaryWriter()): B...
  method decode (line 62) | decode(input: BinaryReader | Uint8Array, length?: number): ISchedule {
  method fromJSON (line 86) | fromJSON(object: any): ISchedule {
  method toJSON (line 90) | toJSON(message: ISchedule): unknown {
  method create (line 98) | create<I extends Exact<DeepPartial<ISchedule>, I>>(base?: I): ISchedule {
  method fromPartial (line 101) | fromPartial<I extends Exact<DeepPartial<ISchedule>, I>>(object: I): ISch...
  function createBaseICron (line 108) | function createBaseICron(): ICron {
  method encode (line 113) | encode(message: ICron, writer: BinaryWriter = new BinaryWriter()): Binar...
  method decode (line 132) | decode(input: BinaryReader | Uint8Array, length?: number): ICron {
  method fromJSON (line 188) | fromJSON(object: any): ICron {
  method toJSON (line 200) | toJSON(message: ICron): unknown {
  method create (line 220) | create<I extends Exact<DeepPartial<ICron>, I>>(base?: I): ICron {
  method fromPartial (line 223) | fromPartial<I extends Exact<DeepPartial<ICron>, I>>(object: I): ICron {
  function createBaseAddCronRequest (line 234) | function createBaseAddCronRequest(): AddCronRequest {
  method encode (line 239) | encode(message: AddCronRequest, writer: BinaryWriter = new BinaryWriter(...
  method decode (line 246) | decode(input: BinaryReader | Uint8Array, length?: number): AddCronRequest {
  method fromJSON (line 270) | fromJSON(object: any): AddCronRequest {
  method toJSON (line 274) | toJSON(message: AddCronRequest): unknown {
  method create (line 282) | create<I extends Exact<DeepPartial<AddCronRequest>, I>>(base?: I): AddCr...
  method fromPartial (line 285) | fromPartial<I extends Exact<DeepPartial<AddCronRequest>, I>>(object: I):...
  function createBaseAddCronResponse (line 292) | function createBaseAddCronResponse(): AddCronResponse {
  method encode (line 297) | encode(_: AddCronResponse, writer: BinaryWriter = new BinaryWriter()): B...
  method decode (line 301) | decode(input: BinaryReader | Uint8Array, length?: number): AddCronRespon...
  method fromJSON (line 317) | fromJSON(_: any): AddCronResponse {
  method toJSON (line 321) | toJSON(_: AddCronResponse): unknown {
  method create (line 326) | create<I extends Exact<DeepPartial<AddCronResponse>, I>>(base?: I): AddC...
  method fromPartial (line 329) | fromPartial<I extends Exact<DeepPartial<AddCronResponse>, I>>(_: I): Add...
  function createBaseDeleteCronRequest (line 335) | function createBaseDeleteCronRequest(): DeleteCronRequest {
  method encode (line 340) | encode(message: DeleteCronRequest, writer: BinaryWriter = new BinaryWrit...
  method decode (line 347) | decode(input: BinaryReader | Uint8Array, length?: number): DeleteCronReq...
  method fromJSON (line 371) | fromJSON(object: any): DeleteCronRequest {
  method toJSON (line 375) | toJSON(message: DeleteCronRequest): unknown {
  method create (line 383) | create<I extends Exact<DeepPartial<DeleteCronRequest>, I>>(base?: I): De...
  method fromPartial (line 386) | fromPartial<I extends Exact<DeepPartial<DeleteCronRequest>, I>>(object: ...
  function createBaseDeleteCronResponse (line 393) | function createBaseDeleteCronResponse(): DeleteCronResponse {
  method encode (line 398) | encode(_: DeleteCronResponse, writer: BinaryWriter = new BinaryWriter())...
  method decode (line 402) | decode(input: BinaryReader | Uint8Array, length?: number): DeleteCronRes...
  method fromJSON (line 418) | fromJSON(_: any): DeleteCronResponse {
  method toJSON (line 422) | toJSON(_: DeleteCronResponse): unknown {
  method create (line 427) | create<I extends Exact<DeepPartial<DeleteCronResponse>, I>>(base?: I): D...
  method fromPartial (line 430) | fromPartial<I extends Exact<DeepPartial<DeleteCronResponse>, I>>(_: I): ...
  type CronService (line 436) | type CronService = typeof CronService;
  type CronServer (line 458) | interface CronServer extends UntypedServiceImplementation {
  type CronClient (line 463) | interface CronClient extends Client {
  type Builtin (line 502) | type Builtin = Date | Function | Uint8Array | string | number | boolean ...
  type DeepPartial (line 504) | type DeepPartial<T> = T extends Builtin ? T
  type KeysOfUnion (line 510) | type KeysOfUnion<T> = T extends T ? keyof T : never;
  type Exact (line 511) | type Exact<P, I extends P> = P extends Builtin ? P
  function isSet (line 514) | function isSet(value: any): boolean {
  type MessageFns (line 518) | interface MessageFns<T> {

FILE: back/protos/health.ts
  type HealthCheckRequest (line 26) | interface HealthCheckRequest {
  type HealthCheckResponse (line 30) | interface HealthCheckResponse {
  type HealthCheckResponse_ServingStatus (line 34) | enum HealthCheckResponse_ServingStatus {
  function healthCheckResponse_ServingStatusFromJSON (line 42) | function healthCheckResponse_ServingStatusFromJSON(object: any): HealthC...
  function healthCheckResponse_ServingStatusToJSON (line 63) | function healthCheckResponse_ServingStatusToJSON(object: HealthCheckResp...
  function createBaseHealthCheckRequest (line 79) | function createBaseHealthCheckRequest(): HealthCheckRequest {
  method encode (line 84) | encode(message: HealthCheckRequest, writer: BinaryWriter = new BinaryWri...
  method decode (line 91) | decode(input: BinaryReader | Uint8Array, length?: number): HealthCheckRe...
  method fromJSON (line 115) | fromJSON(object: any): HealthCheckRequest {
  method toJSON (line 119) | toJSON(message: HealthCheckRequest): unknown {
  method create (line 127) | create<I extends Exact<DeepPartial<HealthCheckRequest>, I>>(base?: I): H...
  method fromPartial (line 130) | fromPartial<I extends Exact<DeepPartial<HealthCheckRequest>, I>>(object:...
  function createBaseHealthCheckResponse (line 137) | function createBaseHealthCheckResponse(): HealthCheckResponse {
  method encode (line 142) | encode(message: HealthCheckResponse, writer: BinaryWriter = new BinaryWr...
  method decode (line 149) | decode(input: BinaryReader | Uint8Array, length?: number): HealthCheckRe...
  method fromJSON (line 173) | fromJSON(object: any): HealthCheckResponse {
  method toJSON (line 177) | toJSON(message: HealthCheckResponse): unknown {
  method create (line 185) | create<I extends Exact<DeepPartial<HealthCheckResponse>, I>>(base?: I): ...
  method fromPartial (line 188) | fromPartial<I extends Exact<DeepPartial<HealthCheckResponse>, I>>(object...
  type HealthService (line 195) | type HealthService = typeof HealthService;
  type HealthServer (line 217) | interface HealthServer extends UntypedServiceImplementation {
  type HealthClient (line 222) | interface HealthClient extends Client {
  type Builtin (line 252) | type Builtin = Date | Function | Uint8Array | string | number | boolean ...
  type DeepPartial (line 254) | type DeepPartial<T> = T extends Builtin ? T
  type KeysOfUnion (line 260) | type KeysOfUnion<T> = T extends T ? keyof T : never;
  type Exact (line 261) | type Exact<P, I extends P> = P extends Builtin ? P
  function isSet (line 264) | function isSet(value: any): boolean {
  type MessageFns (line 268) | interface MessageFns<T> {

FILE: back/schedule/client.ts
  class Client (line 11) | class Client {
    method addCron (line 18) | addCron(request: AddCronRequest['crons']): Promise<AddCronResponse> {
    method delCron (line 29) | delCron(request: DeleteCronRequest['ids']): Promise<DeleteCronResponse> {

FILE: back/services/config.ts
  class ConfigService (line 9) | class ConfigService {
    method constructor (line 10) | constructor() {}
    method getFile (line 12) | public async getFile(filePath: string, res: Response) {

FILE: back/services/cron.ts
  class CronService (line 31) | class CronService {
    method constructor (line 32) | constructor(@Inject('logger') private logger: winston.Logger) { }
    method isNodeCron (line 34) | private isNodeCron(cron: Crontab) {
    method isOnceSchedule (line 42) | private isOnceSchedule(schedule?: string) {
    method isBootSchedule (line 46) | private isBootSchedule(schedule?: string) {
    method isSpecialSchedule (line 50) | private isSpecialSchedule(schedule?: string) {
    method getLogName (line 54) | private async getLogName(cron: Crontab) {
    method create (line 73) | public async create(payload: Crontab): Promise<Crontab> {
    method insert (line 99) | public async insert(payload: Crontab): Promise<Crontab> {
    method update (line 103) | public async update(payload: Partial<Crontab>): Promise<Crontab> {
    method updateDb (line 134) | public async updateDb(payload: Crontab): Promise<Crontab> {
    method status (line 139) | public async status({
    method remove (line 182) | public async remove(ids: number[]) {
    method pin (line 188) | public async pin(ids: number[]) {
    method unPin (line 192) | public async unPin(ids: number[]) {
    method addLabels (line 196) | public async addLabels(ids: string[], labels: string[]) {
    method removeLabels (line 208) | public async removeLabels(ids: string[], labels: string[]) {
    method formatViewQuery (line 220) | private formatViewQuery(query: any, viewQuery: any) {
    method formatSearchText (line 305) | private formatSearchText(query: any, searchText: string | undefined) {
    method formatFilterQuery (line 352) | private formatFilterQuery(query: any, filterQuery: any) {
    method formatViewSort (line 375) | private formatViewSort(order: string[][], viewQuery: any) {
    method find (line 383) | public async find({
    method crontabs (line 396) | public async crontabs(params?: {
    method getDb (line 447) | public async getDb(query: FindOptions<Crontab>['where']): Promise<Cron...
    method run (line 455) | public async run(ids: number[]) {
    method stop (line 465) | public async stop(ids: number[]) {
    method runSingle (line 491) | private async runSingle(cronId: number): Promise<number | void> {
    method disabled (line 571) | public async disabled(ids: number[]) {
    method enabled (line 577) | public async enabled(ids: number[]) {
    method log (line 597) | public async log(id: number) {
    method logs (line 617) | public async logs(id: number) {
    method makeCommand (line 642) | private makeCommand(tab: Crontab, realTime?: boolean) {
    method setCrontab (line 669) | private async setCrontab(data?: { data: Crontab[]; total: number }) {
    method importCrontab (line 703) | public importCrontab() {
    method autosave_crontab (line 742) | public async autosave_crontab() {
    method bootTask (line 767) | public async bootTask() {

FILE: back/services/cronView.ts
  class CronViewService (line 13) | class CronViewService {
    method constructor (line 14) | constructor(@Inject('logger') private logger: winston.Logger) {}
    method create (line 16) | public async create(payload: CrontabView): Promise<CrontabView> {
    method insert (line 30) | public async insert(payload: CrontabView): Promise<CrontabView> {
    method update (line 34) | public async update(payload: CrontabView): Promise<CrontabView> {
    method updateDb (line 41) | public async updateDb(payload: CrontabView): Promise<CrontabView> {
    method remove (line 46) | public async remove(ids: number[]) {
    method list (line 50) | public async list(): Promise<CrontabView[]> {
    method getDb (line 62) | public async getDb(
    method disabled (line 72) | public async disabled(ids: number[]) {
    method enabled (line 76) | public async enabled(ids: number[]) {
    method checkPosition (line 80) | private async checkPosition(position: number) {
    method getPrecisionPosition (line 92) | private getPrecisionPosition(position: number): number {
    method move (line 96) | public async move({

FILE: back/services/dependence.ts
  class DependenceService (line 27) | class DependenceService {
    method constructor (line 28) | constructor(
    method create (line 33) | public async create(payloads: Dependence[]): Promise<Dependence[]> {
    method insert (line 43) | public async insert(payloads: Dependence[]): Promise<Dependence[]> {
    method update (line 48) | public async update(
    method updateDb (line 63) | private async updateDb(payload: Dependence): Promise<Dependence> {
    method remove (line 68) | public async remove(ids: number[], force = false): Promise<Dependence[...
    method removeDb (line 92) | public async removeDb(ids: number[]) {
    method dependencies (line 96) | public async dependencies(
    method installDependenceOneByOne (line 132) | public installDependenceOneByOne(
    method reInstall (line 144) | public async reInstall(ids: number[]): Promise<Dependence[]> {
    method cancel (line 158) | public async cancel(ids: number[]) {
    method find (line 178) | private async find(query: any, sort: any = []): Promise<Dependence[]> {
    method getDb (line 186) | public async getDb(
    method updateLog (line 196) | private async updateLog(ids: number[], log: string): Promise<void> {
    method installOrUninstallDependency (line 210) | public installOrUninstallDependency(

FILE: back/services/env.ts
  class EnvService (line 19) | class EnvService {
    method constructor (line 20) | constructor(@Inject('logger') private logger: winston.Logger) { }
    method create (line 22) | public async create(payloads: Env[]): Promise<Env[]> {
    method insert (line 43) | public async insert(payloads: Env[]): Promise<Env[]> {
    method update (line 52) | public async update(payload: Env): Promise<Env> {
    method updateDb (line 60) | private async updateDb(payload: Env): Promise<Env> {
    method remove (line 65) | public async remove(ids: number[]) {
    method move (line 70) | public async move(
    method checkPosition (line 102) | private async checkPosition(position: number, edge: number = 0) {
    method getPrecisionPosition (line 118) | private getPrecisionPosition(position: number): number {
    method envs (line 122) | public async envs(searchText: string = '', query: any = {}): Promise<E...
    method find (line 160) | private async find(query: any, sort: any = []): Promise<Env[]> {
    method getDb (line 168) | public async getDb(query: FindOptions<Env>['where']): Promise<Env> {
    method disabled (line 176) | public async disabled(ids: number[]) {
    method enabled (line 184) | public async enabled(ids: number[]) {
    method updateNames (line 189) | public async updateNames({ ids, name }: { ids: number[]; name: string ...
    method pin (line 194) | public async pin(ids: number[]) {
    method unPin (line 198) | public async unPin(ids: number[]) {
    method set_envs (line 202) | public async set_envs() {

FILE: back/services/grpc.ts
  class GrpcServerService (line 16) | class GrpcServerService {
    method initialize (line 19) | async initialize() {
    method shutdown (line 44) | async shutdown() {
    method getServer (line 61) | getServer() {

FILE: back/services/health.ts
  type HealthStatus (line 6) | interface HealthStatus {
  class HealthService (line 22) | class HealthService {
    method constructor (line 25) | constructor(
    method check (line 30) | async check(): Promise<HealthStatus> {

FILE: back/services/http.ts
  class HttpServerService (line 8) | class HttpServerService {
    method initialize (line 11) | async initialize(expressApp: express.Application, port: number) {
    method shutdown (line 33) | async shutdown() {
    method getServer (line 50) | getServer() {

FILE: back/services/log.ts
  class LogService (line 7) | class LogService {
    method constructor (line 8) | constructor(@Inject('logger') private logger: winston.Logger) {}
    method checkFilePath (line 10) | public checkFilePath(filePath: string, fileName: string) {

FILE: back/services/metrics.ts
  type Metric (line 4) | interface Metric {
  class MetricsService (line 11) | class MetricsService {
    method constructor (line 15) | private constructor() {
    method getInstance (line 23) | static getInstance(): MetricsService {
    method record (line 30) | record(name: string, value: number, tags?: Record<string, string>) {
    method measure (line 39) | measure(name: string, fn: () => void, tags?: Record<string, string>) {
    method measureAsync (line 49) | async measureAsync(name: string, fn: () => Promise<void>, tags?: Recor...
    method getMetrics (line 59) | getMetrics(name?: string, tags?: Record<string, string>) {
    method report (line 82) | report() {

FILE: back/services/notify.ts
  class NotificationService (line 11) | class NotificationService {
    method constructor (line 47) | constructor() {}
    method notify (line 49) | public async notify(
    method testNotify (line 77) | public async testNotify(
    method gotify (line 93) | private async gotify() {
    method goCqHttpBot (line 120) | private async goCqHttpBot() {
    method serverChan (line 138) | private async serverChan() {
    method pushDeer (line 164) | private async pushDeer() {
    method chat (line 188) | private async chat() {
    method bark (line 206) | private async bark() {
    method telegramBot (line 246) | private async telegramBot() {
    method dingtalkBot (line 282) | private async dingtalkBot() {
    method weWorkBot (line 313) | private async weWorkBot() {
    method weWorkApp (line 337) | private async weWorkApp() {
    method aibotk (line 413) | private async aibotk() {
    method iGot (line 459) | private async iGot() {
    method pushPlus (line 479) | private async pushPlus() {
    method wePlusBot (line 518) | private async wePlusBot() {
    method lark (line 552) | private async lark() {
    method email (line 592) | private async email() {
    method pushMe (line 623) | private async pushMe() {
    method ntfy (line 648) | private async ntfy() {
    method wxPusherBot (line 698) | private async wxPusherBot() {
    method chronocat (line 748) | private async chronocat() {
    method webhook (line 806) | private async webhook() {
    method formatBody (line 848) | private formatBody(contentType: string, body: any): object {

FILE: back/services/open.ts
  class OpenService (line 10) | class OpenService {
    method constructor (line 11) | constructor(@Inject('logger') private logger: winston.Logger) {}
    method findApps (line 13) | public async findApps(): Promise<App[] | null> {
    method create (line 18) | public async create(payload: App): Promise<App> {
    method insert (line 28) | public async insert(payload: App): Promise<App> {
    method update (line 33) | public async update(payload: App): Promise<App> {
    method updateDb (line 42) | private async updateDb(payload: Partial<App>): Promise<App> {
    method getDb (line 49) | public async getDb(query: Record<string, any>): Promise<App> {
    method remove (line 57) | public async remove(ids: number[]) {
    method resetSecret (line 63) | public async resetSecret(id: number): Promise<App> {
    method list (line 79) | public async list(
    method find (line 109) | private async find(query: Record<string, any>, sort?: any): Promise<Ap...
    method authToken (line 114) | public async authToken({
    method generateSystemToken (line 158) | public async generateSystemToken(): Promise<{

FILE: back/services/schedule.ts
  type ScheduleTaskType (line 15) | interface ScheduleTaskType {
  type TaskCallbacks (line 23) | interface TaskCallbacks {
  class ScheduleService (line 39) | class ScheduleService {
    method constructor (line 50) | constructor(@Inject('logger') private logger: winston.Logger) {}
    method runTask (line 52) | async runTask(
    method createCronTask (line 135) | async createCronTask(
    method cancelCronTask (line 173) | async cancelCronTask({ id = 0, name }: ScheduleTaskType) {
    method createIntervalTask (line 182) | async createIntervalTask(
    method cancelIntervalTask (line 232) | async cancelIntervalTask({ id = 0, name }: ScheduleTaskType) {
    method formatId (line 242) | private formatId(id: number): string {

FILE: back/services/script.ts
  class ScriptService (line 13) | class ScriptService {
    method constructor (line 14) | constructor(
    method taskCallbacks (line 21) | private taskCallbacks(filePath: string): TaskCallbacks {
    method runScript (line 41) | public async runScript(filePath: string) {
    method stopScript (line 54) | public async stopScript(filePath: string, pid: number) {
    method checkFilePath (line 67) | public checkFilePath(filePath: string, fileName: string) {
    method getFile (line 72) | public async getFile(filePath: string, fileName: string) {

FILE: back/services/sock.ts
  class SockService (line 7) | class SockService {
    method constructor (line 10) | constructor(@Inject('logger') private logger: winston.Logger) { }
    method getClients (line 12) | public getClients() {
    method addClient (line 16) | public addClient(conn: Connection) {
    method removeClient (line 22) | public removeClient(conn: Connection) {
    method sendMessage (line 29) | public sendMessage(msg: SockMessage) {

FILE: back/services/sshKey.ts
  class SshKeyService (line 13) | class SshKeyService {
    method constructor (line 19) | constructor(@Inject('logger') private logger: winston.Logger) {
    method initSshConfigFile (line 23) | private async initSshConfigFile() {
    method generatePrivateKeyFile (line 40) | private async generatePrivateKeyFile(
    method removePrivateKeyFile (line 57) | private async removePrivateKeyFile(alias: string): Promise<void> {
    method generateSingleSshConfig (line 66) | private async generateSingleSshConfig(
    method removeSshConfig (line 91) | private async removeSshConfig(alias: string) {
    method addSSHKey (line 100) | public async addSSHKey(
    method removeSSHKey (line 110) | public async removeSSHKey(
    method setSshConfig (line 119) | public async setSshConfig(docs: Subscription[]) {
    method addGlobalSSHKey (line 135) | public async addGlobalSSHKey(key: string, alias: string): Promise<void> {
    method removeGlobalSSHKey (line 142) | public async removeGlobalSSHKey(alias: string): Promise<void> {
    method generateGlobalSshConfig (line 147) | private async generateGlobalSshConfig(alias: string) {

FILE: back/services/subscription.ts
  class SubscriptionService (line 37) | class SubscriptionService {
    method constructor (line 38) | constructor(
    method list (line 46) | public async list(
    method handleTask (line 84) | public async handleTask(
    method setSshConfig (line 114) | public async setSshConfig() {
    method taskCallbacks (line 119) | private taskCallbacks(doc: Subscription): TaskCallbacks {
    method create (line 212) | public async create(payload: Subscription): Promise<Subscription> {
    method insert (line 220) | public async insert(payload: Subscription): Promise<SubscriptionInstan...
    method update (line 224) | public async update(payload: Subscription): Promise<Subscription> {
    method updateDb (line 233) | public async updateDb(payload: Subscription): Promise<Subscription> {
    method status (line 238) | public async status({
    method remove (line 269) | public async remove(ids: number[], query: { force?: boolean }) {
    method getDb (line 291) | public async getDb(
    method run (line 301) | public async run(ids: number[]) {
    method stop (line 311) | public async stop(ids: number[]) {
    method runSingle (line 329) | private async runSingle(subscriptionId: number) {
    method disabled (line 346) | public async disabled(ids: number[]) {
    method enabled (line 355) | public async enabled(ids: number[]) {
    method log (line 364) | public async log(id: number) {
    method logs (line 374) | public async logs(id: number) {

FILE: back/services/system.ts
  class SystemService (line 42) | class SystemService {
    method constructor (line 46) | constructor(
    method getSystemConfig (line 52) | public async getSystemConfig() {
    method updateAuthDb (line 60) | private async updateAuthDb(payload: SystemInfo): Promise<SystemInfo> {
    method getDb (line 67) | public async getDb(query: any): Promise<SystemInfo> {
    method updateNotificationMode (line 75) | public async updateNotificationMode(notificationInfo: NotificationInfo) {
    method updateLogRemoveFrequency (line 93) | public async updateLogRemoveFrequency(info: SystemModelInfo) {
    method updateCronConcurrency (line 120) | public async updateCronConcurrency(info: SystemModelInfo) {
    method updateDependenceProxy (line 132) | public async updateDependenceProxy(info: SystemModelInfo) {
    method updateNodeMirror (line 149) | public async updateNodeMirror(info: SystemModelInfo, res?: Response) {
    method updatePythonMirror (line 197) | public async updatePythonMirror(info: SystemModelInfo) {
    method updateLinuxMirror (line 211) | public async updateLinuxMirror(
    method checkUpdate (line 273) | public async checkUpdate() {
    method checkHasNewVersion (line 316) | private checkHasNewVersion(curVersion: string, lastVersion: string) {
    method updateSystem (line 335) | public async updateSystem() {
    method reloadSystem (line 362) | public async reloadSystem(target?: 'system' | 'data') {
    method notify (line 376) | public async notify({
    method run (line 404) | public async run({ command, logPath }: { command: string; logPath?: st...
    method stop (line 416) | public async stop({ command, pid }: { command: string; pid: number }) {
    method exportData (line 438) | public async exportData(res: Response, type?: string[]) {
    method importData (line 455) | public async importData() {
    method getSystemLog (line 467) | public async getSystemLog(
    method deleteSystemLog (line 508) | public async deleteSystemLog() {
    method updateTimezone (line 516) | public async updateTimezone(info: SystemModelInfo) {
    method updateGlobalSshKey (line 533) | public async updateGlobalSshKey(info: SystemModelInfo) {
    method cleanDependence (line 554) | public async cleanDependence(type: 'node' | 'python3') {

FILE: back/services/user.ts
  class UserService (line 30) | class UserService {
    method constructor (line 34) | constructor(
    method login (line 40) | public async login(
    method logout (line 195) | public async logout(platform: string, tokenValue: string): Promise<any> {
    method getLoginLog (line 229) | public async getLoginLog(): Promise<Array<SystemModelInfo | undefined>> {
    method insertDb (line 248) | private async insertDb(payload: SystemInfo): Promise<SystemInfo> {
    method updateUsernameAndPassword (line 253) | public async updateUsernameAndPassword({
    method updateAvatar (line 268) | public async updateAvatar(avatar: string) {
    method initTwoFactor (line 274) | public async initTwoFactor() {
    method activeTwoFactor (line 282) | public async activeTwoFactor(code: string) {
    method twoFactorLogin (line 294) | public async twoFactorLogin(
    method deactiveTwoFactor (line 333) | public async deactiveTwoFactor() {
    method getAuthInfo (line 342) | public async getAuthInfo() {
    method updateAuthInfo (line 351) | private async updateAuthInfo(authInfo: AuthInfo, info: Partial<AuthInf...
    method getNotificationMode (line 360) | public async getNotificationMode(): Promise<NotificationInfo> {
    method updateAuthDb (line 365) | private async updateAuthDb(payload: SystemInfo): Promise<any> {
    method getDb (line 379) | public async getDb(query: any): Promise<SystemInfo> {
    method updateNotificationMode (line 387) | public async updateNotificationMode(notificationInfo: NotificationInfo) {
    method normalizeTokens (line 405) | private normalizeTokens(
    method addTokenToList (line 435) | private addTokenToList(
    method removeTokenFromList (line 469) | private removeTokenFromList(
    method findTokenInList (line 485) | private findTokenInList(
    method resetAuthInfo (line 499) | public async resetAuthInfo(info: Partial<AuthInfo>) {

FILE: back/shared/auth.ts
  function isValidToken (line 12) | function isValidToken(

FILE: back/shared/interface.ts
  type Override (line 4) | type Override<
  type TCron (line 11) | type TCron = Override<Partial<ICron>, { id: string }>;
  type IDependencyFn (line 13) | interface IDependencyFn<T> {
  type ICronFn (line 18) | interface ICronFn<T> {
  type ISchedule (line 23) | interface ISchedule {
  type IScheduleFn (line 30) | interface IScheduleFn<T> {

FILE: back/shared/logStreamManager.ts
  class LogStreamManager (line 7) | class LogStreamManager extends EventEmitter {
    method write (line 16) | async write(filePath: string, data: string): Promise<void> {
    method closeStream (line 71) | async closeStream(filePath: string): Promise<void> {
    method closeAll (line 94) | async closeAll(): Promise<void> {
    method getOpenStreamCount (line 104) | getOpenStreamCount(): number {

FILE: back/shared/pLimit.ts
  class TaskLimit (line 18) | class TaskLimit {
    method cronLimitActiveCount (line 45) | get cronLimitActiveCount() {
    method cronLimitPendingCount (line 49) | get cronLimitPendingCount() {
    method firstDependencyId (line 53) | get firstDependencyId() {
    method constructor (line 59) | constructor() {
    method handleEvents (line 64) | private handleEvents() {
    method removeQueuedDependency (line 93) | public removeQueuedDependency(dependency: Dependence) {
    method removeQueuedCron (line 99) | public removeQueuedCron(id: string) {
    method setCustomLimit (line 109) | public async setCustomLimit(limit?: number) {
    method runWithCronLimit (line 125) | public async runWithCronLimit<T>(
    method manualRunWithCronLimit (line 158) | public async manualRunWithCronLimit<T>(
    method runWithSubscriptionLimit (line 165) | public async runWithSubscriptionLimit<T>(
    method runWithSystemLimit (line 174) | public async runWithSystemLimit<T>(
    method runWithScriptLimit (line 183) | public async runWithScriptLimit<T>(
    method waitDependencyQueueDone (line 192) | public async waitDependencyQueueDone(): Promise<void> {
    method runDependeny (line 205) | public runDependeny<T>(
    method updateDepLog (line 215) | public updateDepLog<T>(

FILE: back/shared/runCron.ts
  function runCron (line 8) | function runCron(cmd: string, cron: ICron): Promise<number | void> {

FILE: back/shared/store.ts
  type EKeyv (line 8) | enum EKeyv {
  type IKeyvStore (line 13) | interface IKeyvStore {
  method getAuthInfo (line 22) | getAuthInfo() {
  method updateAuthInfo (line 25) | updateAuthInfo(value: IKeyvStore['authInfo']) {
  method getApps (line 28) | getApps() {
  method updateApps (line 31) | updateApps(apps: App[]) {

FILE: back/shared/utils.ts
  function getUniqueLockPath (line 7) | function getUniqueLockPath(filePath: string) {
  function writeFileWithLock (line 14) | async function writeFileWithLock(

FILE: back/token.ts
  function getToken (line 12) | async function getToken() {
  function writeFile (line 27) | async function writeFile(data: any) {

FILE: back/types/express.d.ts
  type Request (line 7) | interface Request {

FILE: sample/notify.js
  function request (line 5) | async function request(url, options = {}) {
  function post (line 26) | function post(url, options = {}) {
  function get (line 30) | function get(url, options = {}) {
  function one (line 197) | async function one() {
  function gotifyNotify (line 204) | function gotifyNotify(text, desp) {
  function gobotNotify (line 240) | function gobotNotify(text, desp) {
  function serverNotify (line 277) | function serverNotify(text, desp) {
  function pushDeerNotify (line 325) | function pushDeerNotify(text, desp) {
  function chatNotify (line 368) | function chatNotify(text, desp) {
  function barkNotify (line 404) | function barkNotify(text, desp, params = {}) {
  function tgBotNotify (line 461) | function tgBotNotify(text, desp) {
  function ddBotNotify (line 521) | function ddBotNotify(text, desp) {
  function qywxBotNotify (line 585) | function qywxBotNotify(text, desp) {
  function ChangeUserId (line 625) | function ChangeUserId(desp) {
  function qywxamNotify (line 645) | async function qywxamNotify(text, desp) {
  function do_qywxamNotify (line 656) | function do_qywxamNotify(text, desp) {
  function iGotNotify (line 773) | function iGotNotify(text, desp, params = {}) {
  function pushPlusNotify (line 815) | function pushPlusNotify(text, desp) {
  function wePlusBotNotify (line 885) | function wePlusBotNotify(text, desp) {
  function aibotkNotify (line 934) | function aibotkNotify(text, desp) {
  function fsBotNotify (line 995) | function fsBotNotify(text, desp) {
  function smtpNotify (line 1048) | async function smtpNotify(text, desp) {
  function pushMeNotify (line 1085) | function pushMeNotify(text, desp, params = {}) {
  function chronocatNotify (line 1120) | function chronocatNotify(title, desp) {
  function qmsgNotify (line 1191) | function qmsgNotify(text, desp) {
  function webhookNotify (line 1226) | function webhookNotify(text, desp) {
  function ntfyNotify (line 1281) | function ntfyNotify(text, desp) {
  function wxPusherNotify (line 1341) | function wxPusherNotify(text, desp) {
  function parseString (line 1411) | function parseString(input, valueFormatFn) {
  function parseHeaders (line 1437) | function parseHeaders(headers) {
  function parseBody (line 1461) | function parseBody(body, contentType, valueFormatFn) {
  function formatBodyFun (line 1483) | function formatBodyFun(contentType, body) {
  function sendNotify (line 1504) | async function sendNotify(text, desp, params = {}) {

FILE: sample/notify.py
  function print (line 25) | def print(text, *args, **kw):
  function bark (line 147) | def bark(title: str, content: str) -> None:
  function console (line 191) | def console(title: str, content: str) -> None:
  function dingding_bot (line 198) | def dingding_bot(title: str, content: str) -> None:
  function feishu_bot (line 227) | def feishu_bot(title: str, content: str) -> None:
  function go_cqhttp (line 259) | def go_cqhttp(title: str, content: str) -> None:
  function gotify (line 276) | def gotify(title: str, content: str) -> None:
  function iGot (line 298) | def iGot(title: str, content: str) -> None:
  function serverJ (line 317) | def serverJ(title: str, content: str) -> None:
  function pushdeer (line 342) | def pushdeer(title: str, content: str) -> None:
  function chat (line 367) | def chat(title: str, content: str) -> None:
  function pushplus_bot (line 384) | def pushplus_bot(title: str, content: str) -> None:
  function weplus_bot (line 429) | def weplus_bot(title: str, content: str) -> None:
  function qmsg_bot (line 460) | def qmsg_bot(title: str, content: str) -> None:
  function wecom_app (line 478) | def wecom_app(title: str, content: str) -> None:
  class WeCom (line 512) | class WeCom:
    method __init__ (line 513) | def __init__(self, corpid, corpsecret, agentid):
    method get_access_token (line 521) | def get_access_token(self):
    method send_text (line 531) | def send_text(self, message, touser="@all"):
    method send_mpnews (line 547) | def send_mpnews(self, title, message, media_id, touser="@all"):
  function wecom_bot (line 574) | def wecom_bot(title: str, content: str) -> None:
  function telegram_bot (line 599) | def telegram_bot(title: str, content: str) -> None:
  function aibotk (line 643) | def aibotk(title: str, content: str) -> None:
  function smtp (line 679) | def smtp(title: str, content: str) -> None:
  function pushme (line 728) | def pushme(title: str, content: str) -> None:
  function chronocat (line 756) | def chronocat(title: str, content: str) -> None:
  function ntfy (line 804) | def ntfy(title: str, content: str) -> None:
  function wxpusher_bot (line 845) | def wxpusher_bot(title: str, content: str) -> None:
  function parse_headers (line 901) | def parse_headers(headers):
  function parse_string (line 920) | def parse_string(input_string, value_format_fn=None):
  function parse_body (line 935) | def parse_body(body, content_type, value_format_fn=None):
  function custom_notify (line 952) | def custom_notify(title: str, content: str) -> None:
  function one (line 992) | def one() -> str:
  function add_notify_function (line 1002) | def add_notify_function():
  function send (line 1071) | def send(title: str, content: str, ignore_default_config: bool = False, ...
  function main (line 1102) | def main():

FILE: shell/preload/client.js
  class GrpcClient (line 5) | class GrpcClient {
    method constructor (line 46) | constructor() {
    method #initializeClient (line 51) | #initializeClient() {
    method #promisifyMethod (line 70) | #promisifyMethod(methodName) {
    method #bindMethods (line 92) | #bindMethods() {
    method getApi (line 98) | getApi() {
    method close (line 105) | close() {

FILE: shell/preload/client.py
  function error_handler (line 9) | def error_handler(func):
  class EnvItem (line 44) | class EnvItem(TypedDict, total=False):
  class GetEnvsParams (line 53) | class GetEnvsParams(TypedDict, total=False):
  class CreateEnvParams (line 57) | class CreateEnvParams(TypedDict):
  class UpdateEnvParams (line 61) | class UpdateEnvParams(TypedDict):
  class DeleteEnvsParams (line 65) | class DeleteEnvsParams(TypedDict):
  class MoveEnvParams (line 69) | class MoveEnvParams(TypedDict):
  class DisableEnvsParams (line 75) | class DisableEnvsParams(TypedDict):
  class EnableEnvsParams (line 79) | class EnableEnvsParams(TypedDict):
  class UpdateEnvNamesParams (line 83) | class UpdateEnvNamesParams(TypedDict):
  class GetEnvByIdParams (line 88) | class GetEnvByIdParams(TypedDict):
  class SystemNotifyParams (line 92) | class SystemNotifyParams(TypedDict):
  class EnvsResponse (line 97) | class EnvsResponse(TypedDict):
  class EnvResponse (line 103) | class EnvResponse(TypedDict):
  class Response (line 109) | class Response(TypedDict):
  class ExtraScheduleItem (line 114) | class ExtraScheduleItem(TypedDict, total=False):
  class CronItem (line 118) | class CronItem(TypedDict, total=False):
  class CreateCronParams (line 135) | class CreateCronParams(TypedDict):
  class UpdateCronParams (line 146) | class UpdateCronParams(TypedDict):
  class DeleteCronsParams (line 158) | class DeleteCronsParams(TypedDict):
  class CronDetailParams (line 162) | class CronDetailParams(TypedDict):
  class CronsResponse (line 166) | class CronsResponse(TypedDict):
  class CronResponse (line 172) | class CronResponse(TypedDict):
  class Client (line 178) | class Client:
    method __init__ (line 179) | def __init__(self):
    method __del__ (line 183) | def __del__(self):
    method _execute_node (line 192) | def _execute_node(self, method: str, params: Dict = None) -> Dict:
    method getEnvs (line 230) | def getEnvs(self, params: GetEnvsParams = None) -> EnvsResponse:
    method createEnv (line 234) | def createEnv(self, data: CreateEnvParams) -> EnvsResponse:
    method updateEnv (line 238) | def updateEnv(self, data: UpdateEnvParams) -> EnvResponse:
    method deleteEnvs (line 242) | def deleteEnvs(self, data: DeleteEnvsParams) -> Response:
    method moveEnv (line 246) | def moveEnv(self, data: MoveEnvParams) -> EnvResponse:
    method disableEnvs (line 250) | def disableEnvs(self, data: DisableEnvsParams) -> Response:
    method enableEnvs (line 254) | def enableEnvs(self, data: EnableEnvsParams) -> Response:
    method updateEnvNames (line 258) | def updateEnvNames(self, data: UpdateEnvNamesParams) -> Response:
    method getEnvById (line 262) | def getEnvById(self, data: GetEnvByIdParams) -> EnvResponse:
    method systemNotify (line 266) | def systemNotify(self, data: SystemNotifyParams) -> Response:
    method getCronDetail (line 270) | def getCronDetail(self, data: CronDetailParams) -> CronResponse:
    method createCron (line 274) | def createCron(self, data: CreateCronParams) -> CronResponse:
    method updateCron (line 278) | def updateCron(self, data: UpdateCronParams) -> CronResponse:
    method deleteCrons (line 282) | def deleteCrons(self, data: DeleteCronsParams) -> Response:

FILE: shell/preload/sitecustomize.js
  function expandRange (line 5) | function expandRange(rangeStr, max) {
  function run (line 25) | function run() {

FILE: shell/preload/sitecustomize.py
  function try_parse_int (line 12) | def try_parse_int(value):
  function expand_range (line 19) | def expand_range(range_str, max_value):
  function run (line 39) | def run():
  function handle_sigterm (line 123) | def handle_sigterm(signum, frame):
  class BaseApi (line 134) | class BaseApi(Client):
    method notify (line 135) | def notify(self, *args, **kwargs):

FILE: src/app.ts
  function rootContainer (line 5) | function rootContainer(container: any) {
  function modifyClientRenderOpts (line 32) | function modifyClientRenderOpts(memo: any) {
  function modifyContextOpts (line 40) | function modifyContextOpts(memo: any) {

FILE: src/components/name.tsx
  function Name (line 5) | function Name<

FILE: src/components/terminal.tsx
  type LineType (line 4) | enum LineType {
  type ColorMode (line 9) | enum ColorMode {
  type Props (line 14) | interface Props {

FILE: src/hooks/useFilterTreeData.ts
  function filterOptionFunc (line 20) | function filterOptionFunc(_: string, dataNode: any[]) {
  function dig (line 26) | function dig(list: any[], keepAll: boolean = false): any[] {

FILE: src/layouts/index.tsx
  type SharedContext (line 22) | interface SharedContext {
  type TSystemInfo (line 32) | interface TSystemInfo {

FILE: src/pages/crontab/detail.tsx
  type LogItem (line 51) | interface LogItem {
  method onOk (line 182) | onOk() {
  method onOk (line 218) | onOk() {
  method onOk (line 245) | onOk() {
  method onOk (line 273) | onOk() {
  method onOk (line 309) | onOk() {
  method onEllipsis (line 350) | onEllipsis(ellipsis) {

FILE: src/pages/crontab/index.tsx
  constant SHOW_TAB_COUNT (line 65) | const SHOW_TAB_COUNT = 10;
  method onOk (line 447) | onOk() {
  method onOk (line 477) | onOk() {
  method onOk (line 509) | onOk() {
  method onOk (line 545) | onOk() {
  method onOk (line 585) | onOk() {
  method onOk (line 717) | onOk() {
  method onOk (line 741) | onOk() {

FILE: src/pages/crontab/modal.tsx
  method validator (line 240) | validator(_, value) {
  method validator (line 268) | validator(_, value) {

FILE: src/pages/crontab/type.ts
  type CrontabStatus (line 1) | enum CrontabStatus {
  type OperationName (line 8) | enum OperationName {
  type OperationPath (line 17) | enum OperationPath {
  type ICrontab (line 26) | interface ICrontab {
  type ScheduleType (line 43) | enum ScheduleType {

FILE: src/pages/crontab/viewCreateModal.tsx
  constant PROPERTIES (line 20) | const PROPERTIES = [
  constant OPERATIONS (line 35) | const OPERATIONS = [
  constant SORTTYPES (line 46) | const SORTTYPES = [
  type ViewFilterRelation (line 51) | enum ViewFilterRelation {

FILE: src/pages/crontab/viewManageModal.tsx
  method onOk (line 153) | onOk() {

FILE: src/pages/dependence/index.tsx
  type StatusColor (line 47) | enum StatusColor {
  method onOk (line 302) | onOk() {
  method onOk (line 333) | onOk() {
  method onOk (line 357) | onOk() {
  method onOk (line 401) | onOk() {
  method onOk (line 420) | onOk() {

FILE: src/pages/dependence/modal.tsx
  type DependenceTypes (line 8) | enum DependenceTypes {

FILE: src/pages/dependence/type.ts
  type DependenceStatus (line 1) | enum DependenceStatus {
  type Status (line 12) | enum Status {

FILE: src/pages/env/index.tsx
  type Status (line 44) | enum Status {
  type StatusColor (line 49) | enum StatusColor {
  type OperationName (line 54) | enum OperationName {
  type OperationPath (line 61) | enum OperationPath {
  method onOk (line 283) | onOk() {
  method onOk (line 345) | onOk() {
  method onOk (line 386) | onOk() {
  method onOk (line 503) | onOk() {
  method onOk (line 527) | onOk() {

FILE: src/pages/initialization/index.tsx
  method validator (line 183) | validator(_, value) {

FILE: src/pages/log/index.tsx
  method onOk (line 147) | onOk() {

FILE: src/pages/script/components/UnsupportedFilePreview/index.tsx
  type UnsupportedFilePreviewProps (line 7) | interface UnsupportedFilePreviewProps {

FILE: src/pages/script/index.tsx
  method onOk (line 188) | onOk() {
  method onOk (line 263) | onOk() {
  method onOk (line 303) | onOk() {

FILE: src/pages/setting/checkUpdate.tsx
  method onOk (line 56) | onOk() {
  method onOk (line 88) | onOk() {
  method onOk (line 154) | onOk() {
  method onCancel (line 157) | onCancel() {

FILE: src/pages/setting/index.tsx
  method onOk (line 173) | onOk() {
  method onOk (line 204) | onOk() {

FILE: src/pages/setting/loginLog.tsx
  type LoginStatus (line 10) | enum LoginStatus {
  type LoginStatusColor (line 15) | enum LoginStatusColor {

FILE: src/pages/setting/other.tsx
  method onOk (line 167) | onOk() {

FILE: src/pages/setting/progress.tsx
  function useProgress (line 13) | function useProgress(title: string) {

FILE: src/pages/setting/systemLog.tsx
  method onSuccess (line 40) | async onSuccess(res) {

FILE: src/pages/subscription/index.tsx
  type SubscriptionStatus (line 44) | enum SubscriptionStatus {
  type IntervalSchedule (line 51) | enum IntervalSchedule {
  type SubscriptionType (line 58) | enum SubscriptionType {
  method onOk (line 252) | onOk() {
  method onOk (line 284) | onOk() {
  method onOk (line 349) | onOk() {
  method onOk (line 386) | onOk() {

FILE: src/utils/codemirror/systemLog.ts
  method constructor (line 25) | constructor(view: EditorView) {
  method update (line 29) | update(update: ViewUpdate) {
  method getDecorations (line 35) | getDecorations(view: EditorView) {
  method constructor (line 60) | constructor(view: EditorView) {
  method update (line 64) | update(update: ViewUpdate) {
  method getDecorations (line 70) | getDecorations(view: EditorView) {
  method constructor (line 95) | constructor(view: EditorView) {
  method update (line 99) | update(update: ViewUpdate) {
  method getDecorations (line 105) | getDecorations(view: EditorView) {
  method constructor (line 130) | constructor(view: EditorView) {
  method update (line 134) | update(update: ViewUpdate) {
  method getDecorations (line 140) | getDecorations(view: EditorView) {

FILE: src/utils/const.ts
  constant LOG_END_SYMBOL (line 1) | const LOG_END_SYMBOL = '     ';
  constant LANG_MAP (line 3) | const LANG_MAP = {
  constant TIMEZONES (line 13) | const TIMEZONES = [

FILE: src/utils/date.ts
  function diffTime (line 3) | function diffTime(num: number) {

FILE: src/utils/http.tsx
  type IResponseData (line 13) | interface IResponseData {
  type Override (line 20) | type Override<
  type ICustomConfig (line 27) | interface ICustomConfig {

FILE: src/utils/index.ts
  function browserType (line 6) | function browserType() {
  function getTableScroll (line 184) | function getTableScroll({
  function automaticClick (line 211) | function automaticClick(elment: HTMLElement) {
  function exportJson (line 234) | function exportJson(name: string, data: string) {
  function depthFirstSearch (line 246) | function depthFirstSearch<
  function findNode (line 278) | function findNode<T extends Record<string, any> & { children?: T[] }>(
  function logEnded (line 301) | function logEnded(log: string): boolean {
  function getCommandScript (line 306) | function getCommandScript(
  function parseCrontab (line 334) | function parseCrontab(schedule: string): Date | null {
  function getCrontabsNextDate (line 345) | function getCrontabsNextDate(
  function getExtension (line 361) | function getExtension(filename: string) {
  function getEditorMode (line 367) | function getEditorMode(filename: string) {
  function disableBody (line 372) | function disableBody() {

FILE: src/utils/init.ts
  function init (line 4) | function init(version: string) {

FILE: src/utils/monaco/index.ts
  type FileTypeConfig (line 3) | interface FileTypeConfig {
  function canPreviewInMonaco (line 107) | function canPreviewInMonaco(fileName: string): boolean {
  function getFileCategory (line 140) | function getFileCategory(fileName: string): string {

FILE: src/utils/type.ts
  type SockMessageType (line 1) | type SockMessageType =

FILE: src/utils/websocket.ts
  class WebSocketManager (line 4) | class WebSocketManager {
    method constructor (line 18) | constructor(url: string, options: Partial<typeof WebSocketManager.prot...
    method getInstance (line 29) | public static getInstance(url: string = '', options?: Partial<typeof W...
    method init (line 36) | private async init() {
    method setupEventListeners (line 57) | private setupEventListeners() {
    method waitForClose (line 76) | private async waitForClose() {
    method subscribe (line 82) | public subscribe(topic: SockMessageType, callback: (v: any) => void) {
    method unsubscribe (line 94) | public unsubscribe(topic: SockMessageType, callback: (v: any) => void) {
    method send (line 104) | public send(message: any) {
    method dispatchMessage (line 110) | private dispatchMessage(message: any) {
    method startHeartbeat (line 117) | private startHeartbeat() {
    method stopHeartbeat (line 125) | private stopHeartbeat() {
    method close (line 131) | public close() {
    method handleError (line 140) | private handleError(error: any) {
    method on (line 145) | public on(event: string, listener: Function) {
    method emit (line 149) | public emit(event: string, data?: any) {

FILE: typings.d.ts
  type Window (line 12) | interface Window {
Condensed preview — 222 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (1,260K chars).
[
  {
    "path": ".editorconfig",
    "chars": 245,
    "preview": "# http://editorconfig.org\nroot = true\n\n[*]\nindent_style = space\nindent_size = 2\nend_of_line = lf\ncharset = utf-8\ntrim_tr"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.yml",
    "chars": 1372,
    "preview": "name: \"\\U0001F41E Bug report\"\ndescription: Create a report to help us improve\nbody:\n  - type: input\n    id: version\n    "
  },
  {
    "path": ".github/ISSUE_TEMPLATE/config.yml",
    "chars": 368,
    "preview": "blank_issues_enabled: false\ncontact_links:\n  - name: Telegram Chat\n    url: https://t.me/jiao_long\n    about: Ask questi"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.yml",
    "chars": 1558,
    "preview": "name: \"\\U0001F680 New feature proposal\"\ndescription: Suggest an idea for this project\nlabels: [\":sparkles: feature reque"
  },
  {
    "path": ".github/PULL_REQUEST_TEMPLATE.md",
    "chars": 695,
    "preview": "## PR Type\nWhat kind of change does this PR introduce?\n\n<!-- Please check the one that applies to this PR using \"x\". -->"
  },
  {
    "path": ".github/agents/ql.agent.md",
    "chars": 90,
    "preview": "---\nname: Bug Fixer\ndescription: Fix this issue following our error handling pattern.\n---\n"
  },
  {
    "path": ".github/config.yml",
    "chars": 359,
    "preview": "# Comment to be posted to on PRs from first time contributors in your repository\nnewPRWelcomeComment: |\n  💖 Thanks for o"
  },
  {
    "path": ".github/workflows/build-docker-image.yml",
    "chars": 8048,
    "preview": "name: Build And Push Docker Image\n\non:\n  push:\n    paths-ignore:\n      - \"*.md\"\n    branches:\n      - \"master\"\n      - \""
  },
  {
    "path": ".gitignore",
    "chars": 438,
    "preview": "# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.\n\n# dependencies\n/node_modules\n/npm"
  },
  {
    "path": ".npmrc",
    "chars": 30,
    "preview": "strict-peer-dependencies=false"
  },
  {
    "path": ".prettierignore",
    "chars": 259,
    "preview": "**/*.md\n**/*.svg\n**/*.ejs\n**/*.html\n/.umi\n/.umi-production\n/.umi-test\n/.history\n/.tmp\n/node_modules\nnpm-debug.log*\nyarn-"
  },
  {
    "path": ".prettierrc",
    "chars": 174,
    "preview": "{\n  \"singleQuote\": true,\n  \"trailingComma\": \"all\",\n  \"printWidth\": 80,\n  \"overrides\": [\n    {\n      \"files\": \".prettierr"
  },
  {
    "path": ".umirc.ts",
    "chars": 1105,
    "preview": "import { defineConfig } from '@umijs/max';\nconst CompressionPlugin = require('compression-webpack-plugin');\n\nconst baseU"
  },
  {
    "path": "LICENSE",
    "chars": 10712,
    "preview": "                              Apache License\n                        Version 2.0, January 2004\n                    http:"
  },
  {
    "path": "README-en.md",
    "chars": 5001,
    "preview": "<div align=\"center\">\n<img width=\"100\" src=\"https://user-images.githubusercontent.com/22700758/191449379-f9f56204-0e31-4a"
  },
  {
    "path": "README.md",
    "chars": 3530,
    "preview": "<div align=\"center\">\n<img width=\"100\" src=\"https://user-images.githubusercontent.com/22700758/191449379-f9f56204-0e31-4a"
  },
  {
    "path": "SECURITY.md",
    "chars": 324,
    "preview": "## Reporting a Vulnerability\n\nTo report a vulnerability, please open a private vulnerability report at <https://github.c"
  },
  {
    "path": "back/api/config.ts",
    "chars": 2660,
    "preview": "import { Router, Request, Response, NextFunction } from 'express';\nimport { Container } from 'typedi';\nimport { Logger }"
  },
  {
    "path": "back/api/cron.ts",
    "chars": 12983,
    "preview": "import { Router, Request, Response, NextFunction } from 'express';\nimport { Container } from 'typedi';\nimport { Logger }"
  },
  {
    "path": "back/api/dependence.ts",
    "chars": 4340,
    "preview": "import { Router, Request, Response, NextFunction } from 'express';\nimport { Container } from 'typedi';\nimport Dependence"
  },
  {
    "path": "back/api/env.ts",
    "chars": 7392,
    "preview": "import { Joi, celebrate } from 'celebrate';\nimport { NextFunction, Request, Response, Router } from 'express';\nimport fs"
  },
  {
    "path": "back/api/health.ts",
    "chars": 699,
    "preview": "import { Router } from 'express';\nimport Logger from '../loaders/logger';\nimport { HealthService } from '../services/hea"
  },
  {
    "path": "back/api/index.ts",
    "chars": 646,
    "preview": "import { Router } from 'express';\nimport user from './user';\nimport env from './env';\nimport config from './config';\nimp"
  },
  {
    "path": "back/api/log.ts",
    "chars": 3794,
    "preview": "import { celebrate, Joi } from 'celebrate';\nimport { NextFunction, Request, Response, Router } from 'express';\nimport { "
  },
  {
    "path": "back/api/open.ts",
    "chars": 3337,
    "preview": "import { Router, Request, Response, NextFunction } from 'express';\nimport { Container } from 'typedi';\nimport OpenServic"
  },
  {
    "path": "back/api/script.ts",
    "chars": 10623,
    "preview": "import { fileExist, readDirs, readDir, rmPath, IFile } from '../config/util';\nimport { Router, Request, Response, NextFu"
  },
  {
    "path": "back/api/subscription.ts",
    "chars": 9697,
    "preview": "import { Router, Request, Response, NextFunction } from 'express';\nimport { Container } from 'typedi';\nimport { Logger }"
  },
  {
    "path": "back/api/system.ts",
    "chars": 12630,
    "preview": "import { Router, Request, Response, NextFunction } from 'express';\nimport { Container } from 'typedi';\nimport { Logger }"
  },
  {
    "path": "back/api/update.ts",
    "chars": 1355,
    "preview": "import { NextFunction, Request, Response, Router } from 'express';\nimport Container from 'typedi';\nimport Logger from '."
  },
  {
    "path": "back/api/user.ts",
    "chars": 7302,
    "preview": "import { Router, Request, Response, NextFunction } from 'express';\nimport { Container } from 'typedi';\nimport { Logger }"
  },
  {
    "path": "back/app.ts",
    "chars": 9349,
    "preview": "import 'reflect-metadata';\nimport cluster, { type Worker } from 'cluster';\nimport compression from 'compression';\nimport"
  },
  {
    "path": "back/config/const.ts",
    "chars": 1018,
    "preview": "export const LOG_END_SYMBOL = '     ';\n\nexport const TASK_COMMAND = 'task';\nexport const QL_COMMAND = 'ql';\n\nexport cons"
  },
  {
    "path": "back/config/http.ts",
    "chars": 1662,
    "preview": "import { request as undiciRequest, Dispatcher } from 'undici';\n\ntype RequestBaseOptions = {\n  dispatcher?: Dispatcher;\n "
  },
  {
    "path": "back/config/index.ts",
    "chars": 5123,
    "preview": "import dotenv from 'dotenv';\nimport path from 'path';\nimport { createRandomString } from './share';\n\ndotenv.config({\n  p"
  },
  {
    "path": "back/config/serverEnv.ts",
    "chars": 765,
    "preview": "import { Request, Response } from 'express';\nimport pick from 'lodash/pick';\n\nlet pickedEnv: Record<string, string>;\n\nfu"
  },
  {
    "path": "back/config/share.ts",
    "chars": 1300,
    "preview": "export function createRandomString(min: number, max: number): string {\n  const num = ['0', '1', '2', '3', '4', '5', '6',"
  },
  {
    "path": "back/config/subscription.ts",
    "chars": 1408,
    "preview": "import { Subscription } from '../data/subscription';\nimport isNil from 'lodash/isNil';\n\nexport function formatUrl(doc: S"
  },
  {
    "path": "back/config/util.ts",
    "chars": 14547,
    "preview": "import * as fs from 'fs/promises';\nimport * as path from 'path';\nimport { exec } from 'child_process';\nimport psTreeFun "
  },
  {
    "path": "back/data/cron.ts",
    "chars": 2707,
    "preview": "import { sequelize } from '.';\nimport { DataTypes, Model, ModelDefined } from 'sequelize';\n\nexport class Crontab {\n  nam"
  },
  {
    "path": "back/data/cronView.ts",
    "chars": 1333,
    "preview": "import { sequelize } from '.';\nimport { DataTypes, Model } from 'sequelize';\n\nexport enum CronViewType {\n  '系统' = 1,\n  '"
  },
  {
    "path": "back/data/dependence.ts",
    "chars": 1358,
    "preview": "import { sequelize } from '.';\nimport { DataTypes, Model, ModelDefined } from 'sequelize';\n\nexport class Dependence {\n  "
  },
  {
    "path": "back/data/env.ts",
    "chars": 1305,
    "preview": "import { DataTypes, Model } from 'sequelize';\nimport { sequelize } from '.';\n\nexport class Env {\n  value?: string;\n  tim"
  },
  {
    "path": "back/data/index.ts",
    "chars": 551,
    "preview": "import { Sequelize, Transaction } from 'sequelize';\nimport config from '../config/index';\nimport { join } from 'path';\n\n"
  },
  {
    "path": "back/data/notify.ts",
    "chars": 4976,
    "preview": "export enum NotificationMode {\n  'gotify' = 'gotify',\n  'goCqHttpBot' = 'goCqHttpBot',\n  'serverChan' = 'serverChan',\n  "
  },
  {
    "path": "back/data/open.ts",
    "chars": 922,
    "preview": "import { sequelize } from '.';\nimport { DataTypes, Model, ModelDefined } from 'sequelize';\n\nexport class App {\n  name: s"
  },
  {
    "path": "back/data/sock.ts",
    "chars": 485,
    "preview": "export class SockMessage {\n  message?: string;\n  type?: SockMessageType;\n  references?: number[];\n\n  constructor(options"
  },
  {
    "path": "back/data/subscription.ts",
    "chars": 3441,
    "preview": "import { sequelize } from '.';\nimport { DataTypes, Model, ModelDefined } from 'sequelize';\nimport { SimpleIntervalSchedu"
  },
  {
    "path": "back/data/system.ts",
    "chars": 2077,
    "preview": "import { sequelize } from '.';\nimport { DataTypes, Model, ModelDefined } from 'sequelize';\nimport { NotificationInfo } f"
  },
  {
    "path": "back/interface/schedule.ts",
    "chars": 287,
    "preview": "export enum ScheduleType {\n  BOOT = '@boot',\n  ONCE = '@once',\n}\n\nexport type ScheduleValidator = (schedule?: string) =>"
  },
  {
    "path": "back/loaders/app.ts",
    "chars": 703,
    "preview": "import expressLoader from './express';\nimport depInjectorLoader from './depInjector';\nimport Logger from './logger';\nimp"
  },
  {
    "path": "back/loaders/bootAfter.ts",
    "chars": 192,
    "preview": "import Container from 'typedi';\nimport CronService from '../services/cron';\n\nexport default async () => {\n  const cronSe"
  },
  {
    "path": "back/loaders/db.ts",
    "chars": 2008,
    "preview": "import Logger from './logger';\nimport { EnvModel } from '../data/env';\nimport { CrontabModel } from '../data/cron';\nimpo"
  },
  {
    "path": "back/loaders/depInjector.ts",
    "chars": 262,
    "preview": "import { Container } from 'typedi';\nimport LoggerInstance from './logger';\n\nexport default () => {\n  try {\n    Container"
  },
  {
    "path": "back/loaders/deps.ts",
    "chars": 1857,
    "preview": "import path from 'path';\nimport fs from 'fs/promises';\nimport os from 'os';\nimport chokidar from 'chokidar';\nimport conf"
  },
  {
    "path": "back/loaders/express.ts",
    "chars": 6011,
    "preview": "import express, { Request, Response, NextFunction, Application } from 'express';\nimport bodyParser from 'body-parser';\ni"
  },
  {
    "path": "back/loaders/initData.ts",
    "chars": 6568,
    "preview": "import DependenceService from '../services/dependence';\nimport { exec } from 'child_process';\nimport { Container } from "
  },
  {
    "path": "back/loaders/initFile.ts",
    "chars": 3624,
    "preview": "import fs from 'fs/promises';\nimport path from 'path';\nimport os from 'os';\nimport Logger from './logger';\nimport { file"
  },
  {
    "path": "back/loaders/initTask.ts",
    "chars": 2132,
    "preview": "import { Container } from 'typedi';\nimport SystemService from '../services/system';\nimport ScheduleService, { ScheduleTa"
  },
  {
    "path": "back/loaders/logger.ts",
    "chars": 1610,
    "preview": "import winston from 'winston';\nimport 'winston-daily-rotate-file';\nimport config from '../config';\nimport path from 'pat"
  },
  {
    "path": "back/loaders/server.ts",
    "chars": 437,
    "preview": "import { Server } from 'http';\nimport Logger from './logger';\nimport Sock from './sock';\n\nexport default async ({ server"
  },
  {
    "path": "back/loaders/sock.ts",
    "chars": 1196,
    "preview": "import sockJs from 'sockjs';\nimport { Server } from 'http';\nimport { Container } from 'typedi';\nimport SockService from "
  },
  {
    "path": "back/middlewares/monitoring.ts",
    "chars": 2085,
    "preview": "import { Request, Response, NextFunction } from 'express';\nimport Logger from '../loaders/logger';\nimport { performance "
  },
  {
    "path": "back/protos/api.proto",
    "chars": 7146,
    "preview": "syntax = \"proto3\";\n\npackage com.ql.api;\n\nmessage EnvItem {\n  optional int32 id = 1;\n  optional string name = 2;\n  option"
  },
  {
    "path": "back/protos/api.ts",
    "chars": 147519,
    "preview": "// Code generated by protoc-gen-ts_proto. DO NOT EDIT.\n// versions:\n//   protoc-gen-ts_proto  v2.6.1\n//   protoc        "
  },
  {
    "path": "back/protos/cron.proto",
    "chars": 534,
    "preview": "syntax = \"proto3\";\n\npackage com.ql.cron;\n\nservice Cron {\n  rpc addCron(AddCronRequest) returns (AddCronResponse);\n  rpc "
  },
  {
    "path": "back/protos/cron.ts",
    "chars": 15697,
    "preview": "// Code generated by protoc-gen-ts_proto. DO NOT EDIT.\n// versions:\n//   protoc-gen-ts_proto  v2.6.1\n//   protoc        "
  },
  {
    "path": "back/protos/health.proto",
    "chars": 418,
    "preview": "syntax = \"proto3\";\n\npackage com.ql.health;\n\nmessage HealthCheckRequest {\n  string service = 1;\n}\n\nmessage HealthCheckRes"
  },
  {
    "path": "back/protos/health.ts",
    "chars": 9052,
    "preview": "// Code generated by protoc-gen-ts_proto. DO NOT EDIT.\n// versions:\n//   protoc-gen-ts_proto  v2.6.1\n//   protoc        "
  },
  {
    "path": "back/schedule/addCron.ts",
    "chars": 1573,
    "preview": "import { ServerUnaryCall, sendUnaryData } from '@grpc/grpc-js';\nimport { AddCronRequest, AddCronResponse } from '../prot"
  },
  {
    "path": "back/schedule/api.ts",
    "chars": 11263,
    "preview": "import 'reflect-metadata';\nimport { Container } from 'typedi';\nimport EnvService from '../services/env';\nimport { sendUn"
  },
  {
    "path": "back/schedule/client.ts",
    "chars": 954,
    "preview": "import { credentials } from '@grpc/grpc-js';\nimport {\n  AddCronRequest,\n  AddCronResponse,\n  CronClient,\n  DeleteCronReq"
  },
  {
    "path": "back/schedule/data.ts",
    "chars": 214,
    "preview": "import nodeSchedule from 'node-schedule';\nimport { ToadScheduler } from 'toad-scheduler';\n\nexport const scheduleStacks ="
  },
  {
    "path": "back/schedule/delCron.ts",
    "chars": 654,
    "preview": "import { ServerUnaryCall, sendUnaryData } from '@grpc/grpc-js';\nimport { DeleteCronRequest, DeleteCronResponse } from '."
  },
  {
    "path": "back/schedule/health.ts",
    "chars": 910,
    "preview": "import { ServerUnaryCall, sendUnaryData } from '@grpc/grpc-js';\nimport { HealthCheckRequest, HealthCheckResponse } from "
  },
  {
    "path": "back/services/config.ts",
    "chars": 1252,
    "preview": "import { Service, Inject } from 'typedi';\nimport path, { join } from 'path';\nimport config from '../config';\nimport { ge"
  },
  {
    "path": "back/services/cron.ts",
    "chars": 22803,
    "preview": "import { Service, Inject } from 'typedi';\nimport winston from 'winston';\nimport config from '../config';\nimport { Cronta"
  },
  {
    "path": "back/services/cronView.ts",
    "chars": 3712,
    "preview": "import { Service, Inject } from 'typedi';\nimport winston from 'winston';\nimport { CrontabView, CrontabViewModel } from '"
  },
  {
    "path": "back/services/dependence.ts",
    "chars": 11730,
    "preview": "import { Service, Inject } from 'typedi';\nimport winston from 'winston';\nimport config from '../config';\nimport {\n  Depe"
  },
  {
    "path": "back/services/env.ts",
    "chars": 6734,
    "preview": "import groupBy from 'lodash/groupBy';\nimport { FindOptions, Op } from 'sequelize';\nimport { Inject, Service } from 'type"
  },
  {
    "path": "back/services/grpc.ts",
    "chars": 1866,
    "preview": "import { Server, ServerCredentials } from '@grpc/grpc-js';\nimport { CronService } from '../protos/cron';\nimport { Health"
  },
  {
    "path": "back/services/health.ts",
    "chars": 1633,
    "preview": "import { Service } from 'typedi';\nimport Logger from '../loaders/logger';\nimport { GrpcServerService } from './grpc';\nim"
  },
  {
    "path": "back/services/http.ts",
    "chars": 1403,
    "preview": "import express from 'express';\nimport Logger from '../loaders/logger';\nimport { metricsService } from './metrics';\nimpor"
  },
  {
    "path": "back/services/log.ts",
    "chars": 449,
    "preview": "import path from 'path';\nimport { Inject, Service } from 'typedi';\nimport winston from 'winston';\nimport config from '.."
  },
  {
    "path": "back/services/metrics.ts",
    "chars": 2247,
    "preview": "import { performance } from 'perf_hooks';\nimport Logger from '../loaders/logger';\n\ninterface Metric {\n  name: string;\n  "
  },
  {
    "path": "back/services/notify.ts",
    "chars": 24114,
    "preview": "import crypto from 'crypto';\nimport nodemailer from 'nodemailer';\nimport { Inject, Service } from 'typedi';\nimport { par"
  },
  {
    "path": "back/services/open.ts",
    "chars": 5204,
    "preview": "import { Service, Inject } from 'typedi';\nimport winston from 'winston';\nimport { createRandomString } from '../config/u"
  },
  {
    "path": "back/services/schedule.ts",
    "chars": 6055,
    "preview": "import { Service, Inject } from 'typedi';\nimport winston from 'winston';\nimport nodeSchedule from 'node-schedule';\nimpor"
  },
  {
    "path": "back/services/script.ts",
    "chars": 2466,
    "preview": "import { Service, Inject } from 'typedi';\nimport winston from 'winston';\nimport path, { join } from 'path';\nimport SockS"
  },
  {
    "path": "back/services/sock.ts",
    "chars": 774,
    "preview": "import { Service, Inject } from 'typedi';\nimport winston from 'winston';\nimport { Connection } from 'sockjs';\nimport { S"
  },
  {
    "path": "back/services/sshKey.ts",
    "chars": 4689,
    "preview": "import { Service, Inject } from 'typedi';\nimport winston from 'winston';\nimport fs from 'fs/promises';\nimport os from 'o"
  },
  {
    "path": "back/services/subscription.ts",
    "chars": 11767,
    "preview": "import { Service, Inject } from 'typedi';\nimport winston from 'winston';\nimport config from '../config';\nimport {\n  Subs"
  },
  {
    "path": "back/services/system.ts",
    "chars": 15747,
    "preview": "import { spawn } from 'cross-spawn';\nimport { Response } from 'express';\nimport fs from 'fs';\nimport { Agent, request } "
  },
  {
    "path": "back/services/user.ts",
    "chars": 14039,
    "preview": "import { Service, Inject } from 'typedi';\nimport winston from 'winston';\nimport { createRandomString } from '../config/u"
  },
  {
    "path": "back/shared/auth.ts",
    "chars": 1354,
    "preview": "import { AuthInfo, TokenInfo } from '../data/system';\n\n/**\n * Validates if a token exists in the authentication info.\n *"
  },
  {
    "path": "back/shared/interface.ts",
    "chars": 668,
    "preview": "import { Dependence } from '../data/dependence';\nimport { ICron } from '../protos/cron';\n\nexport type Override<\n  T,\n  K"
  },
  {
    "path": "back/shared/logStreamManager.ts",
    "chars": 2979,
    "preview": "import { createWriteStream, WriteStream } from 'fs';\nimport { EventEmitter } from 'events';\n\n/**\n * Manages write stream"
  },
  {
    "path": "back/shared/pLimit.ts",
    "chars": 6402,
    "preview": "import PQueue, { QueueAddOptions } from 'p-queue-cjs';\nimport os from 'os';\nimport { AuthDataType, SystemModel } from '."
  },
  {
    "path": "back/shared/runCron.ts",
    "chars": 2442,
    "preview": "import { spawn } from 'cross-spawn';\nimport taskLimit from './pLimit';\nimport Logger from '../loaders/logger';\nimport { "
  },
  {
    "path": "back/shared/store.ts",
    "chars": 909,
    "preview": "import { AuthInfo } from '../data/system';\nimport { App } from '../data/open';\nimport Keyv from 'keyv';\nimport KeyvSqlit"
  },
  {
    "path": "back/shared/utils.ts",
    "chars": 1099,
    "preview": "import { lock } from 'proper-lockfile';\nimport os from 'os';\nimport path from 'path';\nimport { writeFile, open, chmod } "
  },
  {
    "path": "back/token.ts",
    "chars": 850,
    "preview": "import 'reflect-metadata';\nimport OpenService from './services/open';\nimport { Container } from 'typedi';\nimport LoggerI"
  },
  {
    "path": "back/tsconfig.json",
    "chars": 656,
    "preview": "{\n  \"compilerOptions\": {\n    \"target\": \"es2017\",\n    \"lib\": [\"ESNext\"],\n    \"typeRoots\": [\n      \"./types\",\n      \"../no"
  },
  {
    "path": "back/types/express.d.ts",
    "chars": 160,
    "preview": "/// <reference types=\"express\" />\n\nexport {};\n\ndeclare global {\n  namespace Express {\n    interface Request {\n      plat"
  },
  {
    "path": "back/validation/schedule.ts",
    "chars": 2367,
    "preview": "import { Joi } from 'celebrate';\nimport CronExpressionParser from 'cron-parser';\nimport { ScheduleType } from '../interf"
  },
  {
    "path": "docker/310.Dockerfile",
    "chars": 2595,
    "preview": "FROM python:3.10-alpine3.18 AS builder\nCOPY package.json .npmrc pnpm-lock.yaml /tmp/build/\nRUN set -x \\\n  && apk update "
  },
  {
    "path": "docker/Dockerfile",
    "chars": 2595,
    "preview": "FROM python:3.11-alpine3.18 AS builder\nCOPY package.json .npmrc pnpm-lock.yaml /tmp/build/\nRUN set -x \\\n  && apk update "
  },
  {
    "path": "docker/docker-compose.yml",
    "chars": 255,
    "preview": "services:\n  web:\n    image: whyour/qinglong:latest # 基于 Debian 的版本:whyour/qinglong:debian  \n    volumes:\n      - ./data:"
  },
  {
    "path": "docker/docker-entrypoint.sh",
    "chars": 1362,
    "preview": "#!/bin/bash\n\ndir_shell=/ql/shell\n. $dir_shell/share.sh\n\nexport_ql_envs() {\n  export BACK_PORT=\"${ql_port}\"\n  export GRPC"
  },
  {
    "path": "ecosystem.config.js",
    "chars": 428,
    "preview": "module.exports = {\n  apps: [\n    {\n      name: 'qinglong',\n      max_restarts: 5,\n      kill_timeout: 1000,\n      wait_r"
  },
  {
    "path": "nodemon.json",
    "chars": 242,
    "preview": "{\n  \"watch\": [\n    \"back\",\n    \".env\"\n  ],\n  \"ext\": \"js,ts,json\",\n  \"env\": {\n    \"NODE_ENV\": \"development\",\n    \"TS_NODE"
  },
  {
    "path": "package.json",
    "chars": 5336,
    "preview": "{\n  \"private\": true,\n  \"packageManager\": \"pnpm@8.3.1\",\n  \"scripts\": {\n    \"start\": \"concurrently -n w: npm:start:*\",\n   "
  },
  {
    "path": "sample/auth.sample.json",
    "chars": 45,
    "preview": "{ \"username\": \"admin\", \"password\": \"admin\" }\n"
  },
  {
    "path": "sample/config.sample.sh",
    "chars": 7378,
    "preview": "## 在运行 ql repo 命令时,是否自动删除失效的脚本与定时任务\nAutoDelCron=\"true\"\n\n## 在运行 ql repo 命令时,是否自动增加新的本地定时任务\nAutoAddCron=\"true\"\n\n## 拉取脚本时默认"
  },
  {
    "path": "sample/extra.sample.sh",
    "chars": 119,
    "preview": "#!/usr/bin/env bash\n\n## 添加你需要重启自动执行的任意命令,比如 ql repo\n## 安装node依赖使用 pnpm add -g xxx xxx\n## 安装python依赖使用 pip3 install xxx\n"
  },
  {
    "path": "sample/notify.js",
    "chars": 42200,
    "preview": "const querystring = require('node:querystring');\nconst { request: undiciRequest, ProxyAgent, FormData } = require('undic"
  },
  {
    "path": "sample/notify.py",
    "chars": 35685,
    "preview": "#!/usr/bin/env python3\n# _*_ coding:utf-8 _*_\nimport base64\nimport hashlib\nimport hmac\nimport json\nimport os\nimport re\ni"
  },
  {
    "path": "sample/notify.py.save",
    "chars": 32051,
    "preview": "#!/usr/bin/env python3\n# _*_ coding:utf-8 _*_\nimport base64\nimport hashlib\nimport hmac\nimport json\nimport os\nimport re\ni"
  },
  {
    "path": "sample/ql_sample.js",
    "chars": 998,
    "preview": "/**\n * 任务名称\n * name: script name\n * 定时规则\n * cron: 1 9 * * *\n */\nconsole.log('test scripts');\nQLAPI.notify('test scripts'"
  },
  {
    "path": "sample/ql_sample.py",
    "chars": 304,
    "preview": "\"\"\"\n任务名称\nname: script name\n定时规则\ncron: 1 9 * * *\n\"\"\"\n\nprint(\"test script\")\nprint(QLAPI.notify(\"test script\", \"test desc\")"
  },
  {
    "path": "sample/task.sample.sh",
    "chars": 20,
    "preview": "#!/usr/bin/env bash\n"
  },
  {
    "path": "sample/tool.ts",
    "chars": 1015,
    "preview": "import * as qiniu from 'qiniu';\nimport dotenv from 'dotenv';\nconst envFound = dotenv.config();\n\nconst accessKey = proces"
  },
  {
    "path": "shell/api.sh",
    "chars": 6420,
    "preview": "#!/usr/bin/env bash\n\ncreate_token() {\n  local token_command=\"ts-node-transpile-only ${dir_root}/back/token.ts\"\n  local t"
  },
  {
    "path": "shell/bot.sh",
    "chars": 1159,
    "preview": "#!/usr/bin/env bash\n\nif [[ -z ${BotRepoUrl} ]]; then\n  url=\"https://github.com/SuMaiKaDe/bot.git\"\n  repo_path=\"${dir_rep"
  },
  {
    "path": "shell/check.sh",
    "chars": 1691,
    "preview": "#!/usr/bin/env bash\n\nreset_env() {\n  echo -e \"---> 1. 开始检测配置文件\\n\"\n  fix_config\n  echo -e \"---> 配置文件检测完成\\n\"\n\n  echo -e \"-"
  },
  {
    "path": "shell/env.sh",
    "chars": 247,
    "preview": "#!/usr/bin/env bash\n\nstore_env_vars() {\n  initial_vars=($(env | cut -d= -f1))\n}\n\nrestore_env_vars() {\n  for key in $(env"
  },
  {
    "path": "shell/otask.sh",
    "chars": 7886,
    "preview": "#!/usr/bin/env bash\n\nrandom_delay() {\n  local random_delay_max=$RandomDelay\n  if [[ $random_delay_max ]] && [[ $random_d"
  },
  {
    "path": "shell/preload/client.js",
    "chars": 3236,
    "preview": "const grpc = require('@grpc/grpc-js');\nconst protoLoader = require('@grpc/proto-loader');\nconst { join } = require('path"
  },
  {
    "path": "shell/preload/client.py",
    "chars": 7531,
    "preview": "import subprocess\nimport json\nimport tempfile\nimport os\nfrom typing import Dict, List, TypedDict, Optional\nfrom functool"
  },
  {
    "path": "shell/preload/sitecustomize.js",
    "chars": 3263,
    "preview": "const { execSync } = require('child_process');\nconst client = require('./client.js');\nrequire(`./env.js`);\n\nfunction exp"
  },
  {
    "path": "shell/preload/sitecustomize.py",
    "chars": 3982,
    "preview": "import os\nimport re\nimport subprocess\nimport json\nimport builtins\nimport sys\nimport env\nimport signal\nfrom client import"
  },
  {
    "path": "shell/pub.sh",
    "chars": 562,
    "preview": "#!/usr/bin/env bash\necho -e \"开始发布\"\n\necho -e \"切换master分支\"\ngit branch -D master\ngit checkout -b master\ngit push --set-upst"
  },
  {
    "path": "shell/rmlog.sh",
    "chars": 1265,
    "preview": "#!/usr/bin/env bash\n\ndays=$1\n\nremove_js_log() {\n  local log_full_path_list=$(find $dir_log -name \"*.log\")\n  local diff_t"
  },
  {
    "path": "shell/share.sh",
    "chars": 9902,
    "preview": "#!/usr/bin/env bash\n\n## 目录\nexport dir_root=$QL_DIR\nexport dir_tmp=$dir_root/.tmp\nexport dir_data=$dir_root/data\n\nif [[ $"
  },
  {
    "path": "shell/task.sh",
    "chars": 3355,
    "preview": "#!/usr/bin/env bash\n\ndir_shell=$QL_DIR/shell\n. $dir_shell/share.sh\n. $dir_shell/api.sh\n\ntrap \"single_hanle\" 2 3 20 15 14"
  },
  {
    "path": "shell/update.sh",
    "chars": 17879,
    "preview": "#!/usr/bin/env bash\n\ndir_shell=$QL_DIR/shell\n. $dir_shell/share.sh\n. $dir_shell/api.sh\nload_ql_envs\n. $dir_shell/env.sh\n"
  },
  {
    "path": "src/app.ts",
    "chars": 1125,
    "preview": "const baseUrl = window.__ENV__QlBaseUrl || '/';\nimport { setLocale } from '@umijs/max';\nimport intl from 'react-intl-uni"
  },
  {
    "path": "src/components/copy.tsx",
    "chars": 1277,
    "preview": "import intl from 'react-intl-universal';\nimport React, { useRef, useState, useEffect } from 'react';\nimport { Tooltip, T"
  },
  {
    "path": "src/components/iconfont.tsx",
    "chars": 195,
    "preview": "import { createFromIconfontCN } from '@ant-design/icons';\n\nconst IconFont = createFromIconfontCN({\n  scriptUrl: ['//at.a"
  },
  {
    "path": "src/components/index.less",
    "chars": 1471,
    "preview": ".react-terminal-wrapper {\n  width: 100%;\n  background: #252a33;\n  color: #eee;\n  font-size: 18px;\n  font-family: 'Fira M"
  },
  {
    "path": "src/components/name.tsx",
    "chars": 540,
    "preview": "import { useRequest } from 'ahooks';\nimport { Service, Options } from 'ahooks/lib/useRequest/src/types';\nimport { Spin, "
  },
  {
    "path": "src/components/tag.tsx",
    "chars": 2517,
    "preview": "import intl from 'react-intl-universal';\nimport { Tag, Input } from 'antd';\nimport { TweenOneGroup } from 'rc-tween-one'"
  },
  {
    "path": "src/components/terminal.tsx",
    "chars": 1948,
    "preview": "import React, { useEffect, useRef } from 'react';\nimport './index.less';\n\nexport enum LineType {\n  Input,\n  Output,\n}\n\ne"
  },
  {
    "path": "src/hooks/useFilterTreeData.ts",
    "chars": 1214,
    "preview": "import { useMemo, useState } from 'react';\n\nexport default (\n  treeData: any[],\n  searchValue: string,\n  {\n    treeNodeF"
  },
  {
    "path": "src/hooks/useScrollHeight.ts",
    "chars": 399,
    "preview": "import { RefObject, useState } from 'react';\nimport useResizeObserver from '@react-hook/resize-observer';\n\nexport defaul"
  },
  {
    "path": "src/hooks/useTableScrollHeight.ts",
    "chars": 614,
    "preview": "import { RefObject, useState } from 'react';\nimport useResizeObserver from '@react-hook/resize-observer';\nimport { getTa"
  },
  {
    "path": "src/layouts/defaultProps.tsx",
    "chars": 2312,
    "preview": "import intl from 'react-intl-universal';\nimport { SettingOutlined } from '@ant-design/icons';\nimport IconFont from '@/co"
  },
  {
    "path": "src/layouts/index.less",
    "chars": 8840,
    "preview": "@import '~antd/es/style/themes/default.less';\n@import '~@/styles/variable.less';\n\n@font-face {\n  font-family: 'Source Co"
  },
  {
    "path": "src/layouts/index.tsx",
    "chars": 9922,
    "preview": "import config from '@/utils/config';\nimport { useCtx, useTheme } from '@/utils/hooks';\nimport { request } from '@/utils/"
  },
  {
    "path": "src/loading.tsx",
    "chars": 169,
    "preview": "import { PageLoading } from '@ant-design/pro-layout';\n\nconst NewPageLoading = () => {\n  return <PageLoading delay={1}></"
  },
  {
    "path": "src/locales/en-US.json",
    "chars": 28485,
    "preview": "{\n  \"复制成功\": \"Copy successful\",\n  \"复制\": \"Copy\",\n  \"新建\": \"New\",\n  \"登录\": \"Login\",\n  \"初始化\": \"Initialize\",\n  \"错误\": \"Error\",\n "
  },
  {
    "path": "src/locales/zh-CN.json",
    "chars": 18136,
    "preview": "{\n  \"复制成功\": \"复制成功\",\n  \"复制\": \"复制\",\n  \"新建\": \"新建\",\n  \"登录\": \"登录\",\n  \"初始化\": \"初始化\",\n  \"错误\": \"错误\",\n  \"定时任务\": \"定时任务\",\n  \"订阅管理\": "
  },
  {
    "path": "src/pages/404.tsx",
    "chars": 375,
    "preview": "import intl from 'react-intl-universal';\nimport React from 'react';\nimport { Button, Result, Typography } from 'antd';\n\n"
  },
  {
    "path": "src/pages/config/index.less",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "src/pages/config/index.tsx",
    "chars": 3915,
    "preview": "import intl from 'react-intl-universal';\nimport React, {\n  PureComponent,\n  Fragment,\n  useState,\n  useEffect,\n  useRef,"
  },
  {
    "path": "src/pages/crontab/const.ts",
    "chars": 399,
    "preview": "import { ScheduleType } from './type';\n\nexport const scheduleTypeMap = {\n  [ScheduleType.Normal]: '',\n  [ScheduleType.On"
  },
  {
    "path": "src/pages/crontab/detail.tsx",
    "chars": 16318,
    "preview": "import intl from 'react-intl-universal';\nimport React, { useEffect, useRef, useState } from 'react';\nimport {\n  Modal,\n "
  },
  {
    "path": "src/pages/crontab/index.less",
    "chars": 3092,
    "preview": ".ant-table-pagination.ant-pagination {\n  margin-bottom: 0 !important;\n}\n\n.crontab-detail {\n  .card-wrapper {\n    .ant-ca"
  },
  {
    "path": "src/pages/crontab/index.tsx",
    "chars": 30630,
    "preview": "import useTableScrollHeight from '@/hooks/useTableScrollHeight';\nimport { SharedContext } from '@/layouts';\nimport { get"
  },
  {
    "path": "src/pages/crontab/logModal.tsx",
    "chars": 4182,
    "preview": "import intl from \"react-intl-universal\";\nimport React, { useEffect, useRef, useState } from \"react\";\nimport {\n  Modal,\n "
  },
  {
    "path": "src/pages/crontab/modal.tsx",
    "chars": 10426,
    "preview": "import EditableTagGroup from '@/components/tag';\nimport config from '@/utils/config';\nimport { request } from '@/utils/h"
  },
  {
    "path": "src/pages/crontab/type.ts",
    "chars": 721,
    "preview": "export enum CrontabStatus {\n  'running' = 0,\n  'queued' = 0.5,\n  'idle' = 1,\n  'disabled',\n}\n\nexport enum OperationName "
  },
  {
    "path": "src/pages/crontab/viewCreateModal.tsx",
    "chars": 11878,
    "preview": "import intl from 'react-intl-universal';\nimport React, { useEffect, useState } from 'react';\nimport {\n  Modal,\n  message"
  },
  {
    "path": "src/pages/crontab/viewManageModal.tsx",
    "chars": 6727,
    "preview": "import intl from 'react-intl-universal';\nimport React, { useCallback, useEffect, useRef, useState } from 'react';\nimport"
  },
  {
    "path": "src/pages/dependence/index.less",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "src/pages/dependence/index.tsx",
    "chars": 17426,
    "preview": "import intl from 'react-intl-universal';\nimport React, { useCallback, useRef, useState, useEffect } from 'react';\nimport"
  },
  {
    "path": "src/pages/dependence/logModal.tsx",
    "chars": 4246,
    "preview": "import intl from 'react-intl-universal';\nimport React, { useEffect, useState } from 'react';\nimport { Modal, message, In"
  },
  {
    "path": "src/pages/dependence/modal.tsx",
    "chars": 3429,
    "preview": "import intl from 'react-intl-universal';\nimport React, { useEffect, useState } from 'react';\nimport { Modal, message, In"
  },
  {
    "path": "src/pages/dependence/type.ts",
    "chars": 252,
    "preview": "export enum DependenceStatus {\n  'installing',\n  'installed',\n  'installFailed',\n  'removing',\n  'removed',\n  'removeFai"
  },
  {
    "path": "src/pages/diff/index.less",
    "chars": 373,
    "preview": ".d2h-files-diff {\n  height: calc(100vh - 130px);\n  height: calc(100vh - var(--vh-offset, 0px) - 130px);\n  overflow: auto"
  },
  {
    "path": "src/pages/diff/index.tsx",
    "chars": 4747,
    "preview": "import intl from 'react-intl-universal';\nimport React, { PureComponent, useRef, useState, useEffect } from 'react';\nimpo"
  },
  {
    "path": "src/pages/env/editNameModal.tsx",
    "chars": 1593,
    "preview": "import intl from 'react-intl-universal'\nimport React, { useEffect, useState } from 'react';\nimport { Modal, message, Inp"
  },
  {
    "path": "src/pages/env/index.less",
    "chars": 218,
    "preview": "tr.drop-over-downward td {\n  border-bottom: 2px dashed #1890ff;\n}\n\ntr.drop-over-upward td {\n  border-top: 2px dashed #18"
  },
  {
    "path": "src/pages/env/index.tsx",
    "chars": 18589,
    "preview": "import useTableScrollHeight from '@/hooks/useTableScrollHeight';\nimport { SharedContext } from '@/layouts';\nimport confi"
  },
  {
    "path": "src/pages/env/modal.tsx",
    "chars": 3373,
    "preview": "import intl from 'react-intl-universal';\nimport React, { useEffect, useState } from 'react';\nimport { Modal, message, In"
  },
  {
    "path": "src/pages/error/index.less",
    "chars": 1171,
    "preview": ".error-wrapper {\n  display: flex;\n  justify-content: center;\n  height: 100vh;\n\n  .react-terminal-wrapper {\n    max-width"
  },
  {
    "path": "src/pages/error/index.tsx",
    "chars": 3239,
    "preview": "import intl from 'react-intl-universal';\nimport React, { useState, useEffect, useRef } from 'react';\nimport config from "
  },
  {
    "path": "src/pages/initialization/index.less",
    "chars": 1302,
    "preview": "@import '~antd/es/style/themes/default.less';\n\n.container {\n  display: flex;\n  flex-direction: column;\n  align-items: ce"
  },
  {
    "path": "src/pages/initialization/index.tsx",
    "chars": 7181,
    "preview": "import intl from 'react-intl-universal';\nimport React, { Fragment, useEffect, useState } from 'react';\nimport {\n  Button"
  },
  {
    "path": "src/pages/log/index.module.less",
    "chars": 514,
    "preview": "@import '~antd/es/style/themes/default.less';\n@import '~@/styles/variable.less';\n\n.left-tree {\n  &-container {\n    overf"
  },
  {
    "path": "src/pages/log/index.tsx",
    "chars": 9276,
    "preview": "import useFilterTreeData from '@/hooks/useFilterTreeData';\nimport { SharedContext } from '@/layouts';\nimport { depthFirs"
  },
  {
    "path": "src/pages/login/index.less",
    "chars": 1675,
    "preview": "@import '~antd/es/style/themes/default.less';\n\n.container {\n  display: flex;\n  flex-direction: column;\n  align-items: ce"
  },
  {
    "path": "src/pages/login/index.tsx",
    "chars": 6978,
    "preview": "import intl from 'react-intl-universal';\nimport React, { Fragment, useEffect, useState } from 'react';\nimport {\n  Button"
  },
  {
    "path": "src/pages/script/components/UnsupportedFilePreview/index.module.less",
    "chars": 1056,
    "preview": ".container {\n  height: 100%;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  background: var(--back"
  },
  {
    "path": "src/pages/script/components/UnsupportedFilePreview/index.tsx",
    "chars": 1199,
    "preview": "import React from 'react';\nimport { Button, Space } from 'antd';\nimport { FileUnknownOutlined, WarningOutlined } from '@"
  },
  {
    "path": "src/pages/script/editModal.tsx",
    "chars": 7166,
    "preview": "import intl from 'react-intl-universal';\nimport React, {\n  useEffect,\n  useState,\n  useRef,\n  useCallback,\n  useReducer,"
  },
  {
    "path": "src/pages/script/editNameModal.tsx",
    "chars": 5101,
    "preview": "import intl from 'react-intl-universal';\nimport React, { useEffect, useState } from 'react';\nimport {\n  Modal,\n  message"
  },
  {
    "path": "src/pages/script/index.module.less",
    "chars": 514,
    "preview": "@import '~antd/es/style/themes/default.less';\n@import '~@/styles/variable.less';\n\n.left-tree {\n  &-container {\n    overf"
  },
  {
    "path": "src/pages/script/index.tsx",
    "chars": 19946,
    "preview": "import IconFont from '@/components/iconfont';\nimport useFilterTreeData from '@/hooks/useFilterTreeData';\nimport { Shared"
  },
  {
    "path": "src/pages/script/renameModal.tsx",
    "chars": 1775,
    "preview": "import intl from 'react-intl-universal';\nimport React, { useEffect, useState } from 'react';\nimport { Modal, message, In"
  },
  {
    "path": "src/pages/script/saveModal.tsx",
    "chars": 1906,
    "preview": "import intl from 'react-intl-universal';\nimport React, { useEffect, useState } from 'react';\nimport { Modal, message, In"
  },
  {
    "path": "src/pages/script/setting.tsx",
    "chars": 1347,
    "preview": "import intl from 'react-intl-universal';\nimport React, { useEffect, useState } from 'react';\nimport { Modal, message, In"
  },
  {
    "path": "src/pages/setting/about.tsx",
    "chars": 2197,
    "preview": "import intl from 'react-intl-universal';\nimport React, { useEffect, useState } from 'react';\nimport { Typography, Input,"
  },
  {
    "path": "src/pages/setting/appModal.tsx",
    "chars": 2597,
    "preview": "import intl from 'react-intl-universal';\nimport React, { useEffect, useState } from 'react';\nimport { Modal, message, In"
  },
  {
    "path": "src/pages/setting/checkUpdate.tsx",
    "chars": 5792,
    "preview": "import { disableBody } from \"@/utils\";\nimport config from \"@/utils/config\";\nimport { request } from \"@/utils/http\";\nimpo"
  },
  {
    "path": "src/pages/setting/dependence.tsx",
    "chars": 8095,
    "preview": "import intl from 'react-intl-universal';\nimport React, { useState, useEffect, useRef } from 'react';\nimport { Button, In"
  },
  {
    "path": "src/pages/setting/index.less",
    "chars": 682,
    "preview": ".container {\n  display: flex;\n  justify-content: center;\n  flex-wrap: wrap;\n  max-width: 800px;\n  margin: 20px auto;\n\n  "
  },
  {
    "path": "src/pages/setting/index.tsx",
    "chars": 9873,
    "preview": "import intl from 'react-intl-universal';\nimport React, { useState, useEffect, useRef } from 'react';\nimport {\n  Button,\n"
  },
  {
    "path": "src/pages/setting/loginLog.tsx",
    "chars": 1725,
    "preview": "import intl from 'react-intl-universal';\nimport React, { useEffect, useState } from 'react';\nimport { Typography, Table,"
  },
  {
    "path": "src/pages/setting/notification.tsx",
    "chars": 3099,
    "preview": "import intl from 'react-intl-universal';\nimport React, { useEffect, useState } from 'react';\nimport { Typography, Input,"
  },
  {
    "path": "src/pages/setting/other.tsx",
    "chars": 12627,
    "preview": "import intl from 'react-intl-universal';\nimport React, { useState, useEffect, useRef } from 'react';\nimport {\n  Button,\n"
  }
]

// ... and 22 more files (download for full content)

About this extraction

This page contains the full source code of the whyour/qinglong GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 222 files (1.1 MB), approximately 318.6k tokens, and a symbol index with 1033 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!