cista 1.1.0__tar.gz → 1.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (88) hide show
  1. {cista-1.1.0 → cista-1.2.0}/PKG-INFO +2 -2
  2. {cista-1.1.0 → cista-1.2.0}/cista/__main__.py +41 -16
  3. {cista-1.1.0 → cista-1.2.0}/cista/_version.py +1 -1
  4. {cista-1.1.0 → cista-1.2.0}/cista/app.py +14 -13
  5. {cista-1.1.0 → cista-1.2.0}/cista/auth.py +89 -0
  6. {cista-1.1.0 → cista-1.2.0}/cista/config.py +44 -24
  7. {cista-1.1.0 → cista-1.2.0}/cista/util/lrucache.py +2 -2
  8. cista-1.1.0/cista/wwwroot/assets/add-file-6a41e251.js → cista-1.2.0/cista/wwwroot/assets/add-file-5a0702fe.js +1 -1
  9. cista-1.1.0/cista/wwwroot/assets/add-folder-62d934ef.js → cista-1.2.0/cista/wwwroot/assets/add-folder-1c5c47c6.js +1 -1
  10. cista-1.1.0/cista/wwwroot/assets/arrow-a3a583f8.js → cista-1.2.0/cista/wwwroot/assets/arrow-cab35945.js +1 -1
  11. cista-1.1.0/cista/wwwroot/assets/arrows-h-d9e36082.js → cista-1.2.0/cista/wwwroot/assets/arrows-h-12b221f0.js +1 -1
  12. cista-1.1.0/cista/wwwroot/assets/arrows-v-5223491a.js → cista-1.2.0/cista/wwwroot/assets/arrows-v-b8ff8185.js +1 -1
  13. cista-1.1.0/cista/wwwroot/assets/check-f7566407.js → cista-1.2.0/cista/wwwroot/assets/check-e4733b70.js +1 -1
  14. cista-1.1.0/cista/wwwroot/assets/code-8e0123d4.js → cista-1.2.0/cista/wwwroot/assets/code-aec4eefb.js +1 -1
  15. cista-1.1.0/cista/wwwroot/assets/copy-a70a3d1c.js → cista-1.2.0/cista/wwwroot/assets/copy-14f467a9.js +1 -1
  16. cista-1.1.0/cista/wwwroot/assets/create-file-d995875e.js → cista-1.2.0/cista/wwwroot/assets/create-file-d68a640f.js +1 -1
  17. cista-1.1.0/cista/wwwroot/assets/create-folder-c448deda.js → cista-1.2.0/cista/wwwroot/assets/create-folder-7e7528a0.js +1 -1
  18. cista-1.1.0/cista/wwwroot/assets/cross-e74ed6f4.js → cista-1.2.0/cista/wwwroot/assets/cross-0739a40a.js +1 -1
  19. cista-1.1.0/cista/wwwroot/assets/disk-f1376321.js → cista-1.2.0/cista/wwwroot/assets/disk-8c5530e3.js +1 -1
  20. cista-1.1.0/cista/wwwroot/assets/download-bad67930.js → cista-1.2.0/cista/wwwroot/assets/download-9e150acd.js +1 -1
  21. cista-1.1.0/cista/wwwroot/assets/exclamation-afec06bb.js → cista-1.2.0/cista/wwwroot/assets/exclamation-b0f9924c.js +1 -1
  22. cista-1.1.0/cista/wwwroot/assets/eye-5fde34a1.js → cista-1.2.0/cista/wwwroot/assets/eye-ed3ae907.js +1 -1
  23. cista-1.1.0/cista/wwwroot/assets/find-11711648.js → cista-1.2.0/cista/wwwroot/assets/find-f597334e.js +1 -1
  24. cista-1.1.0/cista/wwwroot/assets/fullscreen-7f61e522.js → cista-1.2.0/cista/wwwroot/assets/fullscreen-f8859869.js +1 -1
  25. cista-1.1.0/cista/wwwroot/assets/github-c9475017.js → cista-1.2.0/cista/wwwroot/assets/github-631b099e.js +1 -1
  26. cista-1.2.0/cista/wwwroot/assets/index-2536c1be.js +28 -0
  27. cista-1.2.0/cista/wwwroot/assets/index-e1368e9c.css +1 -0
  28. cista-1.1.0/cista/wwwroot/assets/info-b3d76e57.js → cista-1.2.0/cista/wwwroot/assets/info-bf79cac3.js +1 -1
  29. cista-1.1.0/cista/wwwroot/assets/link-5cd893e4.js → cista-1.2.0/cista/wwwroot/assets/link-a01d99f9.js +1 -1
  30. cista-1.1.0/cista/wwwroot/assets/logo-6090454d.js → cista-1.2.0/cista/wwwroot/assets/logo-f514b2f7.js +1 -1
  31. cista-1.1.0/cista/wwwroot/assets/loop-531f6994.js → cista-1.2.0/cista/wwwroot/assets/loop-282a5a0a.js +1 -1
  32. cista-1.1.0/cista/wwwroot/assets/menu-ed0d8c47.js → cista-1.2.0/cista/wwwroot/assets/menu-c4f0e3ff.js +1 -1
  33. cista-1.1.0/cista/wwwroot/assets/next-ee82241a.js → cista-1.2.0/cista/wwwroot/assets/next-2390879c.js +1 -1
  34. cista-1.1.0/cista/wwwroot/assets/open-8364df82.js → cista-1.2.0/cista/wwwroot/assets/open-6ab73637.js +1 -1
  35. cista-1.1.0/cista/wwwroot/assets/paste-0f86e193.js → cista-1.2.0/cista/wwwroot/assets/paste-47e2440d.js +1 -1
  36. cista-1.1.0/cista/wwwroot/assets/pause-a2dd4670.js → cista-1.2.0/cista/wwwroot/assets/pause-0a4fcf8b.js +1 -1
  37. cista-1.1.0/cista/wwwroot/assets/pencil-bfd02151.js → cista-1.2.0/cista/wwwroot/assets/pencil-c803cdf8.js +1 -1
  38. cista-1.1.0/cista/wwwroot/assets/play-83e40a03.js → cista-1.2.0/cista/wwwroot/assets/play-f93536e7.js +1 -1
  39. cista-1.1.0/cista/wwwroot/assets/plus-f463cfbc.js → cista-1.2.0/cista/wwwroot/assets/plus-af617ff3.js +1 -1
  40. cista-1.1.0/cista/wwwroot/assets/previous-cbb778b5.js → cista-1.2.0/cista/wwwroot/assets/previous-53072403.js +1 -1
  41. cista-1.1.0/cista/wwwroot/assets/reload-907eb866.js → cista-1.2.0/cista/wwwroot/assets/reload-0f4b1bef.js +1 -1
  42. cista-1.1.0/cista/wwwroot/assets/rename-51dadde6.js → cista-1.2.0/cista/wwwroot/assets/rename-fb99201e.js +1 -1
  43. cista-1.1.0/cista/wwwroot/assets/scissors-d32350cc.js → cista-1.2.0/cista/wwwroot/assets/scissors-0f03967c.js +1 -1
  44. cista-1.1.0/cista/wwwroot/assets/shuffle-00a003ad.js → cista-1.2.0/cista/wwwroot/assets/shuffle-5f08a6d6.js +1 -1
  45. cista-1.1.0/cista/wwwroot/assets/signin-d7bd57fd.js → cista-1.2.0/cista/wwwroot/assets/signin-9ad9ac2c.js +1 -1
  46. cista-1.1.0/cista/wwwroot/assets/signout-4c4ff7fb.js → cista-1.2.0/cista/wwwroot/assets/signout-355e61bd.js +1 -1
  47. cista-1.1.0/cista/wwwroot/assets/skip-0069d8d7.js → cista-1.2.0/cista/wwwroot/assets/skip-c73d3852.js +1 -1
  48. cista-1.1.0/cista/wwwroot/assets/spinner-164c9b34.js → cista-1.2.0/cista/wwwroot/assets/spinner-37a835af.js +1 -1
  49. cista-1.1.0/cista/wwwroot/assets/stop-3717c27d.js → cista-1.2.0/cista/wwwroot/assets/stop-bb4b72ee.js +1 -1
  50. cista-1.1.0/cista/wwwroot/assets/trash-449f81ef.js → cista-1.2.0/cista/wwwroot/assets/trash-0d970049.js +1 -1
  51. cista-1.1.0/cista/wwwroot/assets/triangle-3ecaf29c.js → cista-1.2.0/cista/wwwroot/assets/triangle-f05bb4fc.js +1 -1
  52. cista-1.1.0/cista/wwwroot/assets/unfullscreen-175d46cd.js → cista-1.2.0/cista/wwwroot/assets/unfullscreen-cacb8dcc.js +1 -1
  53. cista-1.1.0/cista/wwwroot/assets/up-arrow-164a366e.js → cista-1.2.0/cista/wwwroot/assets/up-arrow-7856239a.js +1 -1
  54. cista-1.1.0/cista/wwwroot/assets/upload-cloud-df377f1a.js → cista-1.2.0/cista/wwwroot/assets/upload-cloud-99a0be1d.js +1 -1
  55. cista-1.1.0/cista/wwwroot/assets/user-3b5a32bc.js → cista-1.2.0/cista/wwwroot/assets/user-37302509.js +1 -1
  56. cista-1.1.0/cista/wwwroot/assets/user-cog-27e2c201.js → cista-1.2.0/cista/wwwroot/assets/user-cog-8ed948ad.js +1 -1
  57. cista-1.1.0/cista/wwwroot/assets/volume-high-f4e07edd.js → cista-1.2.0/cista/wwwroot/assets/volume-high-32add965.js +1 -1
  58. cista-1.1.0/cista/wwwroot/assets/volume-low-f754322d.js → cista-1.2.0/cista/wwwroot/assets/volume-low-6d8d78a6.js +1 -1
  59. cista-1.1.0/cista/wwwroot/assets/volume-medium-a5806088.js → cista-1.2.0/cista/wwwroot/assets/volume-medium-3c4b8dba.js +1 -1
  60. cista-1.1.0/cista/wwwroot/assets/volume-mute-405763b1.js → cista-1.2.0/cista/wwwroot/assets/volume-mute-8a80e2fc.js +1 -1
  61. cista-1.1.0/cista/wwwroot/assets/window-d99968ac.js → cista-1.2.0/cista/wwwroot/assets/window-1324ab59.js +1 -1
  62. cista-1.1.0/cista/wwwroot/assets/window-cross-86cb8236.js → cista-1.2.0/cista/wwwroot/assets/window-cross-b47e8de0.js +1 -1
  63. cista-1.1.0/cista/wwwroot/assets/wordwrap-cdcd3f93.js → cista-1.2.0/cista/wwwroot/assets/wordwrap-a762bd10.js +1 -1
  64. cista-1.1.0/cista/wwwroot/assets/zoomin-7d13bb9b.js → cista-1.2.0/cista/wwwroot/assets/zoomin-c5a80f4b.js +1 -1
  65. cista-1.1.0/cista/wwwroot/assets/zoomout-daf52018.js → cista-1.2.0/cista/wwwroot/assets/zoomout-db5e9e0e.js +1 -1
  66. {cista-1.1.0 → cista-1.2.0}/cista/wwwroot/index.html +2 -2
  67. {cista-1.1.0 → cista-1.2.0}/pyproject.toml +1 -1
  68. cista-1.1.0/cista/wwwroot/assets/index-0ccc4ea2.js +0 -28
  69. cista-1.1.0/cista/wwwroot/assets/index-89d850f4.css +0 -1
  70. {cista-1.1.0 → cista-1.2.0}/.gitignore +0 -0
  71. {cista-1.1.0 → cista-1.2.0}/README.md +0 -0
  72. {cista-1.1.0 → cista-1.2.0}/cista/__init__.py +0 -0
  73. {cista-1.1.0 → cista-1.2.0}/cista/api.py +0 -0
  74. {cista-1.1.0 → cista-1.2.0}/cista/droppy.py +0 -0
  75. {cista-1.1.0 → cista-1.2.0}/cista/fileio.py +0 -0
  76. {cista-1.1.0 → cista-1.2.0}/cista/preview.py +0 -0
  77. {cista-1.1.0 → cista-1.2.0}/cista/protocol.py +0 -0
  78. {cista-1.1.0 → cista-1.2.0}/cista/serve.py +0 -0
  79. {cista-1.1.0 → cista-1.2.0}/cista/server80.py +0 -0
  80. {cista-1.1.0 → cista-1.2.0}/cista/session.py +0 -0
  81. {cista-1.1.0 → cista-1.2.0}/cista/util/__init__.py +0 -0
  82. {cista-1.1.0 → cista-1.2.0}/cista/util/apphelpers.py +0 -0
  83. {cista-1.1.0 → cista-1.2.0}/cista/util/asynclink.py +0 -0
  84. {cista-1.1.0 → cista-1.2.0}/cista/util/filename.py +0 -0
  85. {cista-1.1.0 → cista-1.2.0}/cista/util/pwgen.py +0 -0
  86. {cista-1.1.0 → cista-1.2.0}/cista/watching.py +0 -0
  87. {cista-1.1.0 → cista-1.2.0}/cista/wwwroot/assets/logo-97d1d7eb.svg +0 -0
  88. {cista-1.1.0 → cista-1.2.0}/cista/wwwroot/robots.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cista
