15 Commits

Author SHA1 Message Date
dad0dd13ae chore(deps): update dependency eslint to v10
Some checks failed
renovate/artifacts Artifact file update failure
continuous-integration/drone/pr Build is failing
continuous-integration/drone/push Build is passing
2026-03-03 00:20:04 +00:00
e88961a43a fix: add space around send file button
All checks were successful
continuous-integration/drone/push Build is passing
2026-03-02 23:05:11 +01:00
f6169d690f Remove useless lambda variable
All checks were successful
continuous-integration/drone/push Build is passing
2026-03-02 22:53:50 +01:00
5221260e26 Updated frontend dependencies
Some checks failed
continuous-integration/drone/push Build is failing
2026-03-02 22:53:03 +01:00
c562152019 Merge branch 'master' of https://gitea.communiquons.org/pierre/MatrixGW
Some checks failed
continuous-integration/drone/push Build is failing
2026-03-02 22:49:56 +01:00
2ea20e6de4 feat: can upload files in conversations 2026-03-02 22:49:47 +01:00
29939d598f Merge pull request 'Update dependency react-router to ^7.13.1' (#193) from renovate/react-router-7.x into master
All checks were successful
continuous-integration/drone/push Build is passing
2026-03-02 00:21:09 +00:00
b2ab5aceca Update dependency react-router to ^7.13.1
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2026-03-01 00:20:31 +00:00
2a5def11eb fix typo
All checks were successful
continuous-integration/drone/push Build is passing
2026-02-28 15:38:13 +01:00
c9c3eca8f8 Merge pull request 'Update dependency @mui/x-data-grid to ^8.27.3' (#192) from renovate/mui-x-data-grid-8.x into master
All checks were successful
continuous-integration/drone/push Build is passing
2026-02-28 00:15:30 +00:00
1bdb130fb4 Update dependency @mui/x-data-grid to ^8.27.3
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2026-02-27 00:14:11 +00:00
2c2a81c271 Merge pull request 'Update dependency eslint to ^9.39.3' (#191) from renovate/eslint-9.x into master
All checks were successful
continuous-integration/drone/push Build is passing
2026-02-27 00:14:05 +00:00
09099bce9b Update dependency eslint to ^9.39.3
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2026-02-26 00:14:49 +00:00
16595f1fe2 Merge branch 'master' of https://gitea.communiquons.org/pierre/MatrixGW
All checks were successful
continuous-integration/drone/push Build is passing
2026-02-24 23:10:34 +01:00
f19b3e802b Create prod Docker config 2026-02-24 23:10:10 +01:00
22 changed files with 802 additions and 156 deletions

6
docker_prod/.env.sample Normal file
View File

@@ -0,0 +1,6 @@
REDIS_PASS=redis_password
WEBSITE_ORIGIN=http://localhost:8000
APP_SECRET=secretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecret
AUTH_SECRET_KEY=secretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecret
OIDC_CLIENT_ID=bar
OIDC_CLIENT_SECRET=foo

3
docker_prod/.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
.env
storage
auth/users.json

44
docker_prod/README.md Normal file
View File

@@ -0,0 +1,44 @@
# Setup production environment
> Sample release deployment configuration. **MUST BE ADAPTED BEFORE REAL PRODUCTION DEPLOYMENT!**
1. Install prerequisites:
1. `docker`
2. `docker compose`
3. `git`
2. Clone this git repository:
```bash
git clone https://gitea.communiquons.org/pierre/MatrixGW
cd MatrixGW/docker_prod
```
3. Copy and adapt env values
```bash
cp .env.sample .env
nano .env
```
4. Create required directories:
```bash
mkdir -p storage/{redis-data,redis-conf,synapse,maspostgres,matrixgw}
```
5. Start containers
```bash
docker compose up
```
> Note: Before running `docker compose up`, if your user does not belong to the `docker` group, you should run the following command to be able to run docker in rootless mode:
>
> ```bash
> sudo -g docker bash
> ```
6. Done !
* Matrix GW: http://localhost:8000/, the default credentials are `admin` / `admin`
* Element: http://localhost:8080 (you will need to create your accounts)
* Auth platform: http://localhost:5001

View File

@@ -0,0 +1,5 @@
- id: ${OIDC_CLIENT_ID}
name: MatrixGW
description: Matrix Gateway
secret: ${OIDC_CLIENT_SECRET}
redirect_uri: ${APP_ORIGIN}/oidc_cb

View File

@@ -0,0 +1,102 @@
services:
oidc:
image: pierre42100/basic_oidc
user: "1000"
environment:
- LISTEN_ADDRESS=0.0.0.0:9001
- STORAGE_PATH=/storage
- TOKEN_KEY=$AUTH_SECRET_KEY
- WEBSITE_ORIGIN=http://localhost:9001
- OIDC_CLIENT_ID=$OIDC_CLIENT_ID
- OIDC_CLIENT_SECRET=$OIDC_CLIENT_SECRET
- APP_ORIGIN=$WEBSITE_ORIGIN
expose:
- 9001
ports:
- 9001:9001
volumes:
- ./auth:/storage
redis:
image: redis:alpine
user: "1000"
command: redis-server --requirepass ${REDIS_PASS:-secretredis}
expose:
- 6379
ports:
- "6379:6379"
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 3
volumes:
- ./storage/redis-data:/data
- ./storage/redis-conf:/usr/local/etc/redis/redis.conf
mas:
image: ghcr.io/element-hq/matrix-authentication-service:main
user: "1000"
restart: unless-stopped
depends_on:
- masdb
volumes:
- ./mas:/config:ro
command: server -c /config/config.yaml
ports:
- "8778:8778/tcp"
synapse:
image: docker.io/matrixdotorg/synapse:latest
user: "1000"
restart: unless-stopped
environment:
- SYNAPSE_CONFIG_PATH=/config/homeserver.yaml
volumes:
- ./storage/synapse:/data
- ./synapse:/config:ro
ports:
- "8448:8448/tcp"
masdb:
image: docker.io/postgres:18-alpine
user: "1000"
environment:
- POSTGRES_DB=masdb
- POSTGRES_USER=masdb
- POSTGRES_PASSWORD=changeme
- POSTGRES_INITDB_ARGS=--encoding=UTF-8 --lc-collate=C --lc-ctype=C
- PGDATA=/data
volumes:
- ./storage/maspostgres:/data
element:
image: docker.io/vectorim/element-web
ports:
- "8080:80/tcp"
volumes:
- ./element/config.json:/app/config.json:ro
matrixgw:
image: pierre42100/matrix_gateway
user: "1000"
ports:
- 8000:8000
depends_on:
redis:
condition: service_healthy
volumes:
- ./storage/matrixgw:/data
network_mode: host
environment:
- WEBSITE_ORIGIN=${WEBSITE_ORIGIN}
- SECRET=${APP_SECRET}
- OIDC_CONFIGURATION_URL=http://localhost:9001/.well-known/openid-configuration
- OIDC_PROVIDER_NAME=OIDC
- OIDC_CLIENT_ID=$OIDC_CLIENT_ID
- OIDC_CLIENT_SECRET=$OIDC_CLIENT_SECRET
- REDIS_HOSTNAME=localhost #redis
- REDIS_PASSWORD=${REDIS_PASS:-secretredis}
- UNSECURE_AUTO_LOGIN_EMAIL=$UNSECURE_AUTO_LOGIN_EMAIL
- STORAGE_PATH=/data
- MATRIX_HOMESERVER=http://localhost:8448

View File

@@ -0,0 +1,49 @@
{
"default_server_config": {
"m.homeserver": {
"base_url": "http://localhost:8448",
"server_name": "devserver"
},
"m.identity_server": {
"base_url": "https://vector.im"
}
},
"disable_custom_urls": false,
"disable_guests": false,
"disable_login_language_selector": false,
"disable_3pid_login": false,
"brand": "Element",
"integrations_ui_url": "https://scalar.vector.im/",
"integrations_rest_url": "https://scalar.vector.im/api",
"integrations_widgets_urls": [
"https://scalar.vector.im/_matrix/integrations/v1",
"https://scalar.vector.im/api",
"https://scalar-staging.vector.im/_matrix/integrations/v1",
"https://scalar-staging.vector.im/api",
"https://scalar-staging.riot.im/scalar/api"
],
"default_country_code": "GB",
"show_labs_settings": false,
"features": {},
"default_federate": true,
"default_theme": "light",
"room_directory": {
"servers": ["matrix.org"]
},
"enable_presence_by_hs_url": {
"https://matrix.org": false,
"https://matrix-client.matrix.org": false
},
"setting_defaults": {
"breadcrumbs": true
},
"jitsi": {
"preferred_domain": "meet.element.io"
},
"element_call": {
"url": "https://call.element.io",
"participant_limit": 8,
"brand": "Element Call"
},
"map_style_url": "https://api.maptiler.com/maps/streets/style.json?key=fU3vlMsMn4Jb6dnEIFsx"
}

113
docker_prod/mas/config.yaml Normal file
View File

@@ -0,0 +1,113 @@
http:
listeners:
- name: web
resources:
- name: discovery
- name: human
- name: oauth
- name: compat
- name: graphql
- name: assets
binds:
- address: '[::]:8778'
proxy_protocol: false
- name: internal
resources:
- name: health
binds:
- host: localhost
port: 8081
proxy_protocol: false
trusted_proxies:
- 192.168.0.0/16
- 172.16.0.0/12
- 10.0.0.0/10
- 127.0.0.1/8
- fd00::/8
- ::1/128
public_base: http://localhost:8778/
issuer: http://localhost:8778/
database:
uri: postgresql://masdb:changeme@masdb/masdb
max_connections: 10
min_connections: 0
connect_timeout: 30
idle_timeout: 600
max_lifetime: 1800
email:
from: '"Authentication Service" <root@localhost>'
reply_to: '"Authentication Service" <root@localhost>'
transport: blackhole
secrets:
encryption: 12de9ad7bc2bacfa2ab9b1e3f7f1b3feb802195c8ebe66a8293cdb27f00be471
keys:
- kid: Bj2PICQ7mf
key: |
-----BEGIN RSA PRIVATE KEY-----
MIIEogIBAAKCAQEAsCYCrrCJA7IuGbTYzP5yZN74QszbzudBUCX6MyN/+36HO2r6
xL8x1PRJ+Klx9Y90J9pWuo+cIuEmFLqO+Yfblo9fSQgZVvkWAFpO6Xh8J4z9qg49
M8xm0Ct8EnRDZDCEOBnwoDaAB9RTbpJGa1RPVCiamfi+xU+j47Zl4Er5jvLm81O7
DSlH9eK8Eih8AxuKTkAbKE1zyXquImE26Mj2dmMRfjDrWV/I8oqE3WFViAKR12Av
zw6TUyduiz8nK9pONCF3NIcQvBdHntBz1HlDXv6i0fRvlGIhjNL5LBgo6XQ3rNM1
bW2KYOw/iFP0YbfD4/xRjkBPvK2coQ8aRzK2VwIDAQABAoH/G4XU5Xav8ePlUB7x
wRYAycINCGL59Vos2lkUvujNFn6uopoUlKlLH/sLk87l/3hqrc9vvbayrsB/Mr3z
mQmhReUg/khFrVE+Hs/9hH1O6N8ew3N2HKHTbrNcr4V7AiySfDGRZ3ccihyi7KPu
XNbPjlbJ0UUMicfn06ysPl94nt0So0UAmXg+c7sDDqyzh3cY8emedYZ5FCljo/jA
F8k40rs7CywLJYMJB9O1vtomgt1xkDRO4F8UrZrriMIcYn0iFKe7i4AH8D6nkgNu
/v9Z43Leu8yRKrUvbpH3NaX8DlUSFWAXKpwUWr4sAQgWcLkVgjAXG1v9jCE97qW2
f0nBAoGBAOaKrnY5rWeZ74dERnPhSCsYiqRMneQAh7eJR+Er+xu1yF/bxwkhq2tK
/txheTK448DqhQRtr095t/v7TMZcPl3bSmybT1CQg/wiMJsgDMZqlC9tofvcq6uz
xP8vxMFHd0YSMSP693dkny4MzNY6LuoVWDLT+HxKPJyzGs1alruzAoGBAMOZp5J2
3ODcHQlcsGBtj1yVpQ4UXMvrSZF2ygiGK9bagL/f1iAtwACVOh5rgmbiOLSVgmR2
n4nupTgSAXMYkjmAmDyEh0PDaRl4WWvYEKp8GMvTPVPvjc6N0dT+y8Mf9bu+LcEt
+uZqPOZNbO5Vi+UgGeM9zZpxq/K7dpJmM/jNAoGBALsYHRGxKTsEwFEkZZCxaWIg
HpPL4e8hRwL6FC13BeitFBpHQDX27yi5yi+Lo1I4ngz3xk+bvERhYaDLhrkML0j4
KGQPfsTBI3vBO3UJA5Ua9XuwG19M7L0BvYPjfmfk2bUyGlM63w4zyMMUfD/3JA+w
ls1ZHTWxAZOh/sRdGirlAoGAX16B1+XgmDp6ZeAtlzaUGd5U1eKTxFF6U1SJ+VIB
+gYblHI84v+riB06cy6ULDnM0C+9neJAs24KXKZa0pV+Zk8O6yLrGN0kV2jYoL5+
kcFkDa13T3+TssxvLNz22LKyi9GUWYZjuQi/nMLPg/1t8k+Oj7/Iia822WkRzRvL
51kCgYEAwrN5Us8LR+fThm3C0vhvwv2wap6ccw0qq5+FTN+igAZAmmvKKvhow2Vi
LnPKBkc7QvxvQSNoXkdUo4qs3zOQ7DGvJLqSG9pwxFW5X1+78pNEm5OWe8AlT1uZ
Jz8Z1/Ae7fr/fFaucW9LkWjcuoPwPLiZ3b7ZQ6phs8qzoL+FpBI=
-----END RSA PRIVATE KEY-----
- kid: HcRvLHat12
key: |
-----BEGIN EC PRIVATE KEY-----
MHcCAQEEIOCCFSnkfz1ksln6kus8enQstBTu0q62IGJVzuX0WiXPoAoGCCqGSM49
AwEHoUQDQgAEVWPLbvSdxquLAjU3zJLcCWdaxr6QK1tPVbV1IS+87QUMv/zKiCMa
fNpwgBXwU7dF0gY507R2yY9pcdTmRtnRug==
-----END EC PRIVATE KEY-----
- kid: YjMITk5VSn
key: |
-----BEGIN EC PRIVATE KEY-----
MIGkAgEBBDCoPSjaN7qqnPz+vdzHeIy8RZCCtFOqLTkvylM1gz6xOGaVsS63VJw9
Td9BtpolZ0egBwYFK4EEACKhZANiAAT8tH88HYBHNiQTSqZzlxElSuSDC0+Xn0O9
ukj0xTTVBp8rUM9lCJQAlB8PjS2XK/n0YvYdzysQb3AYqszJa45/rOGvSar30YNE
gwpJvu36xNIKZT+nHalNwg069FdjNBc=
-----END EC PRIVATE KEY-----
- kid: NvFzzeMRU3
key: |
-----BEGIN EC PRIVATE KEY-----
MHQCAQEEILJEmFPDGFZoBVBQf1P6h4YfasYsFiu8a6FrFxiJvKXPoAcGBSuBBAAK
oUQDQgAE4NY5H3+D8r9GNOhrpbUn2dvLZIzi4A+SiwfqvtvPEmZkW+KDbd2tzKmx
maydZBn52QWedVY65snGAEoh9mV1TQ==
-----END EC PRIVATE KEY-----
passwords:
enabled: true
schemes:
- version: 1
algorithm: argon2id
minimum_complexity: 0
account:
password_registration_enabled: true
password_registration_email_required: false
matrix:
kind: synapse
homeserver: localhost
secret: IhKoLn6jWf1qRRZWvqgaKuIdwD6H0Mvx
endpoint: http://synapse:8448/
policy:
data:
client_registration:
allow_insecure_uris: true

View File

@@ -0,0 +1,41 @@
# Configuration file for Synapse.
#
# This is a YAML file: see [1] for a quick introduction. Note in particular
# that *indentation is important*: all the elements of a list or dictionary
# should have the same indentation.
#
# [1] https://docs.ansible.com/ansible/latest/reference_appendices/YAMLSyntax.html
#
# For more information on how to configure Synapse, including a complete accounting of
# each option, go to docs/usage/configuration/config_documentation.md or
# https://element-hq.github.io/synapse/latest/usage/configuration/config_documentation.html
server_name: "localhost"
pid_file: /data/homeserver.pid
listeners:
- port: 8448
tls: false
type: http
x_forwarded: true
resources:
- names: [client, federation]
compress: false
database:
name: sqlite3
args:
database: /data/homeserver.db
log_config: "/config/localhost.log.config"
media_store_path: /data/media_store
registration_shared_secret: "+oJd9zgvkQpXN-tt;95Wy,AFAdRH+FSTg&LxUXh6ZSvwMJHT;h"
report_stats: false
macaroon_secret_key: "d@ck1QkQLxlRg^aB#c#oZeII.oxOS6E2DX;YobP^Vm#iB5pQpd"
form_secret: "P.uleBJUYc6AM.UOrFF1q7OKH2N5T*Ae2;fGh46;vIHLIQ#JBP"
signing_key_path: "/config/localhost.signing.key"
trusted_key_servers:
- server_name: "matrix.org"
# vim:ft=yaml
matrix_authentication_service:
enabled: true
endpoint: http://mas:8778/
secret: "IhKoLn6jWf1qRRZWvqgaKuIdwD6H0Mvx"
# Alternatively, using a file:
#secret_file: /path/to/secret.txt

View File

@@ -0,0 +1,39 @@
version: 1
formatters:
precise:
format: '%(asctime)s - %(name)s - %(lineno)d - %(levelname)s - %(request)s - %(message)s'
handlers:
console:
class: logging.StreamHandler
formatter: precise
loggers:
# This is just here so we can leave `loggers` in the config regardless of whether
# we configure other loggers below (avoid empty yaml dict error).
_placeholder:
level: "INFO"
synapse.storage.SQL:
# beware: increasing this to DEBUG will make synapse log sensitive
# information such as access tokens.
level: INFO
root:
level: INFO
handlers: [console]
disable_existing_loggers: false

View File

@@ -0,0 +1 @@
ed25519 a_HEcG Q2iG1Yy5WTiZ/VIy+zHPyHCRUpqyE3qrVttGULrVQK4

View File

@@ -95,6 +95,44 @@ dependencies = [
"syn 2.0.116", "syn 2.0.116",
] ]
[[package]]
name = "actix-multipart"
version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d5118a26dee7e34e894f7e85aa0ee5080ae4c18bf03c0e30d49a80e418f00a53"
dependencies = [
"actix-multipart-derive",
"actix-utils",
"actix-web",
"derive_more 0.99.20",
"futures-core",
"futures-util",
"httparse",
"local-waker",
"log",
"memchr",
"mime",
"rand 0.8.5",
"serde",
"serde_json",
"serde_plain",
"tempfile",
"tokio",
]
[[package]]
name = "actix-multipart-derive"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e11eb847f49a700678ea2fa73daeb3208061afa2b9d1a8527c03390f4c4a1c6b"
dependencies = [
"darling",
"parse-size",
"proc-macro2",
"quote",
"syn 2.0.116",
]
[[package]] [[package]]
name = "actix-remote-ip" name = "actix-remote-ip"
version = "0.1.0" version = "0.1.0"
@@ -243,16 +281,18 @@ dependencies = [
[[package]] [[package]]
name = "actix-ws" name = "actix-ws"
version = "0.3.1" version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "12d4f2fbee3ef7a22fa6cb0e416b962237a167ed0419f22d4e451da2d7f082f8" checksum = "decf53c3cdd63dd6f289980b430238f9a2f6d19f8bce8e418272e08d3da43f0f"
dependencies = [ dependencies = [
"actix-codec", "actix-codec",
"actix-http", "actix-http",
"actix-web", "actix-web",
"bytestring", "bytestring",
"futures-core", "futures-core",
"futures-sink",
"tokio", "tokio",
"tokio-util",
] ]
[[package]] [[package]]
@@ -402,9 +442,9 @@ dependencies = [
[[package]] [[package]]
name = "anyhow" name = "anyhow"
version = "1.0.101" version = "1.0.102"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f0e0fee31ef5ed1ba1316088939cea399010ed7731dba877ed44aeb407a75ea" checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c"
[[package]] [[package]]
name = "anymap2" name = "anymap2"
@@ -872,9 +912,9 @@ dependencies = [
[[package]] [[package]]
name = "clap" name = "clap"
version = "4.5.59" version = "4.5.60"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c5caf74d17c3aec5495110c34cc3f78644bfa89af6c8993ed4de2790e49b6499" checksum = "2797f34da339ce31042b27d23607e051786132987f595b02ba4f6a6dffb7030a"
dependencies = [ dependencies = [
"clap_builder", "clap_builder",
"clap_derive", "clap_derive",
@@ -882,9 +922,9 @@ dependencies = [
[[package]] [[package]]
name = "clap_builder" name = "clap_builder"
version = "4.5.59" version = "4.5.60"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "370daa45065b80218950227371916a1633217ae42b2715b2287b606dcd618e24" checksum = "24a241312cea5059b13574bb9b3861cabf758b879c15190b37b6d6fd63ab6876"
dependencies = [ dependencies = [
"anstream", "anstream",
"anstyle", "anstyle",
@@ -994,6 +1034,12 @@ version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d52eff69cd5e647efe296129160853a42795992097e8af39800e1060caeea9b" checksum = "3d52eff69cd5e647efe296129160853a42795992097e8af39800e1060caeea9b"
[[package]]
name = "convert_case"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e"
[[package]] [[package]]
name = "convert_case" name = "convert_case"
version = "0.10.0" version = "0.10.0"
@@ -1301,6 +1347,19 @@ dependencies = [
"syn 1.0.109", "syn 1.0.109",
] ]
[[package]]
name = "derive_more"
version = "0.99.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6edb4b64a43d977b8e99788fe3a04d483834fba1215a7e02caa415b626497f7f"
dependencies = [
"convert_case 0.4.0",
"proc-macro2",
"quote",
"rustc_version",
"syn 2.0.116",
]
[[package]] [[package]]
name = "derive_more" name = "derive_more"
version = "1.0.0" version = "1.0.0"
@@ -1336,7 +1395,7 @@ version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb"
dependencies = [ dependencies = [
"convert_case", "convert_case 0.10.0",
"proc-macro2", "proc-macro2",
"quote", "quote",
"rustc_version", "rustc_version",
@@ -3213,6 +3272,7 @@ name = "matrixgw_backend"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"actix-cors", "actix-cors",
"actix-multipart",
"actix-remote-ip", "actix-remote-ip",
"actix-session", "actix-session",
"actix-web", "actix-web",
@@ -3535,6 +3595,12 @@ dependencies = [
"windows-link", "windows-link",
] ]
[[package]]
name = "parse-size"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "487f2ccd1e17ce8c1bfab3a65c89525af41cfad4c8659021a1e9a2aacd73b89b"
[[package]] [[package]]
name = "pbkdf2" name = "pbkdf2"
version = "0.12.2" version = "0.12.2"
@@ -4577,6 +4643,15 @@ dependencies = [
"serde_core", "serde_core",
] ]
[[package]]
name = "serde_plain"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ce1fc6db65a611022b23a0dec6975d63fb80a302cb3388835ff02c097258d50"
dependencies = [
"serde",
]
[[package]] [[package]]
name = "serde_spanned" name = "serde_spanned"
version = "1.0.4" version = "1.0.4"
@@ -5071,6 +5146,7 @@ dependencies = [
"bytes", "bytes",
"futures-core", "futures-core",
"futures-sink", "futures-sink",
"futures-util",
"pin-project-lite", "pin-project-lite",
"tokio", "tokio",
] ]

View File

@@ -6,14 +6,15 @@ edition = "2024"
[dependencies] [dependencies]
env_logger = "0.11.9" env_logger = "0.11.9"
log = "0.4.29" log = "0.4.29"
clap = { version = "4.5.59", features = ["derive", "env"] } clap = { version = "4.5.60", features = ["derive", "env"] }
anyhow = "1.0.101" anyhow = "1.0.102"
serde = { version = "1.0.228", features = ["derive"] } serde = { version = "1.0.228", features = ["derive"] }
tokio = { version = "1.49.0", features = ["full"] } tokio = { version = "1.49.0", features = ["full"] }
actix-web = "4.13.0" actix-web = "4.13.0"
actix-session = { version = "0.11.0", features = ["redis-session"] } actix-session = { version = "0.11.0", features = ["redis-session"] }
actix-remote-ip = "0.1.0" actix-remote-ip = "0.1.0"
actix-cors = "0.7.1" actix-cors = "0.7.1"
actix-multipart = "0.7.2"
light-openid = "1.1.0" light-openid = "1.1.0"
bytes = "1.11.1" bytes = "1.11.1"
sha2 = "0.11.0-rc.5" sha2 = "0.11.0-rc.5"
@@ -32,7 +33,7 @@ url = "2.5.8"
ractor = "0.15.10" ractor = "0.15.10"
serde_json = "1.0.149" serde_json = "1.0.149"
lazy-regex = "3.6.0" lazy-regex = "3.6.0"
actix-ws = "0.3.1" actix-ws = "0.4.0"
infer = "0.19.0" infer = "0.19.0"
rust-embed = "8.11.0" rust-embed = "8.11.0"
mime_guess = "2.0.5" mime_guess = "2.0.5"

View File

@@ -2,11 +2,14 @@ use crate::controllers::HttpResult;
use crate::controllers::matrix::matrix_media_controller; use crate::controllers::matrix::matrix_media_controller;
use crate::controllers::matrix::matrix_media_controller::MediaQuery; use crate::controllers::matrix::matrix_media_controller::MediaQuery;
use crate::controllers::matrix::matrix_room_controller::RoomIdInPath; use crate::controllers::matrix::matrix_room_controller::RoomIdInPath;
use crate::controllers::server_controller::ServerConstraints;
use crate::extractors::matrix_client_extractor::MatrixClientExtractor; use crate::extractors::matrix_client_extractor::MatrixClientExtractor;
use actix_multipart::form::MultipartForm;
use actix_web::dev::Payload; use actix_web::dev::Payload;
use actix_web::{FromRequest, HttpRequest, HttpResponse, web}; use actix_web::{FromRequest, HttpRequest, HttpResponse, web};
use futures_util::{StreamExt, stream}; use futures_util::{StreamExt, stream};
use matrix_sdk::Room; use matrix_sdk::Room;
use matrix_sdk::attachment::AttachmentConfig;
use matrix_sdk::deserialized_responses::{TimelineEvent, TimelineEventKind}; use matrix_sdk::deserialized_responses::{TimelineEvent, TimelineEventKind};
use matrix_sdk::media::MediaEventContent; use matrix_sdk::media::MediaEventContent;
use matrix_sdk::room::MessagesOptions; use matrix_sdk::room::MessagesOptions;
@@ -23,6 +26,7 @@ use matrix_sdk::ruma::events::{AnyMessageLikeEvent, AnyTimelineEvent};
use matrix_sdk::ruma::{MilliSecondsSinceUnixEpoch, OwnedEventId, OwnedUserId, RoomId, UInt}; use matrix_sdk::ruma::{MilliSecondsSinceUnixEpoch, OwnedEventId, OwnedUserId, RoomId, UInt};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_json::value::RawValue; use serde_json::value::RawValue;
use std::io::Read;
#[derive(Serialize)] #[derive(Serialize)]
pub struct APIEvent { pub struct APIEvent {
@@ -136,6 +140,55 @@ pub async fn send_text_message(
Ok(HttpResponse::Accepted().finish()) Ok(HttpResponse::Accepted().finish())
} }
#[derive(Debug, actix_multipart::form::MultipartForm)]
pub struct SendFileForm {
#[multipart]
file: actix_multipart::form::tempfile::TempFile,
}
pub async fn send_file(
client: MatrixClientExtractor,
path: web::Path<RoomIdInPath>,
req: HttpRequest,
) -> HttpResult {
let Some(payload) = client.auth.payload else {
return Ok(HttpResponse::BadRequest().body("No payload included in request!"));
};
// Reconstruct multipart form from authenticated request
let mut form = MultipartForm::<SendFileForm>::from_request(
&req,
&mut Payload::from(bytes::Bytes::from(payload)),
)
.await?;
// Read attachment to end
let mut buff = Vec::with_capacity(form.file.size);
form.file.file.read_to_end(&mut buff)?;
if form.file.size > ServerConstraints::default().max_upload_file_size {
return Ok(HttpResponse::NotFound().json("Uploaded file is too large!"));
}
let Some(room) = client.client.client.get_room(&path.room_id) else {
return Ok(HttpResponse::NotFound().json("Room not found!"));
};
let Some(file_name) = form.file.file_name.as_deref() else {
return Ok(HttpResponse::BadRequest().json("File name must be specified!"));
};
let Some(mime_type) = form.file.content_type.as_ref() else {
return Ok(HttpResponse::BadRequest().json("File content type must be specified!"));
};
// Do send the file
room.send_attachment(file_name, mime_type, buff, AttachmentConfig::new())
.await?;
Ok(HttpResponse::Accepted().finish())
}
#[derive(serde::Deserialize)] #[derive(serde::Deserialize)]
pub struct EventIdInPath { pub struct EventIdInPath {
pub(crate) event_id: OwnedEventId, pub(crate) event_id: OwnedEventId,

View File

@@ -39,6 +39,7 @@ pub struct ServerConstraints {
pub token_name: LenConstraints, pub token_name: LenConstraints,
pub token_ip_net: LenConstraints, pub token_ip_net: LenConstraints,
pub token_max_inactivity: LenConstraints, pub token_max_inactivity: LenConstraints,
pub max_upload_file_size: usize,
} }
impl Default for ServerConstraints { impl Default for ServerConstraints {
@@ -47,6 +48,7 @@ impl Default for ServerConstraints {
token_name: LenConstraints::new(5, 255), token_name: LenConstraints::new(5, 255),
token_ip_net: LenConstraints::max_only(44), token_ip_net: LenConstraints::max_only(44),
token_max_inactivity: LenConstraints::new(3600, 3600 * 24 * 365), token_max_inactivity: LenConstraints::new(3600, 3600 * 24 * 365),
max_upload_file_size: 20_000_000,
} }
} }
} }

View File

@@ -58,7 +58,7 @@ pub enum WsMessage {
/// Room reaction event /// Room reaction event
RoomReactionEvent(WsRoomEvent<ReactionEventContent>), RoomReactionEvent(WsRoomEvent<ReactionEventContent>),
/// Room reaction event /// Room redaction event
RoomRedactionEvent(WsRoomEvent<RoomRedactionEventContent>), RoomRedactionEvent(WsRoomEvent<RoomRedactionEventContent>),
/// Fully read message event /// Fully read message event

View File

@@ -13,6 +13,7 @@ use matrixgw_backend::controllers::matrix::{
matrix_event_controller, matrix_media_controller, matrix_profile_controller, matrix_event_controller, matrix_media_controller, matrix_profile_controller,
matrix_room_controller, matrix_space_controller, matrix_room_controller, matrix_space_controller,
}; };
use matrixgw_backend::controllers::server_controller::ServerConstraints;
use matrixgw_backend::controllers::{ use matrixgw_backend::controllers::{
auth_controller, matrix_link_controller, matrix_sync_thread_controller, server_controller, auth_controller, matrix_link_controller, matrix_sync_thread_controller, server_controller,
static_controller, tokens_controller, ws_controller, static_controller, tokens_controller, ws_controller,
@@ -75,6 +76,9 @@ async fn main() -> std::io::Result<()> {
.wrap(Logger::default()) .wrap(Logger::default())
.wrap(session_mw) .wrap(session_mw)
.wrap(cors) .wrap(cors)
.app_data(web::PayloadConfig::new(
ServerConstraints::default().max_upload_file_size,
))
.app_data(web::Data::new(manager_actor_clone.clone())) .app_data(web::Data::new(manager_actor_clone.clone()))
.app_data(web::Data::new(RemoteIPConfig { .app_data(web::Data::new(RemoteIPConfig {
proxy: AppConfig::get().proxy_ip.clone(), proxy: AppConfig::get().proxy_ip.clone(),
@@ -182,6 +186,10 @@ async fn main() -> std::io::Result<()> {
"/api/matrix/room/{room_id}/send_text_message", "/api/matrix/room/{room_id}/send_text_message",
web::post().to(matrix_event_controller::send_text_message), web::post().to(matrix_event_controller::send_text_message),
) )
.route(
"/api/matrix/room/{room_id}/send_file",
web::post().to(matrix_event_controller::send_file),
)
.route( .route(
"/api/matrix/room/{room_id}/event/{event_id}/set_text_content", "/api/matrix/room/{room_id}/event/{event_id}/set_text_content",
web::post().to(matrix_event_controller::set_text_content), web::post().to(matrix_event_controller::set_text_content),

View File

@@ -13,31 +13,32 @@
"@fontsource/roboto": "^5.2.10", "@fontsource/roboto": "^5.2.10",
"@mui/icons-material": "^7.3.8", "@mui/icons-material": "^7.3.8",
"@mui/material": "^7.3.8", "@mui/material": "^7.3.8",
"@mui/x-data-grid": "^8.27.1", "@mui/x-data-grid": "^8.27.3",
"@mui/x-date-pickers": "^8.27.2", "@mui/x-date-pickers": "^8.27.2",
"date-and-time": "^4.2.0", "date-and-time": "^4.3.0",
"dayjs": "^1.11.19", "dayjs": "^1.11.19",
"emoji-picker-react": "^4.17.4", "emoji-picker-react": "^4.18.0",
"filesize": "^11.0.13",
"is-cidr": "^6.0.3", "is-cidr": "^6.0.3",
"qrcode.react": "^4.2.0", "qrcode.react": "^4.2.0",
"react": "^19.2.4", "react": "^19.2.4",
"react-dom": "^19.2.4", "react-dom": "^19.2.4",
"react-favicon": "^2.0.7", "react-favicon": "^2.0.7",
"react-json-view-lite": "^2.5.0", "react-json-view-lite": "^2.5.0",
"react-router": "^7.13.0" "react-router": "^7.13.1"
}, },
"devDependencies": { "devDependencies": {
"@eslint/js": "^9.39.3", "@eslint/js": "^9.39.3",
"@types/node": "^25.1.0", "@types/node": "^25.3.3",
"@types/react": "^19.2.14", "@types/react": "^19.2.14",
"@types/react-dom": "^19.2.3", "@types/react-dom": "^19.2.3",
"@vitejs/plugin-react": "^5.1.4", "@vitejs/plugin-react": "^5.1.4",
"eslint": "^9.39.2", "eslint": "^9.39.3",
"eslint-plugin-react-hooks": "^7.0.1", "eslint-plugin-react-hooks": "^7.0.1",
"eslint-plugin-react-refresh": "^0.4.26", "eslint-plugin-react-refresh": "^0.4.26",
"globals": "^16.5.0", "globals": "^17.4.0",
"typescript": "~5.9.3", "typescript": "~5.9.3",
"typescript-eslint": "^8.54.0", "typescript-eslint": "^8.56.1",
"vite": "npm:rolldown-vite@7.3.1" "vite": "npm:rolldown-vite@7.3.1"
} }
}, },
@@ -1006,9 +1007,9 @@
} }
}, },
"node_modules/@mui/x-data-grid": { "node_modules/@mui/x-data-grid": {
"version": "8.27.1", "version": "8.27.3",
"resolved": "https://registry.npmjs.org/@mui/x-data-grid/-/x-data-grid-8.27.1.tgz", "resolved": "https://registry.npmjs.org/@mui/x-data-grid/-/x-data-grid-8.27.3.tgz",
"integrity": "sha512-Xje197cUzmch33vLip5KR44SQ0CB49iutBTaoIMSxIVSfNqI8BadEh8PpPCacuTeSty06tJKpBMe66NNYWfHFw==", "integrity": "sha512-4JhwMBLGpaSyg8tqnYkegk4gfXovqxT+gRlm2Lb8f3WDFUPsVGL5c/bMi7MSRkYJ2h1pGXaLKaYORLR62xuRAQ==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@babel/runtime": "^7.28.4", "@babel/runtime": "^7.28.4",
@@ -1495,13 +1496,13 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/@types/node": { "node_modules/@types/node": {
"version": "25.1.0", "version": "25.3.3",
"resolved": "https://registry.npmjs.org/@types/node/-/node-25.1.0.tgz", "resolved": "https://registry.npmjs.org/@types/node/-/node-25.3.3.tgz",
"integrity": "sha512-t7frlewr6+cbx+9Ohpl0NOTKXZNV9xHRmNOvql47BFJKcEG1CxtxlPEEe+gR9uhVWM4DwhnvTF110mIL4yP9RA==", "integrity": "sha512-DpzbrH7wIcBaJibpKo9nnSQL0MTRdnWttGyE5haGwK86xgMOkFLp7vEyfQPGLOJh5wNYiJ3V9PmUMDhV9u8kkQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"undici-types": "~7.16.0" "undici-types": "~7.18.0"
} }
}, },
"node_modules/@types/parse-json": { "node_modules/@types/parse-json": {
@@ -1545,17 +1546,17 @@
} }
}, },
"node_modules/@typescript-eslint/eslint-plugin": { "node_modules/@typescript-eslint/eslint-plugin": {
"version": "8.54.0", "version": "8.56.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.54.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.56.1.tgz",
"integrity": "sha512-hAAP5io/7csFStuOmR782YmTthKBJ9ND3WVL60hcOjvtGFb+HJxH4O5huAcmcZ9v9G8P+JETiZ/G1B8MALnWZQ==", "integrity": "sha512-Jz9ZztpB37dNC+HU2HI28Bs9QXpzCz+y/twHOwhyrIRdbuVDxSytJNDl6z/aAKlaRIwC7y8wJdkBv7FxYGgi0A==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@eslint-community/regexpp": "^4.12.2", "@eslint-community/regexpp": "^4.12.2",
"@typescript-eslint/scope-manager": "8.54.0", "@typescript-eslint/scope-manager": "8.56.1",
"@typescript-eslint/type-utils": "8.54.0", "@typescript-eslint/type-utils": "8.56.1",
"@typescript-eslint/utils": "8.54.0", "@typescript-eslint/utils": "8.56.1",
"@typescript-eslint/visitor-keys": "8.54.0", "@typescript-eslint/visitor-keys": "8.56.1",
"ignore": "^7.0.5", "ignore": "^7.0.5",
"natural-compare": "^1.4.0", "natural-compare": "^1.4.0",
"ts-api-utils": "^2.4.0" "ts-api-utils": "^2.4.0"
@@ -1568,8 +1569,8 @@
"url": "https://opencollective.com/typescript-eslint" "url": "https://opencollective.com/typescript-eslint"
}, },
"peerDependencies": { "peerDependencies": {
"@typescript-eslint/parser": "^8.54.0", "@typescript-eslint/parser": "^8.56.1",
"eslint": "^8.57.0 || ^9.0.0", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
"typescript": ">=4.8.4 <6.0.0" "typescript": ">=4.8.4 <6.0.0"
} }
}, },
@@ -1584,16 +1585,16 @@
} }
}, },
"node_modules/@typescript-eslint/parser": { "node_modules/@typescript-eslint/parser": {
"version": "8.54.0", "version": "8.56.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.54.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.56.1.tgz",
"integrity": "sha512-BtE0k6cjwjLZoZixN0t5AKP0kSzlGu7FctRXYuPAm//aaiZhmfq1JwdYpYr1brzEspYyFeF+8XF5j2VK6oalrA==", "integrity": "sha512-klQbnPAAiGYFyI02+znpBRLyjL4/BrBd0nyWkdC0s/6xFLkXYQ8OoRrSkqacS1ddVxf/LDyODIKbQ5TgKAf/Fg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@typescript-eslint/scope-manager": "8.54.0", "@typescript-eslint/scope-manager": "8.56.1",
"@typescript-eslint/types": "8.54.0", "@typescript-eslint/types": "8.56.1",
"@typescript-eslint/typescript-estree": "8.54.0", "@typescript-eslint/typescript-estree": "8.56.1",
"@typescript-eslint/visitor-keys": "8.54.0", "@typescript-eslint/visitor-keys": "8.56.1",
"debug": "^4.4.3" "debug": "^4.4.3"
}, },
"engines": { "engines": {
@@ -1604,19 +1605,19 @@
"url": "https://opencollective.com/typescript-eslint" "url": "https://opencollective.com/typescript-eslint"
}, },
"peerDependencies": { "peerDependencies": {
"eslint": "^8.57.0 || ^9.0.0", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
"typescript": ">=4.8.4 <6.0.0" "typescript": ">=4.8.4 <6.0.0"
} }
}, },
"node_modules/@typescript-eslint/project-service": { "node_modules/@typescript-eslint/project-service": {
"version": "8.54.0", "version": "8.56.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.54.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.56.1.tgz",
"integrity": "sha512-YPf+rvJ1s7MyiWM4uTRhE4DvBXrEV+d8oC3P9Y2eT7S+HBS0clybdMIPnhiATi9vZOYDc7OQ1L/i6ga6NFYK/g==", "integrity": "sha512-TAdqQTzHNNvlVFfR+hu2PDJrURiwKsUvxFn1M0h95BB8ah5jejas08jUWG4dBA68jDMI988IvtfdAI53JzEHOQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@typescript-eslint/tsconfig-utils": "^8.54.0", "@typescript-eslint/tsconfig-utils": "^8.56.1",
"@typescript-eslint/types": "^8.54.0", "@typescript-eslint/types": "^8.56.1",
"debug": "^4.4.3" "debug": "^4.4.3"
}, },
"engines": { "engines": {
@@ -1631,14 +1632,14 @@
} }
}, },
"node_modules/@typescript-eslint/scope-manager": { "node_modules/@typescript-eslint/scope-manager": {
"version": "8.54.0", "version": "8.56.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.54.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.56.1.tgz",
"integrity": "sha512-27rYVQku26j/PbHYcVfRPonmOlVI6gihHtXFbTdB5sb6qA0wdAQAbyXFVarQ5t4HRojIz64IV90YtsjQSSGlQg==", "integrity": "sha512-YAi4VDKcIZp0O4tz/haYKhmIDZFEUPOreKbfdAN3SzUDMcPhJ8QI99xQXqX+HoUVq8cs85eRKnD+rne2UAnj2w==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@typescript-eslint/types": "8.54.0", "@typescript-eslint/types": "8.56.1",
"@typescript-eslint/visitor-keys": "8.54.0" "@typescript-eslint/visitor-keys": "8.56.1"
}, },
"engines": { "engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0" "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -1649,9 +1650,9 @@
} }
}, },
"node_modules/@typescript-eslint/tsconfig-utils": { "node_modules/@typescript-eslint/tsconfig-utils": {
"version": "8.54.0", "version": "8.56.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.54.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.56.1.tgz",
"integrity": "sha512-dRgOyT2hPk/JwxNMZDsIXDgyl9axdJI3ogZ2XWhBPsnZUv+hPesa5iuhdYt2gzwA9t8RE5ytOJ6xB0moV0Ujvw==", "integrity": "sha512-qOtCYzKEeyr3aR9f28mPJqBty7+DBqsdd63eO0yyDwc6vgThj2UjWfJIcsFeSucYydqcuudMOprZ+x1SpF3ZuQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
@@ -1666,15 +1667,15 @@
} }
}, },
"node_modules/@typescript-eslint/type-utils": { "node_modules/@typescript-eslint/type-utils": {
"version": "8.54.0", "version": "8.56.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.54.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.56.1.tgz",
"integrity": "sha512-hiLguxJWHjjwL6xMBwD903ciAwd7DmK30Y9Axs/etOkftC3ZNN9K44IuRD/EB08amu+Zw6W37x9RecLkOo3pMA==", "integrity": "sha512-yB/7dxi7MgTtGhZdaHCemf7PuwrHMenHjmzgUW1aJpO+bBU43OycnM3Wn+DdvDO/8zzA9HlhaJ0AUGuvri4oGg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@typescript-eslint/types": "8.54.0", "@typescript-eslint/types": "8.56.1",
"@typescript-eslint/typescript-estree": "8.54.0", "@typescript-eslint/typescript-estree": "8.56.1",
"@typescript-eslint/utils": "8.54.0", "@typescript-eslint/utils": "8.56.1",
"debug": "^4.4.3", "debug": "^4.4.3",
"ts-api-utils": "^2.4.0" "ts-api-utils": "^2.4.0"
}, },
@@ -1686,14 +1687,14 @@
"url": "https://opencollective.com/typescript-eslint" "url": "https://opencollective.com/typescript-eslint"
}, },
"peerDependencies": { "peerDependencies": {
"eslint": "^8.57.0 || ^9.0.0", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
"typescript": ">=4.8.4 <6.0.0" "typescript": ">=4.8.4 <6.0.0"
} }
}, },
"node_modules/@typescript-eslint/types": { "node_modules/@typescript-eslint/types": {
"version": "8.54.0", "version": "8.56.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.54.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.56.1.tgz",
"integrity": "sha512-PDUI9R1BVjqu7AUDsRBbKMtwmjWcn4J3le+5LpcFgWULN3LvHC5rkc9gCVxbrsrGmO1jfPybN5s6h4Jy+OnkAA==", "integrity": "sha512-dbMkdIUkIkchgGDIv7KLUpa0Mda4IYjo4IAMJUZ+3xNoUXxMsk9YtKpTHSChRS85o+H9ftm51gsK1dZReY9CVw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
@@ -1705,18 +1706,18 @@
} }
}, },
"node_modules/@typescript-eslint/typescript-estree": { "node_modules/@typescript-eslint/typescript-estree": {
"version": "8.54.0", "version": "8.56.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.54.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.56.1.tgz",
"integrity": "sha512-BUwcskRaPvTk6fzVWgDPdUndLjB87KYDrN5EYGetnktoeAvPtO4ONHlAZDnj5VFnUANg0Sjm7j4usBlnoVMHwA==", "integrity": "sha512-qzUL1qgalIvKWAf9C1HpvBjif+Vm6rcT5wZd4VoMb9+Km3iS3Cv9DY6dMRMDtPnwRAFyAi7YXJpTIEXLvdfPxg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@typescript-eslint/project-service": "8.54.0", "@typescript-eslint/project-service": "8.56.1",
"@typescript-eslint/tsconfig-utils": "8.54.0", "@typescript-eslint/tsconfig-utils": "8.56.1",
"@typescript-eslint/types": "8.54.0", "@typescript-eslint/types": "8.56.1",
"@typescript-eslint/visitor-keys": "8.54.0", "@typescript-eslint/visitor-keys": "8.56.1",
"debug": "^4.4.3", "debug": "^4.4.3",
"minimatch": "^9.0.5", "minimatch": "^10.2.2",
"semver": "^7.7.3", "semver": "^7.7.3",
"tinyglobby": "^0.2.15", "tinyglobby": "^0.2.15",
"ts-api-utils": "^2.4.0" "ts-api-utils": "^2.4.0"
@@ -1732,36 +1733,49 @@
"typescript": ">=4.8.4 <6.0.0" "typescript": ">=4.8.4 <6.0.0"
} }
}, },
"node_modules/@typescript-eslint/typescript-estree/node_modules/balanced-match": {
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz",
"integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==",
"dev": true,
"license": "MIT",
"engines": {
"node": "18 || 20 || >=22"
}
},
"node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": {
"version": "2.0.2", "version": "5.0.4",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.4.tgz",
"integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "integrity": "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"balanced-match": "^1.0.0" "balanced-match": "^4.0.2"
},
"engines": {
"node": "18 || 20 || >=22"
} }
}, },
"node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": {
"version": "9.0.5", "version": "10.2.4",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz",
"integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==",
"dev": true, "dev": true,
"license": "ISC", "license": "BlueOak-1.0.0",
"dependencies": { "dependencies": {
"brace-expansion": "^2.0.1" "brace-expansion": "^5.0.2"
}, },
"engines": { "engines": {
"node": ">=16 || 14 >=14.17" "node": "18 || 20 || >=22"
}, },
"funding": { "funding": {
"url": "https://github.com/sponsors/isaacs" "url": "https://github.com/sponsors/isaacs"
} }
}, },
"node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": {
"version": "7.7.3", "version": "7.7.4",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
"integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==",
"dev": true, "dev": true,
"license": "ISC", "license": "ISC",
"bin": { "bin": {
@@ -1772,16 +1786,16 @@
} }
}, },
"node_modules/@typescript-eslint/utils": { "node_modules/@typescript-eslint/utils": {
"version": "8.54.0", "version": "8.56.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.54.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.56.1.tgz",
"integrity": "sha512-9Cnda8GS57AQakvRyG0PTejJNlA2xhvyNtEVIMlDWOOeEyBkYWhGPnfrIAnqxLMTSTo6q8g12XVjjev5l1NvMA==", "integrity": "sha512-HPAVNIME3tABJ61siYlHzSWCGtOoeP2RTIaHXFMPqjrQKCGB9OgUVdiNgH7TJS2JNIQ5qQ4RsAUDuGaGme/KOA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@eslint-community/eslint-utils": "^4.9.1", "@eslint-community/eslint-utils": "^4.9.1",
"@typescript-eslint/scope-manager": "8.54.0", "@typescript-eslint/scope-manager": "8.56.1",
"@typescript-eslint/types": "8.54.0", "@typescript-eslint/types": "8.56.1",
"@typescript-eslint/typescript-estree": "8.54.0" "@typescript-eslint/typescript-estree": "8.56.1"
}, },
"engines": { "engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0" "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -1791,19 +1805,19 @@
"url": "https://opencollective.com/typescript-eslint" "url": "https://opencollective.com/typescript-eslint"
}, },
"peerDependencies": { "peerDependencies": {
"eslint": "^8.57.0 || ^9.0.0", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
"typescript": ">=4.8.4 <6.0.0" "typescript": ">=4.8.4 <6.0.0"
} }
}, },
"node_modules/@typescript-eslint/visitor-keys": { "node_modules/@typescript-eslint/visitor-keys": {
"version": "8.54.0", "version": "8.56.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.54.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.56.1.tgz",
"integrity": "sha512-VFlhGSl4opC0bprJiItPQ1RfUhGDIBokcPwaFH4yiBCaNPeld/9VeXbiPO1cLyorQi1G1vL+ecBk1x8o1axORA==", "integrity": "sha512-KiROIzYdEV85YygXw6BI/Dx4fnBlFQu6Mq4QE4MOH9fFnhohw6wX/OAvDY2/C+ut0I3RSPKenvZJIVYqJNkhEw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@typescript-eslint/types": "8.54.0", "@typescript-eslint/types": "8.56.1",
"eslint-visitor-keys": "^4.2.1" "eslint-visitor-keys": "^5.0.0"
}, },
"engines": { "engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0" "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -1813,6 +1827,19 @@
"url": "https://opencollective.com/typescript-eslint" "url": "https://opencollective.com/typescript-eslint"
} }
}, },
"node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz",
"integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==",
"dev": true,
"license": "Apache-2.0",
"engines": {
"node": "^20.19.0 || ^22.13.0 || >=24"
},
"funding": {
"url": "https://opencollective.com/eslint"
}
},
"node_modules/@vitejs/plugin-react": { "node_modules/@vitejs/plugin-react": {
"version": "5.1.4", "version": "5.1.4",
"resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-5.1.4.tgz", "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-5.1.4.tgz",
@@ -2139,9 +2166,9 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/date-and-time": { "node_modules/date-and-time": {
"version": "4.2.0", "version": "4.3.0",
"resolved": "https://registry.npmjs.org/date-and-time/-/date-and-time-4.2.0.tgz", "resolved": "https://registry.npmjs.org/date-and-time/-/date-and-time-4.3.0.tgz",
"integrity": "sha512-bqLrJqsjXMW3yQlR7QrQZqZNVDwo+GkvcAeRzZScAdUAgQPP+baV+j6HFbVp3cpCgVez3Mx2zIPqwk9FMfkVIw==", "integrity": "sha512-dVj2DNzRQtgIrDOcX1DVuMEv0S8wF/zZ6F9OfD1P+zwmtk3hN28nIff8dBGl320DjVNGssfFrdbUad7lDYLZLQ==",
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=18" "node": ">=18"
@@ -2205,9 +2232,9 @@
"license": "ISC" "license": "ISC"
}, },
"node_modules/emoji-picker-react": { "node_modules/emoji-picker-react": {
"version": "4.17.4", "version": "4.18.0",
"resolved": "https://registry.npmjs.org/emoji-picker-react/-/emoji-picker-react-4.17.4.tgz", "resolved": "https://registry.npmjs.org/emoji-picker-react/-/emoji-picker-react-4.18.0.tgz",
"integrity": "sha512-ZO85fhiyjCQxOSGkmBarbGVe+t7SRUME0XuE7AwThtzEay+xnYH8FSuCspiip2mFJT5DPO2lPnELqEvzwjBcYA==", "integrity": "sha512-vLTrLfApXAIciguGE57pXPWs9lPLBspbEpPMiUq03TIli2dHZBiB+aZ0R9/Wat0xmTfcd4AuEzQgSYxEZ8C88Q==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"flairup": "1.0.0" "flairup": "1.0.0"
@@ -2251,9 +2278,9 @@
} }
}, },
"node_modules/eslint": { "node_modules/eslint": {
"version": "9.39.2", "version": "9.39.3",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.2.tgz", "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.3.tgz",
"integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==", "integrity": "sha512-VmQ+sifHUbI/IcSopBCF/HO3YiHQx/AVd3UVyYL6weuwW+HvON9VYn5l6Zl1WZzPWXPNZrSQpxwkkZ/VuvJZzg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@@ -2263,7 +2290,7 @@
"@eslint/config-helpers": "^0.4.2", "@eslint/config-helpers": "^0.4.2",
"@eslint/core": "^0.17.0", "@eslint/core": "^0.17.0",
"@eslint/eslintrc": "^3.3.1", "@eslint/eslintrc": "^3.3.1",
"@eslint/js": "9.39.2", "@eslint/js": "9.39.3",
"@eslint/plugin-kit": "^0.4.1", "@eslint/plugin-kit": "^0.4.1",
"@humanfs/node": "^0.16.6", "@humanfs/node": "^0.16.6",
"@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/module-importer": "^1.0.1",
@@ -2370,19 +2397,6 @@
"url": "https://opencollective.com/eslint" "url": "https://opencollective.com/eslint"
} }
}, },
"node_modules/eslint/node_modules/@eslint/js": {
"version": "9.39.2",
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.2.tgz",
"integrity": "sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA==",
"dev": true,
"license": "MIT",
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
"url": "https://eslint.org/donate"
}
},
"node_modules/espree": { "node_modules/espree": {
"version": "10.4.0", "version": "10.4.0",
"resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz",
@@ -2499,6 +2513,15 @@
"node": ">=16.0.0" "node": ">=16.0.0"
} }
}, },
"node_modules/filesize": {
"version": "11.0.13",
"resolved": "https://registry.npmjs.org/filesize/-/filesize-11.0.13.tgz",
"integrity": "sha512-mYJ/qXKvREuO0uH8LTQJ6v7GsUvVOguqxg2VTwQUkyTPXXRRWPdjuUPVqdBrJQhvci48OHlNGRnux+Slr2Rnvw==",
"license": "BSD-3-Clause",
"engines": {
"node": ">= 10.8.0"
}
},
"node_modules/find-root": { "node_modules/find-root": {
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz",
@@ -2597,9 +2620,9 @@
} }
}, },
"node_modules/globals": { "node_modules/globals": {
"version": "16.5.0", "version": "17.4.0",
"resolved": "https://registry.npmjs.org/globals/-/globals-16.5.0.tgz", "resolved": "https://registry.npmjs.org/globals/-/globals-17.4.0.tgz",
"integrity": "sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ==", "integrity": "sha512-hjrNztw/VajQwOLsMNT1cbJiH2muO3OROCHnbehc8eY5JyD2gqz4AcMHPqgaOR59DjgUjYAYLeH699g/eWi2jw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
@@ -3516,9 +3539,9 @@
} }
}, },
"node_modules/react-router": { "node_modules/react-router": {
"version": "7.13.0", "version": "7.13.1",
"resolved": "https://registry.npmjs.org/react-router/-/react-router-7.13.0.tgz", "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.13.1.tgz",
"integrity": "sha512-PZgus8ETambRT17BUm/LL8lX3Of+oiLaPuVTRH3l1eLvSPpKO3AvhAEb5N7ihAFZQrYDqkvvWfFh9p0z9VsjLw==", "integrity": "sha512-td+xP4X2/6BJvZoX6xw++A2DdEi++YypA69bJUV5oVvqf6/9/9nNlD70YO1e9d3MyamJEBQFEzk6mbfDYbqrSA==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"cookie": "^1.0.1", "cookie": "^1.0.1",
@@ -3794,16 +3817,16 @@
} }
}, },
"node_modules/typescript-eslint": { "node_modules/typescript-eslint": {
"version": "8.54.0", "version": "8.56.1",
"resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.54.0.tgz", "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.56.1.tgz",
"integrity": "sha512-CKsJ+g53QpsNPqbzUsfKVgd3Lny4yKZ1pP4qN3jdMOg/sisIDLGyDMezycquXLE5JsEU0wp3dGNdzig0/fmSVQ==", "integrity": "sha512-U4lM6pjmBX7J5wk4szltF7I1cGBHXZopnAXCMXb3+fZ3B/0Z3hq3wS/CCUB2NZBNAExK92mCU2tEohWuwVMsDQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@typescript-eslint/eslint-plugin": "8.54.0", "@typescript-eslint/eslint-plugin": "8.56.1",
"@typescript-eslint/parser": "8.54.0", "@typescript-eslint/parser": "8.56.1",
"@typescript-eslint/typescript-estree": "8.54.0", "@typescript-eslint/typescript-estree": "8.56.1",
"@typescript-eslint/utils": "8.54.0" "@typescript-eslint/utils": "8.56.1"
}, },
"engines": { "engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0" "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -3813,14 +3836,14 @@
"url": "https://opencollective.com/typescript-eslint" "url": "https://opencollective.com/typescript-eslint"
}, },
"peerDependencies": { "peerDependencies": {
"eslint": "^8.57.0 || ^9.0.0", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
"typescript": ">=4.8.4 <6.0.0" "typescript": ">=4.8.4 <6.0.0"
} }
}, },
"node_modules/undici-types": { "node_modules/undici-types": {
"version": "7.16.0", "version": "7.18.2",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz",
"integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==",
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },

View File

@@ -15,31 +15,32 @@
"@fontsource/roboto": "^5.2.10", "@fontsource/roboto": "^5.2.10",
"@mui/icons-material": "^7.3.8", "@mui/icons-material": "^7.3.8",
"@mui/material": "^7.3.8", "@mui/material": "^7.3.8",
"@mui/x-data-grid": "^8.27.1", "@mui/x-data-grid": "^8.27.3",
"@mui/x-date-pickers": "^8.27.2", "@mui/x-date-pickers": "^8.27.2",
"date-and-time": "^4.2.0", "date-and-time": "^4.3.0",
"dayjs": "^1.11.19", "dayjs": "^1.11.19",
"emoji-picker-react": "^4.17.4", "emoji-picker-react": "^4.18.0",
"filesize": "^11.0.13",
"is-cidr": "^6.0.3", "is-cidr": "^6.0.3",
"qrcode.react": "^4.2.0", "qrcode.react": "^4.2.0",
"react": "^19.2.4", "react": "^19.2.4",
"react-dom": "^19.2.4", "react-dom": "^19.2.4",
"react-favicon": "^2.0.7", "react-favicon": "^2.0.7",
"react-json-view-lite": "^2.5.0", "react-json-view-lite": "^2.5.0",
"react-router": "^7.13.0" "react-router": "^7.13.1"
}, },
"devDependencies": { "devDependencies": {
"@eslint/js": "^9.39.3", "@eslint/js": "^9.39.3",
"@types/node": "^25.1.0", "@types/node": "^25.3.3",
"@types/react": "^19.2.14", "@types/react": "^19.2.14",
"@types/react-dom": "^19.2.3", "@types/react-dom": "^19.2.3",
"@vitejs/plugin-react": "^5.1.4", "@vitejs/plugin-react": "^5.1.4",
"eslint": "^9.39.2", "eslint": "^10.0.2",
"eslint-plugin-react-hooks": "^7.0.1", "eslint-plugin-react-hooks": "^7.0.1",
"eslint-plugin-react-refresh": "^0.4.26", "eslint-plugin-react-refresh": "^0.4.26",
"globals": "^16.5.0", "globals": "^17.4.0",
"typescript": "~5.9.3", "typescript": "~5.9.3",
"typescript-eslint": "^8.54.0", "typescript-eslint": "^8.56.1",
"vite": "npm:rolldown-vite@7.3.1" "vite": "npm:rolldown-vite@7.3.1"
}, },
"overrides": { "overrides": {

View File

@@ -16,6 +16,7 @@ export interface ServerConstraints {
token_name: LenConstraint; token_name: LenConstraint;
token_ip_net: LenConstraint; token_ip_net: LenConstraint;
token_max_inactivity: LenConstraint; token_max_inactivity: LenConstraint;
max_upload_file_size: number;
} }
export interface LenConstraint { export interface LenConstraint {

View File

@@ -68,7 +68,7 @@ export class MatrixApiEvent {
*/ */
static async GetRoomEvents( static async GetRoomEvents(
room: Room, room: Room,
from?: string from?: string,
): Promise<MatrixEventsList> { ): Promise<MatrixEventsList> {
return ( return (
await APIClient.exec({ await APIClient.exec({
@@ -86,7 +86,7 @@ export class MatrixApiEvent {
static GetEventFileURL( static GetEventFileURL(
room: Room, room: Room,
event_id: string, event_id: string,
thumbnail: boolean thumbnail: boolean,
): string { ): string {
return `${APIClient.ActualBackendURL()}/matrix/room/${ return `${APIClient.ActualBackendURL()}/matrix/room/${
room.id room.id
@@ -104,13 +104,26 @@ export class MatrixApiEvent {
}); });
} }
/**
* Send file message
*/
static async SendFileMessage(room: Room, file: Blob): Promise<void> {
const formData = new FormData();
formData.set("file", file);
await APIClient.exec({
method: "POST",
uri: `/matrix/room/${room.id}/send_file`,
formData,
});
}
/** /**
* Edit text message content * Edit text message content
*/ */
static async SetTextMessageContent( static async SetTextMessageContent(
room: Room, room: Room,
event_id: string, event_id: string,
content: string content: string,
): Promise<void> { ): Promise<void> {
await APIClient.exec({ await APIClient.exec({
method: "POST", method: "POST",
@@ -125,7 +138,7 @@ export class MatrixApiEvent {
static async ReactToEvent( static async ReactToEvent(
room: Room, room: Room,
event_id: string, event_id: string,
key: string key: string,
): Promise<void> { ): Promise<void> {
await APIClient.exec({ await APIClient.exec({
method: "POST", method: "POST",

View File

@@ -0,0 +1,33 @@
import { filesize } from "filesize";
/**
* Select a file to upload
*/
export async function selectFileToUpload(p: {
allowedTypes?: string[];
maxSize?: number;
}): Promise<Blob | null> {
// Create file element
const fileEl = document.createElement("input");
fileEl.type = "file";
if (p.allowedTypes && p.allowedTypes.length > 0)
fileEl.accept = p.allowedTypes.join(",");
fileEl.click();
// Wait for a file to be chosen
await new Promise((res) =>
fileEl.addEventListener("change", () => res(null)),
);
if ((fileEl.files?.length ?? 0) === 0) return null;
const file = fileEl.files![0];
// Check file size
if (p.maxSize && file.size > p.maxSize) {
throw new Error(
`The file is too big ! (max accepted file size : ${filesize(p.maxSize)})`,
);
}
return file;
}

View File

@@ -1,18 +1,23 @@
import AttachFileIcon from "@mui/icons-material/AttachFile";
import SendIcon from "@mui/icons-material/Send"; import SendIcon from "@mui/icons-material/Send";
import { IconButton, TextField } from "@mui/material"; import { IconButton, TextField, Tooltip } from "@mui/material";
import React, { type FormEvent } from "react"; import React, { type SyntheticEvent } from "react";
import { MatrixApiEvent } from "../../api/matrix/MatrixApiEvent"; import { MatrixApiEvent } from "../../api/matrix/MatrixApiEvent";
import type { Room } from "../../api/matrix/MatrixApiRoom"; import type { Room } from "../../api/matrix/MatrixApiRoom";
import { useAlert } from "../../hooks/contexts_provider/AlertDialogProvider"; import { useAlert } from "../../hooks/contexts_provider/AlertDialogProvider";
import { useLoadingMessage } from "../../hooks/contexts_provider/LoadingMessageProvider"; import { useLoadingMessage } from "../../hooks/contexts_provider/LoadingMessageProvider";
import { selectFileToUpload } from "../../utils/FilesUtils";
import { ServerApi } from "../../api/ServerApi";
import { useSnackbar } from "../../hooks/contexts_provider/SnackbarProvider";
export function SendMessageForm(p: { room: Room }): React.ReactElement { export function SendMessageForm(p: { room: Room }): React.ReactElement {
const loadingMessage = useLoadingMessage(); const loadingMessage = useLoadingMessage();
const alert = useAlert(); const alert = useAlert();
const snackbar = useSnackbar();
const [text, setText] = React.useState(""); const [text, setText] = React.useState("");
const handleTextSubmit = async (e: FormEvent) => { const handleTextSubmit = async (e: SyntheticEvent) => {
e.preventDefault(); e.preventDefault();
if (text === "") return; if (text === "") return;
@@ -31,6 +36,26 @@ export function SendMessageForm(p: { room: Room }): React.ReactElement {
} }
}; };
const handleFileSubmit = async () => {
try {
const file = await selectFileToUpload({
maxSize: ServerApi.Config.constraints.max_upload_file_size,
});
if (!file) return;
loadingMessage.show("Uploading file...");
await MatrixApiEvent.SendFileMessage(p.room, file);
snackbar("The file was successfully uploaded!");
} catch (e) {
console.error(e);
alert(`Failed to upload file! ${e}`);
} finally {
loadingMessage.hide();
}
};
return ( return (
<form onSubmit={handleTextSubmit}> <form onSubmit={handleTextSubmit}>
<div <div
@@ -50,6 +75,13 @@ export function SendMessageForm(p: { room: Room }): React.ReactElement {
value={text} value={text}
onChange={(e) => setText(e.target.value)} onChange={(e) => setText(e.target.value)}
/> />
<span style={{ width: "10px" }}></span>
<Tooltip title="Send a file">
<IconButton size="small" onClick={handleFileSubmit}>
<AttachFileIcon />
</IconButton>
</Tooltip>
<span style={{ width: "10px" }}></span>
<IconButton <IconButton
size="small" size="small"
style={{ visibility: text === "" ? "hidden" : "visible" }} style={{ visibility: text === "" ? "hidden" : "visible" }}