diff --git a/alembic/versions/c3d5f7a9b1e2_download_last_download_attempt.py b/alembic/versions/c3d5f7a9b1e2_download_last_download_attempt.py new file mode 100644 index 00000000..1ad2c86a --- /dev/null +++ b/alembic/versions/c3d5f7a9b1e2_download_last_download_attempt.py @@ -0,0 +1,29 @@ +"""download_last_download_attempt + +Add the `last_download_attempt` column to the `download` table. This records the last time a Download was +dispatched (whether the attempt succeeds or fails) and is used to enforce per-domain and global daily download +limits. An index supports the "attempted since local midnight" count query. + +Revision ID: c3d5f7a9b1e2 +Revises: b2e4f6a8c1d3 +Create Date: 2026-06-28 + +""" +from alembic import op +import sqlalchemy as sa + +# revision identifiers, used by Alembic. +revision = 'c3d5f7a9b1e2' +down_revision = 'b2e4f6a8c1d3' +branch_labels = None +depends_on = None + + +def upgrade(): + op.add_column('download', sa.Column('last_download_attempt', sa.DateTime(timezone=True), nullable=True)) + op.create_index('idx_download_last_download_attempt', 'download', ['last_download_attempt']) + + +def downgrade(): + op.drop_index('idx_download_last_download_attempt', table_name='download') + op.drop_column('download', 'last_download_attempt') diff --git a/alembic/versions/d4e6f8a0c2b5_file_group_suffix.py b/alembic/versions/d4e6f8a0c2b5_file_group_suffix.py new file mode 100644 index 00000000..72526929 --- /dev/null +++ b/alembic/versions/d4e6f8a0c2b5_file_group_suffix.py @@ -0,0 +1,69 @@ +"""file_group_suffix + +Add the `suffix` column to the `file_group` table. This stores the lowercased suffix of the primary file +(e.g. ".bin") so searches can filter by file type using an index, rather than relying on full-text tokens. + +`suffix` is populated at ingest time alongside `mimetype` (wrolpi.files.lib._upsert_files and +FileGroup.from_paths). Existing rows are backfilled here using the same `split_path_stem_and_suffix` logic so +special multi-part suffixes like ".info.json" are stored correctly (not a naive ".json"). + +Revision ID: d4e6f8a0c2b5 +Revises: c3d5f7a9b1e2 +Create Date: 2026-07-01 + +""" +from alembic import op +import sqlalchemy as sa + +# revision identifiers, used by Alembic. +revision = 'd4e6f8a0c2b5' +down_revision = 'c3d5f7a9b1e2' +branch_labels = None +depends_on = None + +# Backfill in batches to bound memory on low-RAM devices (e.g. Raspberry Pi). +BATCH_SIZE = 5000 + + +def upgrade(): + op.add_column('file_group', sa.Column('suffix', sa.String(), nullable=True)) + op.create_index('ix_file_group_suffix', 'file_group', ['suffix']) + + # Backfill existing rows using the same suffix logic used at ingest time so the stored value matches what + # future refreshes would produce (including WROLPi special suffixes like ".info.json"). + from wrolpi.files.lib import split_path_stem_and_suffix + conn = op.get_bind() + last_id = 0 + while True: + rows = conn.execute( + sa.text('SELECT id, primary_path FROM file_group WHERE id > :last_id ORDER BY id LIMIT :limit'), + {'last_id': last_id, 'limit': BATCH_SIZE}, + ).fetchall() + if not rows: + break + + ids, suffixes = [], [] + for row_id, primary_path in rows: + suffix = (split_path_stem_and_suffix(primary_path)[1] or '').lower() or None + if suffix: + ids.append(row_id) + suffixes.append(suffix) + + if ids: + conn.execute( + sa.text(''' + UPDATE file_group AS fg + SET suffix = v.suffix + FROM (SELECT unnest(cast(:ids AS integer[])) AS id, + unnest(cast(:suffixes AS text[])) AS suffix) AS v + WHERE fg.id = v.id + '''), + {'ids': ids, 'suffixes': suffixes}, + ) + + last_id = rows[-1][0] + + +def downgrade(): + op.drop_index('ix_file_group_suffix', table_name='file_group') + op.drop_column('file_group', 'suffix') diff --git a/app/package-lock.json b/app/package-lock.json index 80c8e8f5..9e8a8597 100644 --- a/app/package-lock.json +++ b/app/package-lock.json @@ -14,6 +14,7 @@ "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", "cypress": "^13.15.2", + "esptool-js": "^0.6.0", "lodash": "^4.18.1", "maplibre-gl": "^5.24.0", "mathjs": "^15.2.0", @@ -106,6 +107,7 @@ "version": "7.20.12", "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.20.12.tgz", "integrity": "sha512-XsMfHovsUYHFMdrIHkZphTN/2Hzzi78R08NuHfDBehym2VsPDL6Zn/JAD/JQdnRvbSsbQc4mVaU1m6JgtTEElg==", + "peer": true, "dependencies": { "@ampproject/remapping": "^2.1.0", "@babel/code-frame": "^7.18.6", @@ -902,6 +904,7 @@ "version": "7.18.6", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.18.6.tgz", "integrity": "sha512-LUbR+KNTBWCUAqRG9ex5Gnzu2IOkt8jRJbHHXFT9q+L9zm7M/QQbEqXyw1n1pohYvOyWC8CjeyjrSaIwiYjK7A==", + "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.18.6" }, @@ -1463,6 +1466,7 @@ "version": "7.20.13", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.20.13.tgz", "integrity": "sha512-MmTZx/bkUrfJhhYAYt3Urjm+h8DQGrPrnKQ94jLo7NLuOU+T89a7IByhKmrb8SKhrIYIQ0FN0CHMbnFRen4qNw==", + "peer": true, "dependencies": { "@babel/helper-annotate-as-pure": "^7.18.6", "@babel/helper-module-imports": "^7.18.6", @@ -3377,6 +3381,7 @@ "version": "2.11.6", "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.6.tgz", "integrity": "sha512-50/17A98tWUfQ176raKiOGXuYpLyyVMkxxG6oylzL3BPOlA6ADGdK7EYunSa4I064xerltq9TGXs8HmOk5E+vw==", + "peer": true, "funding": { "type": "opencollective", "url": "https://opencollective.com/popperjs" @@ -4164,6 +4169,7 @@ "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.27.tgz", "integrity": "sha512-cisd7gxkzjBKU2GgdYrTdtQx1SORymWyaAFhaxQPK9bYO9ot3Y5OikQRvY0VYQtvwjeQnizCINJAenh/V7MK2w==", "license": "MIT", + "peer": true, "dependencies": { "@types/prop-types": "*", "csstype": "^3.2.2" @@ -4174,6 +4180,7 @@ "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz", "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==", "license": "MIT", + "peer": true, "peerDependencies": { "@types/react": "^18.0.0" } @@ -4297,6 +4304,7 @@ "version": "5.50.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.50.0.tgz", "integrity": "sha512-vwksQWSFZiUhgq3Kv7o1Jcj0DUNylwnIlGvKvLLYsq8pAWha6/WCnXUeaSoNNha/K7QSf2+jvmkxggC1u3pIwQ==", + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "5.50.0", "@typescript-eslint/type-utils": "5.50.0", @@ -4348,6 +4356,7 @@ "version": "5.50.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.50.0.tgz", "integrity": "sha512-KCcSyNaogUDftK2G9RXfQyOCt51uB5yqC6pkUYqhYh8Kgt+DwR5M0EwEAxGPy/+DH6hnmKeGsNhiZRQxjH71uQ==", + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "5.50.0", "@typescript-eslint/types": "5.50.0", @@ -4678,6 +4687,7 @@ "version": "8.15.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -4800,6 +4810,7 @@ "version": "6.14.0", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -5106,6 +5117,12 @@ "node": ">= 4.0.0" } }, + "node_modules/atob-lite": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/atob-lite/-/atob-lite-2.0.0.tgz", + "integrity": "sha512-LEeSAWeh2Gfa2FtlQE1shxQ8zi5F9GHarrGKz08TMdODD5T4eH6BMsvtnhbWZ+XQn+Gb6om/917ucvRu7l7ukw==", + "license": "MIT" + }, "node_modules/attr-accept": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/attr-accept/-/attr-accept-2.2.2.tgz", @@ -5657,6 +5674,7 @@ "url": "https://github.com/sponsors/ai" } ], + "peer": true, "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", @@ -6454,6 +6472,7 @@ "version": "8.18.0", "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -6740,6 +6759,7 @@ "resolved": "https://registry.npmjs.org/cypress/-/cypress-13.15.2.tgz", "integrity": "sha512-ARbnUorjcCM3XiPwgHKuqsyr5W9Qn+pIIBPaoilnoBkLdSC2oLQjV1BUpnmc7KR+b7Avah3Ly2RMFnfxr96E/A==", "hasInstallScript": true, + "peer": true, "dependencies": { "@cypress/request": "^3.0.6", "@cypress/xvfb": "^1.2.4", @@ -7506,6 +7526,7 @@ "version": "2.4.1", "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.4.1.tgz", "integrity": "sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==", + "peer": true, "dependencies": { "ansi-colors": "^4.1.1", "strip-ansi": "^6.0.1" @@ -7785,6 +7806,7 @@ "version": "8.33.0", "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.33.0.tgz", "integrity": "sha512-WjOpFQgKK8VrCnAtl8We0SUOy/oVZ5NHykyMiagV1M9r8IFpIJX7DduK6n1mpfhlG7T1NLWm2SuD8QB7KFySaA==", + "peer": true, "dependencies": { "@eslint/eslintrc": "^1.4.1", "@humanwhocodes/config-array": "^0.11.8", @@ -8198,6 +8220,7 @@ "version": "8.18.0", "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -8422,6 +8445,17 @@ "node": ">=4" } }, + "node_modules/esptool-js": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/esptool-js/-/esptool-js-0.6.0.tgz", + "integrity": "sha512-ZyuSBXvmS+4DRKM6GrDHfCscnq3YSNlRfM+Zw0DoS7uC3SdkyC262s/ji4MjDYVmMH8SE2BiYx3/dZ1J23Xiew==", + "license": "Apache-2.0", + "dependencies": { + "atob-lite": "^2.0.0", + "pako": "^2.1.0", + "tslib": "^2.4.1" + } + }, "node_modules/esquery": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", @@ -10689,6 +10723,7 @@ "version": "27.5.1", "resolved": "https://registry.npmjs.org/jest/-/jest-27.5.1.tgz", "integrity": "sha512-Yn0mADZB89zTtjkPJEXwrac3LHudkQMR+Paqa8uxJHCBr9agxztUifWCyiYrjhMPBoUVBjyny0I7XH6ozDr7QQ==", + "peer": true, "dependencies": { "@jest/core": "^27.5.1", "import-local": "^3.0.2", @@ -13383,6 +13418,7 @@ "version": "8.18.0", "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -13913,6 +13949,22 @@ "node": ">=6" } }, + "node_modules/pako": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/pako/-/pako-2.2.0.tgz", + "integrity": "sha512-zJq6RP/5q+TO2OpFV3FHzlPnFjmkb7Nc99a5SNjJE+uu/PkpChs+NIZSSzbBoD+6kjiISXjfYdwj1ZRQ81dz/w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], + "license": "(MIT AND Zlib)" + }, "node_modules/param-case": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/param-case/-/param-case-3.0.4.tgz", @@ -14223,6 +14275,7 @@ "url": "https://tidelift.com/funding/github/npm/postcss" } ], + "peer": true, "dependencies": { "nanoid": "^3.3.4", "picocolors": "^1.0.0", @@ -15281,6 +15334,7 @@ "version": "6.0.11", "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.11.tgz", "integrity": "sha512-zbARubNdogI9j7WY4nQJBiNqQf3sLS3wCP4WfOidu+p28LofJqDH1tcXypGrcmMHhDk2t9wGhCsYe/+szLTy1g==", + "peer": true, "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" @@ -15470,6 +15524,7 @@ "version": "15.8.1", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "peer": true, "dependencies": { "loose-envify": "^1.4.0", "object-assign": "^4.1.1", @@ -15677,6 +15732,7 @@ "version": "18.2.0", "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", + "peer": true, "dependencies": { "loose-envify": "^1.1.0" }, @@ -15830,6 +15886,7 @@ "version": "18.2.0", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==", + "peer": true, "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.0" @@ -15931,6 +15988,7 @@ "version": "0.11.0", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.11.0.tgz", "integrity": "sha512-F27qZr8uUqwhWZboondsPx8tnC3Ct3SxZA3V5WyEvujRyyNv0VYPhoBg1gZ8/MV5tubQp76Trw8lTv9hzRBa+A==", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -16445,6 +16503,7 @@ "version": "2.80.0", "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.80.0.tgz", "integrity": "sha512-cIFJOD1DESzpjOBl763Kp1AH7UE/0fcdHe6rZXUdQ9c50uvgigvW97u3IcSeBwOkgqL/PXPBktBCh0KEu5L8XQ==", + "peer": true, "bin": { "rollup": "dist/bin/rollup" }, @@ -16720,6 +16779,7 @@ "version": "2.1.5", "resolved": "https://registry.npmjs.org/semantic-ui-react/-/semantic-ui-react-2.1.5.tgz", "integrity": "sha512-nIqmmUNpFHfovEb+RI2w3E2/maZQutd8UIWyRjf1SLse+XF51hI559xbz/sLN3O6RpLjr/echLOOXwKCirPy3Q==", + "peer": true, "dependencies": { "@babel/runtime": "^7.10.5", "@fluentui/react-component-event-listener": "~0.63.0", @@ -17807,6 +17867,7 @@ "version": "8.18.0", "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -17878,7 +17939,8 @@ "node_modules/three": { "version": "0.184.0", "resolved": "https://registry.npmjs.org/three/-/three-0.184.0.tgz", - "integrity": "sha512-wtTRjG92pM5eUg/KuUnHsqSAlPM296brTOcLgMRqEeylYTh/CdtvKUvCyyCQTzFuStieWxvZb8mVTMvdPyUpxg==" + "integrity": "sha512-wtTRjG92pM5eUg/KuUnHsqSAlPM296brTOcLgMRqEeylYTh/CdtvKUvCyyCQTzFuStieWxvZb8mVTMvdPyUpxg==", + "peer": true }, "node_modules/three-stdlib": { "version": "2.17.2", @@ -18126,6 +18188,7 @@ "version": "0.21.3", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "peer": true, "engines": { "node": ">=10" }, @@ -18179,6 +18242,7 @@ "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -18489,6 +18553,7 @@ "version": "5.105.0", "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.105.0.tgz", "integrity": "sha512-gX/dMkRQc7QOMzgTe6KsYFM7DxeIONQSui1s0n/0xht36HvrgbxtM1xBlgx596NbpHuQU8P7QpKwrZYwUX48nw==", + "peer": true, "dependencies": { "@types/eslint-scope": "^3.7.7", "@types/estree": "^1.0.8", @@ -18558,6 +18623,7 @@ "version": "8.18.0", "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -18607,6 +18673,7 @@ "version": "4.11.1", "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-4.11.1.tgz", "integrity": "sha512-lILVz9tAUy1zGFwieuaQtYiadImb5M3d+H+L1zDYalYoDl0cksAB1UNyuE5MMWJrG6zR1tXkCP2fitl7yoUJiw==", + "peer": true, "dependencies": { "@types/bonjour": "^3.5.9", "@types/connect-history-api-fallback": "^1.3.5", @@ -18661,6 +18728,7 @@ "version": "8.18.0", "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -18773,6 +18841,7 @@ "version": "8.18.0", "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -19055,6 +19124,7 @@ "version": "8.18.0", "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -19499,6 +19569,7 @@ "version": "7.20.12", "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.20.12.tgz", "integrity": "sha512-XsMfHovsUYHFMdrIHkZphTN/2Hzzi78R08NuHfDBehym2VsPDL6Zn/JAD/JQdnRvbSsbQc4mVaU1m6JgtTEElg==", + "peer": true, "requires": { "@ampproject/remapping": "^2.1.0", "@babel/code-frame": "^7.18.6", @@ -20041,6 +20112,7 @@ "version": "7.18.6", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.18.6.tgz", "integrity": "sha512-LUbR+KNTBWCUAqRG9ex5Gnzu2IOkt8jRJbHHXFT9q+L9zm7M/QQbEqXyw1n1pohYvOyWC8CjeyjrSaIwiYjK7A==", + "peer": true, "requires": { "@babel/helper-plugin-utils": "^7.18.6" } @@ -20386,6 +20458,7 @@ "version": "7.20.13", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.20.13.tgz", "integrity": "sha512-MmTZx/bkUrfJhhYAYt3Urjm+h8DQGrPrnKQ94jLo7NLuOU+T89a7IByhKmrb8SKhrIYIQ0FN0CHMbnFRen4qNw==", + "peer": true, "requires": { "@babel/helper-annotate-as-pure": "^7.18.6", "@babel/helper-module-imports": "^7.18.6", @@ -21733,7 +21806,8 @@ "@popperjs/core": { "version": "2.11.6", "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.6.tgz", - "integrity": "sha512-50/17A98tWUfQ176raKiOGXuYpLyyVMkxxG6oylzL3BPOlA6ADGdK7EYunSa4I064xerltq9TGXs8HmOk5E+vw==" + "integrity": "sha512-50/17A98tWUfQ176raKiOGXuYpLyyVMkxxG6oylzL3BPOlA6ADGdK7EYunSa4I064xerltq9TGXs8HmOk5E+vw==", + "peer": true }, "@react-three/fiber": { "version": "8.16.5", @@ -22311,6 +22385,7 @@ "version": "18.3.27", "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.27.tgz", "integrity": "sha512-cisd7gxkzjBKU2GgdYrTdtQx1SORymWyaAFhaxQPK9bYO9ot3Y5OikQRvY0VYQtvwjeQnizCINJAenh/V7MK2w==", + "peer": true, "requires": { "@types/prop-types": "*", "csstype": "^3.2.2" @@ -22320,6 +22395,7 @@ "version": "18.3.7", "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz", "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==", + "peer": true, "requires": {} }, "@types/react-reconciler": { @@ -22440,6 +22516,7 @@ "version": "5.50.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.50.0.tgz", "integrity": "sha512-vwksQWSFZiUhgq3Kv7o1Jcj0DUNylwnIlGvKvLLYsq8pAWha6/WCnXUeaSoNNha/K7QSf2+jvmkxggC1u3pIwQ==", + "peer": true, "requires": { "@typescript-eslint/scope-manager": "5.50.0", "@typescript-eslint/type-utils": "5.50.0", @@ -22465,6 +22542,7 @@ "version": "5.50.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.50.0.tgz", "integrity": "sha512-KCcSyNaogUDftK2G9RXfQyOCt51uB5yqC6pkUYqhYh8Kgt+DwR5M0EwEAxGPy/+DH6hnmKeGsNhiZRQxjH71uQ==", + "peer": true, "requires": { "@typescript-eslint/scope-manager": "5.50.0", "@typescript-eslint/types": "5.50.0", @@ -22714,7 +22792,8 @@ "acorn": { "version": "8.15.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", - "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==" + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "peer": true }, "acorn-globals": { "version": "6.0.0", @@ -22801,6 +22880,7 @@ "version": "6.14.0", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", + "peer": true, "requires": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -23017,6 +23097,11 @@ "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==" }, + "atob-lite": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/atob-lite/-/atob-lite-2.0.0.tgz", + "integrity": "sha512-LEeSAWeh2Gfa2FtlQE1shxQ8zi5F9GHarrGKz08TMdODD5T4eH6BMsvtnhbWZ+XQn+Gb6om/917ucvRu7l7ukw==" + }, "attr-accept": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/attr-accept/-/attr-accept-2.2.2.tgz", @@ -23418,6 +23503,7 @@ "version": "4.28.1", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", + "peer": true, "requires": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", @@ -23959,6 +24045,7 @@ "version": "8.18.0", "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "peer": true, "requires": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -24167,6 +24254,7 @@ "version": "13.15.2", "resolved": "https://registry.npmjs.org/cypress/-/cypress-13.15.2.tgz", "integrity": "sha512-ARbnUorjcCM3XiPwgHKuqsyr5W9Qn+pIIBPaoilnoBkLdSC2oLQjV1BUpnmc7KR+b7Avah3Ly2RMFnfxr96E/A==", + "peer": true, "requires": { "@cypress/request": "^3.0.6", "@cypress/xvfb": "^1.2.4", @@ -24730,6 +24818,7 @@ "version": "2.4.1", "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.4.1.tgz", "integrity": "sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==", + "peer": true, "requires": { "ansi-colors": "^4.1.1", "strip-ansi": "^6.0.1" @@ -24948,6 +25037,7 @@ "version": "8.33.0", "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.33.0.tgz", "integrity": "sha512-WjOpFQgKK8VrCnAtl8We0SUOy/oVZ5NHykyMiagV1M9r8IFpIJX7DduK6n1mpfhlG7T1NLWm2SuD8QB7KFySaA==", + "peer": true, "requires": { "@eslint/eslintrc": "^1.4.1", "@humanwhocodes/config-array": "^0.11.8", @@ -25323,6 +25413,7 @@ "version": "8.18.0", "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "peer": true, "requires": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -25394,6 +25485,16 @@ "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==" }, + "esptool-js": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/esptool-js/-/esptool-js-0.6.0.tgz", + "integrity": "sha512-ZyuSBXvmS+4DRKM6GrDHfCscnq3YSNlRfM+Zw0DoS7uC3SdkyC262s/ji4MjDYVmMH8SE2BiYx3/dZ1J23Xiew==", + "requires": { + "atob-lite": "^2.0.0", + "pako": "^2.1.0", + "tslib": "^2.4.1" + } + }, "esquery": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", @@ -26993,6 +27094,7 @@ "version": "27.5.1", "resolved": "https://registry.npmjs.org/jest/-/jest-27.5.1.tgz", "integrity": "sha512-Yn0mADZB89zTtjkPJEXwrac3LHudkQMR+Paqa8uxJHCBr9agxztUifWCyiYrjhMPBoUVBjyny0I7XH6ozDr7QQ==", + "peer": true, "requires": { "@jest/core": "^27.5.1", "import-local": "^3.0.2", @@ -28964,6 +29066,7 @@ "version": "8.18.0", "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "peer": true, "requires": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -29331,6 +29434,11 @@ "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==" }, + "pako": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/pako/-/pako-2.2.0.tgz", + "integrity": "sha512-zJq6RP/5q+TO2OpFV3FHzlPnFjmkb7Nc99a5SNjJE+uu/PkpChs+NIZSSzbBoD+6kjiISXjfYdwj1ZRQ81dz/w==" + }, "param-case": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/param-case/-/param-case-3.0.4.tgz", @@ -29556,6 +29664,7 @@ "version": "8.4.21", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.21.tgz", "integrity": "sha512-tP7u/Sn/dVxK2NnruI4H9BG+x+Wxz6oeZ1cJ8P6G/PZY0IKk4k/63TDsQf2kQq3+qoJeLm2kIBUNlZe3zgb4Zg==", + "peer": true, "requires": { "nanoid": "^3.3.4", "picocolors": "^1.0.0", @@ -30139,6 +30248,7 @@ "version": "6.0.11", "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.11.tgz", "integrity": "sha512-zbARubNdogI9j7WY4nQJBiNqQf3sLS3wCP4WfOidu+p28LofJqDH1tcXypGrcmMHhDk2t9wGhCsYe/+szLTy1g==", + "peer": true, "requires": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" @@ -30278,6 +30388,7 @@ "version": "15.8.1", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "peer": true, "requires": { "loose-envify": "^1.4.0", "object-assign": "^4.1.1", @@ -30435,6 +30546,7 @@ "version": "18.2.0", "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", + "peer": true, "requires": { "loose-envify": "^1.1.0" } @@ -30548,6 +30660,7 @@ "version": "18.2.0", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==", + "peer": true, "requires": { "loose-envify": "^1.1.0", "scheduler": "^0.23.0" @@ -30624,7 +30737,8 @@ "react-refresh": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.11.0.tgz", - "integrity": "sha512-F27qZr8uUqwhWZboondsPx8tnC3Ct3SxZA3V5WyEvujRyyNv0VYPhoBg1gZ8/MV5tubQp76Trw8lTv9hzRBa+A==" + "integrity": "sha512-F27qZr8uUqwhWZboondsPx8tnC3Ct3SxZA3V5WyEvujRyyNv0VYPhoBg1gZ8/MV5tubQp76Trw8lTv9hzRBa+A==", + "peer": true }, "react-router": { "version": "7.16.0", @@ -30993,6 +31107,7 @@ "version": "2.80.0", "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.80.0.tgz", "integrity": "sha512-cIFJOD1DESzpjOBl763Kp1AH7UE/0fcdHe6rZXUdQ9c50uvgigvW97u3IcSeBwOkgqL/PXPBktBCh0KEu5L8XQ==", + "peer": true, "requires": { "fsevents": "~2.3.2" } @@ -31178,6 +31293,7 @@ "version": "2.1.5", "resolved": "https://registry.npmjs.org/semantic-ui-react/-/semantic-ui-react-2.1.5.tgz", "integrity": "sha512-nIqmmUNpFHfovEb+RI2w3E2/maZQutd8UIWyRjf1SLse+XF51hI559xbz/sLN3O6RpLjr/echLOOXwKCirPy3Q==", + "peer": true, "requires": { "@babel/runtime": "^7.10.5", "@fluentui/react-component-event-listener": "~0.63.0", @@ -32007,6 +32123,7 @@ "version": "8.18.0", "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "peer": true, "requires": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -32058,7 +32175,8 @@ "three": { "version": "0.184.0", "resolved": "https://registry.npmjs.org/three/-/three-0.184.0.tgz", - "integrity": "sha512-wtTRjG92pM5eUg/KuUnHsqSAlPM296brTOcLgMRqEeylYTh/CdtvKUvCyyCQTzFuStieWxvZb8mVTMvdPyUpxg==" + "integrity": "sha512-wtTRjG92pM5eUg/KuUnHsqSAlPM296brTOcLgMRqEeylYTh/CdtvKUvCyyCQTzFuStieWxvZb8mVTMvdPyUpxg==", + "peer": true }, "three-stdlib": { "version": "2.17.2", @@ -32260,7 +32378,8 @@ "type-fest": { "version": "0.21.3", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", - "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==" + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "peer": true }, "type-is": { "version": "1.6.18", @@ -32297,7 +32416,8 @@ "typescript": { "version": "4.9.5", "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", - "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==" + "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", + "peer": true }, "unbox-primitive": { "version": "1.0.2", @@ -32524,6 +32644,7 @@ "version": "5.105.0", "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.105.0.tgz", "integrity": "sha512-gX/dMkRQc7QOMzgTe6KsYFM7DxeIONQSui1s0n/0xht36HvrgbxtM1xBlgx596NbpHuQU8P7QpKwrZYwUX48nw==", + "peer": true, "requires": { "@types/eslint-scope": "^3.7.7", "@types/estree": "^1.0.8", @@ -32556,6 +32677,7 @@ "version": "8.18.0", "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "peer": true, "requires": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -32619,6 +32741,7 @@ "version": "8.18.0", "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "peer": true, "requires": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -32656,6 +32779,7 @@ "version": "4.11.1", "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-4.11.1.tgz", "integrity": "sha512-lILVz9tAUy1zGFwieuaQtYiadImb5M3d+H+L1zDYalYoDl0cksAB1UNyuE5MMWJrG6zR1tXkCP2fitl7yoUJiw==", + "peer": true, "requires": { "@types/bonjour": "^3.5.9", "@types/connect-history-api-fallback": "^1.3.5", @@ -32692,6 +32816,7 @@ "version": "8.18.0", "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "peer": true, "requires": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -32938,6 +33063,7 @@ "version": "8.18.0", "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "peer": true, "requires": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", diff --git a/app/package.json b/app/package.json index 2e171d27..be4b3373 100644 --- a/app/package.json +++ b/app/package.json @@ -9,6 +9,7 @@ "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", "cypress": "^13.15.2", + "esptool-js": "^0.6.0", "lodash": "^4.18.1", "maplibre-gl": "^5.24.0", "mathjs": "^15.2.0", diff --git a/app/src/App.css b/app/src/App.css index 6ca76aae..822987dd 100644 --- a/app/src/App.css +++ b/app/src/App.css @@ -247,11 +247,15 @@ textarea.otp { color: #DDDDDD !important; } -/* Direct children only, so nested components that use the `content`/`title` class names (e.g. a Message's - Message.Content) are not given the accordion's dark background. */ +/* The accordion has no background of its own; it inherits whatever container it sits in (Modal + content #171616, Segment #1B1C1D, the page body, etc.). This matches how the light theme already + renders accordions (transparent), and avoids a darker band when the parent isn't #171616. + Direct children only, so nested components that use the `content`/`title` class names (e.g. a + Message's Message.Content) are not affected. */ +.ui.inverted.accordion, .ui.inverted.accordion > .title, .ui.inverted.accordion > .content { - background-color: #171616 !important; + background-color: transparent !important; } /* Mobile-responsive Confirm modal */ diff --git a/app/src/App.js b/app/src/App.js index d82b6980..f1ed37db 100644 --- a/app/src/App.js +++ b/app/src/App.js @@ -25,6 +25,7 @@ import {TagsProvider} from "./Tags"; import {ZimRoute} from "./components/Zim"; import {PlaylistsRoute} from "./components/Playlists"; import {DocsRoute} from "./components/Docs"; +import {FlasherRoute} from "./components/Flasher"; import ErrorBoundary from "./components/ErrorBoundary"; import {KeyboardShortcutsProvider} from "./components/KeyboardShortcutsProvider"; @@ -107,6 +108,7 @@ const router = createBrowserRouter(createRoutesFromElements(}/> }/> }/> + }/> )); export default function App() { diff --git a/app/src/api.js b/app/src/api.js index f357fffb..74efc88d 100644 --- a/app/src/api.js +++ b/app/src/api.js @@ -315,6 +315,8 @@ export async function getSettings() { archive_destination: content.archive_destination, download_manager_disabled: content.download_manager_disabled, download_manager_stopped: content.download_manager_stopped, + download_daily_limit_global: content.download_daily_limit_global, + download_daily_limit_per_domain: content.download_daily_limit_per_domain, download_on_startup: content.download_on_startup, download_timeout: content.download_timeout, download_wait: content.download_wait, @@ -1210,8 +1212,14 @@ export async function deleteDownload(downloadId) { } } -export async function filesSearch(offset, limit, searchStr, mimetypes, model, tagNames, headline, months, fromYear, toYear, anyTag, order) { +export async function filesSearch(offset, limit, searchStr, mimetypes, model, tagNames, headline, months, fromYear, toYear, anyTag, order, suffix, path) { const body = {search_str: searchStr, offset: parseInt(offset), limit: parseInt(limit), any_tag: anyTag}; + if (suffix) { + body['suffix'] = suffix; + } + if (path) { + body['path'] = path; + } if (mimetypes) { body['mimetypes'] = mimetypes; } @@ -1255,6 +1263,51 @@ export async function filesSearch(offset, limit, searchStr, mimetypes, model, ta } } +export async function flasherSearch(chip, path) { + // Search .bin firmware, filtered to ESP images for the given chip (from connecting to the device). + const body = {}; + if (chip) body['chip'] = chip; + if (path) body['path'] = path; + const response = await apiPost(`${API_URI}/flasher/search`, body); + if (response.ok) { + const data = await response.json(); + return [data['file_groups'], data['totals']['file_groups']]; + } + const message = await getErrorMessage(response, 'Cannot search firmware. See server logs.'); + toast({type: 'error', title: 'Unable to search firmware', description: message, time: 5000}); + return [null, null]; +} + +export async function getFlasherConfigs() { + // Return the user's saved firmware flashing configurations. + const response = await apiGet(`${API_URI}/flasher/configs`); + if (response.ok) { + const data = await response.json(); + return data['configurations'] || []; + } + return []; +} + +export async function saveFlasherConfig(name, files, eraseAll) { + const response = await apiPost(`${API_URI}/flasher/configs`, {name, files, erase_all: eraseAll}); + if (!response.ok) { + const message = await getErrorMessage(response, 'Cannot save configuration. See server logs.'); + toast({type: 'error', title: 'Unable to save configuration', description: message, time: 5000}); + return false; + } + return true; +} + +export async function deleteFlasherConfig(name) { + const response = await apiDelete(`${API_URI}/flasher/configs/${encodeURIComponent(name)}`); + if (!response.ok) { + const message = await getErrorMessage(response, 'Cannot delete configuration. See server logs.'); + toast({type: 'error', title: 'Unable to delete configuration', description: message, time: 5000}); + return false; + } + return true; +} + export async function refreshFiles(paths) { let response; if (Array.isArray(paths) && paths.length > 0) { diff --git a/app/src/components/Apps.js b/app/src/components/Apps.js index 877cc183..5085c65b 100644 --- a/app/src/components/Apps.js +++ b/app/src/components/Apps.js @@ -1,302 +1,20 @@ -import React, {useContext, useState} from 'react'; -import { - Checkbox, - Divider, - Icon, - Input, - Label, - Radio, - SegmentGroup, - StatisticLabel, - StatisticValue, - TableCell, - TableRow -} from "semantic-ui-react"; +import React, {useContext} from 'react'; +import {Input, Label, StatisticLabel, StatisticValue} from "semantic-ui-react"; import {Link, Route, Routes} from "react-router"; -import { - appendChecksum, - decryptOTP, - encryptOTP, - formatMessage, - generateHtml, - OTP_CHARS, - OTP_CHARS_ALPHA, - stripChecksum, - validateCharset -} from "./otp"; import { ErrorMessage, humanFileSize, humanNumber, mimetypeColor, PageContainer, - SimpleAccordion, toLocaleString, useTitle } from "./Common"; import {ThemeContext} from "../contexts/contexts"; -import {Button, Form, Header, Loader, Segment, Statistic, Table, TextArea} from "./Theme"; -import {useStatistics, useVINDecoder} from "../hooks/customHooks"; +import {Header, Loader, Segment, Statistic} from "./Theme"; +import {useStatistics} from "../hooks/customHooks"; import {CalculatorsPage} from "./Calculators"; -function Encrypt({chars, checksum}) { - // Encrypt happens live as the user types or pastes. Nothing leaves the browser. - const [otp, setOtp] = useState(''); - const [plaintext, setPlaintext] = useState(''); - - const handleInputChange = (event, {name, value}) => { - if (name === 'otp') { - setOtp(value); - } else { - setPlaintext(value); - } - }; - - let ciphertext = ''; - let error = ''; - if (otp && plaintext && chars) { - try { - const raw = encryptOTP(otp, plaintext, chars).ciphertext; - // When enabled, append a checksum character then re-group the whole message+checksum. - ciphertext = checksum ? formatMessage(appendChecksum(raw, chars)) : raw; - } catch (e) { - error = e.message; - } - } - - return <> -
Encrypt
- - -

Key

-