3
- Version: 1.1.0
3
+ Version: 1.2.0
4
4
  Summary: Dropbox-like file server with modern web interface
5
5
  Project-URL: Homepage, https://git.zi.fi/Vasanko/cista-storage
6
6
  Author: Vasanko
@@ -16,7 +16,6 @@ Requires-Python: >=3.11
16
16
  Requires-Dist: argon2-cffi>=25.1.0
17
17
  Requires-Dist: av>=15.0.0
18
18
  Requires-Dist: blake3>=1.0.5
19
- Requires-Dist: brotli>=1.1.0
20
19
  Requires-Dist: docopt>=0.6.2
21
20
  Requires-Dist: inotify>=0.2.12
22
21
  Requires-Dist: msgspec>=0.19.0
@@ -31,6 +30,7 @@ Requires-Dist: sanic>=25.3.0
31
30
  Requires-Dist: setproctitle>=1.3.6
32
31
  Requires-Dist: stream-zip>=0.0.83
33
32
  Requires-Dist: tomli-w>=1.2.0
33
+ Requires-Dist: zstandard>=0.24.0
34
34
  Provides-Extra: dev
35
35
  Requires-Dist: mypy>=1.13.0; extra == 'dev'
36
36
  Requires-Dist: pre-commit>=4.0.0; extra == 'dev'
@@ -10,8 +10,24 @@ from cista.util import pwgen
10
10
 
11
11
  del app, server80.app # Only import needed, for Sanic multiprocessing
12
12
 
13
- doc = f"""Cista {cista.__version__} - A file storage for the web.
14
13
 
14
+ def create_banner():
15
+ """Create a framed banner with the Cista version."""
16
+ title = f"Cista {cista.__version__}"
17
+ subtitle = "A file storage for the web"
18
+ width = max(len(title), len(subtitle)) + 4
19
+
20
+ return f"""\
21
+ ╭{"─" * width}╮
22
+ │{title:^{width}}│
23
+ │{subtitle:^{width}}│
24
+ ╰{"─" * width}╯
25
+ """
26
+
27
+
28
+ banner = create_banner()
29
+
30
+ doc = """\
15
31
  Usage:
