mirror of
https://github.com/zoriya/flood.git
synced 2025-12-05 23:06:20 +00:00
flood: rearrange, remove misc files and reformat
This commit is contained in:
87
.eslintrc.js
87
.eslintrc.js
@@ -1,87 +0,0 @@
|
||||
module.exports = {
|
||||
extends: [
|
||||
'airbnb',
|
||||
'plugin:import/errors',
|
||||
'plugin:import/warnings',
|
||||
'plugin:import/typescript',
|
||||
'prettier',
|
||||
'prettier/react',
|
||||
'prettier/@typescript-eslint',
|
||||
],
|
||||
parser: 'babel-eslint',
|
||||
plugins: ['import'],
|
||||
rules: {
|
||||
'arrow-parens': 0,
|
||||
'class-methods-use-this': 0,
|
||||
'consistent-return': 0,
|
||||
'implicit-arrow-linebreak': 0,
|
||||
'import/extensions': [
|
||||
'error',
|
||||
'ignorePackages',
|
||||
{
|
||||
js: 'never',
|
||||
jsx: 'never',
|
||||
ts: 'never',
|
||||
tsx: 'never',
|
||||
},
|
||||
],
|
||||
'import/no-extraneous-dependencies': 0,
|
||||
'import/prefer-default-export': 0,
|
||||
'lines-between-class-members': ['error', 'always', {exceptAfterSingleLine: true}],
|
||||
'max-len': [
|
||||
'error',
|
||||
{
|
||||
code: 120,
|
||||
ignoreRegExpLiterals: true,
|
||||
ignoreStrings: true,
|
||||
ignoreTemplateLiterals: true,
|
||||
ignoreUrls: true,
|
||||
},
|
||||
],
|
||||
'no-console': 0,
|
||||
'no-param-reassign': 0,
|
||||
'no-plusplus': 0,
|
||||
'no-underscore-dangle': [2, {allow: ['_id']}],
|
||||
'no-unused-vars': [0, {argsIgnorePattern: '^_'}],
|
||||
|
||||
'object-curly-newline': 0,
|
||||
'object-curly-spacing': 0,
|
||||
'prefer-destructuring': [
|
||||
2,
|
||||
{
|
||||
array: false,
|
||||
object: true,
|
||||
},
|
||||
{
|
||||
enforceForRenamedProperties: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
overrides: [
|
||||
{
|
||||
files: ['*.ts', '*.tsx', '**/*.ts', '**/*.tsx'],
|
||||
extends: [
|
||||
'airbnb-typescript',
|
||||
'plugin:@typescript-eslint/recommended',
|
||||
'prettier',
|
||||
'prettier/react',
|
||||
'prettier/@typescript-eslint',
|
||||
],
|
||||
parserOptions: {
|
||||
project: './tsconfig.json',
|
||||
},
|
||||
rules: {
|
||||
'import/no-extraneous-dependencies': 0,
|
||||
'no-underscore-dangle': [2, {allow: ['_id']}],
|
||||
'no-unused-vars': 0,
|
||||
'@typescript-eslint/lines-between-class-members': ['error', 'always', {exceptAfterSingleLine: true}],
|
||||
'@typescript-eslint/no-unused-vars': ['error', {argsIgnorePattern: '^_'}],
|
||||
// TODO: Explicit return type
|
||||
'@typescript-eslint/explicit-function-return-type': 0,
|
||||
'@typescript-eslint/explicit-module-boundary-types': 0,
|
||||
// TODO: Re-enable after everything is module
|
||||
'@typescript-eslint/no-var-requires': 0,
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
29
.eslintrc.json
Normal file
29
.eslintrc.json
Normal file
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"extends": ["plugin:@typescript-eslint/recommended", "prettier", "prettier/@typescript-eslint"],
|
||||
|
||||
"parserOptions": {
|
||||
"project": "./tsconfig.json"
|
||||
},
|
||||
|
||||
"env": {
|
||||
"browser": false,
|
||||
"node": true
|
||||
},
|
||||
|
||||
"rules": {
|
||||
"import/no-extraneous-dependencies": 0,
|
||||
"no-underscore-dangle": [2, {"allow": ["_id"]}],
|
||||
"@typescript-eslint/lines-between-class-members": ["error", "always", {"exceptAfterSingleLine": true}],
|
||||
"@typescript-eslint/no-unused-vars": ["error", {"argsIgnorePattern": "^_"}]
|
||||
},
|
||||
"overrides": [
|
||||
{
|
||||
"files": ["*.js", "*.jsx"],
|
||||
"extends": ["prettier", "prettier/react"],
|
||||
"parser": "babel-eslint",
|
||||
"rules": {
|
||||
"@typescript-eslint/no-var-requires": 0
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
28
.github/CONTRIBUTING.md
vendored
28
.github/CONTRIBUTING.md
vendored
@@ -4,7 +4,7 @@ We love contributions from everyone.
|
||||
By participating in this project,
|
||||
you agree to abide by the thoughtbot [code of conduct].
|
||||
|
||||
[code of conduct]: https://thoughtbot.com/open-source-code-of-conduct
|
||||
[code of conduct]: https://thoughtbot.com/open-source-code-of-conduct
|
||||
|
||||
# Issue
|
||||
|
||||
@@ -16,21 +16,21 @@ See [PULL_REQUEST_TEMPLATE.md](PULL_REQUEST_TEMPLATE.md).
|
||||
|
||||
# Code quality
|
||||
|
||||
+ Be sure to use 2 spaces instead of tabulations.
|
||||
- Be sure to use 2 spaces instead of tabulations.
|
||||
|
||||
# Labels
|
||||
|
||||
Category | Label(s) | Color(s)
|
||||
--- | --- | ---
|
||||
Platform |      | #BFD4F2
|
||||
Problems |   | #EE3F46
|
||||
Severity |  | #B60205
|
||||
Type |     | #FFC274
|
||||
Feedback |   | #CC317C
|
||||
Improvements |    | #5EBEFF
|
||||
Help |  | #76C3A9
|
||||
Additions |  | #90C954
|
||||
Pending |      | #FBCA04
|
||||
Inactive |      | #D2DAE1
|
||||
| Category | Label(s) | Color(s) |
|
||||
| ------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- |
|
||||
| Platform |      | #BFD4F2 |
|
||||
| Problems |   | #EE3F46 |
|
||||
| Severity |  | #B60205 |
|
||||
| Type |     | #FFC274 |
|
||||
| Feedback |   | #CC317C |
|
||||
| Improvements |    | #5EBEFF |
|
||||
| Help |  | #76C3A9 |
|
||||
| Additions |  | #90C954 |
|
||||
| Pending |      | #FBCA04 |
|
||||
| Inactive |      | #D2DAE1 |
|
||||
|
||||
**Note (if there is a need to add labels)**: in order to take a sharp screenshot of labels with Firefox: Right click the label => Inspect element => Right click the element on the inspector => Screenshot Node
|
||||
|
||||
30
.github/ISSUE_TEMPLATE/01_bug_report.md
vendored
30
.github/ISSUE_TEMPLATE/01_bug_report.md
vendored
@@ -1,7 +1,8 @@
|
||||
---
|
||||
name: "🐞 Bug Report"
|
||||
about: "Report a general bug in flood"
|
||||
name: '🐞 Bug Report'
|
||||
about: 'Report a general bug in flood'
|
||||
---
|
||||
|
||||
Type: Bug Report
|
||||
|
||||
- [ ] Try to follow the update procedure described in the README and try again before opening this issue.
|
||||
@@ -9,36 +10,45 @@ Type: Bug Report
|
||||
- [ ] Please check the [Troubleshooting](https://github.com/Flood-UI/flood/wiki/Troubleshooting) wiki section.
|
||||
|
||||
## Your Environment
|
||||
|
||||
<!--- Include as many relevant details about the environment you experienced the bug in -->
|
||||
* Version used:
|
||||
+ Version (stable release) `git --no-pager tag`
|
||||
+ Commit ID (development release) `git --no-pager log -1`
|
||||
* Environment name and version:
|
||||
+ Node.js version `node --version`
|
||||
+ npm version `npm --version`
|
||||
+ Web browser `name and version`
|
||||
* Operating System and version:
|
||||
|
||||
- Version used:
|
||||
- Version (stable release) `git --no-pager tag`
|
||||
- Commit ID (development release) `git --no-pager log -1`
|
||||
- Environment name and version:
|
||||
- Node.js version `node --version`
|
||||
- npm version `npm --version`
|
||||
- Web browser `name and version`
|
||||
- Operating System and version:
|
||||
|
||||
## Summary
|
||||
|
||||
<!--- Provide a general summary of the issue in the Title above -->
|
||||
|
||||
## Expected Behavior
|
||||
|
||||
<!--- (Optional) Tell us what should happen -->
|
||||
|
||||
## Current Behavior
|
||||
|
||||
<!--- (Optional) Tell us what happens instead of the expected behavior -->
|
||||
|
||||
## Possible Solution
|
||||
|
||||
<!--- (Optional) suggest a fix/reason for the bug, -->
|
||||
<!--- or ideas how to implement the addition or change -->
|
||||
|
||||
## Steps to Reproduce
|
||||
|
||||
<!--- Provide a link to a live example, or an unambiguous set of steps to -->
|
||||
<!--- reproduce this bug. Include code to reproduce, if relevant -->
|
||||
|
||||
1.
|
||||
2.
|
||||
3.
|
||||
4.
|
||||
|
||||
## Context
|
||||
|
||||
<!--- (Optional) What are you trying to accomplish? -->
|
||||
|
||||
8
.github/ISSUE_TEMPLATE/02_feature_request.md
vendored
8
.github/ISSUE_TEMPLATE/02_feature_request.md
vendored
@@ -1,14 +1,16 @@
|
||||
---
|
||||
name: "💡 Feature Request"
|
||||
about: "Suggest an idea for this project"
|
||||
name: '💡 Feature Request'
|
||||
about: 'Suggest an idea for this project'
|
||||
---
|
||||
|
||||
Type: Feature Request
|
||||
|
||||
- [ ] If you want to contribute to the project please review the [contributing guidelines](https://github.com/Flood-UI/flood/blob/master/.github/CONTRIBUTING.md).
|
||||
|
||||
## Summary
|
||||
|
||||
<!--- Provide a general summary of the feature in the Title above -->
|
||||
|
||||
|
||||
## Idea of implementation
|
||||
|
||||
<!--- Suggest ideas how to implement the addition or change -->
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
---
|
||||
name: "🔒 Security Vulnerability"
|
||||
about: "Report a security vulnerability in flood"
|
||||
name: '🔒 Security Vulnerability'
|
||||
about: 'Report a security vulnerability in flood'
|
||||
---
|
||||
|
||||
PLEASE DON'T DISCLOSE SECURITY-RELATED ISSUES PUBLICLY, SEE BELOW.
|
||||
|
||||
If you discover a security vulnerability within flood, please send an e-mail to jfurrow (me@johnfurrow.com) or noraj (cybersecurity@tutamail.com).
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
---
|
||||
name: "📚 Documentation Issue"
|
||||
name: '📚 Documentation Issue'
|
||||
about: 'Report an issue or missing part in the documentation'
|
||||
---
|
||||
|
||||
Type: Documentation Issue
|
||||
|
||||
## Summary
|
||||
|
||||
<!--- Provide a general summary of the issue in the Title above -->
|
||||
|
||||
5
.github/ISSUE_TEMPLATE/05_question.md
vendored
5
.github/ISSUE_TEMPLATE/05_question.md
vendored
@@ -1,7 +1,8 @@
|
||||
---
|
||||
name: "❓ Question"
|
||||
about: "Ask your questions here"
|
||||
name: '❓ Question'
|
||||
about: 'Ask your questions here'
|
||||
---
|
||||
|
||||
Type: Question
|
||||
|
||||
## Question
|
||||
|
||||
5
.github/ISSUE_TEMPLATE/06_discussion.md
vendored
5
.github/ISSUE_TEMPLATE/06_discussion.md
vendored
@@ -1,7 +1,8 @@
|
||||
---
|
||||
name: "🎙️ Discussion"
|
||||
about: "Start a discussion here"
|
||||
name: '🎙️ Discussion'
|
||||
about: 'Start a discussion here'
|
||||
---
|
||||
|
||||
Type: Discussion
|
||||
|
||||
## Discussion
|
||||
|
||||
5
.github/ISSUE_TEMPLATE/07_support.md
vendored
5
.github/ISSUE_TEMPLATE/07_support.md
vendored
@@ -1,5 +1,6 @@
|
||||
---
|
||||
name: "🆘 Support"
|
||||
about: "Ask for help on Discord"
|
||||
name: '🆘 Support'
|
||||
about: 'Ask for help on Discord'
|
||||
---
|
||||
|
||||
If you need support, ask on Discord https://discord.gg/Z7yR5Uf
|
||||
|
||||
8
.github/PULL_REQUEST_TEMPLATE.md
vendored
8
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -1,18 +1,22 @@
|
||||
<!--- Provide a general summary of your changes in the Title above -->
|
||||
|
||||
## Description
|
||||
|
||||
<!--- Describe your changes in detail -->
|
||||
|
||||
## Related Issue
|
||||
|
||||
<!--- This project only accepts pull requests related to open issues -->
|
||||
<!--- If suggesting a new feature or change, please discuss it in an issue first -->
|
||||
<!--- If fixing a bug, there should be an issue describing it with steps to reproduce -->
|
||||
<!--- Please link to the issue here: -->
|
||||
|
||||
## Motivation and Context
|
||||
|
||||
<!--- Why is this change required? What problem does it solve? -->
|
||||
|
||||
## How Has This Been Tested?
|
||||
|
||||
<!--- Please describe in detail how you tested your changes. -->
|
||||
<!--- Include details of your testing environment, and the tests you ran to -->
|
||||
<!--- see how your change affects other areas of the code, etc. -->
|
||||
@@ -20,14 +24,18 @@
|
||||
## Screenshots (if appropriate):
|
||||
|
||||
## Types of changes
|
||||
|
||||
<!--- What types of changes does your code introduce? Put an `x` in all the boxes that apply: -->
|
||||
|
||||
- [ ] Bug fix (non-breaking change which fixes an issue)
|
||||
- [ ] New feature (non-breaking change which adds functionality)
|
||||
- [ ] Breaking change (fix or feature that would cause existing functionality to change)
|
||||
|
||||
## Checklist:
|
||||
|
||||
<!--- Go over all the following points, and put an `x` in all the boxes that apply. -->
|
||||
<!--- If you're unsure about any of these, don't hesitate to ask. We're here to help! -->
|
||||
|
||||
- [ ] My code follows the code style of this project.
|
||||
- [ ] My change requires a change to the documentation.
|
||||
- [ ] I have updated the documentation accordingly.
|
||||
|
||||
24
.github/labels/labels.md
vendored
24
.github/labels/labels.md
vendored
@@ -1,16 +1,16 @@
|
||||
# Labels
|
||||
|
||||
Category | Label(s) | Color(s)
|
||||
--- | --- | ---
|
||||
Platform |      | #BFD4F2
|
||||
Problems |   | #EE3F46
|
||||
Severity |  | #B60205
|
||||
Type |     | #FFC274
|
||||
Feedback |   | #CC317C
|
||||
Improvements |    | #5EBEFF
|
||||
Help |  | #76C3A9
|
||||
Additions |  | #90C954
|
||||
Pending |      | #FBCA04
|
||||
Inactive |      | #D2DAE1
|
||||
| Category | Label(s) | Color(s) |
|
||||
| ------------ | ------------------------------------------------------------------------------------------------------------------------------ | -------- |
|
||||
| Platform |      | #BFD4F2 |
|
||||
| Problems |   | #EE3F46 |
|
||||
| Severity |  | #B60205 |
|
||||
| Type |     | #FFC274 |
|
||||
| Feedback |   | #CC317C |
|
||||
| Improvements |    | #5EBEFF |
|
||||
| Help |  | #76C3A9 |
|
||||
| Additions |  | #90C954 |
|
||||
| Pending |      | #FBCA04 |
|
||||
| Inactive |      | #D2DAE1 |
|
||||
|
||||
Note: in order to take a sharp screenshot of labels with Firefox: Right click the label => Inspect element => Right click the element on the inspector => Screenshot Node
|
||||
|
||||
25
.jsdoc.json
25
.jsdoc.json
@@ -1,25 +0,0 @@
|
||||
{
|
||||
"tags": {
|
||||
"allowUnknownTags": true,
|
||||
"dictionaries": ["jsdoc"]
|
||||
},
|
||||
"source": {
|
||||
"include": ["client", "server", "shared", "package.json", "README.md"],
|
||||
"includePattern": ".js$",
|
||||
"excludePattern": "(node_modules/|docs)"
|
||||
},
|
||||
"plugins": ["plugins/markdown"],
|
||||
"templates": {
|
||||
"cleverLinks": false,
|
||||
"monospaceLinks": true,
|
||||
"useLongnameInNav": false,
|
||||
"showInheritedInNav": true
|
||||
},
|
||||
"opts": {
|
||||
"destination": "./docs/",
|
||||
"encoding": "utf8",
|
||||
"private": true,
|
||||
"recurse": true,
|
||||
"template": "./node_modules/minami"
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,3 @@
|
||||
# Markdown and HTML
|
||||
*.md
|
||||
*.html
|
||||
|
||||
# Distribution files
|
||||
dist/
|
||||
|
||||
|
||||
10
.travis.yml
10
.travis.yml
@@ -1,16 +1,14 @@
|
||||
dist: trusty
|
||||
dist: focal
|
||||
language: node_js
|
||||
node_js:
|
||||
- '10'
|
||||
- '12'
|
||||
- 'node'
|
||||
- 'lts/*'
|
||||
- '13'
|
||||
matrix:
|
||||
fast_finish: true
|
||||
allow_failures:
|
||||
- node_js: 10
|
||||
script:
|
||||
- npm run check-source-formatting
|
||||
- npm run lint
|
||||
- npm run check-types
|
||||
- npm run test
|
||||
- npm run build
|
||||
- npm run test
|
||||
|
||||
273
CHANGELOG.md
273
CHANGELOG.md
@@ -1,152 +1,159 @@
|
||||
# Changelog
|
||||
|
||||
## [4.0.2] (November 11, 2020)
|
||||
* New translations
|
||||
* German, thanks to @chint95
|
||||
* Romanian, thanks to @T-z3P
|
||||
|
||||
- New translations
|
||||
- German, thanks to @chint95
|
||||
- Romanian, thanks to @T-z3P
|
||||
|
||||
## [4.0.1] (November 10, 2020)
|
||||
* Fix the unreliable clear all notification button
|
||||
* Bump dependencies
|
||||
|
||||
- Fix the unreliable clear all notification button
|
||||
- Bump dependencies
|
||||
|
||||
## [4.0.0] (November 9, 2020)
|
||||
* Experimental multi-client support
|
||||
* qBittorrent
|
||||
* Transmission
|
||||
* Stabilized and documented public API endpoints
|
||||
* Defined and documented internal interfaces, data structures and APIs
|
||||
* Better documentation for users and developers
|
||||
* Full migration to TypeScript
|
||||
* Reasonable test coverages for API endpoints
|
||||
* Torrent creation support
|
||||
* Add torrents as completed
|
||||
* Dropdown selector for existing tags
|
||||
* Seeding status in status filter
|
||||
* Set tracker URLs of torrents
|
||||
* Improved handling of rendering, updating and scrolling of torrent list
|
||||
* Preliminary tests show that Flood can now handle 500,000 torrents at least in the frontend.
|
||||
* Note: real-world performance depends on other factors such as method call and deserialization operations in the backend and data transfer between backend and frontend.
|
||||
* Better performance, less memory and CPU consumption in both frontend and backend
|
||||
* New translations
|
||||
* Chinese (Traditional), thanks to @vongola12324
|
||||
* Czech, thanks to Jan Březina
|
||||
* French, thanks to @Zopieux and @Mystere98
|
||||
* German, thanks to @chint95
|
||||
* Bug fixes
|
||||
* Security enhancements
|
||||
* Dockerfile revamp
|
||||
* Native build tools no longer needed as native dependency is replaced with WebAssembly variant
|
||||
* Server is packed before distribution, reduced number of dependencies in production, faster installation
|
||||
|
||||
- Experimental multi-client support
|
||||
- qBittorrent
|
||||
- Transmission
|
||||
- Stabilized and documented public API endpoints
|
||||
- Defined and documented internal interfaces, data structures and APIs
|
||||
- Better documentation for users and developers
|
||||
- Full migration to TypeScript
|
||||
- Reasonable test coverages for API endpoints
|
||||
- Torrent creation support
|
||||
- Add torrents as completed
|
||||
- Dropdown selector for existing tags
|
||||
- Seeding status in status filter
|
||||
- Set tracker URLs of torrents
|
||||
- Improved handling of rendering, updating and scrolling of torrent list
|
||||
- Preliminary tests show that Flood can now handle 500,000 torrents at least in the frontend.
|
||||
- Note: real-world performance depends on other factors such as method call and deserialization operations in the backend and data transfer between backend and frontend.
|
||||
- Better performance, less memory and CPU consumption in both frontend and backend
|
||||
- New translations
|
||||
- Chinese (Traditional), thanks to @vongola12324
|
||||
- Czech, thanks to Jan Březina
|
||||
- French, thanks to @Zopieux and @Mystere98
|
||||
- German, thanks to @chint95
|
||||
- Bug fixes
|
||||
- Security enhancements
|
||||
- Dockerfile revamp
|
||||
- Native build tools no longer needed as native dependency is replaced with WebAssembly variant
|
||||
- Server is packed before distribution, reduced number of dependencies in production, faster installation
|
||||
|
||||
## [3.1.0] (September 4, 2020)
|
||||
* Allow to replace main tracker of torrents
|
||||
* Allow adjustment of visible context menu items
|
||||
* config.cli: make all configs configurable by options and env
|
||||
* styles: properly set width of clipboard icon (fixes #26)
|
||||
* client: hide logout button when auth is disabled
|
||||
* Hungarian support (#21), thanks to @sfu420
|
||||
* New translations:
|
||||
* Chinese Traditional, thanks to @vongola12324
|
||||
* Czech, thanks to @brezina.jn
|
||||
* Portuguese, thanks to @Zamalor
|
||||
* Security enhancements:
|
||||
* Allow restriction on file operations by paths
|
||||
* Do not bypass authentication token validation with disableUsersAndAuth
|
||||
* server: prohibit Cross-Origin Resource Sharing
|
||||
* server: auth: strictly prohibit cross-site cookie
|
||||
* Minor security fixes:
|
||||
* rTorrentDeserializer: avoid double unescaping
|
||||
* SettingsModal: mergeObjects: prevent prototype pollution
|
||||
* server: setSettings: turn inboundTransformations into a Map to validate dynamic call
|
||||
* server: be explicit about client app routes
|
||||
* server: cache index.html into memory
|
||||
* Minor refactoring and other changes
|
||||
* Bump dependencies to the latest revisions
|
||||
|
||||
- Allow to replace main tracker of torrents
|
||||
- Allow adjustment of visible context menu items
|
||||
- config.cli: make all configs configurable by options and env
|
||||
- styles: properly set width of clipboard icon (fixes #26)
|
||||
- client: hide logout button when auth is disabled
|
||||
- Hungarian support (#21), thanks to @sfu420
|
||||
- New translations:
|
||||
- Chinese Traditional, thanks to @vongola12324
|
||||
- Czech, thanks to @brezina.jn
|
||||
- Portuguese, thanks to @Zamalor
|
||||
- Security enhancements:
|
||||
- Allow restriction on file operations by paths
|
||||
- Do not bypass authentication token validation with disableUsersAndAuth
|
||||
- server: prohibit Cross-Origin Resource Sharing
|
||||
- server: auth: strictly prohibit cross-site cookie
|
||||
- Minor security fixes:
|
||||
- rTorrentDeserializer: avoid double unescaping
|
||||
- SettingsModal: mergeObjects: prevent prototype pollution
|
||||
- server: setSettings: turn inboundTransformations into a Map to validate dynamic call
|
||||
- server: be explicit about client app routes
|
||||
- server: cache index.html into memory
|
||||
- Minor refactoring and other changes
|
||||
- Bump dependencies to the latest revisions
|
||||
|
||||
## [3.0.0] (August 25, 2020)
|
||||
* BREAKING CHANGES:
|
||||
* If `baseURI` is set, server will only respond to requests with baseURI. For instance, if you use `location /flood {proxy_pass http://127.0.0.1:3000;}`, you would have to change it to `location /flood {proxy_pass http://127.0.0.1:3000/flood;}`.
|
||||
* Static assets now use relative paths only. It is no longer needed to recompile after `baseURI` change.
|
||||
* Location of runtime files are rearranged. New default location for runtime files is `./run` folder. `tempPath` is now made configurable.
|
||||
* Static assets are relocated to `./dist` folder. You have to change the path from `./server/assets` to `./dist/assets` if you serve static assets from web server.
|
||||
* Flood will refuse to start if secrets are detected in static assets. Former default secret `flood` and some other weak secrets are no longer accepted.
|
||||
* A command line interface is added as `config.cli.js`. Rename it to `config.js` and run `npm run start -- --help` for more details.
|
||||
* With some changes, Flood is now ready for publish to `npm`. You can now use `sudo npm install -g flood` to get a ready-to-use copy of Flood, then run `flood`. It is even easier with `npx`, try `npx flood --help` now.
|
||||
* Better localization:
|
||||
* Flood project is now integrated with [Crowdin](https://crwd.in/flood), a renowned translation management system. It is now easier than ever to contribute your translations to Flood.
|
||||
* Language will now be automatically detected from your browser by default.
|
||||
* New languages are supported: `Čeština`, `Deutsch`, `italiano`, `norsk`, `Polskie`, `русский язык`, `Romanian`, `svenska`, `українська мова`, `日本語` and `اَلْعَرَبِيَّةُ` thanks to `Crowdin Machine Translation`.
|
||||
* New translations for `Chinese (Traditional)` thanks to @vongola12324.
|
||||
* New translations for `Dutch` thanks to @NLxDoDge.
|
||||
* New translations for `Portuguese` thanks to @MiguelNdeCarvalho.
|
||||
* Support for touch and smaller screen devices:
|
||||
* Sidebar is able to be collapsed via a button. It is collapsed by default when screen width is lower than `720px`.
|
||||
* Modals (Settings, Torrent Details, Add Torrent, etc.) are tuned for smaller screen devices.
|
||||
* It is now possible to open context (right click) menu on iOS/Safari devices by long pressing the item.
|
||||
* Drag and drop is now possible for touch devices. You can now adjust the order of columns in Settings on touch devices.
|
||||
* Widths of columns are now adjustable on touch devices. (condensed view)
|
||||
* Dark color scheme support:
|
||||
* Flood now automatically switches between light and dark color scheme based on your system settings.
|
||||
* XML special chars (`&`, `<`, `>`, `'`, `"`) are properly handled. For instance, escaped chars like `&` will be properly displayed as `&` instead of `&`. File operations on torrent with special chars no longer fail.
|
||||
* `squashfs` and `tmpfs` mount points are now excluded by default in disk usage. This hopefully makes sure that useless system mounts won't spam the list.
|
||||
* `More Info` button in expanded view is removed.
|
||||
* More dependencies are bumped to the latest revisions.
|
||||
|
||||
- BREAKING CHANGES:
|
||||
- If `baseURI` is set, server will only respond to requests with baseURI. For instance, if you use `location /flood {proxy_pass http://127.0.0.1:3000;}`, you would have to change it to `location /flood {proxy_pass http://127.0.0.1:3000/flood;}`.
|
||||
- Static assets now use relative paths only. It is no longer needed to recompile after `baseURI` change.
|
||||
- Location of runtime files are rearranged. New default location for runtime files is `./run` folder. `tempPath` is now made configurable.
|
||||
- Static assets are relocated to `./dist` folder. You have to change the path from `./server/assets` to `./dist/assets` if you serve static assets from web server.
|
||||
- Flood will refuse to start if secrets are detected in static assets. Former default secret `flood` and some other weak secrets are no longer accepted.
|
||||
- A command line interface is added as `config.cli.js`. Rename it to `config.js` and run `npm run start -- --help` for more details.
|
||||
- With some changes, Flood is now ready for publish to `npm`. You can now use `sudo npm install -g flood` to get a ready-to-use copy of Flood, then run `flood`. It is even easier with `npx`, try `npx flood --help` now.
|
||||
- Better localization:
|
||||
- Flood project is now integrated with [Crowdin](https://crwd.in/flood), a renowned translation management system. It is now easier than ever to contribute your translations to Flood.
|
||||
- Language will now be automatically detected from your browser by default.
|
||||
- New languages are supported: `Čeština`, `Deutsch`, `italiano`, `norsk`, `Polskie`, `русский язык`, `Romanian`, `svenska`, `українська мова`, `日本語` and `اَلْعَرَبِيَّةُ` thanks to `Crowdin Machine Translation`.
|
||||
- New translations for `Chinese (Traditional)` thanks to @vongola12324.
|
||||
- New translations for `Dutch` thanks to @NLxDoDge.
|
||||
- New translations for `Portuguese` thanks to @MiguelNdeCarvalho.
|
||||
- Support for touch and smaller screen devices:
|
||||
- Sidebar is able to be collapsed via a button. It is collapsed by default when screen width is lower than `720px`.
|
||||
- Modals (Settings, Torrent Details, Add Torrent, etc.) are tuned for smaller screen devices.
|
||||
- It is now possible to open context (right click) menu on iOS/Safari devices by long pressing the item.
|
||||
- Drag and drop is now possible for touch devices. You can now adjust the order of columns in Settings on touch devices.
|
||||
- Widths of columns are now adjustable on touch devices. (condensed view)
|
||||
- Dark color scheme support:
|
||||
- Flood now automatically switches between light and dark color scheme based on your system settings.
|
||||
- XML special chars (`&`, `<`, `>`, `'`, `"`) are properly handled. For instance, escaped chars like `&` will be properly displayed as `&` instead of `&`. File operations on torrent with special chars no longer fail.
|
||||
- `squashfs` and `tmpfs` mount points are now excluded by default in disk usage. This hopefully makes sure that useless system mounts won't spam the list.
|
||||
- `More Info` button in expanded view is removed.
|
||||
- More dependencies are bumped to the latest revisions.
|
||||
|
||||
## [2.0.0] (August 5, 2020)
|
||||
* BREAKING CHANGES:
|
||||
* Bump dependencies to the latest version if possible
|
||||
* Node 12 or later is now required
|
||||
* Supports connecting to multiple rtorrent instances (one per user)
|
||||
* Moved rtorrent configuration to user database
|
||||
* Prompts user for connection details in UI when can't connect to rtorrent
|
||||
* Changed `/list/` route to `/overview/`
|
||||
* Reorganized and renamed component source files
|
||||
* Removed verbose logging from `HistoryEra`
|
||||
* Check existing feed items against new download rules
|
||||
* Switch URL and Label textboxes in Add Feed form to match the Download Rules form
|
||||
* Rate-limit the SCGI calls to rTorrent
|
||||
* Sends only one call at a time
|
||||
* Sends at most one call every 250 miliseconds
|
||||
* Implement "actity stream"
|
||||
* The Flood client no longer polls the Flood server on an interval. Instead,
|
||||
|
||||
- BREAKING CHANGES:
|
||||
- Bump dependencies to the latest version if possible
|
||||
- Node 12 or later is now required
|
||||
- Supports connecting to multiple rtorrent instances (one per user)
|
||||
- Moved rtorrent configuration to user database
|
||||
- Prompts user for connection details in UI when can't connect to rtorrent
|
||||
- Changed `/list/` route to `/overview/`
|
||||
- Reorganized and renamed component source files
|
||||
- Removed verbose logging from `HistoryEra`
|
||||
- Check existing feed items against new download rules
|
||||
- Switch URL and Label textboxes in Add Feed form to match the Download Rules form
|
||||
- Rate-limit the SCGI calls to rTorrent
|
||||
- Sends only one call at a time
|
||||
- Sends at most one call every 250 miliseconds
|
||||
- Implement "actity stream"
|
||||
- The Flood client no longer polls the Flood server on an interval. Instead,
|
||||
the Flood server polls rTorrent on a more regular interval and emits changes
|
||||
via an event-stream. This significantly reduces data usage on the Flood client
|
||||
* Stream covers torrent list, transfer rate summary & history,
|
||||
- Stream covers torrent list, transfer rate summary & history,
|
||||
torrent taxonomy, and notification count.
|
||||
* Close event stream after the window/tab has been inactive for 30 seconds
|
||||
* Refactor development experience, using `Webpack` & `WebpackDevServer`
|
||||
* Require users to build static assets again
|
||||
* Simplify peer geo flag handling
|
||||
* Flag images now serves as static asset
|
||||
* moveTorrents: Allow hash check to be skipped by user
|
||||
* Add an option to completely disable users and authentication
|
||||
* server: Takes baseURI into account for routes and assets
|
||||
* torrentListPropMap: use d.hashing= instead of d.is_hash_checking=
|
||||
* Torrents queued for checking are now shown
|
||||
* sidebar: Add Checking filter view
|
||||
- Close event stream after the window/tab has been inactive for 30 seconds
|
||||
- Refactor development experience, using `Webpack` & `WebpackDevServer`
|
||||
- Require users to build static assets again
|
||||
- Simplify peer geo flag handling
|
||||
- Flag images now serves as static asset
|
||||
- moveTorrents: Allow hash check to be skipped by user
|
||||
- Add an option to completely disable users and authentication
|
||||
- server: Takes baseURI into account for routes and assets
|
||||
- torrentListPropMap: use d.hashing= instead of d.is_hash_checking=
|
||||
- Torrents queued for checking are now shown
|
||||
- sidebar: Add Checking filter view
|
||||
|
||||
## [1.0.0] (April 21, 2017)
|
||||
* First "official" release
|
||||
* Change log and semver versioning (finally)
|
||||
* Control basic rTorrent settings via web UI
|
||||
* Transfer rate limiting
|
||||
* Connection settings
|
||||
* Resource utilization
|
||||
* Add torrents via URLs or files
|
||||
* User authentication
|
||||
* UI translations (only en, fr, and nl)
|
||||
* Custom torrent tags
|
||||
* Customizable torrent list
|
||||
* "Expanded" and "condensed" views
|
||||
* Customizable torrent detail columns
|
||||
* Basic torrent list filtering (by status, tag, and tracker)
|
||||
* Auto-download torrents from RSS feeds
|
||||
|
||||
[Unreleased]:https://github.com/Flood-UI/flood/compare/v1.0.0...HEAD
|
||||
[1.0.0]:https://github.com/Flood-UI/flood/compare/ae520c0a33ffb4ae6f21e47bc6f7e6007dd1e6dc...v1.0.0
|
||||
[2.0.0]:https://github.com/jesec/flood/compare/v1.0.0...v2.0.0
|
||||
[3.0.0]:https://github.com/jesec/flood/compare/v2.0.0...v3.0.0
|
||||
[3.1.0]:https://github.com/jesec/flood/compare/v3.0.0...v3.1.0
|
||||
[4.0.0]:https://github.com/jesec/flood/compare/v3.1.0...v4.0.0
|
||||
[4.0.1]:https://github.com/jesec/flood/compare/v4.0.0...v4.0.1
|
||||
[4.0.2]:https://github.com/jesec/flood/compare/v4.0.1...v4.0.2
|
||||
- First "official" release
|
||||
- Change log and semver versioning (finally)
|
||||
- Control basic rTorrent settings via web UI
|
||||
- Transfer rate limiting
|
||||
- Connection settings
|
||||
- Resource utilization
|
||||
- Add torrents via URLs or files
|
||||
- User authentication
|
||||
- UI translations (only en, fr, and nl)
|
||||
- Custom torrent tags
|
||||
- Customizable torrent list
|
||||
- "Expanded" and "condensed" views
|
||||
- Customizable torrent detail columns
|
||||
- Basic torrent list filtering (by status, tag, and tracker)
|
||||
- Auto-download torrents from RSS feeds
|
||||
|
||||
[unreleased]: https://github.com/Flood-UI/flood/compare/v1.0.0...HEAD
|
||||
[1.0.0]: https://github.com/Flood-UI/flood/compare/ae520c0a33ffb4ae6f21e47bc6f7e6007dd1e6dc...v1.0.0
|
||||
[2.0.0]: https://github.com/jesec/flood/compare/v1.0.0...v2.0.0
|
||||
[3.0.0]: https://github.com/jesec/flood/compare/v2.0.0...v3.0.0
|
||||
[3.1.0]: https://github.com/jesec/flood/compare/v3.0.0...v3.1.0
|
||||
[4.0.0]: https://github.com/jesec/flood/compare/v3.1.0...v4.0.0
|
||||
[4.0.1]: https://github.com/jesec/flood/compare/v4.0.0...v4.0.1
|
||||
[4.0.2]: https://github.com/jesec/flood/compare/v4.0.1...v4.0.2
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to creating a positive environment include:
|
||||
|
||||
* Using welcoming and inclusive language
|
||||
* Being respectful of differing viewpoints and experiences
|
||||
* Gracefully accepting constructive criticism
|
||||
* Focusing on what is best for the community
|
||||
* Showing empathy towards other community members
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
* The use of sexualized language or imagery and unwelcome sexual attention or advances
|
||||
* Trolling, insulting/derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or electronic address, without explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a professional setting
|
||||
|
||||
## Our Responsibilities
|
||||
|
||||
Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
|
||||
|
||||
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at me@johnfurrow.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
|
||||
|
||||
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
|
||||
|
||||
[homepage]: http://contributor-covenant.org
|
||||
[version]: http://contributor-covenant.org/version/1/4/
|
||||
@@ -7,8 +7,9 @@
|
||||
Flood is a monitoring service for various torrent clients. It's a Node.js service that communicates with your favorite torrent client and serves a decent web UI for administration. This project is based on the [original Flood project](https://github.com/Flood-UI/flood).
|
||||
|
||||
#### Supported Clients
|
||||
|
||||
| Client | Support |
|
||||
|--------------------------------------------------------------|--------------------------------------|
|
||||
| ------------------------------------------------------------ | ------------------------------------ |
|
||||
| [rTorrent](https://github.com/rakshasa/rtorrent) | Stable and Tested :white_check_mark: |
|
||||
| [qBittorrent](https://github.com/qbittorrent/qBittorrent) | Experimental :alembic: |
|
||||
| [Transmission](https://github.com/transmission/transmission) | Experimental :alembic: |
|
||||
@@ -26,6 +27,7 @@ Check out the [Wiki](https://github.com/jesec/flood/wiki) for more information.
|
||||
### Pre-Requisites
|
||||
|
||||
Install [Node.js runtime](https://nodejs.org/). Flood tracks `Current` and provides support to `Active LTS` as well.
|
||||
|
||||
- Debian, Ubuntu and RHEL-based distributions users can install latest `nodejs` from [NodeSource](https://github.com/nodesource/distributions) software repository.
|
||||
- Windows and MacOS users may use installer.
|
||||
|
||||
|
||||
@@ -1,50 +0,0 @@
|
||||
const path = require('path');
|
||||
|
||||
module.exports = {
|
||||
extends: '../.eslintrc',
|
||||
env: {
|
||||
browser: 1,
|
||||
node: 0,
|
||||
},
|
||||
globals: {
|
||||
global: 'writable',
|
||||
process: 'writable',
|
||||
window: 'writable',
|
||||
},
|
||||
rules: {
|
||||
'import/no-extraneous-dependencies': 0,
|
||||
'no-restricted-imports': [
|
||||
'error',
|
||||
{
|
||||
patterns: ['**/config', '**/server/**/*'],
|
||||
},
|
||||
],
|
||||
'no-restricted-modules': [
|
||||
'error',
|
||||
{
|
||||
patterns: ['**/server/**/*'],
|
||||
},
|
||||
],
|
||||
// TODO: Enable a11y features
|
||||
'jsx-a11y/click-events-have-key-events': 0,
|
||||
'jsx-a11y/control-has-associated-label': 0,
|
||||
'jsx-a11y/label-has-associated-control': 0,
|
||||
'jsx-a11y/label-has-for': 0,
|
||||
'jsx-a11y/mouse-events-have-key-events': 0,
|
||||
'jsx-a11y/no-noninteractive-element-interactions': 0,
|
||||
'jsx-a11y/no-static-element-interactions': 0,
|
||||
'no-console': [2, {allow: ['warn', 'error']}],
|
||||
'react/destructuring-assignment': 0,
|
||||
'react/jsx-props-no-spreading': 0,
|
||||
'react/jsx-uses-react': 0,
|
||||
'react/react-in-jsx-scope': 0,
|
||||
'react/static-property-placement': [2, 'static public field'],
|
||||
},
|
||||
settings: {
|
||||
'import/resolver': {
|
||||
webpack: {
|
||||
config: path.join(__dirname, 'config/webpack.config.dev.js'),
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -1,10 +0,0 @@
|
||||
module.exports = {
|
||||
env: {
|
||||
browser: 0,
|
||||
node: 1,
|
||||
},
|
||||
rules: {
|
||||
'no-console': 0,
|
||||
'global-require': 0,
|
||||
},
|
||||
};
|
||||
@@ -1,12 +0,0 @@
|
||||
// This is a custom Jest transformer turning style imports into empty objects.
|
||||
// http://facebook.github.io/jest/docs/tutorial-webpack.html
|
||||
|
||||
module.exports = {
|
||||
process() {
|
||||
return 'module.exports = {};';
|
||||
},
|
||||
getCacheKey() {
|
||||
// The output is always the same.
|
||||
return 'cssTransform';
|
||||
},
|
||||
};
|
||||
@@ -1,10 +0,0 @@
|
||||
const path = require('path');
|
||||
|
||||
// This is a custom Jest transformer turning file imports into filenames.
|
||||
// http://facebook.github.io/jest/docs/tutorial-webpack.html
|
||||
|
||||
module.exports = {
|
||||
process(src, filename) {
|
||||
return `module.exports = ${JSON.stringify(path.basename(filename))};`;
|
||||
},
|
||||
};
|
||||
@@ -1,10 +0,0 @@
|
||||
module.exports = {
|
||||
env: {
|
||||
browser: 0,
|
||||
node: 1,
|
||||
},
|
||||
rules: {
|
||||
'no-console': 0,
|
||||
'global-require': 0,
|
||||
},
|
||||
};
|
||||
67
client/src/.eslintrc.json
Normal file
67
client/src/.eslintrc.json
Normal file
@@ -0,0 +1,67 @@
|
||||
{
|
||||
"extends": [
|
||||
"react-app",
|
||||
"airbnb-typescript",
|
||||
"plugin:@typescript-eslint/recommended",
|
||||
"prettier",
|
||||
"prettier/@typescript-eslint",
|
||||
"prettier/react"
|
||||
],
|
||||
|
||||
"parserOptions": {
|
||||
"project": "./tsconfig.json"
|
||||
},
|
||||
|
||||
"env": {
|
||||
"browser": true,
|
||||
"node": false
|
||||
},
|
||||
|
||||
"globals": {
|
||||
"global": "writable",
|
||||
"process": "writable",
|
||||
"window": "writable"
|
||||
},
|
||||
|
||||
"rules": {
|
||||
"import/no-extraneous-dependencies": 0,
|
||||
"no-restricted-imports": [
|
||||
"error",
|
||||
{
|
||||
"patterns": ["**/config", "**/server/**/*"]
|
||||
}
|
||||
],
|
||||
"no-restricted-modules": [
|
||||
"error",
|
||||
{
|
||||
"patterns": ["**/config", "**/server/**/*"]
|
||||
}
|
||||
],
|
||||
// TODO: Enable a11y features
|
||||
"jsx-a11y/click-events-have-key-events": 0,
|
||||
"jsx-a11y/control-has-associated-label": 0,
|
||||
"jsx-a11y/label-has-associated-control": 0,
|
||||
"jsx-a11y/label-has-for": 0,
|
||||
"jsx-a11y/mouse-events-have-key-events": 0,
|
||||
"jsx-a11y/no-noninteractive-element-interactions": 0,
|
||||
"jsx-a11y/no-static-element-interactions": 0,
|
||||
"no-console": [2, {"allow": ["warn", "error"]}],
|
||||
"no-underscore-dangle": [2, {"allow": ["_id"]}],
|
||||
"react/destructuring-assignment": 0,
|
||||
"react/jsx-props-no-spreading": 0,
|
||||
"react/jsx-uses-react": 0,
|
||||
"react/react-in-jsx-scope": 0,
|
||||
"react/static-property-placement": [2, "static public field"],
|
||||
"@typescript-eslint/lines-between-class-members": ["error", "always", {"exceptAfterSingleLine": true}],
|
||||
// TODO: Explicit return type
|
||||
"@typescript-eslint/explicit-function-return-type": 0,
|
||||
"@typescript-eslint/explicit-module-boundary-types": 0
|
||||
},
|
||||
"settings": {
|
||||
"import/resolver": {
|
||||
"webpack": {
|
||||
"config": "client/config/webpack.config.dev.js"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,23 +1,19 @@
|
||||
<!doctype html>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
|
||||
<meta name="theme-color" content="#000000">
|
||||
<link rel="shortcut icon" href="favicon.ico">
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<link rel="shortcut icon" href="favicon.ico" />
|
||||
<script type="text/javascript">
|
||||
var _jipt = [];
|
||||
_jipt.push(['project', 'flood']);
|
||||
</script>
|
||||
<title>Flood</title>
|
||||
</head>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<noscript>
|
||||
You need to enable JavaScript to run this app.
|
||||
</noscript>
|
||||
<body>
|
||||
<noscript> You need to enable JavaScript to run this app. </noscript>
|
||||
<div id="app"></div>
|
||||
</body>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -76,12 +76,18 @@ class AuthForm extends React.Component<AuthFormProps, AuthFormStates> {
|
||||
const formData = submission.formData as Partial<LoginFormData> | Partial<RegisterFormData>;
|
||||
|
||||
if (formData.username == null || formData.username === '') {
|
||||
this.setState({isSubmitting: false, errorMessage: intl.formatMessage({id: 'auth.error.username.empty'})});
|
||||
this.setState({
|
||||
isSubmitting: false,
|
||||
errorMessage: intl.formatMessage({id: 'auth.error.username.empty'}),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (formData.password == null || formData.password === '') {
|
||||
this.setState({isSubmitting: false, errorMessage: intl.formatMessage({id: 'auth.error.password.empty'})});
|
||||
this.setState({
|
||||
isSubmitting: false,
|
||||
errorMessage: intl.formatMessage({id: 'auth.error.password.empty'}),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -102,13 +108,23 @@ class AuthForm extends React.Component<AuthFormProps, AuthFormStates> {
|
||||
const config = formData as RegisterFormData;
|
||||
|
||||
if (this.settingsFormRef.current == null) {
|
||||
this.setState({isSubmitting: false, errorMessage: intl.formatMessage({id: 'connection.settings.error.empty'})});
|
||||
this.setState({
|
||||
isSubmitting: false,
|
||||
errorMessage: intl.formatMessage({
|
||||
id: 'connection.settings.error.empty',
|
||||
}),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const connectionSettings = this.settingsFormRef.current.getConnectionSettings();
|
||||
if (connectionSettings == null) {
|
||||
this.setState({isSubmitting: false, errorMessage: intl.formatMessage({id: 'connection.settings.error.empty'})});
|
||||
this.setState({
|
||||
isSubmitting: false,
|
||||
errorMessage: intl.formatMessage({
|
||||
id: 'connection.settings.error.empty',
|
||||
}),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -45,7 +45,9 @@ const ClientConnectionInterruption: FC = observer(() => {
|
||||
}
|
||||
|
||||
try {
|
||||
await AuthActions.updateUser(currentUsername, {client: connectionSettings})
|
||||
await AuthActions.updateUser(currentUsername, {
|
||||
client: connectionSettings,
|
||||
})
|
||||
.then(() => {
|
||||
// do nothing.
|
||||
})
|
||||
|
||||
@@ -1,7 +1,41 @@
|
||||
import {FC, ReactNode} from 'react';
|
||||
import {FormattedMessage} from 'react-intl';
|
||||
|
||||
import formatUtil from '@shared/util/formatUtil';
|
||||
const secondsToDuration = (
|
||||
cumSeconds: number,
|
||||
): {
|
||||
years?: number;
|
||||
weeks?: number;
|
||||
days?: number;
|
||||
hours?: number;
|
||||
minutes?: number;
|
||||
seconds?: number;
|
||||
cumSeconds: number;
|
||||
} => {
|
||||
const years = Math.floor(cumSeconds / 31536000);
|
||||
const weeks = Math.floor((cumSeconds % 31536000) / 604800);
|
||||
const days = Math.floor(((cumSeconds % 31536000) % 604800) / 86400);
|
||||
const hours = Math.floor((((cumSeconds % 31536000) % 604800) % 86400) / 3600);
|
||||
const minutes = Math.floor(((((cumSeconds % 31536000) % 604800) % 86400) % 3600) / 60);
|
||||
const seconds = Math.floor(cumSeconds - minutes * 60);
|
||||
let timeRemaining = null;
|
||||
|
||||
if (years > 0) {
|
||||
timeRemaining = {years, weeks, cumSeconds};
|
||||
} else if (weeks > 0) {
|
||||
timeRemaining = {weeks, days, cumSeconds};
|
||||
} else if (days > 0) {
|
||||
timeRemaining = {days, hours, cumSeconds};
|
||||
} else if (hours > 0) {
|
||||
timeRemaining = {hours, minutes, cumSeconds};
|
||||
} else if (minutes > 0) {
|
||||
timeRemaining = {minutes, seconds, cumSeconds};
|
||||
} else {
|
||||
timeRemaining = {seconds, cumSeconds};
|
||||
}
|
||||
|
||||
return timeRemaining;
|
||||
};
|
||||
|
||||
interface DurationProps {
|
||||
suffix?: ReactNode;
|
||||
@@ -22,7 +56,7 @@ const Duration: FC<DurationProps> = (props: DurationProps) => {
|
||||
suffixElement = <span className="duration--segment">{suffix}</span>;
|
||||
}
|
||||
|
||||
const duration = value === -1 ? -1 : formatUtil.secondsToDuration(value);
|
||||
const duration = value === -1 ? -1 : secondsToDuration(value);
|
||||
|
||||
if (duration === -1) {
|
||||
content = <FormattedMessage id="unit.time.infinity" />;
|
||||
|
||||
@@ -34,12 +34,15 @@ const Overflow = forwardRef<HTMLDivElement, ComponentProps<'div'>>((props: Compo
|
||||
viewport.removeEventListener('scroll', (e) => onScroll((e as unknown) as UIEvent<HTMLDivElement>));
|
||||
}
|
||||
};
|
||||
}, [onScroll]);
|
||||
}, [onScroll, ref]);
|
||||
|
||||
return (
|
||||
<OverlayScrollbarsComponent
|
||||
{...props}
|
||||
options={{scrollbars: {autoHide: 'leave', clickScrolling: true}, className}}
|
||||
options={{
|
||||
scrollbars: {autoHide: 'leave', clickScrolling: true},
|
||||
className,
|
||||
}}
|
||||
ref={osRef}>
|
||||
{children}
|
||||
</OverlayScrollbarsComponent>
|
||||
|
||||
@@ -11,19 +11,12 @@ const ICONS = {
|
||||
satisfied: <Checkmark />,
|
||||
};
|
||||
|
||||
interface LoadingOverlayProps {
|
||||
dependencies?: Dependencies;
|
||||
}
|
||||
|
||||
const LoadingOverlay: FC<LoadingOverlayProps> = (props: LoadingOverlayProps) => {
|
||||
const {dependencies} = props;
|
||||
const LoadingDependencyList: FC<{dependencies: Dependencies}> = ({dependencies}: {dependencies: Dependencies}) => {
|
||||
const intl = useIntl();
|
||||
|
||||
return (
|
||||
<div className="application__loading-overlay">
|
||||
<LoadingIndicator inverse />
|
||||
<ul className="dependency-list">
|
||||
{dependencies != null
|
||||
? Object.keys(dependencies).map((id: string) => {
|
||||
{Object.keys(dependencies).map((id: string) => {
|
||||
const {message, satisfied} = dependencies[id];
|
||||
const statusIcon = ICONS.satisfied;
|
||||
const classes = classnames('dependency-list__dependency', {
|
||||
@@ -34,13 +27,22 @@ const LoadingOverlay: FC<LoadingOverlayProps> = (props: LoadingOverlayProps) =>
|
||||
<li className={classes} key={id}>
|
||||
{satisfied != null ? <span className="dependency-list__dependency__icon">{statusIcon}</span> : null}
|
||||
<span className="dependency-list__dependency__message">
|
||||
{typeof message === 'string' ? message : useIntl().formatMessage(message)}
|
||||
{typeof message === 'string' ? message : intl.formatMessage(message)}
|
||||
</span>
|
||||
</li>
|
||||
);
|
||||
})
|
||||
: null}
|
||||
})}
|
||||
</ul>
|
||||
);
|
||||
};
|
||||
|
||||
const LoadingOverlay: FC<{dependencies?: Dependencies}> = (props: {dependencies?: Dependencies}) => {
|
||||
const {dependencies} = props;
|
||||
|
||||
return (
|
||||
<div className="application__loading-overlay">
|
||||
<LoadingIndicator inverse />
|
||||
{dependencies != null ? <LoadingDependencyList dependencies={dependencies} /> : null}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -22,7 +22,9 @@ const Size: FC<SizeProps> = ({value, isSpeed, className, precision}: SizeProps)
|
||||
const computed = compute(value, precision);
|
||||
const intl = useIntl();
|
||||
|
||||
let translatedUnit = intl.formatMessage({id: getTranslationString(computed.unit)});
|
||||
let translatedUnit = intl.formatMessage({
|
||||
id: getTranslationString(computed.unit),
|
||||
});
|
||||
|
||||
if (isSpeed) {
|
||||
translatedUnit = intl.formatMessage(
|
||||
|
||||
@@ -333,12 +333,12 @@ class Tooltip extends React.Component<TooltipProps, TooltipStates> {
|
||||
};
|
||||
|
||||
addScrollListener(): void {
|
||||
this.container.addEventListener('scroll', (_e) => this.dismissTooltip());
|
||||
this.container.addEventListener('scroll', () => this.dismissTooltip());
|
||||
}
|
||||
|
||||
removeScrollListener(): void {
|
||||
if (this.container) {
|
||||
this.container.removeEventListener('scroll', (_e) => this.dismissTooltip());
|
||||
this.container.removeEventListener('scroll', () => this.dismissTooltip());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -403,8 +403,8 @@ class Tooltip extends React.Component<TooltipProps, TooltipStates> {
|
||||
<div
|
||||
className={wrapperClassName}
|
||||
onClick={onClick}
|
||||
onMouseEnter={(_e) => this.handleMouseEnter()}
|
||||
onMouseLeave={(_e) => this.handleMouseLeave()}
|
||||
onMouseEnter={() => this.handleMouseEnter()}
|
||||
onMouseLeave={() => this.handleMouseLeave()}
|
||||
ref={(ref) => {
|
||||
this.triggerNode = ref;
|
||||
}}>
|
||||
|
||||
@@ -76,9 +76,13 @@ class ClientConnectionSettingsForm extends React.Component<WrappedComponentProps
|
||||
<FormRow>
|
||||
<Select
|
||||
id="client"
|
||||
label={intl.formatMessage({id: 'connection.settings.client.select'})}
|
||||
label={intl.formatMessage({
|
||||
id: 'connection.settings.client.select',
|
||||
})}
|
||||
onSelect={(selectedClient) => {
|
||||
this.setState({client: selectedClient as ClientConnectionSettings['client']});
|
||||
this.setState({
|
||||
client: selectedClient as ClientConnectionSettings['client'],
|
||||
});
|
||||
}}
|
||||
defaultID={DEFAULT_SELECTION}>
|
||||
{getClientSelectItems()}
|
||||
|
||||
@@ -55,7 +55,10 @@ class DirectoryFiles extends React.Component<DirectoryFilesProps> {
|
||||
handlePriorityChange = (fileIndex: React.ReactText, priorityLevel: number): void => {
|
||||
const {hash} = this.props;
|
||||
|
||||
TorrentActions.setFilePriority(hash, {indices: [Number(fileIndex)], priority: priorityLevel});
|
||||
TorrentActions.setFilePriority(hash, {
|
||||
indices: [Number(fileIndex)],
|
||||
priority: priorityLevel,
|
||||
});
|
||||
};
|
||||
|
||||
handleFileSelect = (file: TorrentContent, isSelected: boolean): void => {
|
||||
|
||||
@@ -18,7 +18,7 @@ const FileDropzone: FC<FileDropzoneProps> = ({onFilesChanged}: FileDropzoneProps
|
||||
|
||||
useEffect(() => {
|
||||
onFilesChanged(files);
|
||||
}, [files]);
|
||||
}, [files, onFilesChanged]);
|
||||
|
||||
return (
|
||||
<FormRowItem>
|
||||
|
||||
@@ -61,7 +61,10 @@ const TextboxRepeater: FC<TextboxRepeaterProps> = ({defaultValues, id, label, pl
|
||||
idCounter.current += 1;
|
||||
|
||||
const newTextboxes = textboxes.slice();
|
||||
newTextboxes.splice(index + 1, 0, {id: idCounter.current, value: ''});
|
||||
newTextboxes.splice(index + 1, 0, {
|
||||
id: idCounter.current,
|
||||
value: '',
|
||||
});
|
||||
setTextboxes(newTextboxes);
|
||||
}}>
|
||||
<AddMini size="mini" />
|
||||
|
||||
@@ -59,9 +59,8 @@ const Modal: FC<ModalProps> = (props: ModalProps) => {
|
||||
let footer;
|
||||
let headerTabs;
|
||||
|
||||
const [activeTabId, setActiveTabId] = useState<string>(initialTabId ?? Object.keys(tabs || {})[0]);
|
||||
if (tabs) {
|
||||
const [activeTabId, setActiveTabId] = useState(initialTabId ?? Object.keys(tabs)[0]);
|
||||
|
||||
const activeTab = tabs[activeTabId];
|
||||
const contentClasses = classnames('modal__content', activeTab.modalContentClasses);
|
||||
|
||||
|
||||
@@ -120,7 +120,11 @@ const AddTorrentsByCreation: FC = () => {
|
||||
UIStore.dismissModal();
|
||||
});
|
||||
|
||||
saveAddTorrentsUserPreferences({start: formData.start, destination: formData.sourcePath, tab: 'by-creation'});
|
||||
saveAddTorrentsUserPreferences({
|
||||
start: formData.start,
|
||||
destination: formData.sourcePath,
|
||||
tab: 'by-creation',
|
||||
});
|
||||
}}
|
||||
isAddingTorrents={isCreatingTorrents}
|
||||
/>
|
||||
|
||||
@@ -84,7 +84,11 @@ const AddTorrentsByFile: FC = () => {
|
||||
UIStore.dismissModal();
|
||||
});
|
||||
|
||||
saveAddTorrentsUserPreferences({start, destination, tab: 'by-file'});
|
||||
saveAddTorrentsUserPreferences({
|
||||
start,
|
||||
destination,
|
||||
tab: 'by-file',
|
||||
});
|
||||
}}
|
||||
isAddingTorrents={isAddingTorrents}
|
||||
/>
|
||||
|
||||
@@ -105,7 +105,11 @@ const AddTorrentsByURL: FC = () => {
|
||||
UIStore.dismissModal();
|
||||
});
|
||||
|
||||
saveAddTorrentsUserPreferences({start: formData.start, destination: formData.destination, tab: 'by-url'});
|
||||
saveAddTorrentsUserPreferences({
|
||||
start: formData.start,
|
||||
destination: formData.destination,
|
||||
tab: 'by-url',
|
||||
});
|
||||
}}
|
||||
isAddingTorrents={isAddingTorrents}
|
||||
/>
|
||||
|
||||
@@ -352,7 +352,11 @@ class DownloadRulesTab extends React.Component<WrappedComponentProps, DownloadRu
|
||||
<li
|
||||
className="interactive-list__detail-list__item
|
||||
interactive-list__detail interactive-list__detail--tertiary"
|
||||
style={{maxWidth: '50%', overflow: 'hidden', textOverflow: 'ellipsis'}}>
|
||||
style={{
|
||||
maxWidth: '50%',
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
}}>
|
||||
<FormattedMessage id="feeds.match" />
|
||||
{': '}
|
||||
{rule.match}
|
||||
@@ -489,7 +493,10 @@ class DownloadRulesTab extends React.Component<WrappedComponentProps, DownloadRu
|
||||
this.setState({doesPatternMatchTest});
|
||||
}
|
||||
|
||||
validateForm(): {errors?: DownloadRulesTabStates['errors']; isValid: boolean} {
|
||||
validateForm(): {
|
||||
errors?: DownloadRulesTabStates['errors'];
|
||||
isValid: boolean;
|
||||
} {
|
||||
const formData = this.getAmendedFormData();
|
||||
|
||||
if (formData == null) {
|
||||
|
||||
@@ -500,7 +500,10 @@ class FeedsTab extends React.Component<WrappedComponentProps, FeedsTabStates> {
|
||||
const feedBrowseForm = input.formData as {feedID: string; search: string};
|
||||
if ((input.event.target as HTMLInputElement).type !== 'checkbox') {
|
||||
this.setState({selectedFeedID: feedBrowseForm.feedID});
|
||||
FeedActions.fetchItems({id: feedBrowseForm.feedID, search: feedBrowseForm.search});
|
||||
FeedActions.fetchItems({
|
||||
id: feedBrowseForm.feedID,
|
||||
search: feedBrowseForm.search,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@@ -516,7 +519,10 @@ class FeedsTab extends React.Component<WrappedComponentProps, FeedsTabStates> {
|
||||
.filter((_item, index) => formData[index])
|
||||
.map((item, index) => ({id: index, value: item.urls[0]}));
|
||||
|
||||
UIActions.displayModal({id: 'add-torrents', initialURLs: torrentsToDownload});
|
||||
UIActions.displayModal({
|
||||
id: 'add-torrents',
|
||||
initialURLs: torrentsToDownload,
|
||||
});
|
||||
};
|
||||
|
||||
validateForm(): {errors?: FeedsTabStates['errors']; isValid: boolean} {
|
||||
|
||||
@@ -56,7 +56,10 @@ const SetTagsModal: FC = () => {
|
||||
const tags = formData.tags ? formData.tags.split(',') : [];
|
||||
|
||||
setIsSettingTags(true);
|
||||
TorrentActions.setTags({hashes: TorrentStore.selectedTorrents, tags});
|
||||
TorrentActions.setTags({
|
||||
hashes: TorrentStore.selectedTorrents,
|
||||
tags,
|
||||
});
|
||||
},
|
||||
isLoading: isSettingTags,
|
||||
triggerDismiss: false,
|
||||
|
||||
@@ -14,7 +14,10 @@ const SetTrackersModal: FC = () => {
|
||||
const formRef = useRef<Form>(null);
|
||||
const intl = useIntl();
|
||||
const [isSettingTrackers, setIsSettingTrackers] = useState<boolean>(false);
|
||||
const [trackerState, setTrackerState] = useState<{isLoadingTrackers: boolean; trackerURLs: Array<string>}>({
|
||||
const [trackerState, setTrackerState] = useState<{
|
||||
isLoadingTrackers: boolean;
|
||||
trackerURLs: Array<string>;
|
||||
}>({
|
||||
isLoadingTrackers: false,
|
||||
trackerURLs: [],
|
||||
});
|
||||
@@ -58,7 +61,10 @@ const SetTrackersModal: FC = () => {
|
||||
defaultValues={
|
||||
trackerState.trackerURLs.length === 0
|
||||
? undefined
|
||||
: trackerState.trackerURLs.map((url, index) => ({id: index, value: url}))
|
||||
: trackerState.trackerURLs.map((url, index) => ({
|
||||
id: index,
|
||||
value: url,
|
||||
}))
|
||||
}
|
||||
/>
|
||||
)}
|
||||
@@ -85,7 +91,10 @@ const SetTrackersModal: FC = () => {
|
||||
const formData = formRef.current.getFormData() as Record<string, string>;
|
||||
const trackers = getTextArray(formData, 'trackers').filter((tracker) => tracker !== '');
|
||||
|
||||
TorrentActions.setTrackers({hashes: TorrentStore.selectedTorrents, trackers}).then(() => {
|
||||
TorrentActions.setTrackers({
|
||||
hashes: TorrentStore.selectedTorrents,
|
||||
trackers,
|
||||
}).then(() => {
|
||||
setIsSettingTrackers(false);
|
||||
UIStore.dismissModal();
|
||||
});
|
||||
|
||||
@@ -98,7 +98,10 @@ class AuthTab extends React.Component<WrappedComponentProps, AuthTabStates> {
|
||||
level: this.formData.isAdmin === true ? AccessLevel.ADMINISTRATOR : AccessLevel.USER,
|
||||
})
|
||||
.then(AuthActions.fetchUsers, (error) => {
|
||||
this.setState({addUserError: error.response.data.message, isAddingUser: false});
|
||||
this.setState({
|
||||
addUserError: error.response.data.message,
|
||||
isAddingUser: false,
|
||||
});
|
||||
})
|
||||
.then(() => {
|
||||
if (this.formRef != null) {
|
||||
|
||||
@@ -42,7 +42,9 @@ class UITab extends SettingsTab {
|
||||
|
||||
if (inputElement.type === 'radio') {
|
||||
this.torrentListViewSize = formData['ui-torrent-size'] as FloodSettings['torrentListViewSize'];
|
||||
this.props.onSettingsChange({torrentListViewSize: this.torrentListViewSize});
|
||||
this.props.onSettingsChange({
|
||||
torrentListViewSize: this.torrentListViewSize,
|
||||
});
|
||||
}
|
||||
|
||||
if (inputElement.name === 'language') {
|
||||
|
||||
@@ -51,7 +51,9 @@ class TorrentListColumnsList extends React.Component<TorrentListColumnsListProps
|
||||
};
|
||||
});
|
||||
|
||||
this.props.onSettingsChange({torrentListColumns: changedTorrentListColumns});
|
||||
this.props.onSettingsChange({
|
||||
torrentListColumns: changedTorrentListColumns,
|
||||
});
|
||||
this.setState({torrentListColumns: changedTorrentListColumns});
|
||||
};
|
||||
|
||||
|
||||
@@ -30,14 +30,18 @@ const TorrentHeading: FC = observer(() => {
|
||||
} else {
|
||||
setTorrentStatus('start');
|
||||
}
|
||||
}, []);
|
||||
}, [torrent?.status]);
|
||||
|
||||
if (torrent == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const torrentClasses = torrentStatusClasses(
|
||||
{status: torrent.status, upRate: torrent.upRate, downRate: torrent.downRate},
|
||||
{
|
||||
status: torrent.status,
|
||||
upRate: torrent.upRate,
|
||||
downRate: torrent.downRate,
|
||||
},
|
||||
'torrent-details__header',
|
||||
);
|
||||
const torrentStatusIcon = torrentStatusIcons(torrent.status);
|
||||
@@ -76,7 +80,10 @@ const TorrentHeading: FC = observer(() => {
|
||||
maxLevel={3}
|
||||
priorityType="torrent"
|
||||
onChange={(hash, level) => {
|
||||
TorrentActions.setPriority({hashes: [`${hash}`], priority: level});
|
||||
TorrentActions.setPriority({
|
||||
hashes: [`${hash}`],
|
||||
priority: level,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</li>
|
||||
|
||||
@@ -144,7 +144,11 @@ class NotificationsButton extends Component<WrappedComponentProps, Notifications
|
||||
|
||||
getNotification = (notification: Notification, index: number) => {
|
||||
const {intl} = this.props;
|
||||
const date = intl.formatDate(notification.ts, {year: 'numeric', month: 'long', day: '2-digit'});
|
||||
const date = intl.formatDate(notification.ts, {
|
||||
year: 'numeric',
|
||||
month: 'long',
|
||||
day: '2-digit',
|
||||
});
|
||||
const time = intl.formatTime(notification.ts);
|
||||
|
||||
return (
|
||||
@@ -160,7 +164,9 @@ class NotificationsButton extends Component<WrappedComponentProps, Notifications
|
||||
</div>
|
||||
<div className="notification__message">
|
||||
{intl.formatMessage(
|
||||
MESSAGES[`${notification.id}.body` as keyof typeof MESSAGES] || {id: 'general.error.unknown'},
|
||||
MESSAGES[`${notification.id}.body` as keyof typeof MESSAGES] || {
|
||||
id: 'general.error.unknown',
|
||||
},
|
||||
notification.data,
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -18,7 +18,11 @@ const Sidebar: FC = () => {
|
||||
return (
|
||||
<OverlayScrollbarsComponent
|
||||
options={{
|
||||
scrollbars: {autoHide: 'scroll', clickScrolling: false, dragScrolling: false},
|
||||
scrollbars: {
|
||||
autoHide: 'scroll',
|
||||
clickScrolling: false,
|
||||
dragScrolling: false,
|
||||
},
|
||||
className: 'application__sidebar os-theme-thin',
|
||||
}}>
|
||||
<SidebarActions>
|
||||
|
||||
@@ -49,7 +49,13 @@ class TransferRateGraph extends React.Component<TransferRateGraphProps> {
|
||||
inspectPoint?: Selection<SVGCircleElement, unknown, HTMLElement, unknown>;
|
||||
rateLine?: Selection<SVGPathElement, unknown, HTMLElement, unknown>;
|
||||
}
|
||||
> = {graph: null, areDefined: false, isHovered: false, download: {}, upload: {}};
|
||||
> = {
|
||||
graph: null,
|
||||
areDefined: false,
|
||||
isHovered: false,
|
||||
download: {},
|
||||
upload: {},
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
width: 240,
|
||||
|
||||
@@ -53,7 +53,11 @@ const TorrentDropzone: FC<{children: ReactNode}> = ({children}: {children: React
|
||||
reader.readAsDataURL(file);
|
||||
});
|
||||
};
|
||||
const {getRootProps, isDragActive} = useDropzone({onDrop: handleFileDrop, noClick: true, noKeyboard: true});
|
||||
const {getRootProps, isDragActive} = useDropzone({
|
||||
onDrop: handleFileDrop,
|
||||
noClick: true,
|
||||
noKeyboard: true,
|
||||
});
|
||||
|
||||
return (
|
||||
<div
|
||||
@@ -96,7 +100,10 @@ class TorrentList extends Component<WrappedComponentProps> {
|
||||
listHeaderRef = createRef<HTMLDivElement>();
|
||||
listViewportRef = createRef<FixedSizeList>();
|
||||
|
||||
torrentListViewportSize = observable.object<{width: number; height: number}>({
|
||||
torrentListViewportSize = observable.object<{
|
||||
width: number;
|
||||
height: number;
|
||||
}>({
|
||||
width: window.innerWidth,
|
||||
height: window.innerHeight,
|
||||
});
|
||||
|
||||
@@ -81,7 +81,9 @@ class UIStore {
|
||||
dependencies: Dependencies = {};
|
||||
globalStyles: Array<string> = [];
|
||||
haveUIDependenciesResolved = false;
|
||||
styleElement: HTMLStyleElement & {styleSheet?: {cssText: string}} = this.createStyleElement();
|
||||
styleElement: HTMLStyleElement & {
|
||||
styleSheet?: {cssText: string};
|
||||
} = this.createStyleElement();
|
||||
|
||||
constructor() {
|
||||
makeAutoObservable(this);
|
||||
|
||||
@@ -39,7 +39,13 @@ export default class Button extends React.Component<ButtonProps> {
|
||||
getButtonContent() {
|
||||
const {children, addonPlacement} = this.props;
|
||||
const buttonContent = React.Children.toArray(children).reduce(
|
||||
(accumulator: {addonNodes: Array<React.ReactNode>; childNodes: Array<React.ReactNode>}, child) => {
|
||||
(
|
||||
accumulator: {
|
||||
addonNodes: Array<React.ReactNode>;
|
||||
childNodes: Array<React.ReactNode>;
|
||||
},
|
||||
child,
|
||||
) => {
|
||||
const childAsElement = child as React.ReactElement;
|
||||
if (childAsElement.type === FormElementAddon) {
|
||||
accumulator.addonNodes.push(
|
||||
|
||||
@@ -4,7 +4,10 @@ import FormRowItem from './FormRowItem';
|
||||
|
||||
import type {FormRowItemProps} from './FormRowItem';
|
||||
|
||||
export default class FormRowItemGroup extends React.Component<{label?: string; width?: FormRowItemProps['width']}> {
|
||||
export default class FormRowItemGroup extends React.Component<{
|
||||
label?: string;
|
||||
width?: FormRowItemProps['width'];
|
||||
}> {
|
||||
getLabel(): React.ReactNode {
|
||||
const {label} = this.props;
|
||||
|
||||
|
||||
@@ -3,6 +3,8 @@ import {createBrowserHistory} from 'history';
|
||||
import stringUtil from '@shared/util/stringUtil';
|
||||
import ConfigStore from '../stores/ConfigStore';
|
||||
|
||||
const history = createBrowserHistory({basename: stringUtil.withoutTrailingSlash(ConfigStore.baseURI)});
|
||||
const history = createBrowserHistory({
|
||||
basename: stringUtil.withoutTrailingSlash(ConfigStore.baseURI),
|
||||
});
|
||||
|
||||
export default history;
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
context('Login', () => {
|
||||
beforeEach(() => {
|
||||
cy.server();
|
||||
cy.route({method: 'GET', url: 'http://127.0.0.1:4200/api/auth/verify?*', response: {}, status: 401}).as(
|
||||
'verify-request',
|
||||
);
|
||||
cy.route({
|
||||
method: 'GET',
|
||||
url: 'http://127.0.0.1:4200/api/auth/verify?*',
|
||||
response: {},
|
||||
status: 401,
|
||||
}).as('verify-request');
|
||||
cy.visit('http://127.0.0.1:4200/login');
|
||||
cy.url().should('include', 'login');
|
||||
});
|
||||
|
||||
@@ -84,9 +84,12 @@ context('Register', () => {
|
||||
cy.get('.input--text[name="socket"]').type('/data/rtorrent.sock');
|
||||
|
||||
cy.server();
|
||||
cy.route({method: 'POST', url: 'http://127.0.0.1:4200/api/auth/register', response: {}, status: 500}).as(
|
||||
'register-request',
|
||||
);
|
||||
cy.route({
|
||||
method: 'POST',
|
||||
url: 'http://127.0.0.1:4200/api/auth/register',
|
||||
response: {},
|
||||
status: 500,
|
||||
}).as('register-request');
|
||||
|
||||
cy.get('.button[type="submit"]').click();
|
||||
|
||||
|
||||
270
package-lock.json
generated
270
package-lock.json
generated
@@ -107,10 +107,8 @@
|
||||
"http-errors": "^1.8.0",
|
||||
"jest": "^26.6.3",
|
||||
"js-file-download": "^0.4.12",
|
||||
"jsdoc": "^3.6.6",
|
||||
"jsonwebtoken": "^8.5.1",
|
||||
"lodash": "^4.17.20",
|
||||
"minami": "^1.2.3",
|
||||
"mini-css-extract-plugin": "^1.3.1",
|
||||
"mobx": "^6.0.4",
|
||||
"mobx-react": "^7.0.5",
|
||||
@@ -4518,12 +4516,6 @@
|
||||
"readable-stream": "^3.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/bluebird": {
|
||||
"version": "3.7.2",
|
||||
"resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz",
|
||||
"integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/bn.js": {
|
||||
"version": "5.1.3",
|
||||
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.1.3.tgz",
|
||||
@@ -5026,18 +5018,6 @@
|
||||
"integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/catharsis": {
|
||||
"version": "0.8.11",
|
||||
"resolved": "https://registry.npmjs.org/catharsis/-/catharsis-0.8.11.tgz",
|
||||
"integrity": "sha512-a+xUyMV7hD1BrDQA/3iPV7oc+6W26BgVJO05PGEoatMyIuPScQKsde6i3YorWX1qs+AZjnJ18NqdKoCtKiNh1g==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"lodash": "^4.17.14"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/chalk": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz",
|
||||
@@ -12878,73 +12858,11 @@
|
||||
"js-yaml": "bin/js-yaml.js"
|
||||
}
|
||||
},
|
||||
"node_modules/js2xmlparser": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/js2xmlparser/-/js2xmlparser-4.0.1.tgz",
|
||||
"integrity": "sha512-KrPTolcw6RocpYjdC7pL7v62e55q7qOMHvLX1UCLc5AAS8qeJ6nukarEJAF2KL2PZxlbGueEbINqZR2bDe/gUw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"xmlcreate": "^2.0.3"
|
||||
}
|
||||
},
|
||||
"node_modules/jsbn": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz",
|
||||
"integrity": "sha1-sBMHyym2GKHtJux56RH4A8TaAEA="
|
||||
},
|
||||
"node_modules/jsdoc": {
|
||||
"version": "3.6.6",
|
||||
"resolved": "https://registry.npmjs.org/jsdoc/-/jsdoc-3.6.6.tgz",
|
||||
"integrity": "sha512-znR99e1BHeyEkSvgDDpX0sTiTu+8aQyDl9DawrkOGZTTW8hv0deIFXx87114zJ7gRaDZKVQD/4tr1ifmJp9xhQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/parser": "^7.9.4",
|
||||
"bluebird": "^3.7.2",
|
||||
"catharsis": "^0.8.11",
|
||||
"escape-string-regexp": "^2.0.0",
|
||||
"js2xmlparser": "^4.0.1",
|
||||
"klaw": "^3.0.0",
|
||||
"markdown-it": "^10.0.0",
|
||||
"markdown-it-anchor": "^5.2.7",
|
||||
"marked": "^0.8.2",
|
||||
"mkdirp": "^1.0.4",
|
||||
"requizzle": "^0.2.3",
|
||||
"strip-json-comments": "^3.1.0",
|
||||
"taffydb": "2.6.2",
|
||||
"underscore": "~1.10.2"
|
||||
},
|
||||
"bin": {
|
||||
"jsdoc": "jsdoc.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8.15.0"
|
||||
}
|
||||
},
|
||||
"node_modules/jsdoc/node_modules/linkify-it": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-2.2.0.tgz",
|
||||
"integrity": "sha512-GnAl/knGn+i1U/wjBz3akz2stz+HrHLsxMwHQGofCDfPvlf+gDKN58UtfmUquTY4/MXeE2x7k19KQmeoZi94Iw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"uc.micro": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/jsdoc/node_modules/markdown-it": {
|
||||
"version": "10.0.0",
|
||||
"resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-10.0.0.tgz",
|
||||
"integrity": "sha512-YWOP1j7UbDNz+TumYP1kpwnP0aEa711cJjrAQrzd0UXlbJfc5aAq0F/PZHjiioqDC1NKgvIMX+o+9Bk7yuM2dg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"argparse": "^1.0.7",
|
||||
"entities": "~2.0.0",
|
||||
"linkify-it": "^2.0.0",
|
||||
"mdurl": "^1.0.1",
|
||||
"uc.micro": "^1.0.5"
|
||||
},
|
||||
"bin": {
|
||||
"markdown-it": "bin/markdown-it.js"
|
||||
}
|
||||
},
|
||||
"node_modules/jsdom": {
|
||||
"version": "16.4.0",
|
||||
"resolved": "https://registry.npmjs.org/jsdom/-/jsdom-16.4.0.tgz",
|
||||
@@ -13194,15 +13112,6 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/klaw": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/klaw/-/klaw-3.0.0.tgz",
|
||||
"integrity": "sha512-0Fo5oir+O9jnXu5EefYbVK+mHMBeEVEy2cmctR1O1NECcCkPRreJKrS6Qt/j3KC2C148Dfo9i3pCmCMsdqGr0g==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"graceful-fs": "^4.1.9"
|
||||
}
|
||||
},
|
||||
"node_modules/kleur": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz",
|
||||
@@ -13630,27 +13539,6 @@
|
||||
"markdown-it": "bin/markdown-it.js"
|
||||
}
|
||||
},
|
||||
"node_modules/markdown-it-anchor": {
|
||||
"version": "5.3.0",
|
||||
"resolved": "https://registry.npmjs.org/markdown-it-anchor/-/markdown-it-anchor-5.3.0.tgz",
|
||||
"integrity": "sha512-/V1MnLL/rgJ3jkMWo84UR+K+jF1cxNG1a+KwqeXqTIJ+jtA8aWSHuigx8lTzauiIjBDbwF3NcWQMotd0Dm39jA==",
|
||||
"dev": true,
|
||||
"peerDependencies": {
|
||||
"markdown-it": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/marked": {
|
||||
"version": "0.8.2",
|
||||
"resolved": "https://registry.npmjs.org/marked/-/marked-0.8.2.tgz",
|
||||
"integrity": "sha512-EGwzEeCcLniFX51DhTpmTom+dSA/MG/OBUDjnWtHbEnjAH180VzUeAw+oE4+Zv+CoYBWyRlYOTR0N8SO9R1PVw==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"marked": "bin/marked"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 8.16.2"
|
||||
}
|
||||
},
|
||||
"node_modules/md5.js": {
|
||||
"version": "1.3.5",
|
||||
"resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz",
|
||||
@@ -13963,12 +13851,6 @@
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/minami": {
|
||||
"version": "1.2.3",
|
||||
"resolved": "https://registry.npmjs.org/minami/-/minami-1.2.3.tgz",
|
||||
"integrity": "sha1-mbbc37LwpU2hycj3qjoyd4eq+fg=",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/mini-create-react-context": {
|
||||
"version": "0.4.1",
|
||||
"resolved": "https://registry.npmjs.org/mini-create-react-context/-/mini-create-react-context-0.4.1.tgz",
|
||||
@@ -20886,15 +20768,6 @@
|
||||
"integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/requizzle": {
|
||||
"version": "0.2.3",
|
||||
"resolved": "https://registry.npmjs.org/requizzle/-/requizzle-0.2.3.tgz",
|
||||
"integrity": "sha512-YanoyJjykPxGHii0fZP0uUPEXpvqfBDxWV7s6GKAiiOsiqhX6vHNyW3Qzdmqp/iq/ExbhaGbVrjB4ruEVSM4GQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"lodash": "^4.17.14"
|
||||
}
|
||||
},
|
||||
"node_modules/resize-observer-polyfill": {
|
||||
"version": "1.5.1",
|
||||
"resolved": "https://registry.npmjs.org/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz",
|
||||
@@ -23863,12 +23736,6 @@
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/taffydb": {
|
||||
"version": "2.6.2",
|
||||
"resolved": "https://registry.npmjs.org/taffydb/-/taffydb-2.6.2.tgz",
|
||||
"integrity": "sha1-fLy2S1oUG2ou/CxdLGe04VCyomg=",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/tapable": {
|
||||
"version": "0.1.10",
|
||||
"resolved": "https://registry.npmjs.org/tapable/-/tapable-0.1.10.tgz",
|
||||
@@ -24601,12 +24468,6 @@
|
||||
"integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/underscore": {
|
||||
"version": "1.10.2",
|
||||
"resolved": "https://registry.npmjs.org/underscore/-/underscore-1.10.2.tgz",
|
||||
"integrity": "sha512-N4P+Q/BuyuEKFJ43B9gYuOj4TQUHXX+j2FqguVOpjkssLUUrnJofCcBccJSCoeturDoZU6GorDTHSvUDlSQbTg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/unicode-canonical-property-names-ecmascript": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz",
|
||||
@@ -26355,12 +26216,6 @@
|
||||
"integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/xmlcreate": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/xmlcreate/-/xmlcreate-2.0.3.tgz",
|
||||
"integrity": "sha512-HgS+X6zAztGa9zIK3Y3LXuJes33Lz9x+YyTxgrkIdabu2vqcGOWwdfCpf1hWLRrd553wd4QCDf6BBO6FfdsRiQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/xmlrpc": {
|
||||
"version": "1.3.2",
|
||||
"resolved": "https://registry.npmjs.org/xmlrpc/-/xmlrpc-1.3.2.tgz",
|
||||
@@ -30204,12 +30059,6 @@
|
||||
"readable-stream": "^3.4.0"
|
||||
}
|
||||
},
|
||||
"bluebird": {
|
||||
"version": "3.7.2",
|
||||
"resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz",
|
||||
"integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==",
|
||||
"dev": true
|
||||
},
|
||||
"bn.js": {
|
||||
"version": "5.1.3",
|
||||
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.1.3.tgz",
|
||||
@@ -30655,15 +30504,6 @@
|
||||
"integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=",
|
||||
"dev": true
|
||||
},
|
||||
"catharsis": {
|
||||
"version": "0.8.11",
|
||||
"resolved": "https://registry.npmjs.org/catharsis/-/catharsis-0.8.11.tgz",
|
||||
"integrity": "sha512-a+xUyMV7hD1BrDQA/3iPV7oc+6W26BgVJO05PGEoatMyIuPScQKsde6i3YorWX1qs+AZjnJ18NqdKoCtKiNh1g==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"lodash": "^4.17.14"
|
||||
}
|
||||
},
|
||||
"chalk": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz",
|
||||
@@ -36909,66 +36749,11 @@
|
||||
"esprima": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"js2xmlparser": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/js2xmlparser/-/js2xmlparser-4.0.1.tgz",
|
||||
"integrity": "sha512-KrPTolcw6RocpYjdC7pL7v62e55q7qOMHvLX1UCLc5AAS8qeJ6nukarEJAF2KL2PZxlbGueEbINqZR2bDe/gUw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"xmlcreate": "^2.0.3"
|
||||
}
|
||||
},
|
||||
"jsbn": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz",
|
||||
"integrity": "sha1-sBMHyym2GKHtJux56RH4A8TaAEA="
|
||||
},
|
||||
"jsdoc": {
|
||||
"version": "3.6.6",
|
||||
"resolved": "https://registry.npmjs.org/jsdoc/-/jsdoc-3.6.6.tgz",
|
||||
"integrity": "sha512-znR99e1BHeyEkSvgDDpX0sTiTu+8aQyDl9DawrkOGZTTW8hv0deIFXx87114zJ7gRaDZKVQD/4tr1ifmJp9xhQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/parser": "^7.9.4",
|
||||
"bluebird": "^3.7.2",
|
||||
"catharsis": "^0.8.11",
|
||||
"escape-string-regexp": "^2.0.0",
|
||||
"js2xmlparser": "^4.0.1",
|
||||
"klaw": "^3.0.0",
|
||||
"markdown-it": "^10.0.0",
|
||||
"markdown-it-anchor": "^5.2.7",
|
||||
"marked": "^0.8.2",
|
||||
"mkdirp": "^1.0.4",
|
||||
"requizzle": "^0.2.3",
|
||||
"strip-json-comments": "^3.1.0",
|
||||
"taffydb": "2.6.2",
|
||||
"underscore": "~1.10.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"linkify-it": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-2.2.0.tgz",
|
||||
"integrity": "sha512-GnAl/knGn+i1U/wjBz3akz2stz+HrHLsxMwHQGofCDfPvlf+gDKN58UtfmUquTY4/MXeE2x7k19KQmeoZi94Iw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"uc.micro": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"markdown-it": {
|
||||
"version": "10.0.0",
|
||||
"resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-10.0.0.tgz",
|
||||
"integrity": "sha512-YWOP1j7UbDNz+TumYP1kpwnP0aEa711cJjrAQrzd0UXlbJfc5aAq0F/PZHjiioqDC1NKgvIMX+o+9Bk7yuM2dg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"argparse": "^1.0.7",
|
||||
"entities": "~2.0.0",
|
||||
"linkify-it": "^2.0.0",
|
||||
"mdurl": "^1.0.1",
|
||||
"uc.micro": "^1.0.5"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"jsdom": {
|
||||
"version": "16.4.0",
|
||||
"resolved": "https://registry.npmjs.org/jsdom/-/jsdom-16.4.0.tgz",
|
||||
@@ -37172,15 +36957,6 @@
|
||||
"integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==",
|
||||
"dev": true
|
||||
},
|
||||
"klaw": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/klaw/-/klaw-3.0.0.tgz",
|
||||
"integrity": "sha512-0Fo5oir+O9jnXu5EefYbVK+mHMBeEVEy2cmctR1O1NECcCkPRreJKrS6Qt/j3KC2C148Dfo9i3pCmCMsdqGr0g==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"graceful-fs": "^4.1.9"
|
||||
}
|
||||
},
|
||||
"kleur": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz",
|
||||
@@ -37544,19 +37320,6 @@
|
||||
"uc.micro": "^1.0.5"
|
||||
}
|
||||
},
|
||||
"markdown-it-anchor": {
|
||||
"version": "5.3.0",
|
||||
"resolved": "https://registry.npmjs.org/markdown-it-anchor/-/markdown-it-anchor-5.3.0.tgz",
|
||||
"integrity": "sha512-/V1MnLL/rgJ3jkMWo84UR+K+jF1cxNG1a+KwqeXqTIJ+jtA8aWSHuigx8lTzauiIjBDbwF3NcWQMotd0Dm39jA==",
|
||||
"dev": true,
|
||||
"requires": {}
|
||||
},
|
||||
"marked": {
|
||||
"version": "0.8.2",
|
||||
"resolved": "https://registry.npmjs.org/marked/-/marked-0.8.2.tgz",
|
||||
"integrity": "sha512-EGwzEeCcLniFX51DhTpmTom+dSA/MG/OBUDjnWtHbEnjAH180VzUeAw+oE4+Zv+CoYBWyRlYOTR0N8SO9R1PVw==",
|
||||
"dev": true
|
||||
},
|
||||
"md5.js": {
|
||||
"version": "1.3.5",
|
||||
"resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz",
|
||||
@@ -37809,12 +37572,6 @@
|
||||
"integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==",
|
||||
"dev": true
|
||||
},
|
||||
"minami": {
|
||||
"version": "1.2.3",
|
||||
"resolved": "https://registry.npmjs.org/minami/-/minami-1.2.3.tgz",
|
||||
"integrity": "sha1-mbbc37LwpU2hycj3qjoyd4eq+fg=",
|
||||
"dev": true
|
||||
},
|
||||
"mini-create-react-context": {
|
||||
"version": "0.4.1",
|
||||
"resolved": "https://registry.npmjs.org/mini-create-react-context/-/mini-create-react-context-0.4.1.tgz",
|
||||
@@ -43240,15 +42997,6 @@
|
||||
"integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=",
|
||||
"dev": true
|
||||
},
|
||||
"requizzle": {
|
||||
"version": "0.2.3",
|
||||
"resolved": "https://registry.npmjs.org/requizzle/-/requizzle-0.2.3.tgz",
|
||||
"integrity": "sha512-YanoyJjykPxGHii0fZP0uUPEXpvqfBDxWV7s6GKAiiOsiqhX6vHNyW3Qzdmqp/iq/ExbhaGbVrjB4ruEVSM4GQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"lodash": "^4.17.14"
|
||||
}
|
||||
},
|
||||
"resize-observer-polyfill": {
|
||||
"version": "1.5.1",
|
||||
"resolved": "https://registry.npmjs.org/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz",
|
||||
@@ -45675,12 +45423,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"taffydb": {
|
||||
"version": "2.6.2",
|
||||
"resolved": "https://registry.npmjs.org/taffydb/-/taffydb-2.6.2.tgz",
|
||||
"integrity": "sha1-fLy2S1oUG2ou/CxdLGe04VCyomg=",
|
||||
"dev": true
|
||||
},
|
||||
"tapable": {
|
||||
"version": "0.1.10",
|
||||
"resolved": "https://registry.npmjs.org/tapable/-/tapable-0.1.10.tgz",
|
||||
@@ -46247,12 +45989,6 @@
|
||||
"integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==",
|
||||
"dev": true
|
||||
},
|
||||
"underscore": {
|
||||
"version": "1.10.2",
|
||||
"resolved": "https://registry.npmjs.org/underscore/-/underscore-1.10.2.tgz",
|
||||
"integrity": "sha512-N4P+Q/BuyuEKFJ43B9gYuOj4TQUHXX+j2FqguVOpjkssLUUrnJofCcBccJSCoeturDoZU6GorDTHSvUDlSQbTg==",
|
||||
"dev": true
|
||||
},
|
||||
"unicode-canonical-property-names-ecmascript": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz",
|
||||
@@ -47653,12 +47389,6 @@
|
||||
"integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==",
|
||||
"dev": true
|
||||
},
|
||||
"xmlcreate": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/xmlcreate/-/xmlcreate-2.0.3.tgz",
|
||||
"integrity": "sha512-HgS+X6zAztGa9zIK3Y3LXuJes33Lz9x+YyTxgrkIdabu2vqcGOWwdfCpf1hWLRrd553wd4QCDf6BBO6FfdsRiQ==",
|
||||
"dev": true
|
||||
},
|
||||
"xmlrpc": {
|
||||
"version": "1.3.2",
|
||||
"resolved": "https://registry.npmjs.org/xmlrpc/-/xmlrpc-1.3.2.tgz",
|
||||
|
||||
@@ -31,9 +31,7 @@
|
||||
"build": "npm run build-assets && npm run build-ts",
|
||||
"build-assets": "node client/scripts/build.js",
|
||||
"build-ts": "ncc build server/bin/start.ts -m -t -e geoip-country",
|
||||
"build-docs": "jsdoc -c ./.jsdoc.json",
|
||||
"build-i18n": "formatjs compile --ast --format simple client/src/javascript/i18n/strings.json --out-file client/src/javascript/i18n/strings.compiled.json && formatjs compile-folder --ast --format simple client/src/javascript/i18n/translations client/src/javascript/i18n/compiled",
|
||||
"deprecated-warning": "node client/scripts/deprecated-warning.js && sleep 10",
|
||||
"format-source": "prettier -w .",
|
||||
"check-compiled-i18n": "npm run build-i18n && npm run format-source && git diff --quiet client/src/javascript/i18n/compiled",
|
||||
"check-source-formatting": "prettier -c .",
|
||||
@@ -44,8 +42,6 @@
|
||||
"start:development": "UPDATED_SCRIPT=start:development:server npm run deprecated-warning && npm run start:development:server",
|
||||
"start:development:client": "node client/scripts/start.js",
|
||||
"start:development:server": "NODE_ENV=development TS_NODE_PROJECT=server/tsconfig.json ts-node-dev --transpile-only server/bin/start.ts",
|
||||
"start:production": "UPDATED_SCRIPT=start npm run deprecated-warning && npm start",
|
||||
"start:watch": "UPDATED_SCRIPT=start:development:client npm run deprecated-warning && npm run start:development:client",
|
||||
"test": "jest --forceExit",
|
||||
"test:watch": "jest --watchAll --forceExit",
|
||||
"test:client": "FLOOD_OPTION_port=4200 start-server-and-test start 4200 'cypress run'"
|
||||
@@ -147,10 +143,8 @@
|
||||
"http-errors": "^1.8.0",
|
||||
"jest": "^26.6.3",
|
||||
"js-file-download": "^0.4.12",
|
||||
"jsdoc": "^3.6.6",
|
||||
"jsonwebtoken": "^8.5.1",
|
||||
"lodash": "^4.17.20",
|
||||
"minami": "^1.2.3",
|
||||
"mini-css-extract-plugin": "^1.3.1",
|
||||
"mobx": "^6.0.4",
|
||||
"mobx-react": "^7.0.5",
|
||||
@@ -202,9 +196,6 @@
|
||||
"yargs": "^16.1.0",
|
||||
"zod": "^1.11.10"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": "react-app"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.0.0",
|
||||
"npm": ">=6.0.0"
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
module.exports = {
|
||||
extends: '../.eslintrc',
|
||||
|
||||
env: {
|
||||
browser: 0,
|
||||
node: 1,
|
||||
},
|
||||
|
||||
rules: {
|
||||
'no-console': 0,
|
||||
'no-restricted-imports': [
|
||||
'error',
|
||||
{
|
||||
patterns: ['**/client/**/*'],
|
||||
},
|
||||
],
|
||||
'no-restricted-modules': [
|
||||
'error',
|
||||
{
|
||||
patterns: ['**/client/**/*'],
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
21
server/.eslintrc.json
Normal file
21
server/.eslintrc.json
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"extends": "../.eslintrc.json",
|
||||
|
||||
"rules": {
|
||||
"no-restricted-imports": [
|
||||
"error",
|
||||
{
|
||||
"patterns": ["**/client/**/*"]
|
||||
}
|
||||
],
|
||||
"no-restricted-modules": [
|
||||
"error",
|
||||
{
|
||||
"patterns": ["**/client/**/*"]
|
||||
}
|
||||
],
|
||||
// TODO: Explicit return type
|
||||
"@typescript-eslint/explicit-function-return-type": 0,
|
||||
"@typescript-eslint/explicit-module-boundary-types": 0
|
||||
}
|
||||
}
|
||||
@@ -50,7 +50,9 @@ app.use(`${paths.servedPath}api`, apiRoutes);
|
||||
app.use(paths.servedPath, express.static(paths.appDist));
|
||||
|
||||
// Client app routes, serve index.html and client js will figure it out
|
||||
const html = fs.readFileSync(path.join(paths.appDist, 'index.html'), {encoding: 'utf8'});
|
||||
const html = fs.readFileSync(path.join(paths.appDist, 'index.html'), {
|
||||
encoding: 'utf8',
|
||||
});
|
||||
|
||||
app.get(`${paths.servedPath}login`, (_req, res) => {
|
||||
res.send(html);
|
||||
|
||||
@@ -11,7 +11,7 @@ enforcePrerequisites()
|
||||
.then(migrateData)
|
||||
.then(() => {
|
||||
// We do this because we don't want the side effects of importing server functions before migration is completed.
|
||||
const startWebServer = require('./web-server').default; // eslint-disable-line global-require
|
||||
const startWebServer = require('./web-server').default; // eslint-disable-line @typescript-eslint/no-var-requires
|
||||
return startWebServer();
|
||||
})
|
||||
.catch((error) => {
|
||||
|
||||
@@ -85,7 +85,11 @@ export default async (req: Request<unknown, unknown, unknown, {historySnapshot:
|
||||
if (error == null && snapshot != null && lastTimestamp != null) {
|
||||
serverEvent.emit(lastTimestamp, 'TRANSFER_HISTORY_FULL_UPDATE', snapshot);
|
||||
} else {
|
||||
const fallbackHistory: TransferHistory = {download: [0], upload: [0], timestamps: [Date.now()]};
|
||||
const fallbackHistory: TransferHistory = {
|
||||
download: [0],
|
||||
upload: [0],
|
||||
timestamps: [Date.now()],
|
||||
};
|
||||
serverEvent.emit(Date.now(), 'TRANSFER_HISTORY_FULL_UPDATE', fallbackHistory);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -71,7 +71,10 @@ class Users {
|
||||
return;
|
||||
}
|
||||
|
||||
argon2Verify({password: credentials.password, hash: user.password}).then(
|
||||
argon2Verify({
|
||||
password: credentials.password,
|
||||
hash: user.password,
|
||||
}).then(
|
||||
(isMatch) => {
|
||||
if (isMatch) {
|
||||
resolve(user.level);
|
||||
|
||||
@@ -50,7 +50,11 @@ export const getAuthToken = (username: string, res?: Response): string => {
|
||||
});
|
||||
|
||||
if (res != null) {
|
||||
res.cookie('jwt', token, {expires: new Date(cookieExpiration), httpOnly: true, sameSite: 'strict'});
|
||||
res.cookie('jwt', token, {
|
||||
expires: new Date(cookieExpiration),
|
||||
httpOnly: true,
|
||||
sameSite: 'strict',
|
||||
});
|
||||
}
|
||||
|
||||
return token;
|
||||
|
||||
@@ -99,7 +99,10 @@ describe('POST /api/torrents/add-urls', () => {
|
||||
it('Adds torrents to disallowed path via URLs', (done) => {
|
||||
request
|
||||
.post('/api/torrents/add-urls')
|
||||
.send({...addTorrentByURLOptions, destination: path.join(os.tmpdir(), 'notAllowed')})
|
||||
.send({
|
||||
...addTorrentByURLOptions,
|
||||
destination: path.join(os.tmpdir(), 'notAllowed'),
|
||||
})
|
||||
.set('Cookie', [authToken])
|
||||
.set('Accept', 'application/json')
|
||||
.expect(500)
|
||||
@@ -198,7 +201,10 @@ describe('POST /api/torrents/add-files', () => {
|
||||
it('Adds torrents to disallowed path via files', (done) => {
|
||||
request
|
||||
.post('/api/torrents/add-files')
|
||||
.send({...addTorrentByFileOptions, destination: path.join(os.tmpdir(), 'notAllowed')})
|
||||
.send({
|
||||
...addTorrentByFileOptions,
|
||||
destination: path.join(os.tmpdir(), 'notAllowed'),
|
||||
})
|
||||
.set('Cookie', [authToken])
|
||||
.set('Accept', 'application/json')
|
||||
.expect(500)
|
||||
|
||||
@@ -566,7 +566,15 @@ router.get('/:hash/contents/:indices/data', (req, res) => {
|
||||
|
||||
res.attachment(`${selectedTorrent.name}.tar`);
|
||||
return tar
|
||||
.c({cwd: archiveRootFolder, follow: false, noDirRecurse: true, portable: true}, relativeFilePaths)
|
||||
.c(
|
||||
{
|
||||
cwd: archiveRootFolder,
|
||||
follow: false,
|
||||
noDirRecurse: true,
|
||||
portable: true,
|
||||
},
|
||||
relativeFilePaths,
|
||||
)
|
||||
.pipe(res);
|
||||
});
|
||||
} catch (error) {
|
||||
|
||||
@@ -5,7 +5,9 @@ import type {UserInDatabase} from '@shared/schema/Auth';
|
||||
|
||||
import type {UserServices} from '.';
|
||||
|
||||
class BaseService<E = unknown> extends (EventEmitter as {new <T>(): TypedEmitter<T>})<E> {
|
||||
class BaseService<E = unknown> extends (EventEmitter as {
|
||||
new <T>(): TypedEmitter<T>;
|
||||
})<E> {
|
||||
user: UserInDatabase;
|
||||
services?: UserServices;
|
||||
|
||||
|
||||
@@ -39,7 +39,11 @@ class TransmissionClientGatewayService extends ClientGatewayService {
|
||||
files.map(async (file) => {
|
||||
const {hashString} =
|
||||
(await this.clientRequestManager
|
||||
.addTorrent({metainfo: file, 'download-dir': destination, paused: !start})
|
||||
.addTorrent({
|
||||
metainfo: file,
|
||||
'download-dir': destination,
|
||||
paused: !start,
|
||||
})
|
||||
.then(this.processClientRequestSuccess, this.processClientRequestError)
|
||||
.catch(() => undefined)) || {};
|
||||
return hashString;
|
||||
@@ -197,7 +201,10 @@ class TransmissionClientGatewayService extends ClientGatewayService {
|
||||
}
|
||||
|
||||
return this.clientRequestManager
|
||||
.setTorrentsProperties({ids: hashes, bandwidthPriority: transmissionPriority})
|
||||
.setTorrentsProperties({
|
||||
ids: hashes,
|
||||
bandwidthPriority: transmissionPriority,
|
||||
})
|
||||
.then(this.processClientRequestSuccess, this.processClientRequestError);
|
||||
}
|
||||
|
||||
|
||||
@@ -266,7 +266,10 @@ class ClientRequestManager {
|
||||
return axios
|
||||
.post(
|
||||
this.rpcURL,
|
||||
{method: 'torrent-set-location', arguments: torrentsSetLocationArguments},
|
||||
{
|
||||
method: 'torrent-set-location',
|
||||
arguments: torrentsSetLocationArguments,
|
||||
},
|
||||
{
|
||||
headers: await this.getRequestHeaders(),
|
||||
},
|
||||
|
||||
@@ -297,7 +297,10 @@ class FeedService extends BaseService {
|
||||
}
|
||||
|
||||
// Create two arrays, one for feeds and one for rules.
|
||||
const feedsSummary: {feeds: Array<Feed>; rules: Array<Rule>} = docs.reduce(
|
||||
const feedsSummary: {
|
||||
feeds: Array<Feed>;
|
||||
rules: Array<Rule>;
|
||||
} = docs.reduce(
|
||||
(accumulator: {feeds: Array<Feed>; rules: Array<Rule>}, doc) => {
|
||||
if (doc.type === 'feed') {
|
||||
accumulator.feeds.push(doc);
|
||||
@@ -404,7 +407,14 @@ class FeedService extends BaseService {
|
||||
}
|
||||
|
||||
this.feedReaders.push(
|
||||
new FeedReader({feedID, feedLabel, url, interval, maxHistory: 100, onNewItems: this.handleNewItems}),
|
||||
new FeedReader({
|
||||
feedID,
|
||||
feedLabel,
|
||||
url,
|
||||
interval,
|
||||
maxHistory: 100,
|
||||
onNewItems: this.handleNewItems,
|
||||
}),
|
||||
);
|
||||
|
||||
return true;
|
||||
|
||||
@@ -86,7 +86,13 @@ class NotificationService extends BaseService<NotificationServiceEvents> {
|
||||
|
||||
getNotifications(
|
||||
query: NotificationFetchOptions,
|
||||
callback: (data: {notifications: Notification[][]; count: NotificationCount} | null, err?: Error) => void,
|
||||
callback: (
|
||||
data: {
|
||||
notifications: Notification[][];
|
||||
count: NotificationCount;
|
||||
} | null,
|
||||
err?: Error,
|
||||
) => void,
|
||||
) {
|
||||
const sortedNotifications = this.db.find({}).sort({ts: -1});
|
||||
const queryCallback = (err: Error | null, notifications: Notification[][]) => {
|
||||
|
||||
@@ -205,7 +205,10 @@ class ClientRequestManager {
|
||||
form.append(property, `${options[property]}`);
|
||||
});
|
||||
|
||||
const headers = form.getHeaders({Cookie: await this.authCookie, 'Content-Length': form.getLengthSync()});
|
||||
const headers = form.getHeaders({
|
||||
Cookie: await this.authCookie,
|
||||
'Content-Length': form.getLengthSync(),
|
||||
});
|
||||
|
||||
axios
|
||||
.post(`${this.apiBase}/torrents/add`, form, {
|
||||
@@ -226,7 +229,10 @@ class ClientRequestManager {
|
||||
form.append(property, `${options[property]}`);
|
||||
});
|
||||
|
||||
const headers = form.getHeaders({Cookie: await this.authCookie, 'Content-Length': form.getLengthSync()});
|
||||
const headers = form.getHeaders({
|
||||
Cookie: await this.authCookie,
|
||||
'Content-Length': form.getLengthSync(),
|
||||
});
|
||||
|
||||
axios
|
||||
.post(`${this.apiBase}/torrents/add`, form, {
|
||||
|
||||
@@ -48,7 +48,9 @@ import {
|
||||
|
||||
import type {MultiMethodCalls} from './util/rTorrentMethodCallUtil';
|
||||
|
||||
const filePathMethodCalls = getMethodCalls({pathComponents: torrentContentMethodCallConfigs.pathComponents});
|
||||
const filePathMethodCalls = getMethodCalls({
|
||||
pathComponents: torrentContentMethodCallConfigs.pathComponents,
|
||||
});
|
||||
|
||||
class RTorrentClientGatewayService extends ClientGatewayService {
|
||||
clientRequestManager = new ClientRequestManager(this.user.client as RTorrentConnectionSettings);
|
||||
@@ -63,7 +65,9 @@ class RTorrentClientGatewayService extends ClientGatewayService {
|
||||
}: Required<AddTorrentByFileOptions>): Promise<void> {
|
||||
const torrentPaths = await Promise.all(
|
||||
files.map(async (file) => {
|
||||
return saveBufferToTempFile(Buffer.from(file, 'base64'), 'torrent', {mode: 0o664});
|
||||
return saveBufferToTempFile(Buffer.from(file, 'base64'), 'torrent', {
|
||||
mode: 0o664,
|
||||
});
|
||||
}),
|
||||
);
|
||||
|
||||
|
||||
@@ -7,7 +7,10 @@ export type MethodCallConfigs = Readonly<{
|
||||
[propLabel: string]: MethodCallConfig;
|
||||
}>;
|
||||
|
||||
export type MultiMethodCalls = Array<{methodName: string; params: Array<string | Buffer>}>;
|
||||
export type MultiMethodCalls = Array<{
|
||||
methodName: string;
|
||||
params: Array<string | Buffer>;
|
||||
}>;
|
||||
|
||||
export const stringTransformer = (value: unknown): string => {
|
||||
return value as string;
|
||||
|
||||
@@ -99,7 +99,10 @@ class TorrentService extends BaseService<TorrentServiceEvents> {
|
||||
handleFetchTorrentListSuccess = (nextTorrentListSummary: this['torrentListSummary']) => {
|
||||
const diff = jsonpatch.compare(this.torrentListSummary.torrents, nextTorrentListSummary.torrents);
|
||||
if (diff.length > 0) {
|
||||
this.emit('TORRENT_LIST_DIFF_CHANGE', {diff, id: nextTorrentListSummary.id});
|
||||
this.emit('TORRENT_LIST_DIFF_CHANGE', {
|
||||
diff,
|
||||
id: nextTorrentListSummary.id,
|
||||
});
|
||||
}
|
||||
|
||||
this.torrentListSummary = nextTorrentListSummary;
|
||||
|
||||
@@ -20,7 +20,7 @@ const delayedDelete = (tempPath: string): void => {
|
||||
setTimeout(() => {
|
||||
try {
|
||||
fs.unlinkSync(tempPath);
|
||||
} catch (_e) {
|
||||
} catch {
|
||||
// do nothing.
|
||||
}
|
||||
}, 1000 * 60 * 5);
|
||||
|
||||
@@ -11,7 +11,7 @@ const openAndDecodeTorrent = async (torrentPath: string): Promise<TorrentFile |
|
||||
|
||||
try {
|
||||
torrentData = bencode.decode(fs.readFileSync(torrentPath));
|
||||
} catch (_e) {
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -59,7 +59,7 @@ export const setTrackers = async (torrent: string, trackers: Array<string>): Pro
|
||||
|
||||
try {
|
||||
fs.writeFileSync(torrent, bencode.encode(torrentData));
|
||||
} catch (_e) {
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -140,7 +140,7 @@ export const setCompleted = async (torrent: string, destination: string, isBaseP
|
||||
|
||||
try {
|
||||
fs.writeFileSync(torrent, bencode.encode(torrentDataWithResume));
|
||||
} catch (_e) {
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,10 @@ import type {AuthMethod} from '../Auth';
|
||||
// All auth requests are schema validated to ensure security.
|
||||
|
||||
// POST /api/auth/authenticate
|
||||
export const authAuthenticationSchema = credentialsSchema.pick({username: true, password: true});
|
||||
export const authAuthenticationSchema = credentialsSchema.pick({
|
||||
username: true,
|
||||
password: true,
|
||||
});
|
||||
export type AuthAuthenticationOptions = Required<zodInfer<typeof authAuthenticationSchema>>;
|
||||
|
||||
// POST /api/auth/authenticate - success response
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
// eslint-disable-next-line import/prefer-default-export
|
||||
export enum AccessLevel {
|
||||
USER = 5,
|
||||
ADMINISTRATOR = 10,
|
||||
|
||||
@@ -1,2 +1 @@
|
||||
// eslint-disable-next-line import/prefer-default-export
|
||||
export const SUPPORTED_CLIENTS = ['qBittorrent', 'rTorrent', 'Transmission'] as const;
|
||||
|
||||
@@ -3,16 +3,6 @@ import type {TorrentPeer} from './TorrentPeer';
|
||||
import type {TorrentStatus} from '../constants/torrentStatusMap';
|
||||
import type {TorrentTracker} from './TorrentTracker';
|
||||
|
||||
export interface Duration {
|
||||
years?: number;
|
||||
weeks?: number;
|
||||
days?: number;
|
||||
hours?: number;
|
||||
minutes?: number;
|
||||
seconds?: number;
|
||||
cumSeconds: number;
|
||||
}
|
||||
|
||||
export interface TorrentDetails {
|
||||
contents: Array<TorrentContent>;
|
||||
peers: Array<TorrentPeer>;
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
const formatUtil = {
|
||||
secondsToDuration: (cumSeconds: number) => {
|
||||
const years = Math.floor(cumSeconds / 31536000);
|
||||
const weeks = Math.floor((cumSeconds % 31536000) / 604800);
|
||||
const days = Math.floor(((cumSeconds % 31536000) % 604800) / 86400);
|
||||
const hours = Math.floor((((cumSeconds % 31536000) % 604800) % 86400) / 3600);
|
||||
const minutes = Math.floor(((((cumSeconds % 31536000) % 604800) % 86400) % 3600) / 60);
|
||||
const seconds = Math.floor(cumSeconds - minutes * 60);
|
||||
let timeRemaining = null;
|
||||
|
||||
if (years > 0) {
|
||||
timeRemaining = {years, weeks, cumSeconds};
|
||||
} else if (weeks > 0) {
|
||||
timeRemaining = {weeks, days, cumSeconds};
|
||||
} else if (days > 0) {
|
||||
timeRemaining = {days, hours, cumSeconds};
|
||||
} else if (hours > 0) {
|
||||
timeRemaining = {hours, minutes, cumSeconds};
|
||||
} else if (minutes > 0) {
|
||||
timeRemaining = {minutes, seconds, cumSeconds};
|
||||
} else {
|
||||
timeRemaining = {seconds, cumSeconds};
|
||||
}
|
||||
|
||||
return timeRemaining;
|
||||
},
|
||||
};
|
||||
|
||||
export default formatUtil;
|
||||
Reference in New Issue
Block a user