16
32
  cista [-c <confdir>] [-l <host>] [--import-droppy] [--dev] [<path>]
17
33
  cista [-c <confdir>] --user <name> [--privileged] [--password]
@@ -35,6 +51,14 @@ User management:
35
51
  --password Reset password
36
52
  """
37
53
 
54
+ first_time_help = """\
55
+ No config file found! Get started with:
56
+ cista --user yourname --privileged # If you want user accounts
57
+ cista -l :8000 /path/to/files # Run the server on localhost:8000
58
+
59
+ See cista --help for other options!
60
+ """
61
+
38
62
 
39
63
  def main():
40
64
  # Dev mode doesn't catch exceptions
@@ -44,11 +68,19 @@ def main():
44
68
  try:
45
69
  return _main()
46
70
  except Exception as e:
47
- print("Error:", e)
71
+ sys.stderr.write(f"Error: {e}\n")
48
72
  return 1
49
73
 
50
74
 
51
75
  def _main():
76
+ # The banner printing differs by mode, and needs to be done before docopt() printing its messages
77
+ if any(arg in sys.argv for arg in ("--help", "-h")):
78
+ sys.stdout.write(banner)
79
+ elif "--version" in sys.argv:
80
+ sys.stdout.write(f"cista {cista.__version__}\n")
81
+ return 0
82
+ else:
83
+ sys.stderr.write(banner)
52
84
  args = docopt(doc)
53
85
  if args["--user"]:
54
86
  return _user(args)
@@ -62,18 +94,11 @@ def _main():
62
94
  path = None
63
95
  _confdir(args)
64
96
  exists = config.conffile.exists()
65
- print(config.conffile, exists)
66
97
  import_droppy = args["--import-droppy"]
67
98
  necessary_opts = exists or import_droppy or path
68
99
  if not necessary_opts:
69
100
  # Maybe run without arguments
70
- print(doc)
71
- print(
72
- "No config file found! Get started with one of:\n"
73
- " cista --user yourname --privileged\n"
74
- " cista --import-droppy\n"
75
- " cista -l :8000 /path/to/files\n"
76
- )
101
+ sys.stderr.write(first_time_help)
77
102
  return 1
78
103
  settings = {}
79
104
  if import_droppy:
@@ -94,7 +119,7 @@ def _main():
94
119
  # We have no users, so make it public
95
120
  settings["public"] = True
96
121
  operation = config.update_config(settings)
97
- print(f"Config {operation}: {config.conffile}")
122
+ sys.stderr.write(f"Config {operation}: {config.conffile}\n")
98
123
  # Prepare to serve
99
124
  unix = None
100
125
  url, _ = serve.parse_listen(config.config.listen)
@@ -104,7 +129,7 @@ def _main():
104
129
  dev = args["--dev"]
105
130
  if dev:
106
131
  extra += " (dev mode)"
107
- print(f"Serving {config.config.path} at {url}{extra}")
132
+ sys.stderr.write(f"Serving {config.config.path} at {url}{extra}\n")
108
133
  # Run the server
109
134
  serve.run(dev=dev)
110
135
  return 0
@@ -137,7 +162,7 @@ def _user(args):
137
162
  "public": False,
138
163
  }
139
164
  )
140
- print(f"Config {operation}: {config.conffile}\n")
165
+ sys.stderr.write(f"Config {operation}: {config.conffile}\n\n")
141
166
 
142
167
  name = args["--user"]
143
168
  if not name or not name.isidentifier():
@@ -155,12 +180,12 @@ def _user(args):
155
180
  changes["password"] = pw = pwgen.generate()
156
181
  info += f"\n Password: {pw}\n"
157
182
  res = config.update_user(name, changes)
158
- print(info)
183
+ sys.stderr.write(f"{info}\n")
159
184
  if res == "read":
160
- print(" No changes")
185
+ sys.stderr.write(" No changes\n")
161
186
 
162
187
  if operation == "created":
163
- print(
188
+ sys.stderr.write(
164
189
  "Now you can run the server:\n cista # defaults set: -l :8000 ~/Downloads\n"
165
190
  )
166
191
 
@@ -1,2 +1,2 @@
1
1
  # This file is automatically generated by hatch build.
2
- __version__ = '1.1.0'
2
+ __version__ = '1.2.0'
@@ -9,7 +9,6 @@ from stat import S_IFDIR, S_IFREG
9
9
  from urllib.parse import unquote
10
10
  from wsgiref.handlers import format_date_time
11
11
 
12
- import brotli
13
12
  import sanic.helpers
14
13
  from blake3 import blake3
15
14
  from sanic import Blueprint, Sanic, empty, raw, redirect
@@ -17,6 +16,7 @@ from sanic.exceptions import Forbidden, NotFound
17
16
  from sanic.log import logger
18
17
  from setproctitle import setproctitle
19
18
  from stream_zip import ZIP_AUTO, stream_zip
19
+ from zstandard import ZstdCompressor
20
20
 
21
21
  from cista import auth, config, preview, session, watching
22
22
  from cista.api import bp
@@ -95,6 +95,7 @@ def _load_wwwroot(www):
95
95
  wwwnew = {}
96
96
  base = Path(__file__).with_name("wwwroot")
97
97
  paths = [PurePath()]
98
+ zstd = ZstdCompressor(level=10)
98
99
  while paths:
99
100
  path = paths.pop(0)
100
101
  current = base / path
@@ -126,11 +127,11 @@ def _load_wwwroot(www):
126
127
  else "no-cache",
127
128
  "content-type": mime,
128
129
  }
129
- # Precompress with Brotli
130
- br = brotli.compress(data)
131
- if len(br) >= len(data):
132
- br = False
133
- wwwnew[name] = data, br, headers
130
+ # Precompress with ZSTD
131
+ zs = zstd.compress(data)
132
+ if len(zs) >= len(data):
133
+ zs = False
134
+ wwwnew[name] = data, zs, headers
134
135
  if not wwwnew:
135
136
  msg = f"Web frontend missing from {base}\n Did you forget: hatch build\n"
136
137
  if not www:
@@ -182,9 +183,9 @@ async def refresh_wwwroot():
182
183
  for name in sorted(set(wwwold) - set(www)):
183
184
  changes += f"Deleted /{name}\n"
184
185
  if changes:
185
- print(f"Updated wwwroot:\n{changes}", end="", flush=True)
186
+ logger.info(f"Updated wwwroot:\n{changes}", end="", flush=True)
186
187
  except Exception as e:
187
- print(f"Error loading wwwroot: {e!r}")
188
+ logger.error(f"Error loading wwwroot: {e!r}")
188
189
  await asyncio.sleep(0.5)
189
190
  except asyncio.CancelledError:
190
191
  pass
@@ -196,14 +197,14 @@ async def wwwroot(req, path=""):
196
197
  name = unquote(path)
197
198
  if name not in www:
198
199
  raise NotFound(f"File not found: /{path}", extra={"name": name})
199
- data, br, headers = www[name]
200
+ data, zs, headers = www[name]
200
201
  if req.headers.if_none_match == headers["etag"]:
201
202
  # The client has it cached, respond 304 Not Modified
202
203
  return empty(304, headers=headers)
203
- # Brotli compressed?
204
- if br and "br" in req.headers.accept_encoding.split(", "):
205
- headers = {**headers, "content-encoding": "br"}
206
- data = br
204
+ # Zstandard compressed?
205
+ if zs and "zstd" in req.headers.accept_encoding.split(", "):
206
+ headers = {**headers, "content-encoding": "zstd"}
207
+ data = zs
207
208
  return raw(data, headers=headers)
208
209
 
209
210
 
@@ -10,6 +10,7 @@ from sanic import Blueprint, html, json, redirect
10
10
  from sanic.exceptions import BadRequest, Forbidden, Unauthorized
11
11
 
12
12
  from cista import config, session
13
+ from cista.util import pwgen
13
14
 
14
15
  _argon = argon2.PasswordHasher()
15
16
  _droppyhash = re.compile(r"^([a-f0-9]{64})\$([a-f0-9]{8})$")
@@ -191,3 +192,91 @@ async def change_password(request):
191
192
  res = json({"message": "Password updated"})
192
193
  session.create(res, username)
193
194
  return res
195
+
196
+
197
+ @bp.get("/users")
198
+ async def list_users(request):
199
+ verify(request, privileged=True)
200
+ users = []
201
+ for name, user in config.config.users.items():
202
+ users.append(
203
+ {
204
+ "username": name,
205
+ "privileged": user.privileged,
206
+ "lastSeen": user.lastSeen,
207
+ }
208
+ )
209
+ return json({"users": users})
210
+
211
+
212
+ @bp.post("/users")
213
+ async def create_user(request):
214
+ verify(request, privileged=True)
215
+ try:
216
+ if request.headers.content_type == "application/json":
217
+ username = request.json["username"]
218
+ password = request.json.get("password")
219
+ privileged = request.json.get("privileged", False)
220
+ else:
221
+ username = request.form["username"][0]
222
+ password = request.form.get("password", [None])[0]
223
+ privileged = request.form.get("privileged", ["false"])[0].lower() == "true"
224
+ if not username or not username.isidentifier():
225
+ raise ValueError("Invalid username")
226
+ except (KeyError, ValueError) as e:
227
+ raise BadRequest(str(e)) from e
228
+ if username in config.config.users:
229
+ raise BadRequest("User already exists")
230
+ if not password:
231
+ password = pwgen.generate()
232
+ changes = {"privileged": privileged}
233
+ changes["hash"] = _argon.hash(_pwnorm(password))
234
+ try:
235
+ config.update_user(username, changes)
236
+ except Exception as e:
237
+ raise BadRequest(str(e)) from e
238
+ return json({"message": f"User {username} created", "password": password})
239
+
240
+
241
+ @bp.put("/users/<username>")
242
+ async def update_user(request, username):
243
+ verify(request, privileged=True)
244
+ try:
245
+ if request.headers.content_type == "application/json":
246
+ changes = request.json
247
+ else:
248
+ changes = {}
249
+ if "password" in request.form:
250
+ changes["password"] = request.form["password"][0]
251
+ if "privileged" in request.form:
252
+ changes["privileged"] = request.form["privileged"][0].lower() == "true"
253
+ except KeyError as e:
254
+ raise BadRequest("Missing fields") from e
255
+ password_response = None
256
+ if "password" in changes:
257
+ if changes["password"] == "":
258
+ changes["password"] = pwgen.generate()
259
+ password_response = changes["password"]
260
+ changes["hash"] = _argon.hash(_pwnorm(changes["password"]))
261
+ del changes["password"]
262
+ if not changes:
263
+ return json({"message": "No changes"})
264
+ try:
265
+ config.update_user(username, changes)
266
+ except Exception as e:
267
+ raise BadRequest(str(e)) from e
268
+ response = {"message": f"User {username} updated"}
269
+ if password_response:
270
+ response["password"] = password_response
271
+ return json(response)
272
+
273
+
274
+ @bp.put("/config/public")
275
+ async def update_public(request):
276
+ verify(request, privileged=True)
277
+ try:
278
+ public = request.json["public"]
279
+ except KeyError:
280
+ raise BadRequest("Missing public field") from None
281
+ config.update_config({"public": public})
282
+ return json({"message": "Public setting updated"})
@@ -7,9 +7,11 @@ from contextlib import suppress
7
7
  from functools import wraps
8
8
  from hashlib import sha256
9
9
  from pathlib import Path, PurePath
10
- from time import time
10
+ from time import sleep, time
11
+ from typing import Callable, Concatenate, Literal, ParamSpec
11
12
 
12
13
  import msgspec
14
+ import msgspec.toml
13
15
 
14
16
 
15
17
  class Config(msgspec.Struct):
@@ -22,6 +24,13 @@ class Config(msgspec.Struct):
22
24
  links: dict[str, Link] = {}
23
25
 
24
26
 
27
+ # Typing: arguments for config-modifying functions
28
+ P = ParamSpec("P")
29
+ ResultStr = Literal["modified", "created", "read"]
30
+ RawModifyFunc = Callable[Concatenate[Config, P], Config]
31
+ ModifyPublic = Callable[P, ResultStr]
32
+
33
+
25
34
  class User(msgspec.Struct, omit_defaults=True):
26
35
  privileged: bool = False
27
36
  hash: str = ""
@@ -34,11 +43,13 @@ class Link(msgspec.Struct, omit_defaults=True):
34
43
  expires: int = 0
35
44
 
36
45
 
37
- config = None
38
- conffile = None
46
+ # Global variables - initialized during application startup
47
+ config: Config
48
+ conffile: Path
39
49
 
40
50
 
41
- def init_confdir():
51
+ def init_confdir() -> None:
52
+ global conffile
42
53
  if p := os.environ.get("CISTA_HOME"):
43
54
  home = Path(p)
44
55
  else:
@@ -49,8 +60,6 @@ def init_confdir():
49
60
  if not home.is_dir():
50
61
  home.mkdir(parents=True, exist_ok=True)
51
62
  home.chmod(0o700)
52
-
53
- global conffile
54
63
  conffile = home / "db.toml"
55
64
 
56
65
 
@@ -77,10 +86,10 @@ def dec_hook(typ, obj):
77
86
  raise TypeError
78
87
 
79
88
 
80
- def config_update(modify):
89
+ def config_update(
90
+ modify: RawModifyFunc,
91
+ ) -> ResultStr | Literal["collision"]:
81
92
  global config
82
- if conffile is None:
83
- init_confdir()
84
93
  tmpname = conffile.with_suffix(".tmp")
85
94
  try:
86
95
  f = tmpname.open("xb")
@@ -95,7 +104,7 @@ def config_update(modify):
95
104
  c = msgspec.toml.decode(old, type=Config, dec_hook=dec_hook)
96
105
  except FileNotFoundError:
97
106
  old = b""
98
- c = None
107
+ c = Config(path=Path(), listen="", secret=secrets.token_hex(12))
99
108
  c = modify(c)
100
109
  new = msgspec.toml.encode(c, enc_hook=enc_hook)
101
110
  if old == new:
@@ -118,17 +127,23 @@ def config_update(modify):
118
127
  return "modified" if old else "created"
119
128
 
120
129
 
121
- def modifies_config(modify):
122
- """Decorator for functions that modify the config file"""
130
+ def modifies_config(
131
+ modify: Callable[Concatenate[Config, P], Config],
132
+ ) -> Callable[P, ResultStr]:
133
+ """Decorator for functions that modify the config file
134
+
135
+ The decorated function takes as first arg Config and returns it modified.
136
+ The wrapper handles atomic modification and returns a string indicating the result.
137
+ """
123
138
 
124
139
  @wraps(modify)
125
- def wrapper(*args, **kwargs):
126
- def m(c):
140
+ def wrapper(*args: P.args, **kwargs: P.kwargs) -> ResultStr:
141
+ def m(c: Config) -> Config:
127
142
  return modify(c, *args, **kwargs)
128
143
 
129
144
  # Retry modification in case of write collision
130
145
  while (c := config_update(m)) == "collision":
131
- time.sleep(0.01)
146
+ sleep(0.01)
132
147
  return c
133
148
 
134
149
  return wrapper
@@ -136,8 +151,7 @@ def modifies_config(modify):
136
151
 
137
152
  def load_config():
138
153
  global config
139
- if conffile is None:
140
- init_confdir()
154
+ init_confdir()
141
155
  config = msgspec.toml.decode(conffile.read_bytes(), type=Config, dec_hook=dec_hook)
142
156
 
143
157
 
@@ -145,7 +159,7 @@ def load_config():
145
159
  def update_config(conf: Config, changes: dict) -> Config:
146
160
  """Create/update the config with new values, respecting changes done by others."""
147
161
  # Encode into dict, update values with new, convert to Config
148
- settings = {} if conf is None else msgspec.to_builtins(conf, enc_hook=enc_hook)
162
+ settings = msgspec.to_builtins(conf, enc_hook=enc_hook)
149
163
  settings.update(changes)
150
164
  return msgspec.convert(settings, Config, dec_hook=dec_hook)
151
165
 
@@ -155,8 +169,13 @@ def update_user(conf: Config, name: str, changes: dict) -> Config:
155
169
  """Create/update a user with new values, respecting changes done by others."""
156
170
  # Encode into dict, update values with new, convert to Config
157
171
  try:
158
- u = conf.users[name].__copy__()
159
- except (KeyError, AttributeError):
172
+ # Copy user by converting to dict and back
173
+ u = msgspec.convert(
174
+ msgspec.to_builtins(conf.users[name], enc_hook=enc_hook),
175
+ User,
176
+ dec_hook=dec_hook,
177
+ )
178
+ except KeyError:
160
179
  u = User()
161
180
  if "password" in changes:
162
181
  from . import auth
@@ -165,7 +184,7 @@ def update_user(conf: Config, name: str, changes: dict) -> Config:
165
184
  del changes["password"]
166
185
  udict = msgspec.to_builtins(u, enc_hook=enc_hook)
167
186
  udict.update(changes)
168
- settings = msgspec.to_builtins(conf, enc_hook=enc_hook) if conf else {"users": {}}
187
+ settings = msgspec.to_builtins(conf, enc_hook=enc_hook)
169
188
  settings["users"][name] = msgspec.convert(udict, User, dec_hook=dec_hook)
170
189
  return msgspec.convert(settings, Config, dec_hook=dec_hook)
171
190
 
@@ -173,6 +192,7 @@ def update_user(conf: Config, name: str, changes: dict) -> Config:
173
192
  @modifies_config
174
193
  def del_user(conf: Config, name: str) -> Config:
175
194
  """Delete named user account."""
176
- ret = conf.__copy__()
177
- ret.users.pop(name)
178
- return ret
195
+ # Create a copy by converting to dict and back
196
+ settings = msgspec.to_builtins(conf, enc_hook=enc_hook)
197
+ settings["users"].pop(name)
198
+ return msgspec.convert(settings, Config, dec_hook=dec_hook)
@@ -1,4 +1,5 @@
1
1
  from time import monotonic
2
+ from typing import Callable
2
3
 
3
4
 
4
5
  class LRUCache:
@@ -12,7 +13,7 @@ class LRUCache:
12
13
  cache (list): Internal list storing the cache items.
13
14
  """
14
15
 
15
- def __init__(self, open: callable, *, capacity: int, maxage: float):
16
+ def __init__(self, open: Callable, *, capacity: int, maxage: float):
16
17
  """
17
18
  Initialize LRUCache.
18
19
 
@@ -50,7 +51,6 @@ class LRUCache:
50
51
  # Add/restore to end of cache
51
52
  self.cache.insert(0, (key, f, monotonic()))
52
53
  self.expire_items()
53
- print(self.cache)
54
54
  return f
55
55
 
56
56
  def expire_items(self):
@@ -1 +1 @@
1
- import{o as t,c as o,a as r}from"./index-0ccc4ea2.js";const n={xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 28 28"};function s(a,e){return t(),o("svg",n,e[0]||(e[0]=[r("path",{d:"M19.2 2.6H6.1V29h19.8V9.3zM18.5 16v7.1h-5.3V16H8.7l7.1-7.1L23 16z"},null,-1)]))}const d={render:s};export{d as default,s as render};
1
+ import{o as t,c as o,a as r}from"./index-2536c1be.js";const n={xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 28 28"};function s(a,e){return t(),o("svg",n,e[0]||(e[0]=[r("path",{d:"M19.2 2.6H6.1V29h19.8V9.3zM18.5 16v7.1h-5.3V16H8.7l7.1-7.1L23 16z"},null,-1)]))}const d={render:s};export{d as default,s as render};
@@ -1 +1 @@
1
- import{o,c as t,a as r}from"./index-0ccc4ea2.js";const l={xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 32 32"};function n(s,e){return o(),t("svg",l,e[0]||(e[0]=[r("path",{d:"M29 6H16l-1-2H4L2 8h28zM0 10l2 20h28l2-20zm18.3 9.5V27h-5.6v-7.5H8l7.5-7.5 7.5 7.5z"},null,-1)]))}const d={render:n};export{d as default,n as render};
1
+ import{o,c as t,a as r}from"./index-2536c1be.js";const l={xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 32 32"};function n(s,e){return o(),t("svg",l,e[0]||(e[0]=[r("path",{d:"M29 6H16l-1-2H4L2 8h28zM0 10l2 20h28l2-20zm18.3 9.5V27h-5.6v-7.5H8l7.5-7.5 7.5 7.5z"},null,-1)]))}const d={render:n};export{d as default,n as render};
@@ -1 +1 @@
1
- import{o as t,c as o,a as r}from"./index-0ccc4ea2.js";const s={xmlns:"http://www.w3.org/2000/svg",width:"640",height:"640",viewBox:"0 -32 640 640"};function a(n,e){return t(),o("svg",s,e[0]||(e[0]=[r("path",{d:"M495.46 365.98c-13.03-13.37-150.24-144.06-150.24-144.06A35.16 35.16 0 0 0 320 211.2a35.06 35.06 0 0 0-25.22 10.72s-137.2 130.7-150.27 144.06c-13 13.38-13.9 37.44 0 51.72 14 14.24 33.4 15.4 50.48 0L320 297.8l125.02 119.9c17.1 15.4 36.55 14.24 50.44 0 13.95-14.3 13.08-38.37 0-51.72"},null,-1)]))}const l={render:a};export{l as default,a as render};
1
+ import{o as t,c as o,a as r}from"./index-2536c1be.js";const s={xmlns:"http://www.w3.org/2000/svg",width:"640",height:"640",viewBox:"0 -32 640 640"};function a(n,e){return t(),o("svg",s,e[0]||(e[0]=[r("path",{d:"M495.46 365.98c-13.03-13.37-150.24-144.06-150.24-144.06A35.16 35.16 0 0 0 320 211.2a35.06 35.06 0 0 0-25.22 10.72s-137.2 130.7-150.27 144.06c-13 13.38-13.9 37.44 0 51.72 14 14.24 33.4 15.4 50.48 0L320 297.8l125.02 119.9c17.1 15.4 36.55 14.24 50.44 0 13.95-14.3 13.08-38.37 0-51.72"},null,-1)]))}const l={render:a};export{l as default,a as render};
@@ -1 +1 @@
1
- import{o,c as t,a as r}from"./index-0ccc4ea2.js";const s={xmlns:"http://www.w3.org/2000/svg",viewBox:"-6 -2 44 36"};function n(a,e){return o(),t("svg",s,e[0]||(e[0]=[r("path",{d:"M12 18H6v4l-6-6 6-6v4h6zm8-4h6v-4l6 6-6 6v-4h-6z"},null,-1)]))}const c={render:n};export{c as default,n as render};
1
+ import{o,c as t,a as r}from"./index-2536c1be.js";const s={xmlns:"http://www.w3.org/2000/svg",viewBox:"-6 -2 44 36"};function n(a,e){return o(),t("svg",s,e[0]||(e[0]=[r("path",{d:"M12 18H6v4l-6-6 6-6v4h6zm8-4h6v-4l6 6-6 6v-4h-6z"},null,-1)]))}const c={render:n};export{c as default,n as render};
@@ -1 +1 @@
1
- import{o,c as t,a as r}from"./index-0ccc4ea2.js";const s={xmlns:"http://www.w3.org/2000/svg",viewBox:"-2 -6 16 44"};function n(a,e){return o(),t("svg",s,e[0]||(e[0]=[r("path",{d:"M8 20v6h4l-6 6-6-6h4v-6zm-4-8V6H0l6-6 6 6H8v6z"},null,-1)]))}const c={render:n};export{c as default,n as render};
1
+ import{o,c as t,a as r}from"./index-2536c1be.js";const s={xmlns:"http://www.w3.org/2000/svg",viewBox:"-2 -6 16 44"};function n(a,e){return o(),t("svg",s,e[0]||(e[0]=[r("path",{d:"M8 20v6h4l-6 6-6-6h4v-6zm-4-8V6H0l6-6 6 6H8v6z"},null,-1)]))}const c={render:n};export{c as default,n as render};
@@ -1 +1 @@
1
- import{o as t,c as o,a as r}from"./index-0ccc4ea2.js";const n={xmlns:"http://www.w3.org/2000/svg",width:"512",height:"512",viewBox:"-48 0 512 512"};function s(a,e){return t(),o("svg",n,e[0]||(e[0]=[r("path",{d:"M320 96 128 288l-64-64-64 64 128 128 256-256z"},null,-1)]))}const l={render:s};export{l as default,s as render};
1
+ import{o as t,c as o,a as r}from"./index-2536c1be.js";const n={xmlns:"http://www.w3.org/2000/svg",width:"512",height:"512",viewBox:"-48 0 512 512"};function s(a,e){return t(),o("svg",n,e[0]||(e[0]=[r("path",{d:"M320 96 128 288l-64-64-64 64 128 128 256-256z"},null,-1)]))}const l={render:s};export{l as default,s as render};
@@ -1 +1 @@
1
- import{o as t,c as o,a as r}from"./index-0ccc4ea2.js";const n={xmlns:"http://www.w3.org/2000/svg",width:"512",height:"512",viewBox:"-24 8 512 512"};function s(a,e){return t(),o("svg",n,e[0]||(e[0]=[r("path",{d:"m304 96-48 48 112 112-112 112 48 48 144-160zm-160 0L0 256l144 160 48-48L80 256l112-112z"},null,-1)]))}const c={render:s};export{c as default,s as render};
1
+ import{o as t,c as o,a as r}from"./index-2536c1be.js";const n={xmlns:"http://www.w3.org/2000/svg",width:"512",height:"512",viewBox:"-24 8 512 512"};function s(a,e){return t(),o("svg",n,e[0]||(e[0]=[r("path",{d:"m304 96-48 48 112 112-112 112 48 48 144-160zm-160 0L0 256l144 160 48-48L80 256l112-112z"},null,-1)]))}const c={render:s};export{c as default,s as render};
@@ -1 +1 @@
1
- import{o,c as t,a as r}from"./index-0ccc4ea2.js";const n={xmlns:"http://www.w3.org/2000/svg",viewBox:"-2 -2 36 36"};function s(h,e){return o(),t("svg",n,e[0]||(e[0]=[r("path",{d:"M26 8h-6V6l-6-6H0v24h12v8h20V14zm0 2.83L29.17 14H26zm-12-8L17.17 6H14zM2 2h10v6h6v14H2zm28 28H14v-6h6V10h4v6h6z"},null,-1)]))}const v={render:s};export{v as default,s as render};
1
+ import{o,c as t,a as r}from"./index-2536c1be.js";const n={xmlns:"http://www.w3.org/2000/svg",viewBox:"-2 -2 36 36"};function s(h,e){return o(),t("svg",n,e[0]||(e[0]=[r("path",{d:"M26 8h-6V6l-6-6H0v24h12v8h20V14zm0 2.83L29.17 14H26zm-12-8L17.17 6H14zM2 2h10v6h6v14H2zm28 28H14v-6h6V10h4v6h6z"},null,-1)]))}const v={render:s};export{v as default,s as render};
@@ -1 +1 @@
1
- import{o as c,c as t,a as o}from"./index-0ccc4ea2.js";const r={xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 32 32"};function n(s,e){return c(),t("svg",r,e[0]||(e[0]=[o("path",{d:"M19.2 2.6H6.1V29h19.8V9.3zm3 15c0 .2-.2.4-.4.4h-4.4v4.4c0 .2-.2.4-.4.4h-2.4c-.2 0-.4-.2-.4-.4V18H9.9c-.2 0-.4-.2-.4-.4v-2.4c0-.2.2-.4.4-.4h4.4v-4.4c0-.2.2-.4.4-.4H17c.2 0 .4.2.4.4v4.4h4.4c.2 0 .4.2.4.4z"},null,-1)]))}const l={render:n};export{l as default,n as render};
1
+ import{o as c,c as t,a as o}from"./index-2536c1be.js";const r={xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 32 32"};function n(s,e){return c(),t("svg",r,e[0]||(e[0]=[o("path",{d:"M19.2 2.6H6.1V29h19.8V9.3zm3 15c0 .2-.2.4-.4.4h-4.4v4.4c0 .2-.2.4-.4.4h-2.4c-.2 0-.4-.2-.4-.4V18H9.9c-.2 0-.4-.2-.4-.4v-2.4c0-.2.2-.4.4-.4h4.4v-4.4c0-.2.2-.4.4-.4H17c.2 0 .4.2.4.4v4.4h4.4c.2 0 .4.2.4.4z"},null,-1)]))}const l={render:n};export{l as default,n as render};
@@ -1 +1 @@
1
- import{o as c,c as t,a as o}from"./index-0ccc4ea2.js";const r={xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 32 32"};function l(n,e){return c(),t("svg",r,e[0]||(e[0]=[o("path",{d:"M29 6H16l-1-2H4L2 8h28zM0 10l2 20h28l2-20zm22.8 11.2c0 .3-.2.5-.5.5h-5.2v5.2c0 .3-.2.5-.5.5h-2.8c-.3 0-.5-.2-.5-.5v-5.2H8.1c-.3 0-.5-.2-.5-.5v-2.8c0-.3.2-.5.5-.5h5.2v-5.2c0-.3.2-.5.5-.5h2.8c.3 0 .5.2.5.5v5.2h5.2c.3 0 .5.2.5.5z"},null,-1)]))}const a={render:l};export{a as default,l as render};
1
+ import{o as c,c as t,a as o}from"./index-2536c1be.js";const r={xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 32 32"};function l(n,e){return c(),t("svg",r,e[0]||(e[0]=[o("path",{d:"M29 6H16l-1-2H4L2 8h28zM0 10l2 20h28l2-20zm22.8 11.2c0 .3-.2.5-.5.5h-5.2v5.2c0 .3-.2.5-.5.5h-2.8c-.3 0-.5-.2-.5-.5v-5.2H8.1c-.3 0-.5-.2-.5-.5v-2.8c0-.3.2-.5.5-.5h5.2v-5.2c0-.3.2-.5.5-.5h2.8c.3 0 .5.2.5.5v5.2h5.2c.3 0 .5.2.5.5z"},null,-1)]))}const a={render:l};export{a as default,l as render};
@@ -1 +1 @@
1
- import{o,c as t,a as r}from"./index-0ccc4ea2.js";const s={xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 32 32"};function n(l,e){return o(),t("svg",s,e[0]||(e[0]=[r("path",{d:"M25.3 8.56 17.88 16l7.44 7.44-1.86 1.87L16 17.9l-7.44 7.4-1.86-1.85L14.12 16 6.68 8.56 8.55 6.7 16 14.12l7.44-7.44z"},null,-1)]))}const c={render:n};export{c as default,n as render};
1
+ import{o,c as t,a as r}from"./index-2536c1be.js";const s={xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 32 32"};function n(l,e){return o(),t("svg",s,e[0]||(e[0]=[r("path",{d:"M25.3 8.56 17.88 16l7.44 7.44-1.86 1.87L16 17.9l-7.44 7.4-1.86-1.85L14.12 16 6.68 8.56 8.55 6.7 16 14.12l7.44-7.44z"},null,-1)]))}const c={render:n};export{c as default,n as render};
@@ -1 +1 @@
1
- import{o as t,c as o,a}from"./index-0ccc4ea2.js";const r={xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 32 32"};function s(n,e){return t(),o("svg",r,e[0]||(e[0]=[a("path",{d:"M24.27 3.2H6.4a3.2 3.2 0 0 0-3.2 3.2v19.2a3.2 3.2 0 0 0 3.2 3.2h19.2a3.2 3.2 0 0 0 3.2-3.2V8.2zm-1.87 9.6c0 .88-.72 1.6-1.6 1.6h-9.6a1.6 1.6 0 0 1-1.6-1.6v-8h12.8zm-1.6-6.4h-3.2v6.4h3.2z"},null,-1)]))}const d={render:s};export{d as default,s as render};
1
+ import{o as t,c as o,a}from"./index-2536c1be.js";const r={xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 32 32"};function s(n,e){return t(),o("svg",r,e[0]||(e[0]=[a("path",{d:"M24.27 3.2H6.4a3.2 3.2 0 0 0-3.2 3.2v19.2a3.2 3.2 0 0 0 3.2 3.2h19.2a3.2 3.2 0 0 0 3.2-3.2V8.2zm-1.87 9.6c0 .88-.72 1.6-1.6 1.6h-9.6a1.6 1.6 0 0 1-1.6-1.6v-8h12.8zm-1.6-6.4h-3.2v6.4h3.2z"},null,-1)]))}const d={render:s};export{d as default,s as render};
@@ -1 +1 @@
1
- import{o as s,c as e,a as o}from"./index-0ccc4ea2.js";const t={xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 30 30"};function l(n,c){return s(),e("svg",t,c[0]||(c[0]=[o("path",{d:"M23 25.9c0-.3-.1-.6-.3-.8s-.5-.3-.8-.3-.6.1-.8.3-.3.5-.3.8.1.6.3.8.5.3.8.3.6-.1.8-.3c.2-.3.3-.5.3-.8m4.6 0c0-.3-.1-.6-.3-.8s-.5-.3-.8-.3-.6.1-.8.3-.3.5-.3.8.1.6.3.8.5.3.8.3.6-.1.8-.3c.2-.3.3-.5.3-.8m2.3-4v5.7c0 .5-.2.9-.5 1.2s-.7.5-1.2.5H1.9c-.5 0-.9-.2-1.2-.5s-.5-.7-.5-1.2v-5.7c0-.5.2-.9.5-1.2s.7-.5 1.2-.5h8.3l2.4 2.4c.7.7 1.5 1 2.4 1s1.7-.3 2.4-1l2.4-2.4h8.3c.5 0 .9.2 1.2.5q.6.45.6 1.2m-5.8-10.2c.2.5.1.9-.3 1.3l-8 8c-.2.2-.5.3-.8.3s-.6-.1-.8-.3l-8-8c-.4-.3-.5-.8-.3-1.3S6.5 11 7 11h4.6V3c0-.3.1-.6.3-.8s.5-.3.8-.3h4.6c.3 0 .6.1.8.3s.3.5.3.8v8H23c.5 0 .8.2 1.1.7"},null,-1)]))}const a={render:l};export{a as default,l as render};
1
+ import{o as s,c as e,a as o}from"./index-2536c1be.js";const t={xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 30 30"};function l(n,c){return s(),e("svg",t,c[0]||(c[0]=[o("path",{d:"M23 25.9c0-.3-.1-.6-.3-.8s-.5-.3-.8-.3-.6.1-.8.3-.3.5-.3.8.1.6.3.8.5.3.8.3.6-.1.8-.3c.2-.3.3-.5.3-.8m4.6 0c0-.3-.1-.6-.3-.8s-.5-.3-.8-.3-.6.1-.8.3-.3.5-.3.8.1.6.3.8.5.3.8.3.6-.1.8-.3c.2-.3.3-.5.3-.8m2.3-4v5.7c0 .5-.2.9-.5 1.2s-.7.5-1.2.5H1.9c-.5 0-.9-.2-1.2-.5s-.5-.7-.5-1.2v-5.7c0-.5.2-.9.5-1.2s.7-.5 1.2-.5h8.3l2.4 2.4c.7.7 1.5 1 2.4 1s1.7-.3 2.4-1l2.4-2.4h8.3c.5 0 .9.2 1.2.5q.6.45.6 1.2m-5.8-10.2c.2.5.1.9-.3 1.3l-8 8c-.2.2-.5.3-.8.3s-.6-.1-.8-.3l-8-8c-.4-.3-.5-.8-.3-1.3S6.5 11 7 11h4.6V3c0-.3.1-.6.3-.8s.5-.3.8-.3h4.6c.3 0 .6.1.8.3s.3.5.3.8v8H23c.5 0 .8.2 1.1.7"},null,-1)]))}const a={render:l};export{a as default,l as render};
@@ -1 +1 @@
1
- import{o as t,c as o,a as n}from"./index-0ccc4ea2.js";const r={xmlns:"http://www.w3.org/2000/svg",width:"448",height:"448",viewBox:"-136 0 448 448"};function a(s,e){return t(),o("svg",r,e[0]||(e[0]=[n("path",{d:"M128 312v56q0 6.5-4.75 11.25T112 384H48q-6.5 0-11.25-4.75T32 368v-56q0-6.5 4.75-11.25T48 296h64q6.5 0 11.25 4.75T128 312m7.5-264-7 192q-.25 6.5-5.13 11.25T112 256H48q-6.5 0-11.38-4.75T31.5 240l-7-192q-.25-6.5 4.38-11.25T40 32h80q6.5 0 11.13 4.75T135.5 48"},null,-1)]))}const c={render:a};export{c as default,a as render};
1
+ import{o as t,c as o,a as n}from"./index-2536c1be.js";const r={xmlns:"http://www.w3.org/2000/svg",width:"448",height:"448",viewBox:"-136 0 448 448"};function a(s,e){return t(),o("svg",r,e[0]||(e[0]=[n("path",{d:"M128 312v56q0 6.5-4.75 11.25T112 384H48q-6.5 0-11.25-4.75T32 368v-56q0-6.5 4.75-11.25T48 296h64q6.5 0 11.25 4.75T128 312m7.5-264-7 192q-.25 6.5-5.13 11.25T112 256H48q-6.5 0-11.38-4.75T31.5 240l-7-192q-.25-6.5 4.38-11.25T40 32h80q6.5 0 11.13 4.75T135.5 48"},null,-1)]))}const c={render:a};export{c as default,a as render};
@@ -1 +1 @@
1
- import{o as s,c,a as t}from"./index-0ccc4ea2.js";const o={xmlns:"http://www.w3.org/2000/svg",viewBox:"-2 -2 36 36"};function r(a,e){return s(),c("svg",o,e[0]||(e[0]=[t("path",{d:"M29.715 16c-1.696-2.625-4.018-4.875-6.804-6.304A7.94 7.94 0 0 1 24 13.714c0 4.411-3.589 8-8 8s-8-3.589-8-8c0-1.411.375-2.804 1.089-4.018C6.303 11.125 3.982 13.375 2.285 16c3.054 4.714 7.982 8 13.714 8s10.661-3.286 13.714-8zM16.858 9.143a.87.87 0 0 0-.857-.857c-2.982 0-5.429 2.446-5.429 5.429 0 .464.393.857.857.857s.857-.393.857-.857c0-2.036 1.679-3.714 3.714-3.714a.87.87 0 0 0 .857-.857zM32 16c0 .446-.143.857-.357 1.232-3.286 5.411-9.304 9.054-15.643 9.054S3.643 22.625.357 17.232C.143 16.857 0 16.446 0 16s.143-.857.357-1.232C3.643 9.375 9.661 5.714 16 5.714s12.357 3.661 15.643 9.054c.214.375.357.786.357 1.232"},null,-1)]))}const l={render:r};export{l as default,r as render};
1
+ import{o as s,c,a as t}from"./index-2536c1be.js";const o={xmlns:"http://www.w3.org/2000/svg",viewBox:"-2 -2 36 36"};function r(a,e){return s(),c("svg",o,e[0]||(e[0]=[t("path",{d:"M29.715 16c-1.696-2.625-4.018-4.875-6.804-6.304A7.94 7.94 0 0 1 24 13.714c0 4.411-3.589 8-8 8s-8-3.589-8-8c0-1.411.375-2.804 1.089-4.018C6.303 11.125 3.982 13.375 2.285 16c3.054 4.714 7.982 8 13.714 8s10.661-3.286 13.714-8zM16.858 9.143a.87.87 0 0 0-.857-.857c-2.982 0-5.429 2.446-5.429 5.429 0 .464.393.857.857.857s.857-.393.857-.857c0-2.036 1.679-3.714 3.714-3.714a.87.87 0 0 0 .857-.857zM32 16c0 .446-.143.857-.357 1.232-3.286 5.411-9.304 9.054-15.643 9.054S3.643 22.625.357 17.232C.143 16.857 0 16.446 0 16s.143-.857.357-1.232C3.643 9.375 9.661 5.714 16 5.714s12.357 3.661 15.643 9.054c.214.375.357.786.357 1.232"},null,-1)]))}const l={render:r};export{l as default,r as render};
@@ -1 +1 @@
1
- import{o as t,c as o,a as n}from"./index-0ccc4ea2.js";const r={xmlns:"http://www.w3.org/2000/svg",viewBox:"-12 -12 512 512"};function s(a,e){return t(),o("svg",r,e[0]||(e[0]=[n("path",{d:"M480 416 355.44 291.44C373.22 262.4 384 228.58 384 192 384 85.98 298 0 192 0 85.98 0 0 85.98 0 192c0 106 85.98 192 192 192 36.58 0 70.4-10.78 99.44-28.5L416 480c8.75 8.75 23.25 8.7 32 0l32-32a22.8 22.8 0 0 0 0-32m-288-96c-70.7 0-128-57.3-128-128S121.3 64 192 64s128 57.3 128 128-57.3 128-128 128"},null,-1)]))}const l={render:s};export{l as default,s as render};
1
+ import{o as t,c as o,a as n}from"./index-2536c1be.js";const r={xmlns:"http://www.w3.org/2000/svg",viewBox:"-12 -12 512 512"};function s(a,e){return t(),o("svg",r,e[0]||(e[0]=[n("path",{d:"M480 416 355.44 291.44C373.22 262.4 384 228.58 384 192 384 85.98 298 0 192 0 85.98 0 0 85.98 0 192c0 106 85.98 192 192 192 36.58 0 70.4-10.78 99.44-28.5L416 480c8.75 8.75 23.25 8.7 32 0l32-32a22.8 22.8 0 0 0 0-32m-288-96c-70.7 0-128-57.3-128-128S121.3 64 192 64s128 57.3 128 128-57.3 128-128 128"},null,-1)]))}const l={render:s};export{l as default,s as render};
@@ -1 +1 @@
1
- import{o as t,c as o,a as r}from"./index-0ccc4ea2.js";const v={xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 32 32"};function n(s,e){return t(),o("svg",v,e[0]||(e[0]=[r("path",{d:"M18.7 6.7h6.6v6.6h-2.6v-4h-4zm4 16v-4h2.6v6.6h-6.6v-2.6zm-16-9.4V6.7h6.6v2.6h-4v4zm2.6 5.4v4h4v2.6H6.7v-6.6z"},null,-1)]))}const a={render:n};export{a as default,n as render};
1
+ import{o as t,c as o,a as r}from"./index-2536c1be.js";const v={xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 32 32"};function n(s,e){return t(),o("svg",v,e[0]||(e[0]=[r("path",{d:"M18.7 6.7h6.6v6.6h-2.6v-4h-4zm4 16v-4h2.6v6.6h-6.6v-2.6zm-16-9.4V6.7h6.6v2.6h-4v4zm2.6 5.4v4h4v2.6H6.7v-6.6z"},null,-1)]))}const a={render:n};export{a as default,n as render};
@@ -1 +1 @@
1
- import{o as e,c as o,a as r}from"./index-0ccc4ea2.js";const c={xmlns:"http://www.w3.org/2000/svg",width:"512",height:"512"};function n(s,t){return e(),o("svg",c,t[0]||(t[0]=[r("path",{d:"M256 6.3C114.6 6.3 0 121 0 262.3c0 113 73.4 209 175 243 13 2.3 17.6-5.6 17.6-12.4l-.4-48C121 460.5 106 415 106 415c-11.7-29.5-28.5-37.4-28.5-37.4-23.2-16 1.8-15.6 1.8-15.6 25.7 1.8 39.2 26.4 39.2 26.4 23 39.2 60 27.8 74.5 21.3 2.3-16.5 9-27.8 16.3-34.2C152.3 369 92.6 347 92.6 249c0-28 10-50.8 26.4-68.8-2.6-6.4-11.4-32.5 2.5-67.7 0 0 21.5-7 70.4 26.2 20-5.6 42-8.5 64-8.6 21.3.7 43.2 3 64 9 49-33 70-26 70-26 14 35.3 5 61.4 2.4 67.8 16.3 18 26.2 40.8 26.2 68.7 0 98.4-60 120-117 126.4 9.2 8 17.4 23.4 17.4 47.3l-.2 70.2c0 6.6 4.7 14.6 17.7 12 101.7-34 175-129.7 175-243C512 121 397.5 6 256 6z"},null,-1)]))}const l={render:n};export{l as default,n as render};
1
+ import{o as e,c as o,a as r}from"./index-2536c1be.js";const c={xmlns:"http://www.w3.org/2000/svg",width:"512",height:"512"};function n(s,t){return e(),o("svg",c,t[0]||(t[0]=[r("path",{d:"M256 6.3C114.6 6.3 0 121 0 262.3c0 113 73.4 209 175 243 13 2.3 17.6-5.6 17.6-12.4l-.4-48C121 460.5 106 415 106 415c-11.7-29.5-28.5-37.4-28.5-37.4-23.2-16 1.8-15.6 1.8-15.6 25.7 1.8 39.2 26.4 39.2 26.4 23 39.2 60 27.8 74.5 21.3 2.3-16.5 9-27.8 16.3-34.2C152.3 369 92.6 347 92.6 249c0-28 10-50.8 26.4-68.8-2.6-6.4-11.4-32.5 2.5-67.7 0 0 21.5-7 70.4 26.2 20-5.6 42-8.5 64-8.6 21.3.7 43.2 3 64 9 49-33 70-26 70-26 14 35.3 5 61.4 2.4 67.8 16.3 18 26.2 40.8 26.2 68.7 0 98.4-60 120-117 126.4 9.2 8 17.4 23.4 17.4 47.3l-.2 70.2c0 6.6 4.7 14.6 17.7 12 101.7-34 175-129.7 175-243C512 121 397.5 6 256 6z"},null,-1)]))}const l={render:n};export{l as default,n as render};