Compare commits
No commits in common. "8b1ffb202ea93683fadd4a9fc964c58485e0dfd3" and "8d03a3e3b04c78869c8d259db1360fca55a0ec32" have entirely different histories.
8b1ffb202e
...
8d03a3e3b0
3
.gitignore
vendored
@ -13,6 +13,3 @@ __pycache__/
|
||||
|
||||
.pytest_cache
|
||||
.coverage
|
||||
|
||||
/site
|
||||
src/materia/docs
|
||||
|
52
docs/api.md
@ -1,52 +0,0 @@
|
||||
---
|
||||
hide:
|
||||
- navigation
|
||||
- toc
|
||||
---
|
||||
<style>
|
||||
.md-typeset h1,
|
||||
.md-content__button {
|
||||
display: none;
|
||||
}
|
||||
.md-main__inner {
|
||||
max-width: 100%; /* or 100%, if you want to stretch to full-width */
|
||||
margin-top: 0;
|
||||
}
|
||||
.md-content__inner {
|
||||
margin: 0;
|
||||
padding-top: 0;
|
||||
}
|
||||
.md-content__inner > p {
|
||||
margin: 0;
|
||||
}
|
||||
.md-content__inner::before {
|
||||
display: none;
|
||||
}
|
||||
.md-footer__inner {
|
||||
display: none;
|
||||
}
|
||||
.md-footer__inner:not([hidden]) {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
<rapi-doc
|
||||
spec-url="/api/openapi.json"
|
||||
theme = "dark"
|
||||
show-header = "false"
|
||||
show-info = "false"
|
||||
allow-authentication = "true"
|
||||
allow-server-selection = "true"
|
||||
allow-api-list-style-selection = "true"
|
||||
theme = "dark"
|
||||
render-style = "focused"
|
||||
bg-color="#1e2129"
|
||||
primary-color="#a47bea"
|
||||
regular-font="Roboto"
|
||||
mono-font="Roboto Mono"
|
||||
show-method-in-nav-bar="as-colored-text">
|
||||
<img slot="logo" style="display: none"/>
|
||||
</rapi-doc>
|
||||
<script
|
||||
type="module"
|
||||
src="https://unpkg.com/rapidoc/dist/rapidoc-min.js"
|
||||
></script>
|
Before Width: | Height: | Size: 129 KiB |
Before Width: | Height: | Size: 78 KiB |
Before Width: | Height: | Size: 81 KiB |
Before Width: | Height: | Size: 78 KiB |
Before Width: | Height: | Size: 130 KiB |
Before Width: | Height: | Size: 122 KiB |
@ -1,12 +0,0 @@
|
||||
# Materia
|
||||
|
||||
<style>
|
||||
.md-content .md-typeset h1 { display: none; }
|
||||
</style>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://materia.elnafo.ru"><img src="img/logo-full.png" alt="Materia"></a>
|
||||
</p>
|
||||
<p align="center">
|
||||
<em>Materia is easy and fast cloud storage</em>
|
||||
</p>
|
@ -1 +0,0 @@
|
||||
::: materia.app
|
@ -1 +0,0 @@
|
||||
::: materia.core
|
@ -1,5 +0,0 @@
|
||||
# Reference
|
||||
|
||||
Here's the reference or code API, the classes, functions, parameters, attributes, and
|
||||
all the Materia parts.
|
||||
|
@ -1 +0,0 @@
|
||||
::: materia.models
|
@ -1 +0,0 @@
|
||||
::: materia.routers
|
@ -1 +0,0 @@
|
||||
::: materia.security
|
@ -1 +0,0 @@
|
||||
::: materia.tasks
|
43
flake.nix
@ -164,49 +164,6 @@
|
||||
postgresql-devel = bonfire.packages.x86_64-linux.postgresql;
|
||||
|
||||
redis-devel = bonfire.packages.x86_64-linux.redis;
|
||||
|
||||
materia-devel = let
|
||||
user = "materia";
|
||||
dataDir = "/var/lib/materia";
|
||||
entryPoint = pkgs.writeTextDir "entrypoint.sh" ''
|
||||
materia start
|
||||
'';
|
||||
in
|
||||
pkgs.dockerTools.buildImage {
|
||||
name = "materia";
|
||||
tag = "latest";
|
||||
|
||||
copyToRoot = pkgs.buildEnv {
|
||||
name = "image-root";
|
||||
pathsToLink = ["/bin" "/etc" "/"];
|
||||
paths = with pkgs; [
|
||||
bash
|
||||
self.packages.x86_64-linux.materia
|
||||
entryPoint
|
||||
];
|
||||
};
|
||||
runAsRoot = with pkgs; ''
|
||||
#!${runtimeShell}
|
||||
${dockerTools.shadowSetup}
|
||||
groupadd -r ${user}
|
||||
useradd -r -g ${user} --home-dir=${dataDir} ${user}
|
||||
mkdir -p ${dataDir}
|
||||
chown -R ${user}:${user} ${dataDir}
|
||||
'';
|
||||
|
||||
config = {
|
||||
Entrypoint = ["bash" "/entrypoint.sh"];
|
||||
StopSignal = "SIGINT";
|
||||
User = "${user}:${user}";
|
||||
WorkingDir = dataDir;
|
||||
ExposedPorts = {
|
||||
"54601/tcp" = {};
|
||||
};
|
||||
Env = [
|
||||
"MATERIA_APPLICATION__WORKING_DIRECTORY=${dataDir}"
|
||||
];
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
devShells.x86_64-linux.default = pkgs.mkShell {
|
||||
|
89
mkdocs.yml
@ -1,89 +0,0 @@
|
||||
site_name: Materia Documentation
|
||||
site_description: Materia cloud storage
|
||||
#site_url:
|
||||
repo_name: L-Nafaryus/materia
|
||||
repo_url: https://vcs.elnafo.ru/L-Nafaryus/materia
|
||||
copyright: Copyright © 2024 L-Nafaryus
|
||||
|
||||
theme:
|
||||
name: material
|
||||
features:
|
||||
- content.code.annotate
|
||||
- content.code.copy
|
||||
# - content.code.select
|
||||
- content.footnote.tooltips
|
||||
- content.tabs.link
|
||||
- content.tooltips
|
||||
- navigation.footer
|
||||
- navigation.indexes
|
||||
- navigation.instant
|
||||
- navigation.instant.prefetch
|
||||
# - navigation.instant.preview
|
||||
- navigation.instant.progress
|
||||
- navigation.path
|
||||
- navigation.tabs
|
||||
- navigation.tabs.sticky
|
||||
- navigation.top
|
||||
- navigation.tracking
|
||||
- search.highlight
|
||||
- search.share
|
||||
- search.suggest
|
||||
- toc.follow
|
||||
logo: img/favicon.png
|
||||
favicon: img/favicon.png
|
||||
palette:
|
||||
- media: "(prefers-color-scheme: light)"
|
||||
scheme: slate
|
||||
primary: deep purple
|
||||
accent: deep purple
|
||||
toggle:
|
||||
icon: material/weather-sunny
|
||||
name: Switch to light mode
|
||||
- media: "(prefers-color-scheme: dark)"
|
||||
scheme: default
|
||||
primary: deep purple
|
||||
accent: deep purple
|
||||
toggle:
|
||||
icon: material/weather-night
|
||||
name: Switch to dark mode
|
||||
|
||||
|
||||
plugins:
|
||||
- search:
|
||||
- mkdocstrings:
|
||||
handlers:
|
||||
python:
|
||||
paths: [src] # search packages in the src folder
|
||||
options:
|
||||
extensions:
|
||||
- griffe_typingdoc
|
||||
#preload_modules:
|
||||
#- sqlalchemy
|
||||
#docstring_style: sphinx
|
||||
show_submodules: true
|
||||
show_source: true
|
||||
show_if_no_docstring: true
|
||||
show_symbol_type_heading: true
|
||||
show_symbol_type_toc: true
|
||||
show_root_heading: true
|
||||
unwrap_annotated: true
|
||||
merge_init_into_class: true
|
||||
docstring_section_style: spacy
|
||||
signature_crossrefs: true
|
||||
inherited_members: true
|
||||
members_order: source
|
||||
separate_signature: true
|
||||
filters:
|
||||
- '!^_'
|
||||
|
||||
nav:
|
||||
- Materia: index.md
|
||||
- Reference:
|
||||
- reference/index.md
|
||||
- reference/app.md
|
||||
- reference/core.md
|
||||
- reference/models.md
|
||||
- reference/routers.md
|
||||
- reference/security.md
|
||||
- reference/tasks.md
|
||||
- API: api.md
|
332
pdm.lock
@ -5,7 +5,7 @@
|
||||
groups = ["default", "dev"]
|
||||
strategy = ["cross_platform", "inherit_metadata"]
|
||||
lock_version = "4.4.1"
|
||||
content_hash = "sha256:764de758c9c4b274659cef493a76abb117253e6ccf30dce9f4e61a74afce2228"
|
||||
content_hash = "sha256:ba7a816a8bfe503b899a8eba3e5ca58e2d751a278c19b1c1db6e647f83fcd62d"
|
||||
|
||||
[[package]]
|
||||
name = "aiofiles"
|
||||
@ -157,17 +157,6 @@ files = [
|
||||
{file = "authlib-1.3.1.tar.gz", hash = "sha256:7ae843f03c06c5c0debd63c9db91f9fda64fa62a42a77419fa15fbb7e7a58917"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "babel"
|
||||
version = "2.16.0"
|
||||
requires_python = ">=3.8"
|
||||
summary = "Internationalization utilities"
|
||||
groups = ["dev"]
|
||||
files = [
|
||||
{file = "babel-2.16.0-py3-none-any.whl", hash = "sha256:368b5b98b37c06b7daf6696391c3240c938b37767d4584413e8438c5c435fa8b"},
|
||||
{file = "babel-2.16.0.tar.gz", hash = "sha256:d1f3554ca26605fe173f3de0c65f750f5a42f924499bf134de6423582298e316"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bcrypt"
|
||||
version = "4.1.2"
|
||||
@ -275,7 +264,7 @@ name = "certifi"
|
||||
version = "2024.7.4"
|
||||
requires_python = ">=3.6"
|
||||
summary = "Python package for providing Mozilla's CA Bundle."
|
||||
groups = ["default", "dev"]
|
||||
groups = ["default"]
|
||||
files = [
|
||||
{file = "certifi-2024.7.4-py3-none-any.whl", hash = "sha256:c198e21b1289c2ab85ee4e67bb4b4ef3ead0892059901a8d5b622f24a1101e90"},
|
||||
{file = "certifi-2024.7.4.tar.gz", hash = "sha256:5a1e7645bc0ec61a09e26c36f6106dd4cf40c6db3a1fb6352b0244e7fb057c7b"},
|
||||
@ -321,7 +310,7 @@ name = "charset-normalizer"
|
||||
version = "3.3.2"
|
||||
requires_python = ">=3.7.0"
|
||||
summary = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet."
|
||||
groups = ["default", "dev"]
|
||||
groups = ["default"]
|
||||
files = [
|
||||
{file = "charset-normalizer-3.3.2.tar.gz", hash = "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5"},
|
||||
{file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8"},
|
||||
@ -404,6 +393,7 @@ version = "0.4.6"
|
||||
requires_python = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
|
||||
summary = "Cross-platform colored terminal text."
|
||||
groups = ["default", "dev"]
|
||||
marker = "sys_platform == \"win32\" or platform_system == \"Windows\""
|
||||
files = [
|
||||
{file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"},
|
||||
{file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"},
|
||||
@ -594,19 +584,6 @@ files = [
|
||||
{file = "fastapi-0.112.0.tar.gz", hash = "sha256:d262bc56b7d101d1f4e8fc0ad2ac75bb9935fec504d2b7117686cec50710cf05"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ghp-import"
|
||||
version = "2.1.0"
|
||||
summary = "Copy your docs directly to the gh-pages branch."
|
||||
groups = ["dev"]
|
||||
dependencies = [
|
||||
"python-dateutil>=2.8.1",
|
||||
]
|
||||
files = [
|
||||
{file = "ghp-import-2.1.0.tar.gz", hash = "sha256:9c535c4c61193c2df8871222567d7fd7e5014d835f97dc7b7439069e2413d343"},
|
||||
{file = "ghp_import-2.1.0-py3-none-any.whl", hash = "sha256:8337dd7b50877f163d4c0289bc1f1c7f127550241988d568c1db512c4324a619"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "greenlet"
|
||||
version = "3.0.3"
|
||||
@ -626,35 +603,6 @@ files = [
|
||||
{file = "greenlet-3.0.3.tar.gz", hash = "sha256:43374442353259554ce33599da8b692d5aa96f8976d567d4badf263371fbe491"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "griffe"
|
||||
version = "1.2.0"
|
||||
requires_python = ">=3.8"
|
||||
summary = "Signatures for entire Python programs. Extract the structure, the frame, the skeleton of your project, to generate API documentation or find breaking changes in your API."
|
||||
groups = ["dev"]
|
||||
dependencies = [
|
||||
"colorama>=0.4",
|
||||
]
|
||||
files = [
|
||||
{file = "griffe-1.2.0-py3-none-any.whl", hash = "sha256:a8b2fcb1ecdc5a412e646b0b4375eb20a5d2eac3a11dd8c10c56967a4097663c"},
|
||||
{file = "griffe-1.2.0.tar.gz", hash = "sha256:1c9f6ef7455930f3f9b0c4145a961c90385d1e2cbc496f7796fbff560ec60d31"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "griffe-typingdoc"
|
||||
version = "0.2.6"
|
||||
requires_python = ">=3.8"
|
||||
summary = "Griffe extension for PEP 727 – Documentation Metadata in Typing."
|
||||
groups = ["dev"]
|
||||
dependencies = [
|
||||
"griffe>=0.49",
|
||||
"typing-extensions>=4.7",
|
||||
]
|
||||
files = [
|
||||
{file = "griffe_typingdoc-0.2.6-py3-none-any.whl", hash = "sha256:2726e6cf1e986f42fe9cab4a95cef103a745327f035a32055816849ca47893e4"},
|
||||
{file = "griffe_typingdoc-0.2.6.tar.gz", hash = "sha256:852a17c1e2d29bbbf14a287e7cc5982669343cf60a4ea1618e486eb57aba3248"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gunicorn"
|
||||
version = "22.0.0"
|
||||
@ -778,7 +726,7 @@ name = "idna"
|
||||
version = "3.7"
|
||||
requires_python = ">=3.5"
|
||||
summary = "Internationalized Domain Names in Applications (IDNA)"
|
||||
groups = ["default", "dev"]
|
||||
groups = ["default"]
|
||||
files = [
|
||||
{file = "idna-3.7-py3-none-any.whl", hash = "sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0"},
|
||||
{file = "idna-3.7.tar.gz", hash = "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc"},
|
||||
@ -800,7 +748,7 @@ name = "jinja2"
|
||||
version = "3.1.4"
|
||||
requires_python = ">=3.7"
|
||||
summary = "A very fast and expressive template engine."
|
||||
groups = ["default", "dev"]
|
||||
groups = ["default"]
|
||||
dependencies = [
|
||||
"MarkupSafe>=2.0",
|
||||
]
|
||||
@ -908,23 +856,12 @@ files = [
|
||||
{file = "Mako-1.3.5.tar.gz", hash = "sha256:48dbc20568c1d276a2698b36d968fa76161bf127194907ea6fc594fa81f943bc"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "markdown"
|
||||
version = "3.7"
|
||||
requires_python = ">=3.8"
|
||||
summary = "Python implementation of John Gruber's Markdown."
|
||||
groups = ["dev"]
|
||||
files = [
|
||||
{file = "Markdown-3.7-py3-none-any.whl", hash = "sha256:7eb6df5690b81a1d7942992c97fad2938e956e79df20cbc6186e9c3a77b1c803"},
|
||||
{file = "markdown-3.7.tar.gz", hash = "sha256:2ae2471477cfd02dbbf038d5d9bc226d40def84b4fe2986e49b59b6b472bbed2"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "markupsafe"
|
||||
version = "2.1.5"
|
||||
requires_python = ">=3.7"
|
||||
summary = "Safely add untrusted strings to HTML/XML markup."
|
||||
groups = ["default", "dev"]
|
||||
groups = ["default"]
|
||||
files = [
|
||||
{file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:8dec4936e9c3100156f8a2dc89c4b88d5c435175ff03413b443469c7c8c5f4d1"},
|
||||
{file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:3c6b973f22eb18a789b1460b4b91bf04ae3f0c4234a0a6aa6b0a92f6f7b951d4"},
|
||||
@ -951,147 +888,6 @@ dependencies = [
|
||||
"loguru<1.0.0,>=0.7.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mergedeep"
|
||||
version = "1.3.4"
|
||||
requires_python = ">=3.6"
|
||||
summary = "A deep merge function for 🐍."
|
||||
groups = ["dev"]
|
||||
files = [
|
||||
{file = "mergedeep-1.3.4-py3-none-any.whl", hash = "sha256:70775750742b25c0d8f36c55aed03d24c3384d17c951b3175d898bd778ef0307"},
|
||||
{file = "mergedeep-1.3.4.tar.gz", hash = "sha256:0096d52e9dad9939c3d975a774666af186eda617e6ca84df4c94dec30004f2a8"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mkdocs"
|
||||
version = "1.6.1"
|
||||
requires_python = ">=3.8"
|
||||
summary = "Project documentation with Markdown."
|
||||
groups = ["dev"]
|
||||
dependencies = [
|
||||
"click>=7.0",
|
||||
"colorama>=0.4; platform_system == \"Windows\"",
|
||||
"ghp-import>=1.0",
|
||||
"jinja2>=2.11.1",
|
||||
"markdown>=3.3.6",
|
||||
"markupsafe>=2.0.1",
|
||||
"mergedeep>=1.3.4",
|
||||
"mkdocs-get-deps>=0.2.0",
|
||||
"packaging>=20.5",
|
||||
"pathspec>=0.11.1",
|
||||
"pyyaml-env-tag>=0.1",
|
||||
"pyyaml>=5.1",
|
||||
"watchdog>=2.0",
|
||||
]
|
||||
files = [
|
||||
{file = "mkdocs-1.6.1-py3-none-any.whl", hash = "sha256:db91759624d1647f3f34aa0c3f327dd2601beae39a366d6e064c03468d35c20e"},
|
||||
{file = "mkdocs-1.6.1.tar.gz", hash = "sha256:7b432f01d928c084353ab39c57282f29f92136665bdd6abf7c1ec8d822ef86f2"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mkdocs-autorefs"
|
||||
version = "1.2.0"
|
||||
requires_python = ">=3.8"
|
||||
summary = "Automatically link across pages in MkDocs."
|
||||
groups = ["dev"]
|
||||
dependencies = [
|
||||
"Markdown>=3.3",
|
||||
"markupsafe>=2.0.1",
|
||||
"mkdocs>=1.1",
|
||||
]
|
||||
files = [
|
||||
{file = "mkdocs_autorefs-1.2.0-py3-none-any.whl", hash = "sha256:d588754ae89bd0ced0c70c06f58566a4ee43471eeeee5202427da7de9ef85a2f"},
|
||||
{file = "mkdocs_autorefs-1.2.0.tar.gz", hash = "sha256:a86b93abff653521bda71cf3fc5596342b7a23982093915cb74273f67522190f"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mkdocs-get-deps"
|
||||
version = "0.2.0"
|
||||
requires_python = ">=3.8"
|
||||
summary = "MkDocs extension that lists all dependencies according to a mkdocs.yml file"
|
||||
groups = ["dev"]
|
||||
dependencies = [
|
||||
"mergedeep>=1.3.4",
|
||||
"platformdirs>=2.2.0",
|
||||
"pyyaml>=5.1",
|
||||
]
|
||||
files = [
|
||||
{file = "mkdocs_get_deps-0.2.0-py3-none-any.whl", hash = "sha256:2bf11d0b133e77a0dd036abeeb06dec8775e46efa526dc70667d8863eefc6134"},
|
||||
{file = "mkdocs_get_deps-0.2.0.tar.gz", hash = "sha256:162b3d129c7fad9b19abfdcb9c1458a651628e4b1dea628ac68790fb3061c60c"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mkdocs-material"
|
||||
version = "9.5.34"
|
||||
requires_python = ">=3.8"
|
||||
summary = "Documentation that simply works"
|
||||
groups = ["dev"]
|
||||
dependencies = [
|
||||
"babel~=2.10",
|
||||
"colorama~=0.4",
|
||||
"jinja2~=3.0",
|
||||
"markdown~=3.2",
|
||||
"mkdocs-material-extensions~=1.3",
|
||||
"mkdocs~=1.6",
|
||||
"paginate~=0.5",
|
||||
"pygments~=2.16",
|
||||
"pymdown-extensions~=10.2",
|
||||
"regex>=2022.4",
|
||||
"requests~=2.26",
|
||||
]
|
||||
files = [
|
||||
{file = "mkdocs_material-9.5.34-py3-none-any.whl", hash = "sha256:54caa8be708de2b75167fd4d3b9f3d949579294f49cb242515d4653dbee9227e"},
|
||||
{file = "mkdocs_material-9.5.34.tar.gz", hash = "sha256:1e60ddf716cfb5679dfd65900b8a25d277064ed82d9a53cd5190e3f894df7840"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mkdocs-material-extensions"
|
||||
version = "1.3.1"
|
||||
requires_python = ">=3.8"
|
||||
summary = "Extension pack for Python Markdown and MkDocs Material."
|
||||
groups = ["dev"]
|
||||
files = [
|
||||
{file = "mkdocs_material_extensions-1.3.1-py3-none-any.whl", hash = "sha256:adff8b62700b25cb77b53358dad940f3ef973dd6db797907c49e3c2ef3ab4e31"},
|
||||
{file = "mkdocs_material_extensions-1.3.1.tar.gz", hash = "sha256:10c9511cea88f568257f960358a467d12b970e1f7b2c0e5fb2bb48cab1928443"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mkdocstrings"
|
||||
version = "0.26.0"
|
||||
requires_python = ">=3.8"
|
||||
summary = "Automatic documentation from sources, for MkDocs."
|
||||
groups = ["dev"]
|
||||
dependencies = [
|
||||
"Jinja2>=2.11.1",
|
||||
"Markdown>=3.6",
|
||||
"MarkupSafe>=1.1",
|
||||
"click>=7.0",
|
||||
"mkdocs-autorefs>=1.2",
|
||||
"mkdocs>=1.4",
|
||||
"platformdirs>=2.2",
|
||||
"pymdown-extensions>=6.3",
|
||||
]
|
||||
files = [
|
||||
{file = "mkdocstrings-0.26.0-py3-none-any.whl", hash = "sha256:1aa227fe94f88e80737d37514523aacd473fc4b50a7f6852ce41447ab23f2654"},
|
||||
{file = "mkdocstrings-0.26.0.tar.gz", hash = "sha256:ff9d0de28c8fa877ed9b29a42fe407cfe6736d70a1c48177aa84fcc3dc8518cd"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mkdocstrings-python"
|
||||
version = "1.10.9"
|
||||
requires_python = ">=3.8"
|
||||
summary = "A Python handler for mkdocstrings."
|
||||
groups = ["dev"]
|
||||
dependencies = [
|
||||
"griffe>=0.49",
|
||||
"mkdocs-autorefs>=1.0",
|
||||
"mkdocstrings>=0.25",
|
||||
]
|
||||
files = [
|
||||
{file = "mkdocstrings_python-1.10.9-py3-none-any.whl", hash = "sha256:cbe98710a6757dfd4dff79bf36cb9731908fb4c69dd2736b15270ae7a488243d"},
|
||||
{file = "mkdocstrings_python-1.10.9.tar.gz", hash = "sha256:f344aaa47e727d8a2dc911e063025e58e2b7fb31a41110ccc3902aa6be7ca196"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "more-itertools"
|
||||
version = "10.3.0"
|
||||
@ -1136,16 +932,6 @@ files = [
|
||||
{file = "packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "paginate"
|
||||
version = "0.5.7"
|
||||
summary = "Divides large result sets into pages for easier browsing"
|
||||
groups = ["dev"]
|
||||
files = [
|
||||
{file = "paginate-0.5.7-py2.py3-none-any.whl", hash = "sha256:b885e2af73abcf01d9559fd5216b57ef722f8c42affbb63942377668e35c7591"},
|
||||
{file = "paginate-0.5.7.tar.gz", hash = "sha256:22bd083ab41e1a8b4f3690544afb2c60c25e5c9a63a30fa2f483f6c60c8e5945"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pathspec"
|
||||
version = "0.12.1"
|
||||
@ -1380,17 +1166,6 @@ files = [
|
||||
{file = "pyflakes-3.2.0.tar.gz", hash = "sha256:1c61603ff154621fb2a9172037d84dca3500def8c8b630657d1701f026f8af3f"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pygments"
|
||||
version = "2.18.0"
|
||||
requires_python = ">=3.8"
|
||||
summary = "Pygments is a syntax highlighting package written in Python."
|
||||
groups = ["dev"]
|
||||
files = [
|
||||
{file = "pygments-2.18.0-py3-none-any.whl", hash = "sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a"},
|
||||
{file = "pygments-2.18.0.tar.gz", hash = "sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pyjwt"
|
||||
version = "2.9.0"
|
||||
@ -1402,21 +1177,6 @@ files = [
|
||||
{file = "pyjwt-2.9.0.tar.gz", hash = "sha256:7e1e5b56cc735432a7369cbfa0efe50fa113ebecdc04ae6922deba8b84582d0c"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pymdown-extensions"
|
||||
version = "10.9"
|
||||
requires_python = ">=3.8"
|
||||
summary = "Extension pack for Python Markdown."
|
||||
groups = ["dev"]
|
||||
dependencies = [
|
||||
"markdown>=3.6",
|
||||
"pyyaml",
|
||||
]
|
||||
files = [
|
||||
{file = "pymdown_extensions-10.9-py3-none-any.whl", hash = "sha256:d323f7e90d83c86113ee78f3fe62fc9dee5f56b54d912660703ea1816fed5626"},
|
||||
{file = "pymdown_extensions-10.9.tar.gz", hash = "sha256:6ff740bcd99ec4172a938970d42b96128bdc9d4b9bcad72494f29921dc69b753"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pyright"
|
||||
version = "1.1.374"
|
||||
@ -1482,7 +1242,7 @@ name = "python-dateutil"
|
||||
version = "2.9.0.post0"
|
||||
requires_python = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7"
|
||||
summary = "Extensions to the standard Python datetime module"
|
||||
groups = ["default", "dev"]
|
||||
groups = ["default"]
|
||||
dependencies = [
|
||||
"six>=1.5",
|
||||
]
|
||||
@ -1518,7 +1278,7 @@ name = "pyyaml"
|
||||
version = "6.0.1"
|
||||
requires_python = ">=3.6"
|
||||
summary = "YAML parser and emitter for Python"
|
||||
groups = ["default", "dev"]
|
||||
groups = ["default"]
|
||||
files = [
|
||||
{file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"},
|
||||
{file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"},
|
||||
@ -1530,20 +1290,6 @@ files = [
|
||||
{file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pyyaml-env-tag"
|
||||
version = "0.1"
|
||||
requires_python = ">=3.6"
|
||||
summary = "A custom YAML tag for referencing environment variables in YAML files. "
|
||||
groups = ["dev"]
|
||||
dependencies = [
|
||||
"pyyaml",
|
||||
]
|
||||
files = [
|
||||
{file = "pyyaml_env_tag-0.1-py3-none-any.whl", hash = "sha256:af31106dec8a4d68c60207c1886031cbf839b68aa7abccdb19868200532c2069"},
|
||||
{file = "pyyaml_env_tag-0.1.tar.gz", hash = "sha256:70092675bda14fdec33b31ba77e7543de9ddc88f2e5b99160396572d11525bdb"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redis"
|
||||
version = "5.0.8"
|
||||
@ -1571,37 +1317,12 @@ files = [
|
||||
{file = "redis-5.0.8.tar.gz", hash = "sha256:0c5b10d387568dfe0698c6fad6615750c24170e548ca2deac10c649d463e9870"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "2024.7.24"
|
||||
requires_python = ">=3.8"
|
||||
summary = "Alternative regular expression module, to replace re."
|
||||
groups = ["dev"]
|
||||
files = [
|
||||
{file = "regex-2024.7.24-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:fe4ebef608553aff8deb845c7f4f1d0740ff76fa672c011cc0bacb2a00fbde86"},
|
||||
{file = "regex-2024.7.24-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:74007a5b25b7a678459f06559504f1eec2f0f17bca218c9d56f6a0a12bfffdad"},
|
||||
{file = "regex-2024.7.24-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7df9ea48641da022c2a3c9c641650cd09f0cd15e8908bf931ad538f5ca7919c9"},
|
||||
{file = "regex-2024.7.24-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6a1141a1dcc32904c47f6846b040275c6e5de0bf73f17d7a409035d55b76f289"},
|
||||
{file = "regex-2024.7.24-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:80c811cfcb5c331237d9bad3bea2c391114588cf4131707e84d9493064d267f9"},
|
||||
{file = "regex-2024.7.24-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7214477bf9bd195894cf24005b1e7b496f46833337b5dedb7b2a6e33f66d962c"},
|
||||
{file = "regex-2024.7.24-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d55588cba7553f0b6ec33130bc3e114b355570b45785cebdc9daed8c637dd440"},
|
||||
{file = "regex-2024.7.24-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:558a57cfc32adcf19d3f791f62b5ff564922942e389e3cfdb538a23d65a6b610"},
|
||||
{file = "regex-2024.7.24-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a512eed9dfd4117110b1881ba9a59b31433caed0c4101b361f768e7bcbaf93c5"},
|
||||
{file = "regex-2024.7.24-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:86b17ba823ea76256b1885652e3a141a99a5c4422f4a869189db328321b73799"},
|
||||
{file = "regex-2024.7.24-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:5eefee9bfe23f6df09ffb6dfb23809f4d74a78acef004aa904dc7c88b9944b05"},
|
||||
{file = "regex-2024.7.24-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:731fcd76bbdbf225e2eb85b7c38da9633ad3073822f5ab32379381e8c3c12e94"},
|
||||
{file = "regex-2024.7.24-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:eaef80eac3b4cfbdd6de53c6e108b4c534c21ae055d1dbea2de6b3b8ff3def38"},
|
||||
{file = "regex-2024.7.24-cp312-cp312-win32.whl", hash = "sha256:185e029368d6f89f36e526764cf12bf8d6f0e3a2a7737da625a76f594bdfcbfc"},
|
||||
{file = "regex-2024.7.24-cp312-cp312-win_amd64.whl", hash = "sha256:2f1baff13cc2521bea83ab2528e7a80cbe0ebb2c6f0bfad15be7da3aed443908"},
|
||||
{file = "regex-2024.7.24.tar.gz", hash = "sha256:9cfd009eed1a46b27c14039ad5bbc5e71b6367c5b2e6d5f5da0ea91600817506"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "requests"
|
||||
version = "2.32.3"
|
||||
requires_python = ">=3.8"
|
||||
summary = "Python HTTP for Humans."
|
||||
groups = ["default", "dev"]
|
||||
groups = ["default"]
|
||||
dependencies = [
|
||||
"certifi>=2017.4.17",
|
||||
"charset-normalizer<4,>=2",
|
||||
@ -1618,7 +1339,7 @@ name = "six"
|
||||
version = "1.16.0"
|
||||
requires_python = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"
|
||||
summary = "Python 2 and 3 compatibility utilities"
|
||||
groups = ["default", "dev"]
|
||||
groups = ["default"]
|
||||
files = [
|
||||
{file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"},
|
||||
{file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"},
|
||||
@ -1750,7 +1471,7 @@ name = "typing-extensions"
|
||||
version = "4.12.2"
|
||||
requires_python = ">=3.8"
|
||||
summary = "Backported and Experimental Type Hints for Python 3.8+"
|
||||
groups = ["default", "dev"]
|
||||
groups = ["default"]
|
||||
files = [
|
||||
{file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"},
|
||||
{file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"},
|
||||
@ -1772,7 +1493,7 @@ name = "urllib3"
|
||||
version = "2.2.2"
|
||||
requires_python = ">=3.8"
|
||||
summary = "HTTP library with thread-safe connection pooling, file post, and more."
|
||||
groups = ["default", "dev"]
|
||||
groups = ["default"]
|
||||
files = [
|
||||
{file = "urllib3-2.2.2-py3-none-any.whl", hash = "sha256:a448b2f64d686155468037e1ace9f2d2199776e17f0a46610480d311f73e3472"},
|
||||
{file = "urllib3-2.2.2.tar.gz", hash = "sha256:dd505485549a7a552833da5e6063639d0d177c04f23bc3864e41e5dc5f612168"},
|
||||
@ -1858,33 +1579,6 @@ files = [
|
||||
{file = "vine-5.1.0.tar.gz", hash = "sha256:8b62e981d35c41049211cf62a0a1242d8c1ee9bd15bb196ce38aefd6799e61e0"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "watchdog"
|
||||
version = "5.0.0"
|
||||
requires_python = ">=3.9"
|
||||
summary = "Filesystem events monitoring"
|
||||
groups = ["dev"]
|
||||
files = [
|
||||
{file = "watchdog-5.0.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:1e26f570dd7f5178656affb24d6f0e22ce66c8daf88d4061a27bfb9ac866b40d"},
|
||||
{file = "watchdog-5.0.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:d146331e6b206baa9f6dd40f72b5783ad2302c240df68e7fce196d30588ccf7b"},
|
||||
{file = "watchdog-5.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6c96b1706430839872a3e33b9370ee3f7a0079f6b828129d88498ad1f96a0f45"},
|
||||
{file = "watchdog-5.0.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:bc16d448a74a929b896ed9578c25756b2125400b19b3258be8d9a681c7ae8e71"},
|
||||
{file = "watchdog-5.0.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:7e6b0e9b8a9dc3865d65888b5f5222da4ba9c4e09eab13cff5e305e7b7e7248f"},
|
||||
{file = "watchdog-5.0.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:4fe6780915000743074236b21b6c37419aea71112af62237881bc265589fe463"},
|
||||
{file = "watchdog-5.0.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:0710e9502727f688a7e06d48078545c54485b3d6eb53b171810879d8223c362a"},
|
||||
{file = "watchdog-5.0.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:d76efab5248aafbf8a2c2a63cd7b9545e6b346ad1397af8b862a3bb3140787d8"},
|
||||
{file = "watchdog-5.0.0-py3-none-manylinux2014_armv7l.whl", hash = "sha256:ff4e957c45c446de34c513eadce01d0b65da7eee47c01dce472dd136124552c9"},
|
||||
{file = "watchdog-5.0.0-py3-none-manylinux2014_i686.whl", hash = "sha256:16c1aa3377bb1f82c5e24277fcbf4e2cac3c4ce46aaaf7212d53caa9076eb7b7"},
|
||||
{file = "watchdog-5.0.0-py3-none-manylinux2014_ppc64.whl", hash = "sha256:22fcad6168fc43cf0e709bd854be5b8edbb0b260f0a6f28f1ea9baa53c6907f7"},
|
||||
{file = "watchdog-5.0.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:0120b2fa65732797ffa65fa8ee5540c288aa861d91447df298626d6385a24658"},
|
||||
{file = "watchdog-5.0.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:2aa59fab7ff75281778c649557275ca3085eccbdf825a0e2a5ca3810e977afe5"},
|
||||
{file = "watchdog-5.0.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:78db0fe0336958fc0e1269545c980b6f33d04d184ba191b2800a8b71d3e971a9"},
|
||||
{file = "watchdog-5.0.0-py3-none-win32.whl", hash = "sha256:d1acef802916083f2ad7988efc7decf07e46e266916c0a09d8fb9d387288ea12"},
|
||||
{file = "watchdog-5.0.0-py3-none-win_amd64.whl", hash = "sha256:3c2d50fdb86aa6df3973313272f5a17eb26eab29ff5a0bf54b6d34597b4dc4e4"},
|
||||
{file = "watchdog-5.0.0-py3-none-win_ia64.whl", hash = "sha256:1d17ec7e022c34fa7ddc72aa41bf28c9d1207ffb193df18ba4f6fde453725b3c"},
|
||||
{file = "watchdog-5.0.0.tar.gz", hash = "sha256:990aedb9e2f336b45a70aed9c014450e7c4a70fd99c5f5b1834d57e1453a177e"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "watchfiles"
|
||||
version = "0.22.0"
|
||||
|
@ -48,7 +48,7 @@ requires = ["pdm-backend"]
|
||||
build-backend = "pdm.backend"
|
||||
|
||||
[project.scripts]
|
||||
materia = "materia.app.cli:cli"
|
||||
materia = "materia.main:main"
|
||||
|
||||
[tool.pyright]
|
||||
reportGeneralTypeIssues = false
|
||||
@ -70,9 +70,6 @@ dev = [
|
||||
"pytest-asyncio>=0.23.7",
|
||||
"asgi-lifespan>=2.1.0",
|
||||
"pytest-cov>=5.0.0",
|
||||
"mkdocs-material>=9.5.34",
|
||||
"mkdocstrings-python>=1.10.9",
|
||||
"griffe-typingdoc>=0.2.6",
|
||||
]
|
||||
|
||||
[tool.pdm.build]
|
||||
|
@ -2,13 +2,11 @@ from contextlib import _AsyncGeneratorContextManager, asynccontextmanager
|
||||
import os
|
||||
import sys
|
||||
from typing import AsyncIterator, TypedDict, Self, Optional
|
||||
from pathlib import Path
|
||||
|
||||
import uvicorn
|
||||
from fastapi import FastAPI
|
||||
from fastapi.routing import APIRoute
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
from materia.core import (
|
||||
Config,
|
||||
Logger,
|
||||
@ -80,9 +78,8 @@ class Application:
|
||||
|
||||
async def prepare_working_directory(self):
|
||||
try:
|
||||
path = self.config.application.working_directory.resolve()
|
||||
self.logger.debug(f"Changing working directory to {path}")
|
||||
os.chdir(path)
|
||||
self.logger.debug("Changing working directory")
|
||||
os.chdir(self.config.application.working_directory.resolve())
|
||||
except FileNotFoundError as e:
|
||||
self.logger.error("Failed to change working directory: {}", e)
|
||||
sys.exit()
|
||||
@ -120,11 +117,7 @@ class Application:
|
||||
self.backend = FastAPI(
|
||||
title="materia",
|
||||
version="0.1.0",
|
||||
docs_url=None,
|
||||
redoc_url=None,
|
||||
swagger_ui_init_oauth=None,
|
||||
swagger_ui_oauth2_redirect_url=None,
|
||||
openapi_url="/api/openapi.json",
|
||||
docs_url="/api/docs",
|
||||
lifespan=lifespan,
|
||||
)
|
||||
self.backend.add_middleware(
|
||||
@ -134,7 +127,6 @@ class Application:
|
||||
allow_methods=["*"],
|
||||
allow_headers=["*"],
|
||||
)
|
||||
self.backend.include_router(routers.docs.router)
|
||||
self.backend.include_router(routers.api.router)
|
||||
self.backend.include_router(routers.resources.router)
|
||||
self.backend.include_router(routers.root.router)
|
||||
|
@ -15,8 +15,7 @@ def cli():
|
||||
|
||||
@cli.command()
|
||||
@click.option("--config", type=Path)
|
||||
@click.option("--debug", "-d", is_flag=True, default=False, help="Enable debug output.")
|
||||
def start(config: Path, debug: bool):
|
||||
def start(config: Path):
|
||||
config_path = config
|
||||
logger = Logger.new()
|
||||
|
||||
@ -49,9 +48,6 @@ def start(config: Path, debug: bool):
|
||||
logger.info("Using the default configuration.")
|
||||
config = Config()
|
||||
|
||||
if debug:
|
||||
config.log.level = "debug"
|
||||
|
||||
async def main():
|
||||
app = await Application.new(config)
|
||||
await app.start()
|
||||
|
@ -150,7 +150,7 @@ class Repository(BaseModel):
|
||||
capacity: int = 5 << 30
|
||||
|
||||
|
||||
class Config(BaseSettings, env_prefix="materia_", env_nested_delimiter="__"):
|
||||
class Config(BaseSettings, env_prefix="materia_", env_nested_delimiter="_"):
|
||||
application: Application = Application()
|
||||
log: Log = Log()
|
||||
server: Server = Server()
|
||||
|
@ -77,9 +77,7 @@ class Database:
|
||||
async with database.connection() as connection:
|
||||
await connection.rollback()
|
||||
except Exception as e:
|
||||
raise DatabaseError(
|
||||
f"Failed to connect to database '{url}': {e}"
|
||||
) from e
|
||||
raise DatabaseError(f"Failed to connect to database: {url}") from e
|
||||
|
||||
return database
|
||||
|
||||
|
@ -16,7 +16,6 @@ from materia.models.directory import (
|
||||
Directory,
|
||||
DirectoryLink,
|
||||
DirectoryInfo,
|
||||
DirectoryContent,
|
||||
DirectoryPath,
|
||||
DirectoryRename,
|
||||
DirectoryCopyMove,
|
||||
|
@ -59,11 +59,11 @@ class Directory(Base):
|
||||
|
||||
if self.directories:
|
||||
for directory in self.directories:
|
||||
await directory.remove(session, config)
|
||||
directory.remove(session, config)
|
||||
|
||||
if self.files:
|
||||
for file in self.files:
|
||||
await file.remove(session, config)
|
||||
file.remove(session, config)
|
||||
|
||||
repository_path = await self.repository.real_path(session, config)
|
||||
directory_path = await self.real_path(session, config)
|
||||
@ -284,12 +284,6 @@ class DirectoryInfo(BaseModel):
|
||||
used: Optional[int] = None
|
||||
|
||||
|
||||
class DirectoryContent(BaseModel):
|
||||
model_config = ConfigDict(arbitrary_types_allowed=True)
|
||||
files: list["FileInfo"]
|
||||
directories: list["DirectoryInfo"]
|
||||
|
||||
|
||||
class DirectoryPath(BaseModel):
|
||||
path: Path
|
||||
|
||||
@ -307,4 +301,4 @@ class DirectoryCopyMove(BaseModel):
|
||||
|
||||
|
||||
from materia.models.repository import Repository
|
||||
from materia.models.file import File, FileInfo
|
||||
from materia.models.file import File
|
||||
|
@ -216,12 +216,10 @@ class File(Base):
|
||||
await session.flush()
|
||||
return self
|
||||
|
||||
async def info(self, session: SessionContext) -> Optional["FileInfo"]:
|
||||
info = FileInfo.model_validate(self)
|
||||
relative_path = await self.relative_path(session)
|
||||
info.path = Path("/").joinpath(relative_path) if relative_path else None
|
||||
|
||||
return info
|
||||
def info(self) -> Optional["FileInfo"]:
|
||||
# if self.is_public:
|
||||
return FileInfo.model_validate(self)
|
||||
# return None
|
||||
|
||||
|
||||
def convert_bytes(size: int):
|
||||
@ -254,8 +252,6 @@ class FileInfo(BaseModel):
|
||||
is_public: bool
|
||||
size: int
|
||||
|
||||
path: Optional[Path] = None
|
||||
|
||||
|
||||
class FilePath(BaseModel):
|
||||
path: Path
|
||||
|
@ -139,6 +139,9 @@ class User(Base):
|
||||
def info(self) -> "UserInfo":
|
||||
user_info = UserInfo.model_validate(self)
|
||||
|
||||
if user_info.is_email_private:
|
||||
user_info.email = None
|
||||
|
||||
return user_info
|
||||
|
||||
async def edit_avatar(
|
||||
|
@ -1 +1 @@
|
||||
from materia.routers import middleware, api, resources, root, docs
|
||||
from materia.routers import middleware, api, resources, root
|
||||
|
@ -1,17 +1,11 @@
|
||||
from fastapi import APIRouter, HTTPException
|
||||
from fastapi import APIRouter
|
||||
from materia.routers.api.auth import auth, oauth
|
||||
from materia.routers.api import docs, user, repository, directory, file
|
||||
from materia.routers.api import user, repository, directory, file
|
||||
|
||||
router = APIRouter(prefix="/api")
|
||||
router.include_router(docs.router)
|
||||
router.include_router(auth.router)
|
||||
router.include_router(oauth.router)
|
||||
router.include_router(user.router)
|
||||
router.include_router(repository.router)
|
||||
router.include_router(directory.router)
|
||||
router.include_router(file.router)
|
||||
|
||||
|
||||
@router.get("/api/{catchall:path}", status_code=404, include_in_schema=False)
|
||||
def not_found():
|
||||
raise HTTPException(status_code=404)
|
||||
|
@ -4,7 +4,6 @@ from materia.models import (
|
||||
User,
|
||||
Directory,
|
||||
DirectoryInfo,
|
||||
DirectoryContent,
|
||||
DirectoryPath,
|
||||
DirectoryRename,
|
||||
DirectoryCopyMove,
|
||||
@ -172,27 +171,3 @@ async def copy(
|
||||
|
||||
await directory.copy(target_directory, session, ctx.config, force=data.force)
|
||||
await session.commit()
|
||||
|
||||
|
||||
@router.get("/directory/content", response_model=DirectoryContent)
|
||||
async def content(
|
||||
path: Path,
|
||||
repository: Repository = Depends(middleware.repository),
|
||||
ctx: middleware.Context = Depends(),
|
||||
):
|
||||
async with ctx.database.session() as session:
|
||||
directory = await validate_current_directory(
|
||||
path, repository, session, ctx.config
|
||||
)
|
||||
session.add(directory)
|
||||
await session.refresh(directory, attribute_names=["directories"])
|
||||
await session.refresh(directory, attribute_names=["files"])
|
||||
|
||||
content = DirectoryContent(
|
||||
files=[await _file.info(session) for _file in directory.files],
|
||||
directories=[
|
||||
await _directory.info(session) for _directory in directory.directories
|
||||
],
|
||||
)
|
||||
|
||||
return content
|
||||
|
@ -1,40 +0,0 @@
|
||||
from fastapi import APIRouter, Request
|
||||
from fastapi.responses import HTMLResponse
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@router.get("/docs", response_class=HTMLResponse, include_in_schema=False)
|
||||
async def rapidoc(request: Request):
|
||||
return f"""
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<link href='http://fonts.googleapis.com/css?family=Roboto' rel='stylesheet' type='text/css'>
|
||||
<script
|
||||
type="module"
|
||||
src="https://unpkg.com/rapidoc/dist/rapidoc-min.js"
|
||||
></script>
|
||||
</head>
|
||||
<body>
|
||||
<rapi-doc
|
||||
spec-url="{request.app.openapi_url}"
|
||||
theme = "dark"
|
||||
show-header = "false"
|
||||
show-info = "true"
|
||||
allow-authentication = "true"
|
||||
allow-server-selection = "true"
|
||||
allow-api-list-style-selection = "true"
|
||||
theme = "dark"
|
||||
render-style = "focused"
|
||||
bg-color="#1e2129"
|
||||
primary-color="#a47bea"
|
||||
regular-font="Roboto"
|
||||
mono-font="Roboto Mono"
|
||||
show-method-in-nav-bar="as-colored-text">
|
||||
<img slot="logo" style="display: none"/>
|
||||
</rapi-doc>
|
||||
</body>
|
||||
</html>
|
||||
"""
|
@ -64,10 +64,20 @@ async def content(
|
||||
await session.refresh(repository, attribute_names=["files"])
|
||||
|
||||
content = RepositoryContent(
|
||||
files=[await _file.info(session) for _file in repository.files],
|
||||
directories=[
|
||||
await _directory.info(session) for _directory in repository.directories
|
||||
],
|
||||
files=list(
|
||||
map(
|
||||
lambda file: FileInfo.model_validate(file),
|
||||
filter(lambda file: file.path is None, repository.files),
|
||||
)
|
||||
),
|
||||
directories=list(
|
||||
map(
|
||||
lambda directory: DirectoryInfo.model_validate(directory),
|
||||
filter(
|
||||
lambda directory: directory.path is None, repository.directories
|
||||
),
|
||||
)
|
||||
),
|
||||
)
|
||||
|
||||
return content
|
||||
|
@ -1,33 +0,0 @@
|
||||
from fastapi import APIRouter, Request, Response, status, HTTPException, Depends
|
||||
from fastapi.responses import HTMLResponse, FileResponse
|
||||
from fastapi.templating import Jinja2Templates
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
import mimetypes
|
||||
from pathlib import Path
|
||||
from materia.core.misc import optional
|
||||
from materia.routers import middleware
|
||||
|
||||
from materia import docs as materia_docs
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
# templates = Jinja2Templates(directory=Path(materia_docs.__path__[0]))
|
||||
# p = Path(__file__).parent.joinpath("..", "docs").resolve()
|
||||
# router.mount("/docs", StaticFiles(directory="doces", html=True), name="docs")
|
||||
|
||||
|
||||
@router.get("/docs/{catchall:path}", include_in_schema=False)
|
||||
async def docs(request: Request, ctx: middleware.Context = Depends()):
|
||||
docs_directory = Path(materia_docs.__path__[0]).resolve()
|
||||
target = docs_directory.joinpath(request.path_params["catchall"]).resolve()
|
||||
|
||||
if not optional(target.relative_to, docs_directory):
|
||||
raise HTTPException(status.HTTP_403_FORBIDDEN)
|
||||
|
||||
if target.is_dir() and (index := target.joinpath("index.html")).is_file():
|
||||
return FileResponse(index)
|
||||
|
||||
if not target.is_file():
|
||||
raise HTTPException(status.HTTP_404_NOT_FOUND)
|
||||
|
||||
return FileResponse(target)
|
@ -1,5 +1,5 @@
|
||||
from pathlib import Path
|
||||
from fastapi import APIRouter, Request, HTTPException
|
||||
from fastapi import APIRouter, Request
|
||||
from fastapi.responses import HTMLResponse
|
||||
from fastapi.templating import Jinja2Templates
|
||||
|
||||
@ -13,7 +13,6 @@ else:
|
||||
|
||||
templates = Jinja2Templates(directory=Path(materia_frontend.__path__[0]) / "dist")
|
||||
|
||||
@router.get("/{spa:path}", response_class=HTMLResponse, include_in_schema=False)
|
||||
@router.get("/{spa:path}", response_class=HTMLResponse)
|
||||
async def root(request: Request):
|
||||
# raise HTTPException(404)
|
||||
return templates.TemplateResponse(request, "base.html", {"view": "app"})
|
||||
|
1
workspaces/frontend/.gitignore
vendored
@ -11,5 +11,4 @@ node_modules/
|
||||
*.mjs
|
||||
*.log
|
||||
|
||||
openapi.json
|
||||
src/client
|
||||
|
481
workspaces/frontend/package-lock.json
generated
@ -9,10 +9,8 @@
|
||||
"version": "0.0.5",
|
||||
"dependencies": {
|
||||
"@catppuccin/tailwindcss": "^0.1.6",
|
||||
"@hey-api/client-axios": "^0.2.3",
|
||||
"autoprefixer": "^10.4.18",
|
||||
"axios": "^1.6.8",
|
||||
"filesize": "^10.1.6",
|
||||
"pinia": "^2.1.7",
|
||||
"postcss": "^8.4.35",
|
||||
"tailwindcss": "^3.4.1",
|
||||
@ -20,7 +18,6 @@
|
||||
"vue-router": "^4.3.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@hey-api/openapi-ts": "^0.53.0",
|
||||
"@tsconfig/node18": "^18.2.2",
|
||||
"@types/node": "^18.19.3",
|
||||
"@vitejs/plugin-vue": "^4.5.2",
|
||||
@ -882,44 +879,6 @@
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@hey-api/client-axios": {
|
||||
"version": "0.2.3",
|
||||
"resolved": "https://registry.npmjs.org/@hey-api/client-axios/-/client-axios-0.2.3.tgz",
|
||||
"integrity": "sha512-v1BoTozp8LQ9JawZF9atXdmaBdQEvoIf39pIYf/0WSdkZSv0rUJDVXxNWHnDDs+S1/6pOfbOhM/0VXD5YJqr8w==",
|
||||
"peerDependencies": {
|
||||
"axios": ">= 1.0.0 < 2"
|
||||
}
|
||||
},
|
||||
"node_modules/@hey-api/openapi-ts": {
|
||||
"version": "0.53.0",
|
||||
"resolved": "https://registry.npmjs.org/@hey-api/openapi-ts/-/openapi-ts-0.53.0.tgz",
|
||||
"integrity": "sha512-5pDd/s0yHJniruYyKYmEsAMbY10Nh/EwhHlgIrdpQ1KZWQdyTbH/tn8rVHT5Mopr1dMuYX0kq0TzpjcNlvrROQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@apidevtools/json-schema-ref-parser": "11.7.0",
|
||||
"c12": "1.11.1",
|
||||
"commander": "12.1.0",
|
||||
"handlebars": "4.7.8"
|
||||
},
|
||||
"bin": {
|
||||
"openapi-ts": "bin/index.cjs"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.0.0 || >=20.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": "^5.x"
|
||||
}
|
||||
},
|
||||
"node_modules/@hey-api/openapi-ts/node_modules/commander": {
|
||||
"version": "12.1.0",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz",
|
||||
"integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@isaacs/cliui": {
|
||||
"version": "8.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
|
||||
@ -1463,18 +1422,6 @@
|
||||
"integrity": "sha512-VcZK7MvpjuTPx2w6blwnwZAu5/LgBUtejFOi3pPGQFXQN5Ela03FUtd2Qtg4yWGGissVL0dr6Ro1LfOFh+PCuQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/acorn": {
|
||||
"version": "8.12.1",
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz",
|
||||
"integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"acorn": "bin/acorn"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/ansi-regex": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz",
|
||||
@ -1640,34 +1587,6 @@
|
||||
"node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
|
||||
}
|
||||
},
|
||||
"node_modules/c12": {
|
||||
"version": "1.11.1",
|
||||
"resolved": "https://registry.npmjs.org/c12/-/c12-1.11.1.tgz",
|
||||
"integrity": "sha512-KDU0TvSvVdaYcQKQ6iPHATGz/7p/KiVjPg4vQrB6Jg/wX9R0yl5RZxWm9IoZqaIHD2+6PZd81+KMGwRr/lRIUg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"chokidar": "^3.6.0",
|
||||
"confbox": "^0.1.7",
|
||||
"defu": "^6.1.4",
|
||||
"dotenv": "^16.4.5",
|
||||
"giget": "^1.2.3",
|
||||
"jiti": "^1.21.6",
|
||||
"mlly": "^1.7.1",
|
||||
"ohash": "^1.1.3",
|
||||
"pathe": "^1.1.2",
|
||||
"perfect-debounce": "^1.0.0",
|
||||
"pkg-types": "^1.1.1",
|
||||
"rc9": "^2.1.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"magicast": "^0.3.4"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"magicast": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/camelcase": {
|
||||
"version": "6.3.0",
|
||||
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz",
|
||||
@ -1755,24 +1674,6 @@
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/chownr": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz",
|
||||
"integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/citty": {
|
||||
"version": "0.1.6",
|
||||
"resolved": "https://registry.npmjs.org/citty/-/citty-0.1.6.tgz",
|
||||
"integrity": "sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"consola": "^3.2.3"
|
||||
}
|
||||
},
|
||||
"node_modules/color-convert": {
|
||||
"version": "1.9.3",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
|
||||
@ -1813,21 +1714,6 @@
|
||||
"integrity": "sha512-7CEBgcMjVmitjYo5q8JTJVra6X5mQ20uTThdK+0kR7UEaDrAWEQcRiBtWJzga4eRpP6afNwwLsX2SET2JhVB1Q==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/confbox": {
|
||||
"version": "0.1.7",
|
||||
"resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.7.tgz",
|
||||
"integrity": "sha512-uJcB/FKZtBMCJpK8MQji6bJHgu1tixKPxRLeGkNzBoOZzpnZUJm0jm2/sBDWcuBx1dYgxV4JU+g5hmNxCyAmdA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/consola": {
|
||||
"version": "3.2.3",
|
||||
"resolved": "https://registry.npmjs.org/consola/-/consola-3.2.3.tgz",
|
||||
"integrity": "sha512-I5qxpzLv+sJhTVEoLYNcTW+bThDCPsit0vLNKShZx6rLtpilNpmmeTPaeqJb9ZE9dV3DGaeby6Vuhrw38WjeyQ==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": "^14.18.0 || >=16.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/convert-source-map": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
|
||||
@ -1886,12 +1772,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/defu": {
|
||||
"version": "6.1.4",
|
||||
"resolved": "https://registry.npmjs.org/defu/-/defu-6.1.4.tgz",
|
||||
"integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/delayed-stream": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
||||
@ -1900,12 +1780,6 @@
|
||||
"node": ">=0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/destr": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/destr/-/destr-2.0.3.tgz",
|
||||
"integrity": "sha512-2N3BOUU4gYMpTP24s5rF5iP7BDr7uNTCs4ozw3kf/eKfvWSIu93GEBi5m427YoyJoeOzQ5smuu4nNAPGb8idSQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/didyoumean": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz",
|
||||
@ -1916,18 +1790,6 @@
|
||||
"resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz",
|
||||
"integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA=="
|
||||
},
|
||||
"node_modules/dotenv": {
|
||||
"version": "16.4.5",
|
||||
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz",
|
||||
"integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://dotenvx.com"
|
||||
}
|
||||
},
|
||||
"node_modules/eastasianwidth": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
|
||||
@ -2014,29 +1876,6 @@
|
||||
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
|
||||
"integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="
|
||||
},
|
||||
"node_modules/execa": {
|
||||
"version": "8.0.1",
|
||||
"resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz",
|
||||
"integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"cross-spawn": "^7.0.3",
|
||||
"get-stream": "^8.0.1",
|
||||
"human-signals": "^5.0.0",
|
||||
"is-stream": "^3.0.0",
|
||||
"merge-stream": "^2.0.0",
|
||||
"npm-run-path": "^5.1.0",
|
||||
"onetime": "^6.0.0",
|
||||
"signal-exit": "^4.1.0",
|
||||
"strip-final-newline": "^3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.17"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sindresorhus/execa?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/fast-glob": {
|
||||
"version": "3.3.2",
|
||||
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz",
|
||||
@ -2071,14 +1910,6 @@
|
||||
"reusify": "^1.0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/filesize": {
|
||||
"version": "10.1.6",
|
||||
"resolved": "https://registry.npmjs.org/filesize/-/filesize-10.1.6.tgz",
|
||||
"integrity": "sha512-sJslQKU2uM33qH5nqewAwVB2QgR6w1aMNsYUp3aN5rMRyXEwJGmZvaWzeJFNTOXWlHQyBFCWrdj3fV/fsTOX8w==",
|
||||
"engines": {
|
||||
"node": ">= 10.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/fill-range": {
|
||||
"version": "7.1.1",
|
||||
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
|
||||
@ -2163,36 +1994,6 @@
|
||||
"node": ">=14.14"
|
||||
}
|
||||
},
|
||||
"node_modules/fs-minipass": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz",
|
||||
"integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"minipass": "^3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/fs-minipass/node_modules/minipass": {
|
||||
"version": "3.3.6",
|
||||
"resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz",
|
||||
"integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"yallist": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/fs-minipass/node_modules/yallist": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
|
||||
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/fsevents": {
|
||||
"version": "2.3.3",
|
||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
||||
@ -2223,37 +2024,6 @@
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/get-stream": {
|
||||
"version": "8.0.1",
|
||||
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz",
|
||||
"integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=16"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/giget": {
|
||||
"version": "1.2.3",
|
||||
"resolved": "https://registry.npmjs.org/giget/-/giget-1.2.3.tgz",
|
||||
"integrity": "sha512-8EHPljDvs7qKykr6uw8b+lqLiUc/vUg+KVTI0uND4s63TdsZM2Xus3mflvF0DDG9SiM4RlCkFGL+7aAjRmV7KA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"citty": "^0.1.6",
|
||||
"consola": "^3.2.3",
|
||||
"defu": "^6.1.4",
|
||||
"node-fetch-native": "^1.6.3",
|
||||
"nypm": "^0.3.8",
|
||||
"ohash": "^1.1.3",
|
||||
"pathe": "^1.1.2",
|
||||
"tar": "^6.2.0"
|
||||
},
|
||||
"bin": {
|
||||
"giget": "dist/cli.mjs"
|
||||
}
|
||||
},
|
||||
"node_modules/glob": {
|
||||
"version": "10.3.10",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-10.3.10.tgz",
|
||||
@ -2363,15 +2133,6 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/human-signals": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz",
|
||||
"integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=16.17.0"
|
||||
}
|
||||
},
|
||||
"node_modules/is-binary-path": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
|
||||
@ -2429,18 +2190,6 @@
|
||||
"node": ">=0.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/is-stream": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz",
|
||||
"integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/isexe": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
|
||||
@ -2576,12 +2325,6 @@
|
||||
"node": ">= 0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/merge-stream": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
|
||||
"integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/merge2": {
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
|
||||
@ -2621,18 +2364,6 @@
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/mimic-fn": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz",
|
||||
"integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/minimatch": {
|
||||
"version": "9.0.3",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz",
|
||||
@ -2664,61 +2395,6 @@
|
||||
"node": ">=16 || 14 >=14.17"
|
||||
}
|
||||
},
|
||||
"node_modules/minizlib": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz",
|
||||
"integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"minipass": "^3.0.0",
|
||||
"yallist": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/minizlib/node_modules/minipass": {
|
||||
"version": "3.3.6",
|
||||
"resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz",
|
||||
"integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"yallist": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/minizlib/node_modules/yallist": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
|
||||
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/mkdirp": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz",
|
||||
"integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"mkdirp": "bin/cmd.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/mlly": {
|
||||
"version": "1.7.1",
|
||||
"resolved": "https://registry.npmjs.org/mlly/-/mlly-1.7.1.tgz",
|
||||
"integrity": "sha512-rrVRZRELyQzrIUAVMHxP97kv+G786pHmOKzuFII8zDYahFBS7qnHh2AlYSl1GAHhaMPCz6/oHjVMcfFYgFYHgA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"acorn": "^8.11.3",
|
||||
"pathe": "^1.1.2",
|
||||
"pkg-types": "^1.1.1",
|
||||
"ufo": "^1.5.3"
|
||||
}
|
||||
},
|
||||
"node_modules/ms": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
||||
@ -2764,12 +2440,6 @@
|
||||
"integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/node-fetch-native": {
|
||||
"version": "1.6.4",
|
||||
"resolved": "https://registry.npmjs.org/node-fetch-native/-/node-fetch-native-1.6.4.tgz",
|
||||
"integrity": "sha512-IhOigYzAKHd244OC0JIMIUrjzctirCmPkaIfhDeGcEETWof5zKYUW7e7MYvChGWh/4CJeXEgsRyGzuF334rOOQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/node-releases": {
|
||||
"version": "2.0.14",
|
||||
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz",
|
||||
@ -2837,53 +2507,6 @@
|
||||
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/npm-run-path": {
|
||||
"version": "5.3.0",
|
||||
"resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz",
|
||||
"integrity": "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"path-key": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/npm-run-path/node_modules/path-key": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz",
|
||||
"integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/nypm": {
|
||||
"version": "0.3.11",
|
||||
"resolved": "https://registry.npmjs.org/nypm/-/nypm-0.3.11.tgz",
|
||||
"integrity": "sha512-E5GqaAYSnbb6n1qZyik2wjPDZON43FqOJO59+3OkWrnmQtjggrMOVnsyzfjxp/tS6nlYJBA4zRA5jSM2YaadMg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"citty": "^0.1.6",
|
||||
"consola": "^3.2.3",
|
||||
"execa": "^8.0.1",
|
||||
"pathe": "^1.1.2",
|
||||
"pkg-types": "^1.2.0",
|
||||
"ufo": "^1.5.4"
|
||||
},
|
||||
"bin": {
|
||||
"nypm": "dist/cli.mjs"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^14.16.0 || >=16.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/object-assign": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
||||
@ -2900,27 +2523,6 @@
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/ohash": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/ohash/-/ohash-1.1.3.tgz",
|
||||
"integrity": "sha512-zuHHiGTYTA1sYJ/wZN+t5HKZaH23i4yI1HMwbuXm24Nid7Dv0KcuRlKoNKS9UNfAVSBlnGLcuQrnOKWOZoEGaw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/onetime": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz",
|
||||
"integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"mimic-fn": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/openapi-typescript-codegen": {
|
||||
"version": "0.29.0",
|
||||
"resolved": "https://registry.npmjs.org/openapi-typescript-codegen/-/openapi-typescript-codegen-0.29.0.tgz",
|
||||
@ -2988,18 +2590,6 @@
|
||||
"node": "14 || >=16.14"
|
||||
}
|
||||
},
|
||||
"node_modules/pathe": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz",
|
||||
"integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/perfect-debounce": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-1.0.0.tgz",
|
||||
"integrity": "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/picocolors": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
|
||||
@ -3094,17 +2684,6 @@
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/pkg-types": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.2.0.tgz",
|
||||
"integrity": "sha512-+ifYuSSqOQ8CqP4MbZA5hDpb97n3E8SVWdJe+Wms9kj745lmd3b7EZJiqvmLwAlmRfjrI7Hi5z3kdBJ93lFNPA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"confbox": "^0.1.7",
|
||||
"mlly": "^1.7.1",
|
||||
"pathe": "^1.1.2"
|
||||
}
|
||||
},
|
||||
"node_modules/postcss": {
|
||||
"version": "8.4.38",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz",
|
||||
@ -3270,16 +2849,6 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"node_modules/rc9": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/rc9/-/rc9-2.1.2.tgz",
|
||||
"integrity": "sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"defu": "^6.1.4",
|
||||
"destr": "^2.0.3"
|
||||
}
|
||||
},
|
||||
"node_modules/read-cache": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
|
||||
@ -3544,18 +3113,6 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/strip-final-newline": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz",
|
||||
"integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/sucrase": {
|
||||
"version": "3.35.0",
|
||||
"resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz",
|
||||
@ -3642,38 +3199,6 @@
|
||||
"node": ">=14.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/tar": {
|
||||
"version": "6.2.1",
|
||||
"resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz",
|
||||
"integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"chownr": "^2.0.0",
|
||||
"fs-minipass": "^2.0.0",
|
||||
"minipass": "^5.0.0",
|
||||
"minizlib": "^2.1.1",
|
||||
"mkdirp": "^1.0.3",
|
||||
"yallist": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/tar/node_modules/minipass": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz",
|
||||
"integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/tar/node_modules/yallist": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
|
||||
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/thenify": {
|
||||
"version": "3.3.1",
|
||||
"resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz",
|
||||
@ -3731,12 +3256,6 @@
|
||||
"node": ">=14.17"
|
||||
}
|
||||
},
|
||||
"node_modules/ufo": {
|
||||
"version": "1.5.4",
|
||||
"resolved": "https://registry.npmjs.org/ufo/-/ufo-1.5.4.tgz",
|
||||
"integrity": "sha512-UsUk3byDzKd04EyoZ7U4DOlxQaD14JUKQl6/P7wiX4FNvUfm3XL246n9W5AmqwW5RSFJ27NAuM0iLscAOYUiGQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/uglify-js": {
|
||||
"version": "3.19.3",
|
||||
"resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz",
|
||||
|
@ -9,15 +9,12 @@
|
||||
"preview": "vite preview",
|
||||
"build": "vite build",
|
||||
"type-check": "vue-tsc --build --force",
|
||||
"generate-client": "openapi --input ./openapi.json --output ./src/client_old/ --client axios --name Client",
|
||||
"openapi-ts": "openapi-ts --input ./openapi.json --output ./src/client/ --client @hey-api/client-axios"
|
||||
"generate-client": "openapi --input ./openapi.json --output ./src/client/ --client axios"
|
||||
},
|
||||
"dependencies": {
|
||||
"@catppuccin/tailwindcss": "^0.1.6",
|
||||
"@hey-api/client-axios": "^0.2.3",
|
||||
"autoprefixer": "^10.4.18",
|
||||
"axios": "^1.6.8",
|
||||
"filesize": "^10.1.6",
|
||||
"pinia": "^2.1.7",
|
||||
"postcss": "^8.4.35",
|
||||
"tailwindcss": "^3.4.1",
|
||||
@ -25,7 +22,6 @@
|
||||
"vue-router": "^4.3.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@hey-api/openapi-ts": "^0.53.0",
|
||||
"@tsconfig/node18": "^18.2.2",
|
||||
"@types/node": "^18.19.3",
|
||||
"@vitejs/plugin-vue": "^4.5.2",
|
||||
|
22
workspaces/frontend/src/api/auth.ts
Normal file
@ -0,0 +1,22 @@
|
||||
import { api_client, type ResponseError, handle_error } from "@/client";
|
||||
|
||||
export interface UserCredentials {
|
||||
name: string,
|
||||
password: string,
|
||||
email?: string
|
||||
}
|
||||
|
||||
export async function signup(body: UserCredentials): Promise<null | ResponseError> {
|
||||
return await api_client.post("/auth/signup", JSON.stringify(body))
|
||||
.catch(handle_error);
|
||||
}
|
||||
|
||||
export async function signin(body: UserCredentials): Promise<null | ResponseError> {
|
||||
return await api_client.post("/auth/signin", JSON.stringify(body))
|
||||
.catch(handle_error);
|
||||
}
|
||||
|
||||
export async function signout(): Promise<null | ResponseError> {
|
||||
return await api_client.get("/auth/signout")
|
||||
.catch(handle_error);
|
||||
}
|
13
workspaces/frontend/src/api/directory.ts
Normal file
@ -0,0 +1,13 @@
|
||||
import { api_client, type ResponseError, handle_error } from "@/client";
|
||||
|
||||
export interface DirectoryInfo {
|
||||
id: number,
|
||||
repository_id: number,
|
||||
parent_id?: number,
|
||||
created: number,
|
||||
updated: number,
|
||||
name: string,
|
||||
path?: string,
|
||||
is_public: boolean,
|
||||
used?: number
|
||||
}
|
13
workspaces/frontend/src/api/file.ts
Normal file
@ -0,0 +1,13 @@
|
||||
import { api_client, type ResponseError, handle_error } from "@/client";
|
||||
|
||||
export interface FileInfo {
|
||||
id: number,
|
||||
repository_id: number,
|
||||
parent_id?: number,
|
||||
created: number,
|
||||
updated: number,
|
||||
name: string,
|
||||
path?: string,
|
||||
is_public: boolean,
|
||||
size: number
|
||||
}
|
5
workspaces/frontend/src/api/index.ts
Normal file
@ -0,0 +1,5 @@
|
||||
export * as auth from "@/api/auth";
|
||||
export * as user from "@/api/user";
|
||||
export * as repository from "@/api/repository";
|
||||
export * as directory from "@/api/directory";
|
||||
export * as file from "@/api/file";
|
35
workspaces/frontend/src/api/repository.ts
Normal file
@ -0,0 +1,35 @@
|
||||
import { api_client, type ResponseError, handle_error } from "@/client";
|
||||
import { file, directory } from "@/api"
|
||||
|
||||
export interface RepositoryInfo {
|
||||
id: number,
|
||||
capacity: number,
|
||||
used?: number
|
||||
}
|
||||
|
||||
export interface RepositoryContent {
|
||||
files: file.FileInfo[],
|
||||
directories: directory.DirectoryInfo[]
|
||||
}
|
||||
|
||||
export async function info(): Promise<RepositoryInfo | ResponseError> {
|
||||
return await api_client.get("/repository")
|
||||
.then(async response => { return Promise.resolve<RepositoryInfo>(response.data); })
|
||||
.catch(handle_error);
|
||||
}
|
||||
|
||||
export async function create(): Promise<null | ResponseError> {
|
||||
return await api_client.post("/repository")
|
||||
.catch(handle_error);
|
||||
}
|
||||
|
||||
export async function remove(): Promise<null | ResponseError> {
|
||||
return await api_client.delete("/repository")
|
||||
.catch(handle_error);
|
||||
}
|
||||
|
||||
export async function content(): Promise<RepositoryContent | ResponseError> {
|
||||
return await api_client.get("/repository/content")
|
||||
.then(async response => { return Promise.resolve<RepositoryContent>(response.data); })
|
||||
.catch(handle_error);
|
||||
}
|
35
workspaces/frontend/src/api/user.ts
Normal file
@ -0,0 +1,35 @@
|
||||
import { api_client, type ResponseError, handle_error } from "@/client";
|
||||
|
||||
export interface UserCredentials {
|
||||
name: string,
|
||||
password: string,
|
||||
email?: string
|
||||
}
|
||||
|
||||
export interface UserInfo {
|
||||
id: string,
|
||||
name: string,
|
||||
lower_name: string,
|
||||
full_name?: string,
|
||||
email?: string,
|
||||
is_email_private: boolean,
|
||||
must_change_password: boolean,
|
||||
login_type: string,
|
||||
created: number,
|
||||
updated: number,
|
||||
last_login?: number,
|
||||
is_active: boolean,
|
||||
is_admin: boolean,
|
||||
avatar?: string
|
||||
}
|
||||
|
||||
|
||||
export type Image = string | ArrayBuffer;
|
||||
|
||||
export async function info(): Promise<UserInfo | ResponseError> {
|
||||
return await api_client.get("/user")
|
||||
.then(async response => { return Promise.resolve<UserInfo>(response.data); })
|
||||
.catch(handle_error);
|
||||
}
|
||||
|
||||
|
@ -10,16 +10,11 @@
|
||||
}
|
||||
|
||||
a {
|
||||
@apply text-ctp-lavender;
|
||||
@apply text-ctp-green;
|
||||
}
|
||||
|
||||
.input {
|
||||
@apply w-full pl-3 pr-3 pt-2 pb-2 rounded border bg-ctp-mantle border-ctp-overlay0 hover:border-ctp-overlay1 focus:border-ctp-lavender text-ctp-text outline-none;
|
||||
}
|
||||
|
||||
.input-file {
|
||||
@apply block w-full border rounded cursor-pointer bg-ctp-base border-ctp-surface0 text-ctp-subtext0 focus:outline-none;
|
||||
@apply file:bg-ctp-mantle file:border-ctp-surface0 file:mr-5 file:py-2 file:px-3 file:h-full file:border-y-0 file:border-l-0 file:border-r file:text-ctp-blue hover:file:cursor-pointer hover:file:bg-ctp-base;
|
||||
@apply w-full pl-3 pr-3 pt-2 pb-2 rounded border bg-ctp-mantle border-ctp-overlay0 hover:border-ctp-overlay1 focus:border-ctp-green text-ctp-text outline-none;
|
||||
}
|
||||
|
||||
label {
|
||||
@ -27,15 +22,15 @@
|
||||
}
|
||||
|
||||
.button {
|
||||
@apply pt-1 pb-1 pl-3 pr-3 sm:pt-2 sm:pb-2 sm:pl-5 sm:pr-5 rounded bg-ctp-mantle border border-ctp-surface0 hover:bg-ctp-base text-ctp-blue cursor-pointer;
|
||||
@apply pt-2 pb-2 pl-5 pr-5 rounded bg-ctp-mantle hover:bg-ctp-base text-ctp-blue cursor-pointer;
|
||||
}
|
||||
|
||||
.link-button {
|
||||
@apply button text-ctp-lavender cursor-pointer border-none;
|
||||
@apply button text-ctp-green cursor-pointer;
|
||||
}
|
||||
|
||||
.hline {
|
||||
@apply border-t border-ctp-surface0 ml-0 mr-0;
|
||||
@apply border-t border-ctp-overlay0 ml-0 mr-0;
|
||||
}
|
||||
|
||||
h1 {
|
||||
@ -45,10 +40,6 @@
|
||||
label {
|
||||
@apply text-ctp-text;
|
||||
}
|
||||
|
||||
.icon {
|
||||
@apply inline-block select-none text-center overflow-visible w-6 h-6 stroke-ctp-text;
|
||||
}
|
||||
}
|
||||
|
||||
@layer utilities {
|
||||
|
45
workspaces/frontend/src/client.ts
Normal file
@ -0,0 +1,45 @@
|
||||
import axios, { type AxiosInstance, AxiosError } from "axios";
|
||||
|
||||
export class HttpError extends Error {
|
||||
status_code: number;
|
||||
|
||||
constructor(status_code: number, message: string) {
|
||||
super(JSON.stringify({ status_code: status_code, message: message }));
|
||||
Object.setPrototypeOf(this, new.target.prototype);
|
||||
|
||||
this.name = Error.name;
|
||||
this.status_code = status_code;
|
||||
}
|
||||
}
|
||||
|
||||
export interface ResponseError {
|
||||
status: number | null,
|
||||
message: string | null
|
||||
}
|
||||
|
||||
export function handle_error(error: AxiosError): Promise<ResponseError> {
|
||||
let message = error.response?.data?.detail || error.response?.data;
|
||||
console.log(error);
|
||||
// extract pydantic error message
|
||||
if (error.response.status == 422) {
|
||||
message = error.response?.data?.detail[1].ctx.reason;
|
||||
}
|
||||
|
||||
return Promise.reject<ResponseError>({ status: error.response.status, message: message});
|
||||
}
|
||||
|
||||
const debug = import.meta.hot;
|
||||
|
||||
export const api_client: AxiosInstance = axios.create({
|
||||
baseURL: debug ? "http://localhost:54601/api" : "/api",
|
||||
headers: {
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
withCredentials: true,
|
||||
});
|
||||
|
||||
export const resources_client: AxiosInstance = axios.create({
|
||||
baseURL: debug ? "http://localhost:54601/resources" : "/resources",
|
||||
responseType: "blob"
|
||||
});
|
||||
|
@ -1,4 +1,12 @@
|
||||
<script setup lang="ts">
|
||||
<template>
|
||||
<div class="fixed h-1/3 z-50 context-menu" :style="{ top: y + 'px', left: x + 'px' }">
|
||||
<div v-for="action in actions" :key="action.action" @click="emitAction(action.action)">
|
||||
{{ action.label }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, defineProps, defineEmits } from 'vue';
|
||||
|
||||
const { actions, x, y } = defineProps(['actions', 'x', 'y']);
|
||||
@ -9,19 +17,11 @@ const emitAction = (action) => {
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="absolute z-50 context-menu bg-ctp-mantle border rounded border-ctp-overlay0"
|
||||
:style="{ top: y + 'px', left: x + 'px' }">
|
||||
<div v-for="action in actions" :key="action.action" @click="emitAction(action.action)"
|
||||
class="hover:bg-ctp-base text-ctp-blue">
|
||||
{{ action.label }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.context-menu {
|
||||
position: absolute;
|
||||
background: white;
|
||||
border: 1px solid #ccc;
|
||||
box-shadow: 2px 2px 4px rgba(0, 0, 0, 0.2);
|
||||
min-width: 150px;
|
||||
}
|
||||
@ -30,4 +30,8 @@ const emitAction = (action) => {
|
||||
padding: 10px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.context-menu div:hover {
|
||||
background-color: #f0f0f0;
|
||||
}
|
||||
</style>
|
||||
|
@ -1,38 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from "vue";
|
||||
|
||||
const { data, actions } = defineProps(["data", "actions"]);
|
||||
const isShow = ref(false);
|
||||
const posX = ref(0);
|
||||
const posY = ref(0);
|
||||
const anchor = ref(null);
|
||||
|
||||
const emit = defineEmits(["onEvent"]);
|
||||
|
||||
const showMenu = (event) => {
|
||||
event.preventDefault();
|
||||
console.log("pos", event);
|
||||
posX.value = event.pageX;
|
||||
posY.value = event.pageY;
|
||||
isShow.value = true;
|
||||
|
||||
emit("onEvent", event);
|
||||
};
|
||||
|
||||
const closeMenu = () => {
|
||||
isShow.value = false;
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div ref="anchor" @contextmenu="showMenu($event)" style="display: contents" v-click-outside="closeMenu">
|
||||
<slot></slot>
|
||||
</div>
|
||||
<div v-if="isShow" class="absolute z-50 min-w-40 bg-ctp-mantle border rounded border-ctp-surface0"
|
||||
:style="{ top: posY + 'px', left: posX + 'px' }">
|
||||
<div v-for="action in actions" v-show="action.show()" :key="action.event" @click="action.event(data)"
|
||||
class="hover:bg-ctp-base text-ctp-blue select-none pl-4 pr-4 pt-2 pb-2">
|
||||
{{ action.label }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
@ -1,84 +0,0 @@
|
||||
<template>
|
||||
<div>
|
||||
<div v-if="header">
|
||||
<h2>
|
||||
{{ header.title }}
|
||||
</h2>
|
||||
<p>
|
||||
{{ header.description }}
|
||||
</p>
|
||||
</div>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th v-if="rowSelector">
|
||||
<div>
|
||||
<input id="contact-selectAll" type="checkbox" value="" @change="selectAll">
|
||||
</div>
|
||||
</th>
|
||||
<th v-for="(item, idx) in fields" :key="idx">
|
||||
{{ item.label }}
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="(item, index) in data" :key="index">
|
||||
<td v-if="rowSelector">
|
||||
<div>
|
||||
<input :id="`contact-${index}`" v-model="item.selected" type="checkbox">
|
||||
</div>
|
||||
</td>
|
||||
<td v-for="(field, idx) in fields" :key="idx" @click="rowSelected(item)">
|
||||
<span v-if="!hasNamedSlot(field.key)" :item="item">
|
||||
{{ item[field.key] }}
|
||||
</span>
|
||||
<slot v-else :name="field.key" :item="item" />
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div v-if="hasNamedSlot('footer')">
|
||||
<slot name="footer" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
data: {
|
||||
type: Array,
|
||||
required: true,
|
||||
default: () => []
|
||||
},
|
||||
header: {
|
||||
type: Object,
|
||||
required: false,
|
||||
default: () => null
|
||||
},
|
||||
fields: {
|
||||
type: Array,
|
||||
required: true,
|
||||
default: () => []
|
||||
},
|
||||
rowSelector: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
rowSelected(item) {
|
||||
this.$emit('rowSelected', item)
|
||||
},
|
||||
selectAll(e) {
|
||||
const checked = e.target.checked
|
||||
this.data.forEach((item) => { item.selected = checked })
|
||||
this.$forceUpdate()
|
||||
},
|
||||
hasNamedSlot(slotName) {
|
||||
return Object.prototype.hasOwnProperty.call(this.$scopedSlots, slotName)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
@ -1,15 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { defineProps } from "vue";
|
||||
|
||||
const { data } = defineProps(["data"]);
|
||||
|
||||
const onDragBeginEvent = (event) => {
|
||||
event.dataTransfer.setData("value", JSON.stringify(data));
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<tr draggable="true" @dragstart="onDragBeginEvent">
|
||||
<slot />
|
||||
</tr>
|
||||
</template>
|
@ -1,18 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { defineProps } from "vue";
|
||||
|
||||
const { onDragOver, onDragLeave, onDrop } = defineProps(["onDragOver", "onDragLeave", "onDrop"]);
|
||||
|
||||
const onDragOverEvent = (event) => {
|
||||
event.preventDefault();
|
||||
if (onDragOver) {
|
||||
onDragOver(event);
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<tr @dragover="onDragOverEvent" @dragleave="onDragLeave" @drop="onDrop">
|
||||
<slot />
|
||||
</tr>
|
||||
</template>
|
@ -1,40 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from "vue";
|
||||
const { value } = defineProps(["value"]);
|
||||
|
||||
const handleError = (e) => {
|
||||
let entries = [];
|
||||
let titlecase = (str: string) => {
|
||||
if (!str) return str;
|
||||
return str.charAt(0).toUpperCase() + str.slice(1);
|
||||
};
|
||||
|
||||
if (e?.response) {
|
||||
if (e.response.status == 422) {
|
||||
for (let detail of e.response.data.detail) {
|
||||
entries.push(titlecase(detail.msg));
|
||||
}
|
||||
} else {
|
||||
if (e.response.data?.detail) {
|
||||
entries.push(e.response.data.detail);
|
||||
} else {
|
||||
entries.push(e.response.data);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
entries.push(e);
|
||||
}
|
||||
|
||||
return entries;
|
||||
};
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section v-if="value" class="mt-1 mb-1">
|
||||
<p v-for="entry in handleError(value)"
|
||||
class="text-center pt-3 pb-3 bg-ctp-red/25 rounded border border-ctp-red text-ctp-red">
|
||||
{{ entry }}
|
||||
</p>
|
||||
</section>
|
||||
<h1 class="text-center pt-3 pb-3 bg-ctp-red/25 rounded border border-ctp-red text-ctp-red">
|
||||
<slot></slot>
|
||||
</h1>
|
||||
</template>
|
||||
|
@ -1,48 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { defineProps, defineEmits, ref } from "vue";
|
||||
|
||||
const props = defineProps({
|
||||
isOpen: Boolean,
|
||||
});
|
||||
|
||||
const emit = defineEmits(["close-modal"]);
|
||||
|
||||
const closeModal = (action) => {
|
||||
emit("close-modal", action);
|
||||
};
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="isOpen" class="modal-mask">
|
||||
<div class="modal-wrapper">
|
||||
<div class="modal-container">
|
||||
<div class="flex mb-4">
|
||||
<slot name="header"></slot>
|
||||
<button class="mr-0 ml-auto button" @click="closeModal">x</button>
|
||||
</div>
|
||||
<div>
|
||||
<slot name="content"></slot>
|
||||
</div>
|
||||
<div class="flex mt-4">
|
||||
<slot name="footer"></slot>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.modal-mask {
|
||||
@apply fixed z-[666] top-0 left-0 w-full h-full;
|
||||
@apply bg-ctp-crust bg-opacity-50;
|
||||
}
|
||||
|
||||
.modal-wrapper {}
|
||||
|
||||
.modal-container {
|
||||
@apply flex-grow w-full lg:w-[calc(100%-100px)] max-w-[1000px] ml-auto mr-auto lg:mt-20 lg:mb-20 p-5;
|
||||
@apply bg-ctp-crust shadow-ctp-overlay0 border rounded border-ctp-surface1 text-ctp-text;
|
||||
@apply h-screen lg:h-full;
|
||||
}
|
||||
</style>
|
@ -1,6 +1,7 @@
|
||||
<template>
|
||||
<div class="relative h-12">
|
||||
<nav class="absolute w-full h-full flex justify-between items-center m-0 pl-3 pr-3 bg-ctp-mantle">
|
||||
<nav
|
||||
class="absolute w-full h-full flex justify-between items-center m-0 pl-3 pr-3 bg-ctp-mantle">
|
||||
<div class="items-center m-0 flex">
|
||||
<slot name="left"></slot>
|
||||
</div>
|
||||
|
@ -1,242 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import IconDirectory from "@/components/icons/IconDirectory.vue";
|
||||
import IconFile from "@/components/icons/IconFile.vue";
|
||||
import ContextMenu from "@/components/ContextMenu.vue";
|
||||
import CtxMenu from "@/components/CtxMenu.vue";
|
||||
import Modal from "@/components/Modal.vue";
|
||||
import { defineProps, ref, defineModel } from "vue";
|
||||
import { filesize } from "filesize";
|
||||
import { store, router, api } from "@";
|
||||
import { useRoute } from "vue-router"
|
||||
|
||||
const repoStore = store.useRepository();
|
||||
const route = useRoute();
|
||||
|
||||
const actions = [
|
||||
{
|
||||
label: "Copy",
|
||||
show: () => true,
|
||||
event: (data) => { repoStore.copyBufferSelected(); }
|
||||
},
|
||||
{
|
||||
label: "Paste",
|
||||
show: () => true,
|
||||
event: async (data) => { await repoStore.copyItems(repoStore.buffer, repoStore.currentPath); }
|
||||
},
|
||||
{
|
||||
label: "Move",
|
||||
show: () => true,
|
||||
event: async (data) => { await repoStore.moveItems(repoStore.buffer, repoStore.currentPath); }
|
||||
},
|
||||
{
|
||||
label: "Delete",
|
||||
show: () => true,
|
||||
event: async (data) => { await repoStore.deleteItem(data); }
|
||||
},
|
||||
{
|
||||
label: "Delete selected",
|
||||
show: () => { return repoStore.content.filter((item) => item.meta.selected).length > 1; },
|
||||
event: async (data) => { await repoStore.deleteSelectedItems(); }
|
||||
},
|
||||
];
|
||||
|
||||
// Properties
|
||||
const data = defineModel()
|
||||
const { onDragOver, onDragLeave, onDrop } = defineProps(["onDragOver", "onDragLeave", "onDrop"]);
|
||||
|
||||
// Drag and drop
|
||||
const onDragOverEvent = (event, value) => {
|
||||
event.preventDefault();
|
||||
console.log("drag over", JSON.stringify(value));
|
||||
|
||||
if (value.type === "directory") {
|
||||
value.meta.dragOvered = true;
|
||||
}
|
||||
|
||||
if (onDragOver) {
|
||||
onDragOver(event);
|
||||
}
|
||||
};
|
||||
|
||||
const onDragLeaveEvent = (event, value) => {
|
||||
event.preventDefault();
|
||||
console.log("drag leave", JSON.stringify(value));
|
||||
|
||||
if (value.type === "directory") {
|
||||
value.meta.dragOvered = false;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
const onDragBegin = (event, value) => {
|
||||
value.meta.selected = true;
|
||||
|
||||
let items = repoStore.content.filter((item) => item.meta.selected);
|
||||
|
||||
let elem = document.createElement("div");
|
||||
elem.id = "dragItem";
|
||||
elem.className = "min-w-16 h-8 absolute top-[-1000px] bg-ctp-mantle border rounded border-ctp-surface0 px-4 py-1";
|
||||
elem.appendChild(document.createTextNode("Move " + items.length + " items"));
|
||||
document.body.appendChild(elem);
|
||||
|
||||
event.dataTransfer.setDragImage(elem, 0, 0);
|
||||
event.dataTransfer.setData("value", JSON.stringify(items));
|
||||
};
|
||||
|
||||
const onDragEnd = (event) => {
|
||||
let elem = document.getElementById("dragItem");
|
||||
if (elem.parentNode) {
|
||||
elem.parentNode.removeChild(elem);
|
||||
}
|
||||
deselectAll();
|
||||
};
|
||||
|
||||
const onDropEvent = async (event, item) => {
|
||||
if (item.type === "directory") {
|
||||
item.meta.dragOvered = false;
|
||||
let items = JSON.parse(event.dataTransfer.getData("value"));
|
||||
|
||||
await repoStore.moveItems(items, item.info.path);
|
||||
}
|
||||
console.log("drop data", JSON.parse(event.dataTransfer.getData("value")));
|
||||
};
|
||||
|
||||
const isDraggable = ref(false);
|
||||
|
||||
// Selection
|
||||
const deselectAll = () => {
|
||||
console.log("deselect", data.value);
|
||||
for (let item of data.value) {
|
||||
item.meta.selected = false;
|
||||
}
|
||||
};
|
||||
|
||||
const selectAll = (event) => {
|
||||
console.log("select all", event);
|
||||
for (let item of data.value) {
|
||||
item.meta.selected = true;
|
||||
}
|
||||
};
|
||||
|
||||
const onSingleClick = (event: Event, item) => {
|
||||
console.log(event);
|
||||
if (!event.ctrlKey) {
|
||||
deselectAll();
|
||||
}
|
||||
if (event.shiftKey) {
|
||||
selectAll();
|
||||
} else {
|
||||
item.meta.selected = !item.meta.selected;
|
||||
}
|
||||
};
|
||||
|
||||
const onDoubleClick = (item) => {
|
||||
if (item.type === "directory") {
|
||||
repoStore.changeDirectory(route.path, item.info);
|
||||
}
|
||||
};
|
||||
|
||||
const onClickEvent = (event: Event, item) => {
|
||||
item.meta.clickCount++;
|
||||
if (item.meta.clickCount === 1) {
|
||||
onSingleClick(event, item);
|
||||
|
||||
item.meta.clickTimer = setTimeout(() => {
|
||||
item.meta.clickCount = 0;
|
||||
}, 300);
|
||||
} else {
|
||||
onDoubleClick(item);
|
||||
clearTimeout(item.meta.clickTimer);
|
||||
item.meta.clickCount = 0;
|
||||
}
|
||||
};
|
||||
|
||||
const onCtrlClickEvent = (event: Event, item) => {
|
||||
//console.log("ctrl click", event);
|
||||
//item.meta.selected = !item.meta.selected;
|
||||
};
|
||||
|
||||
// TODO: ctrl+a select all, ctrl-left-mouse select one more item, shift-left-mouse select range of items
|
||||
|
||||
// Misc
|
||||
|
||||
function timeAgo(timestamp: number) {
|
||||
const seconds = Math.floor((new Date().getTime() - new Date(timestamp * 1000).getTime()) / 1000)
|
||||
let interval = seconds / 31536000
|
||||
const rtf = new Intl.RelativeTimeFormat("en", { numeric: 'auto' })
|
||||
if (interval > 1) { return rtf.format(-Math.floor(interval), 'year') }
|
||||
interval = seconds / 2592000
|
||||
if (interval > 1) { return rtf.format(-Math.floor(interval), 'month') }
|
||||
interval = seconds / 86400
|
||||
if (interval > 1) { return rtf.format(-Math.floor(interval), 'day') }
|
||||
interval = seconds / 3600
|
||||
if (interval > 1) { return rtf.format(-Math.floor(interval), 'hour') }
|
||||
interval = seconds / 60
|
||||
if (interval > 1) { return rtf.format(-Math.floor(interval), 'minute') }
|
||||
return rtf.format(-Math.floor(interval), 'second')
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex flex-col h-full border-y lg:border rounded border-ctp-surface0 select-none my-2"
|
||||
@contextmenu.prevent>
|
||||
<div class="flex w-full py-2 px-4">
|
||||
<span class="text-left w-1/2">Name</span>
|
||||
<span class="hidden lg:inline w-1/4 text-right">Size</span>
|
||||
<span class="w-1/2 lg:w-1/4 text-right">Created</span>
|
||||
</div>
|
||||
<div class="hline"></div>
|
||||
<div class="flex flex-col overflow-y-auto" v-click-outside="deselectAll">
|
||||
<div class="flex hover:bg-ctp-surface0 py-2 px-4" v-if="!repoStore.isRoot()"
|
||||
@click="repoStore.previousDirectory(route.path)">
|
||||
<div class="w-3/5 lg:w-1/2 flex items-center">
|
||||
<IconDirectory />
|
||||
|
||||
<span class="ml-4 text-ellipsis whitespace-nowrap overflow-auto">
|
||||
..
|
||||
</span>
|
||||
</div>
|
||||
<div class="hidden lg:flex w-1/4 items-center justify-end">
|
||||
<span>-</span>
|
||||
</div>
|
||||
<div class="w-2/5 lg:w-1/4 flex items-center justify-end">
|
||||
<span>-</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex hover:bg-ctp-surface0 py-2 px-4"
|
||||
v-if="repoStore.isRoot() && repoStore.content.length === 0">
|
||||
<div class="flex items-center ml-auto mr-auto">
|
||||
Empty
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex hover:bg-ctp-surface1 py-2 px-4" v-for="(item, index) in repoStore.content"
|
||||
:class="{ 'bg-ctp-surface0': item.meta.selected, 'bg-ctp-lavender': item.meta.dragOvered }"
|
||||
@click="onClickEvent($event, item)" @keypress.ctrl.65="selectAll" draggable="true"
|
||||
@dragstart="onDragBegin($event, item)" @dragover="onDragOverEvent($event, item)"
|
||||
@dragend="onDragEnd($event)" @dragleave="onDragLeaveEvent($event, item)"
|
||||
@drop="onDropEvent($event, item)">
|
||||
<CtxMenu :data="item" :actions="actions" @onEvent="onSingleClick($event, item)">
|
||||
<div class="w-3/5 lg:w-1/2 flex items-center">
|
||||
<IconDirectory v-if="item.type == 'directory'" />
|
||||
<IconFile v-else />
|
||||
|
||||
<span class="ml-4 text-ellipsis whitespace-nowrap overflow-hidden">
|
||||
{{ item.info.name }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="hidden lg:flex w-1/4 items-center justify-end">
|
||||
<span v-if="item.type == 'directory'">-</span>
|
||||
<span v-else>{{ filesize(item.info.size) }}</span>
|
||||
</div>
|
||||
<div class="w-2/5 lg:w-1/4 flex items-center justify-end">
|
||||
<span>{{ timeAgo(item.info.created) }}</span>
|
||||
</div>
|
||||
</CtxMenu>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</template>
|
@ -1,45 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from "vue";
|
||||
|
||||
const { text } = defineProps(["text"]);
|
||||
const isShow = ref(false);
|
||||
const posX = ref(0);
|
||||
const posY = ref(0);
|
||||
const anchor = ref(null);
|
||||
|
||||
const showTooltip = (event, value) => {
|
||||
event.preventDefault();
|
||||
|
||||
if (isShow.value)
|
||||
return;
|
||||
|
||||
posX.value = anchor.value.getBoundingClientRect().left;
|
||||
posY.value = anchor.value.getBoundingClientRect().top;
|
||||
isShow.value = true;
|
||||
};
|
||||
|
||||
const closeTooltip = () => {
|
||||
isShow.value = false;
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div ref="anchor" @mouseover="showTooltip" @mouseleave="closeTooltip" style="display: unset">
|
||||
<slot></slot>
|
||||
<div class="tooltip" :style="{ top: posY + 32 + 'px', left: posX + 16 + 'px' }" v-if="isShow">
|
||||
{{
|
||||
text
|
||||
}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</template>
|
||||
|
||||
<style>
|
||||
.tooltip {
|
||||
@apply hidden sm:inline z-[666] absolute bg-ctp-mantle rounded p-2 border border-ctp-surface0 max-w-[600px];
|
||||
overflow-wrap: break-all;
|
||||
word-break: break-all;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
</style>
|
@ -1,90 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import Modal from "@/components/Modal.vue";
|
||||
import Tooltip from "@/components/Tooltip.vue";
|
||||
import IconDelete from "@/components/icons/IconDelete.vue";
|
||||
import IconRemove from "@/components/icons/IconRemove.vue";
|
||||
import IconUpload from "@/components/icons/IconUpload.vue";
|
||||
import IconAccept from "@/components/icons/IconAccept.vue";
|
||||
import { filesize } from "filesize";
|
||||
import { ref, shallowRef } from "vue";
|
||||
import { store } from "@";
|
||||
|
||||
const props = defineProps({
|
||||
isOpen: Boolean,
|
||||
});
|
||||
|
||||
const emit = defineEmits(["close-uploader"]);
|
||||
|
||||
const closeUploader = (action) => {
|
||||
emit("close-uploader", action);
|
||||
};
|
||||
|
||||
const inputFileRef = shallowRef();
|
||||
const uploaderStore = store.useUploader();
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Modal :isOpen="isOpen" @close-modal="closeUploader">
|
||||
<template #header>
|
||||
<h2>Uploader</h2>
|
||||
</template>
|
||||
<template #content>
|
||||
<div class="flex mb-8">
|
||||
<input type="file" multiple ref="inputFileRef" @change="uploaderStore.loadFiles($event)"
|
||||
class="input-file" />
|
||||
|
||||
<button class="button ml-2" :disabled="uploaderStore.files.length === 0"
|
||||
@click="uploaderStore.uploadFiles">Upload</button>
|
||||
<button class="button ml-2 text-ctp-red" @click="uploaderStore.removeFiles()">Clear</button>
|
||||
</div>
|
||||
<div v-if="uploaderStore.files.length > 0" class="flex flex-col h-full">
|
||||
<div class="flex w-full mb-2">
|
||||
<span class="text-left w-1/2">Name</span>
|
||||
<span class="hidden lg:inline w-1/4 text-right">Size</span>
|
||||
<span class="w-1/2 lg:w-1/4 text-right"></span>
|
||||
</div>
|
||||
<div class="hline"></div>
|
||||
<div class="flex flex-col overflow-y-auto h-[480px]">
|
||||
<div class="flex hover:bg-ctp-surface0 mt-1 mb-1" v-for="(item, index) in uploaderStore.files">
|
||||
<div class="w-3/5 lg:w-1/2 flex items-center text-ellipsis whitespace-nowrap overflow-hidden">
|
||||
<Tooltip :text="item.content.name">
|
||||
<span class="pl-2">{{ item.content.name }}</span>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<div class="hidden lg:flex w-1/4 items-center justify-end">
|
||||
<span>{{ filesize(item.content.size) }}</span>
|
||||
</div>
|
||||
<div class="w-2/5 lg:w-1/4 flex items-center justify-end pr-4">
|
||||
<Tooltip :text="'Stop'" v-if="item.status === 'transfer'">
|
||||
<button class="button" @click="item.cancel()">
|
||||
{{ Math.round(item.progress * 100) }}%
|
||||
</button>
|
||||
</Tooltip>
|
||||
<button class="button" v-if="item.status === 'success'">
|
||||
<IconAccept class="stroke-ctp-green" />
|
||||
</button>
|
||||
<Tooltip :text="item.error" v-if="item.status === 'fail'">
|
||||
<button class="button" @click="uploaderStore.uploadFile(item)">
|
||||
<IconUpload class="stroke-ctp-red" />
|
||||
</button>
|
||||
</Tooltip>
|
||||
<Tooltip :text="'Upload'" v-if="item.status === 'idle'">
|
||||
<button class="button" @click="uploaderStore.uploadFile(item)">
|
||||
<IconUpload class="stroke-ctp-blue" />
|
||||
</button>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip :text="'Remove'">
|
||||
<button class="button ml-2" @click="uploaderStore.removeFile(item)">
|
||||
<IconDelete class="stroke-ctp-peach" />
|
||||
</button>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="hline"></div>
|
||||
</div>
|
||||
</template>
|
||||
</Modal>
|
||||
</template>
|
@ -1,10 +0,0 @@
|
||||
<template>
|
||||
<svg aria-hidden="true" focusable="false" role="img" class="icon" viewBox="0 0 28 28" fill="none">
|
||||
<path
|
||||
d="M6.65263 14.0304C6.29251 13.6703 6.29251 13.0864 6.65263 12.7263C7.01276 12.3662 7.59663 12.3662 7.95676 12.7263L11.6602 16.4297L19.438 8.65183C19.7981 8.29171 20.382 8.29171 20.7421 8.65183C21.1023 9.01195 21.1023 9.59583 20.7421 9.95596L12.3667 18.3314C11.9762 18.7219 11.343 18.7219 10.9525 18.3314L6.65263 14.0304Z"
|
||||
stroke-width="2" />
|
||||
<path clip-rule="evenodd"
|
||||
d="M14 1C6.8203 1 1 6.8203 1 14C1 21.1797 6.8203 27 14 27C21.1797 27 27 21.1797 27 14C27 6.8203 21.1797 1 14 1ZM3 14C3 7.92487 7.92487 3 14 3C20.0751 3 25 7.92487 25 14C25 20.0751 20.0751 25 14 25C7.92487 25 3 20.0751 3 14Z"
|
||||
fill-rule="evenodd" stroke-width="2" />
|
||||
</svg>
|
||||
</template>
|
@ -1,11 +0,0 @@
|
||||
<template>
|
||||
<svg aria-hidden="true" focusable="false" role="img" class="icon" viewBox="0 0 24 24" fill="none">
|
||||
<path d="M10 12V17" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
|
||||
<path d="M14 12V17" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
|
||||
<path d="M4 7H20" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
|
||||
<path d="M6 10V18C6 19.6569 7.34315 21 9 21H15C16.6569 21 18 19.6569 18 18V10" stroke-width="2"
|
||||
stroke-linecap="round" stroke-linejoin="round" />
|
||||
<path d="M9 5C9 3.89543 9.89543 3 11 3H13C14.1046 3 15 3.89543 15 5V7H9V5Z" stroke-width="2"
|
||||
stroke-linecap="round" stroke-linejoin="round" />
|
||||
</svg>
|
||||
</template>
|
@ -1,8 +0,0 @@
|
||||
<template>
|
||||
<svg aria-hidden="true" focusable="false" role="img" class="icon" viewBox="0 0 1024 1024" fill="none">
|
||||
<path
|
||||
d="M905.92 237.76a32 32 0 0 0-52.48 36.48A416 416 0 1 1 96 512a418.56 418.56 0 0 1 297.28-398.72 32 32 0 1 0-18.24-61.44A480 480 0 1 0 992 512a477.12 477.12 0 0 0-86.08-274.24z" />
|
||||
<path
|
||||
d="M630.72 113.28A413.76 413.76 0 0 1 768 185.28a32 32 0 0 0 39.68-50.24 476.8 476.8 0 0 0-160-83.2 32 32 0 0 0-18.24 61.44zM489.28 86.72a36.8 36.8 0 0 0 10.56 6.72 30.08 30.08 0 0 0 24.32 0 37.12 37.12 0 0 0 10.56-6.72A32 32 0 0 0 544 64a33.6 33.6 0 0 0-9.28-22.72A32 32 0 0 0 505.6 32a20.8 20.8 0 0 0-5.76 1.92 23.68 23.68 0 0 0-5.76 2.88l-4.8 3.84a32 32 0 0 0-6.72 10.56A32 32 0 0 0 480 64a32 32 0 0 0 2.56 12.16 37.12 37.12 0 0 0 6.72 10.56zM726.72 297.28a32 32 0 0 0-45.12 0l-169.6 169.6-169.28-169.6A32 32 0 0 0 297.6 342.4l169.28 169.6-169.6 169.28a32 32 0 1 0 45.12 45.12l169.6-169.28 169.28 169.28a32 32 0 0 0 45.12-45.12L557.12 512l169.28-169.28a32 32 0 0 0 0.32-45.44z" />
|
||||
</svg>
|
||||
</template>
|
@ -1,7 +0,0 @@
|
||||
<template>
|
||||
<svg aria-hidden="true" focusable="false" role="img" class="icon" viewBox="0 0 24 24" fill="none">
|
||||
<path
|
||||
d="M12 19V12M12 12L9.75 14.3333M12 12L14.25 14.3333M6.6 17.8333C4.61178 17.8333 3 16.1917 3 14.1667C3 12.498 4.09438 11.0897 5.59198 10.6457C5.65562 10.6268 5.7 10.5675 5.7 10.5C5.7 7.46243 8.11766 5 11.1 5C14.0823 5 16.5 7.46243 16.5 10.5C16.5 10.5582 16.5536 10.6014 16.6094 10.5887C16.8638 10.5306 17.1284 10.5 17.4 10.5C19.3882 10.5 21 12.1416 21 14.1667C21 16.1917 19.3882 17.8333 17.4 17.8333"
|
||||
stroke-linecap="round" stroke-linejoin="round" stroke-width="2" />
|
||||
</svg>
|
||||
</template>
|
@ -1,2 +0,0 @@
|
||||
<?xml version="1.0" ?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||
<svg width="800px" height="800px" viewBox="0 0 28 28" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M6.65263 14.0304C6.29251 13.6703 6.29251 13.0864 6.65263 12.7263C7.01276 12.3662 7.59663 12.3662 7.95676 12.7263L11.6602 16.4297L19.438 8.65183C19.7981 8.29171 20.382 8.29171 20.7421 8.65183C21.1023 9.01195 21.1023 9.59583 20.7421 9.95596L12.3667 18.3314C11.9762 18.7219 11.343 18.7219 10.9525 18.3314L6.65263 14.0304Z" fill="#000000"/><path clip-rule="evenodd" d="M14 1C6.8203 1 1 6.8203 1 14C1 21.1797 6.8203 27 14 27C21.1797 27 27 21.1797 27 14C27 6.8203 21.1797 1 14 1ZM3 14C3 7.92487 7.92487 3 14 3C20.0751 3 25 7.92487 25 14C25 20.0751 20.0751 25 14 25C7.92487 25 3 20.0751 3 14Z" fill="#000000" fill-rule="evenodd"/></svg>
|
Before Width: | Height: | Size: 842 B |
@ -1,8 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||
<svg width="800px" height="800px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M10 12V17" stroke="#000000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M14 12V17" stroke="#000000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M4 7H20" stroke="#000000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M6 10V18C6 19.6569 7.34315 21 9 21H15C16.6569 21 18 19.6569 18 18V10" stroke="#000000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M9 5C9 3.89543 9.89543 3 11 3H13C14.1046 3 15 3.89543 15 5V7H9V5Z" stroke="#000000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
Before Width: | Height: | Size: 862 B |
@ -1,3 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||
<svg width="800px" height="800px" viewBox="0 0 1024 1024" class="icon" version="1.1" xmlns="http://www.w3.org/2000/svg"><path d="M905.92 237.76a32 32 0 0 0-52.48 36.48A416 416 0 1 1 96 512a418.56 418.56 0 0 1 297.28-398.72 32 32 0 1 0-18.24-61.44A480 480 0 1 0 992 512a477.12 477.12 0 0 0-86.08-274.24z" fill="#231815" /><path d="M630.72 113.28A413.76 413.76 0 0 1 768 185.28a32 32 0 0 0 39.68-50.24 476.8 476.8 0 0 0-160-83.2 32 32 0 0 0-18.24 61.44zM489.28 86.72a36.8 36.8 0 0 0 10.56 6.72 30.08 30.08 0 0 0 24.32 0 37.12 37.12 0 0 0 10.56-6.72A32 32 0 0 0 544 64a33.6 33.6 0 0 0-9.28-22.72A32 32 0 0 0 505.6 32a20.8 20.8 0 0 0-5.76 1.92 23.68 23.68 0 0 0-5.76 2.88l-4.8 3.84a32 32 0 0 0-6.72 10.56A32 32 0 0 0 480 64a32 32 0 0 0 2.56 12.16 37.12 37.12 0 0 0 6.72 10.56zM726.72 297.28a32 32 0 0 0-45.12 0l-169.6 169.6-169.28-169.6A32 32 0 0 0 297.6 342.4l169.28 169.6-169.6 169.28a32 32 0 1 0 45.12 45.12l169.6-169.28 169.28 169.28a32 32 0 0 0 45.12-45.12L557.12 512l169.28-169.28a32 32 0 0 0 0.32-45.44z" fill="#231815" /></svg>
|
Before Width: | Height: | Size: 1.1 KiB |
@ -1,4 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||
<svg width="800px" height="800px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M12 19V12M12 12L9.75 14.3333M12 12L14.25 14.3333M6.6 17.8333C4.61178 17.8333 3 16.1917 3 14.1667C3 12.498 4.09438 11.0897 5.59198 10.6457C5.65562 10.6268 5.7 10.5675 5.7 10.5C5.7 7.46243 8.11766 5 11.1 5C14.0823 5 16.5 7.46243 16.5 10.5C16.5 10.5582 16.5536 10.6014 16.6094 10.5887C16.8638 10.5306 17.1284 10.5 17.4 10.5C19.3882 10.5 21 12.1416 21 14.1667C21 16.1917 19.3882 17.8333 17.4 17.8333" stroke="#464455" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
Before Width: | Height: | Size: 702 B |
14
workspaces/frontend/src/directives/click-outside.ts
Normal file
@ -0,0 +1,14 @@
|
||||
export const click_outside = {
|
||||
beforeMount: function(element: any, binding: any) {
|
||||
element.clickOutsideEvent = function(event: any) {
|
||||
if (!(element == event.target || element.contains(event.target))) {
|
||||
binding.value(event);
|
||||
}
|
||||
};
|
||||
|
||||
document.body.addEventListener("click", element.clickOutsideEvent);
|
||||
},
|
||||
unmounted: function(element: any) {
|
||||
document.body.removeEventListener("click", element.clickOutsideEvent);
|
||||
}
|
||||
}
|
@ -1,9 +1,2 @@
|
||||
export * as plugins from "@/plugins";
|
||||
export { router, check_auth } from "@/router";
|
||||
export { client } from "@/client";
|
||||
export * as api from "@/client/services.gen.ts";
|
||||
export * as schemas from "@/client/schemas.gen.ts";
|
||||
export * as api_types from "@/client/types.gen.ts";
|
||||
export * as store from "@/store";
|
||||
export * as style from "@/assets/style.css";
|
||||
export * as types from "@/types";
|
||||
export * as api from "@/api";
|
||||
export * as resources from "@/resources";
|
||||
|
@ -2,20 +2,14 @@ import App from "@/App.vue";
|
||||
|
||||
import { createApp } from "vue";
|
||||
import { createPinia } from "pinia";
|
||||
import { plugins, router, client, style } from "@";
|
||||
|
||||
|
||||
const debug = import.meta.hot;
|
||||
|
||||
client.setConfig({
|
||||
baseURL: debug ? "http://localhost:54601" : "/",
|
||||
withCredentials: true,
|
||||
});
|
||||
import router from "@/router";
|
||||
import { click_outside } from "@/directives/click-outside";
|
||||
import "@/assets/style.css";
|
||||
|
||||
createApp(App)
|
||||
.use(createPinia())
|
||||
.use(router)
|
||||
.directive("click-outside", plugins.clickOutside)
|
||||
.directive("tooltip", plugins.tooltip)
|
||||
.directive("click-outside", click_outside)
|
||||
.mount('#app');
|
||||
|
||||
|
@ -1,34 +0,0 @@
|
||||
export const clickOutside = {
|
||||
beforeMount: function(element: any, binding: any) {
|
||||
element.clickOutsideEvent = function(event: any) {
|
||||
if (!(element == event.target || element.contains(event.target))) {
|
||||
binding.value(event);
|
||||
}
|
||||
};
|
||||
|
||||
document.body.addEventListener("click", element.clickOutsideEvent);
|
||||
document.body.addEventListener("contextmenu", element.clickOutsideEvent);
|
||||
},
|
||||
unmounted: function(element: any) {
|
||||
document.body.removeEventListener("click", element.clickOutsideEvent);
|
||||
document.body.removeEventListener("contextmenu", element.clickOutsideEvent);
|
||||
}
|
||||
};
|
||||
|
||||
export const tooltip = {
|
||||
beforeMount: function (element: any, binding: any) {
|
||||
element.tooltip = function (event) {
|
||||
let target = event.target;
|
||||
if (target.offsetWidth < target.scrollWidth) {
|
||||
target.setAttribute('title', binding.value?.text ? binding.value.text : event.target.textContent);
|
||||
} else {
|
||||
target.hasAttribute('title') && target.removeAttribute('title');
|
||||
}
|
||||
};
|
||||
|
||||
document.body.addEventListener('mouseover', element.tooltip);
|
||||
},
|
||||
unmounted: function(element: any) {
|
||||
document.body.removeEventListener("mouseover", element.tooltip);
|
||||
}
|
||||
};
|
@ -1 +0,0 @@
|
||||
export * from "@/plugins/directives";
|
23
workspaces/frontend/src/resources/index.ts
Normal file
@ -0,0 +1,23 @@
|
||||
import { resources_client, type ResponseError, handle_error } from "@/client";
|
||||
|
||||
|
||||
export type Image = string | ArrayBuffer;
|
||||
|
||||
export async function avatars(avatar_id: string, format?: string): Promise<Image | null | ResponseError> {
|
||||
return await resources_client.get("/avatars/".concat(avatar_id), { params: { format: format ? format : "png" }})
|
||||
.then(async response => {
|
||||
return new Promise<Image | null>((resolve, reject) => {
|
||||
let reader = new FileReader();
|
||||
|
||||
reader.onload = () => {
|
||||
resolve(reader.result);
|
||||
};
|
||||
reader.onerror = (e) => {
|
||||
reject(e);
|
||||
};
|
||||
reader.readAsDataURL(response.data);
|
||||
})
|
||||
|
||||
})
|
||||
.catch(handle_error);
|
||||
}
|
@ -1,50 +1,43 @@
|
||||
import { createRouter, createWebHistory, useRoute } from "vue-router";
|
||||
import { store, api, schemas } from "@";
|
||||
import { createRouter, createWebHistory } from "vue-router";
|
||||
|
||||
import { useUserStore } from "@/stores";
|
||||
import { api, resources } from "@";
|
||||
|
||||
export const is_authorized = async (): Promise<boolean> => {
|
||||
const userStore = store.useUser();
|
||||
async function check_authorized(): Promise<boolean> {
|
||||
const userStore = useUserStore();
|
||||
|
||||
return await api.userInfo({ throwOnError: true })
|
||||
.then(async res => { userStore.info = res.data; })
|
||||
return await api.user.info()
|
||||
.then(async user_info => { userStore.info = user_info; })
|
||||
.then(async () => {
|
||||
if (!userStore.avatar && userStore.info.avatar) {
|
||||
await api.resourcesAvatar(userStore.info.avatar)
|
||||
.then(async res => { userStore.avatar = res.data; })
|
||||
await resources.avatars(userStore.info.avatar)
|
||||
.then(async avatar => { userStore.avatar = avatar; })
|
||||
}
|
||||
})
|
||||
.then(async () => { return true; })
|
||||
.catch(() => {
|
||||
userStore.clear();
|
||||
return false;
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
const bypass_auth = async (to: any, from: any): any => {
|
||||
if (await is_authorized() && (to.name === "signin" || to.name === "signup")) {
|
||||
async function bypass_auth(to: any, from: any) {
|
||||
if (await check_authorized() && (to.name === "signin" || to.name === "signup")) {
|
||||
return from;
|
||||
}
|
||||
};
|
||||
|
||||
const required_auth = async (to: any, from: any): any => {
|
||||
if (!await is_authorized()) {
|
||||
return { name: "signin", query: {redirect: to.path } };
|
||||
}
|
||||
};
|
||||
|
||||
const required_admin = async (to: any, from: any): boolean => {
|
||||
const userStore = store.useUser();
|
||||
async function required_auth(to: any, from: any) {
|
||||
if (!await check_authorized()) {
|
||||
return { name: "signin" };
|
||||
}
|
||||
}
|
||||
|
||||
async function required_admin(to: any, from: any) {
|
||||
const userStore = useUserStore();
|
||||
return userStore.current.is_admin;
|
||||
};
|
||||
|
||||
export const check_auth = async () => {
|
||||
const route = useRoute();
|
||||
if (!await is_authorized()) {
|
||||
router.push({ name: "signin", query: {redirect: route.path} });
|
||||
}
|
||||
};
|
||||
|
||||
export const router = createRouter({
|
||||
const router = createRouter({
|
||||
history: createWebHistory(),
|
||||
routes: [
|
||||
{
|
||||
@ -53,33 +46,33 @@ export const router = createRouter({
|
||||
},
|
||||
{
|
||||
path: "/auth/signin", name: "signin", beforeEnter: [bypass_auth],
|
||||
component: () => import("@/views/AuthSignIn.vue")
|
||||
component: () => import("@/views/auth/SignIn.vue")
|
||||
},
|
||||
{
|
||||
path: "/auth/signup", name: "signup", beforeEnter: [bypass_auth],
|
||||
component: () => import("@/views/AuthSignUp.vue")
|
||||
path: "/auth/signup", name: "signup", //beforeEnter: [bypass_auth],
|
||||
component: () => import("@/views/auth/SignUp.vue")
|
||||
},
|
||||
{
|
||||
path: "/user/preferencies", name: "prefs", redirect: { name: "prefs-profile" }, beforeEnter: [required_auth],
|
||||
component: () => import("@/views/UserPrefs.vue"),
|
||||
component: () => import("@/views/user/Preferencies.vue"),
|
||||
children: [
|
||||
{
|
||||
path: "profile", name: "prefs-profile", beforeEnter: [required_auth],
|
||||
component: () => import("@/views/UserPrefsProfile.vue")
|
||||
component: () => import("@/views/user/preferencies/Profile.vue")
|
||||
},
|
||||
{
|
||||
path: "account", name: "prefs-account", beforeEnter: [required_auth],
|
||||
component: () => import("@/views/UserPrefsAccount.vue")
|
||||
component: () => import("@/views/user/preferencies/Account.vue")
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
path: "/:user/repository/:pathMatch(.*)*", name: "repository", beforeEnter: [required_auth],
|
||||
component: () => import("@/views/Repository.vue"),
|
||||
path: "/:user/repository", name: "repository", beforeEnter: [required_auth],
|
||||
component: () => import("@/views/Repository.vue")
|
||||
},
|
||||
{
|
||||
path: "/admin/settings", name: "settings", beforeEnter: [required_auth, required_admin],
|
||||
component: () => import("@/views/AdminSettings.vue")
|
||||
component: () => import("@/views/admin/Settings.vue")
|
||||
},
|
||||
{
|
||||
path: "/:pathMatch(.*)*", name: "not-found", beforeEnter: [bypass_auth],
|
||||
|
@ -1,363 +0,0 @@
|
||||
import { defineStore } from "pinia";
|
||||
import { ref, type Ref } from "vue";
|
||||
import { useRoute } from "vue-router";
|
||||
import axios, { CancelToken } from "axios";
|
||||
|
||||
import { schemas, types, api, router, check_auth } from "@";
|
||||
|
||||
export const useUser = defineStore("user", () => {
|
||||
const info: Ref<schemas.UserInfoSchema | null> = ref(null);
|
||||
const avatar: Ref<string | ArrayBuffer | null> = ref(null);
|
||||
const isAccessTokenAlive: Ref<boolean> = ref(false);
|
||||
const isRefreshTokenAlive: Ref<boolean> = ref(false);
|
||||
const _accessTokenTimer: object | null = ref(null);
|
||||
const _refreshTokenTimer: object | null = ref(null);
|
||||
const _route = useRoute();
|
||||
|
||||
const clear = () => {
|
||||
info.value = null;
|
||||
avatar.value = null;
|
||||
isAccessTokenAlive.value = false;
|
||||
isRefreshTokenAlive.value = false;
|
||||
_accessTokenTimer.value = null;
|
||||
_refreshTokenTimer.value = null;
|
||||
};
|
||||
|
||||
const refreshInfo = async (error?: Ref<object> | null = null) => {
|
||||
await api.userInfo()
|
||||
.then(async res => {
|
||||
info.value = res.data;
|
||||
|
||||
})
|
||||
.then(async () => {
|
||||
if (!avatar.value && info.value.avatar) {
|
||||
await api.resourcesAvatar(info.value.avatar)
|
||||
.then(async res => { avatar.value = res.data; })
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
if (error) {
|
||||
error.value = err;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const isAuthorized = async (): boolean => {
|
||||
if (!isAccessTokenAlive.value) {
|
||||
await refreshInfo();
|
||||
}
|
||||
if (!info.value) {
|
||||
clear();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
const isAdmin = (): boolean | null => {
|
||||
return info.value?.is_admin;
|
||||
};
|
||||
|
||||
const checkAuthorizationRedirect = async (path: string) => {
|
||||
if (!(await isAuthorized())) {
|
||||
router.push({ name: "signin", query: {redirect: path}});
|
||||
}
|
||||
};
|
||||
|
||||
return { info, avatar, clear, refreshInfo, isAuthorized, isAdmin, checkAuthorizationRedirect };
|
||||
});
|
||||
|
||||
export const useMisc = defineStore("misc", () => {
|
||||
// preferencies current tab
|
||||
const p_current_tab: Ref<number> = ref(0);
|
||||
|
||||
return { p_current_tab };
|
||||
});
|
||||
|
||||
export const useUploader = defineStore("uploader", () => {
|
||||
const files: Ref<types.UploadFile[]> = ref([]);
|
||||
const _repoStore = useRepository();
|
||||
|
||||
const loadFiles = (event: Event) => {
|
||||
for (let file of event.target.files) {
|
||||
let uploadFile: types.UploadFile ={
|
||||
content: file,
|
||||
status: "idle",
|
||||
progress: 0
|
||||
};
|
||||
|
||||
files.value.push(uploadFile);
|
||||
}
|
||||
};
|
||||
|
||||
const removeFile = (item: types.UploadFile) => {
|
||||
item.status === "transfer" && item.cancel();
|
||||
|
||||
const index = files.value.indexOf(item);
|
||||
files.value.splice(index, 1);
|
||||
};
|
||||
const removeFiles = () => {
|
||||
for (let file of files.value) {
|
||||
file.status === "transfer" && file.cancel();
|
||||
}
|
||||
files.value = [];
|
||||
};
|
||||
const _uploadFile = async (item: types.UploadFile) => {
|
||||
item.status = "transfer";
|
||||
|
||||
await api.fileCreate({
|
||||
body: {
|
||||
file: item.content,
|
||||
path: "/"
|
||||
},
|
||||
throwOnError: true,
|
||||
onUploadProgress: (progressEvent) => {
|
||||
item.progress = progressEvent.progress;
|
||||
},
|
||||
cancelToken: new CancelToken((c) => {
|
||||
item.cancel = c;
|
||||
})
|
||||
})
|
||||
.then(async () => {
|
||||
item.status = "success";
|
||||
await _repoStore.refreshInfo();
|
||||
})
|
||||
.catch(err => {
|
||||
item.status = "fail";
|
||||
if (err.response?.data) {
|
||||
item.error = err.response.data?.detail || err.response.data;
|
||||
}
|
||||
});
|
||||
};
|
||||
const uploadFile = async (item: types.UploadFile) => {
|
||||
await check_auth();
|
||||
await _uploadFile(item);
|
||||
};
|
||||
const uploadFiles = async () => {
|
||||
await check_auth();
|
||||
let promises = [];
|
||||
|
||||
for (let item of files.value) {
|
||||
if (item.status === "idle" || item.status === "fail") {
|
||||
promises.push(_uploadFile(item));
|
||||
}
|
||||
}
|
||||
|
||||
await axios.all(promises);
|
||||
};
|
||||
|
||||
return { files, loadFiles, removeFile, removeFiles, uploadFile, uploadFiles };
|
||||
});
|
||||
|
||||
export const useRepository = defineStore("repository", () => {
|
||||
const info: Ref<api_types.RepositoryInfo> = ref(null);
|
||||
const content: Ref<types.RepositoryContent> = ref([]);
|
||||
const isCreated: Ref<boolean> = ref(false);
|
||||
const currentPath: Ref<string> = ref("/");
|
||||
const buffer: Ref<types.RepositoryContent> = ref([]);
|
||||
|
||||
const clear = () => {
|
||||
info.value = null;
|
||||
content.value = [];
|
||||
isCreated.value = false;
|
||||
currentPath.value = "/";
|
||||
};
|
||||
|
||||
const refreshInfo = async (path: string | null = null, error?: Ref<object> | null) => {
|
||||
if (path) {
|
||||
currentPath.value = path;
|
||||
}
|
||||
|
||||
await api.repositoryInfo({ throwOnError: true })
|
||||
.then(async res => {
|
||||
isCreated.value = true;
|
||||
info.value = res.data;
|
||||
})
|
||||
.then(async () => {
|
||||
await refreshContent(error);
|
||||
})
|
||||
.catch(err => {
|
||||
if (err.response.status === 404) {
|
||||
clear();
|
||||
}
|
||||
if (error) {
|
||||
error.value = err;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const _processContent = (_content: api_types.RepositoryContent | api_types.DirectoryContent) => {
|
||||
content.value = [];
|
||||
const item_meta: types.RepositoryItemMeta = {
|
||||
selected: false,
|
||||
clickCount: 0,
|
||||
clickTimer: null,
|
||||
dragOvered: false
|
||||
};
|
||||
|
||||
for (let directory of _content.directories) {
|
||||
content.value.push({ info: directory, meta: { ...item_meta }, type: "directory" });
|
||||
}
|
||||
|
||||
for (let file of _content.files) {
|
||||
content.value.push({ info: file, meta: { ...item_meta }, type: "file" });
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
const refreshContent = async (error?: Ref<object> | null = null) => {
|
||||
let promise = null;
|
||||
|
||||
if (isRoot()) {
|
||||
promise = api.repositoryContent({ throwOnError: true });
|
||||
} else {
|
||||
promise = api.directoryContent({ query: { path: currentPath.value }, throwOnError: true });
|
||||
}
|
||||
|
||||
await promise.then(async res => {
|
||||
_processContent(res.data);
|
||||
})
|
||||
.catch(err => {
|
||||
if (error) {
|
||||
error.value = err;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const create = async () => {
|
||||
await api.repositoryCreate({ throwOnError: true })
|
||||
.catch(err => {
|
||||
if (error) {
|
||||
error.value = err;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const sizePercent = (): number => {
|
||||
return Math.round(info.value.used / info.value.capacity * 100);
|
||||
};
|
||||
|
||||
const isRoot = (): boolean => {
|
||||
return currentPath.value === "/";
|
||||
};
|
||||
|
||||
const changeDirectory = (current_path: string, directory: api_types.DirectoryInfo) => {
|
||||
router.push({ path: [current_path, directory.name].join("/") });
|
||||
};
|
||||
|
||||
const previousDirectory = (current_path: string) => {
|
||||
let path = current_path.split("/");
|
||||
path.splice(path.length - 1, 1);
|
||||
router.push({ path: path.join("/") });
|
||||
};
|
||||
|
||||
const makeDirectory = async (name: string, error?: Ref<object> | null = null) => {
|
||||
if (name === "") {
|
||||
return;
|
||||
}
|
||||
let path = currentPath.value;
|
||||
if (isRoot()) {
|
||||
path = path + name;
|
||||
} else {
|
||||
path = [path, name].join("/");
|
||||
}
|
||||
await api.directoryCreate({ body: { path: path }, throwOnError: true })
|
||||
.then(async () => {
|
||||
await refreshInfo(currentPath.value, error);
|
||||
})
|
||||
.catch(err => {
|
||||
if (error) {
|
||||
error.value = err;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const deleteItem = async (item: types.RepositoryItemInfo, error?: Ref<object> | null = null) => {
|
||||
let query = { query: { path: item.info.path }, throwOnError: true };
|
||||
let promise = null;
|
||||
|
||||
if (item.type === "directory") {
|
||||
promise = api.directoryRemove(query);
|
||||
}
|
||||
if (item.type === "file") {
|
||||
promise = api.fileRemove(query);
|
||||
}
|
||||
|
||||
await promise.then(async () => {
|
||||
await refreshInfo(null, error);
|
||||
})
|
||||
.catch(err => {
|
||||
if (error) {
|
||||
error.value = err;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const deleteSelectedItems = async (error?: Ref<object> | null = null) => {
|
||||
let items = content.value.filter((item) => item.meta.selected);
|
||||
let promises = [];
|
||||
|
||||
items.forEach((item) => {
|
||||
let query = { query: { path: item.info.path }, throwOnError: true };
|
||||
let callback = async () => {
|
||||
await refreshInfo(null, error);
|
||||
};
|
||||
if (item.type === "directory") {
|
||||
promises.push(api.directoryRemove(query).then(callback));
|
||||
}
|
||||
if (item.type === "file") {
|
||||
promises.push(api.fileRemove(query).then(callback));
|
||||
}
|
||||
});
|
||||
|
||||
await axios.all(promises);
|
||||
};
|
||||
|
||||
const moveItems = async (items: types.RepositoryContent, path: string, error?: Ref<object | null> = null) => {
|
||||
let promises = [];
|
||||
|
||||
items.forEach((item) => {
|
||||
let query = { body: { path: item.info.path, target: path }, throwOnError: true };
|
||||
let callback = async () => {
|
||||
await refreshInfo(null, error);
|
||||
};
|
||||
if (item.type === "directory") {
|
||||
promises.push(api.directoryMove(query).then(callback));
|
||||
}
|
||||
if (item.type === "file") {
|
||||
promises.push(api.fileMove(query).then(callback));
|
||||
}
|
||||
});
|
||||
|
||||
await axios.all(promises);
|
||||
};
|
||||
|
||||
const copyBuffer = (items: types.RepositoryContent) => {
|
||||
buffer.value = items;
|
||||
};
|
||||
|
||||
const copyBufferSelected = () => {
|
||||
let items = content.value.filter((item) => item.meta.selected);
|
||||
copyBuffer(items);
|
||||
};
|
||||
|
||||
const copyItems = async (items: types.RepositoryContent, path: string, error?: Ref<object | null> = null) => {
|
||||
let promises = [];
|
||||
|
||||
items.forEach((item) => {
|
||||
let query = { body: { path: item.info.path, target: path }, throwOnError: true };
|
||||
let callback = async () => {
|
||||
await refreshInfo(null, error);
|
||||
};
|
||||
if (item.type === "directory") {
|
||||
promises.push(api.directoryCopy(query).then(callback));
|
||||
}
|
||||
if (item.type === "file") {
|
||||
promises.push(api.fileCopy(query).then(callback));
|
||||
}
|
||||
});
|
||||
|
||||
await axios.all(promises);
|
||||
};
|
||||
|
||||
return { info, content, currentPath, buffer, isCreated, clear, refreshInfo, refreshContent, create, sizePercent, isRoot, changeDirectory, previousDirectory, makeDirectory, deleteItem, deleteSelectedItems, moveItems, copyBuffer, copyBufferSelected, copyItems };
|
||||
});
|
24
workspaces/frontend/src/stores/index.ts
Normal file
@ -0,0 +1,24 @@
|
||||
import { defineStore } from "pinia";
|
||||
import { ref, type Ref } from "vue";
|
||||
|
||||
import { user } from "@/api";
|
||||
import { resources } from "@";
|
||||
|
||||
export const useUserStore = defineStore("user", () => {
|
||||
const info: Ref<user.UserInfo | null> = ref(null);
|
||||
const avatar: Ref<resources.Image | null> = ref(null);
|
||||
|
||||
function clear() {
|
||||
info.value = null;
|
||||
avatar.value = null;
|
||||
}
|
||||
|
||||
return { info, avatar, clear };
|
||||
});
|
||||
|
||||
export const useMiscStore = defineStore("misc", () => {
|
||||
// preferencies current tab
|
||||
const p_current_tab: Ref<number> = ref(0);
|
||||
|
||||
return { p_current_tab };
|
||||
});
|
@ -1,30 +0,0 @@
|
||||
import { api_types } from "@";
|
||||
import { CancelToken } from "axios";
|
||||
|
||||
export type Error = object;
|
||||
|
||||
export type RepositoryItemMeta = {
|
||||
selected: bool;
|
||||
clickCount: number;
|
||||
clickTimer: (object | null);
|
||||
};
|
||||
|
||||
export type RepositoryItemType = "directory" | "file";
|
||||
|
||||
export type RepositoryItemInfo = {
|
||||
info: (api_types.DirectoryInfo | api_types.FileInfo);
|
||||
meta: RepositoryItemMeta;
|
||||
type: RepositoryItemType;
|
||||
};
|
||||
|
||||
export type RepositoryContent = RepositoryItemInfo[];
|
||||
|
||||
export type UploadFileStatus = "success" | "fail" | "transfer" | "idle";
|
||||
|
||||
export type UploadFile = {
|
||||
content: File;
|
||||
status: UploadFileStatus;
|
||||
progress: number;
|
||||
cancel: CancelToken | null;
|
||||
error: string | null;
|
||||
};
|
@ -1,25 +1,30 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from "vue";
|
||||
import { router, api, store } from "@";
|
||||
import NavBar from "@/components/NavBar.vue";
|
||||
import DropdownMenu from "@/components/DropdownMenu.vue";
|
||||
|
||||
const userStore = store.useUser();
|
||||
import { ref, onMounted } from "vue";
|
||||
|
||||
import router from "@/router";
|
||||
import { user, auth } from "@/api";
|
||||
import { resources } from "@";
|
||||
import { useUserStore } from "@/stores";
|
||||
|
||||
const userStore = useUserStore();
|
||||
const error = ref(null);
|
||||
|
||||
const signout = async () => {
|
||||
await api.authSignout()
|
||||
async function signout() {
|
||||
await auth.signout()
|
||||
.then(async () => {
|
||||
userStore.clear();
|
||||
router.push({ path: "/" });
|
||||
})
|
||||
.catch(err => { error.value = err; });
|
||||
};
|
||||
.catch(error => { error.value = error; });
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex-grow sm:pb-20">
|
||||
<div class="flex-grow pb-20">
|
||||
<NavBar>
|
||||
<template #left>
|
||||
<RouterLink class="link-button" to="/">Home</RouterLink>
|
||||
@ -57,12 +62,13 @@ const signout = async () => {
|
||||
</template>
|
||||
</NavBar>
|
||||
|
||||
<main class="w-full max-w-[1000px] ml-auto mr-auto">
|
||||
<main class="w-[1000px] ml-auto mr-auto pt-5 pb-5">
|
||||
<slot></slot>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="relative overflow-hidden h-full ">
|
||||
</div>
|
||||
<footer class="flex justify-between pb-2 pt-2 pl-5 pr-5 bg-ctp-mantle">
|
||||
<a href="/">Made with glove by Elnafo, 2024</a>
|
||||
<div>
|
||||
|
@ -4,106 +4,157 @@ import Error from "@/components/Error.vue";
|
||||
import IconDirectory from "@/components/icons/IconDirectory.vue";
|
||||
import IconFile from "@/components/icons/IconFile.vue";
|
||||
import ContextMenu from "@/components/ContextMenu.vue";
|
||||
import DragItem from "@/components/DragItem.vue";
|
||||
import DropItem from "@/components/DropItem.vue";
|
||||
import RepositoryBrowser from "@/components/RepositoryBrowser.vue";
|
||||
import Modal from "@/components/Modal.vue";
|
||||
import Uploader from "@/components/Uploader.vue";
|
||||
|
||||
import { ref, shallowRef, onMounted, watch } from "vue";
|
||||
import { ref, onMounted, watch } from "vue";
|
||||
import { onBeforeRouteUpdate, useRoute } from "vue-router"
|
||||
import { filesize } from "filesize";
|
||||
import { router, api, store, types, api_types, check_auth } from "@";
|
||||
|
||||
import { repository } from "@/api";
|
||||
import { useUserStore } from "@/stores";
|
||||
import router from "@/router";
|
||||
|
||||
const route = useRoute();
|
||||
const userStore = store.useUser();
|
||||
const repoStore = store.useRepository();
|
||||
const error = ref<object>(null);
|
||||
const userStore = useUserStore();
|
||||
const error = ref<string>(null);
|
||||
|
||||
const repository_info = ref(null);
|
||||
const is_created = ref(null);
|
||||
const repository_content = ref(null);
|
||||
|
||||
onMounted(async () => {
|
||||
let path = route.params.pathMatch ? "/" + route.params.pathMatch.join("/") : "/";
|
||||
await repoStore.refreshInfo(path, error);
|
||||
console.log(route);
|
||||
await repository.info()
|
||||
.then(async _repository_info => {
|
||||
is_created.value = true;
|
||||
repository_info.value = _repository_info;
|
||||
})
|
||||
.catch(err => {
|
||||
is_created.value = false;
|
||||
});
|
||||
|
||||
onBeforeRouteUpdate(async (to, from) => {
|
||||
let path = to.params.pathMatch ? "/" + to.params.pathMatch.join("/") : "/";
|
||||
userStore.checkAuthorizationRedirect(to.path);
|
||||
await repoStore.refreshInfo(path, error);
|
||||
console.log("route", route);
|
||||
console.log("store", repoStore);
|
||||
if (is_created.value) {
|
||||
await repository.content()
|
||||
.then(async _repository_content => {
|
||||
repository_content.value = _repository_content;
|
||||
})
|
||||
.catch(err => {
|
||||
error.value = err;
|
||||
})
|
||||
}
|
||||
});
|
||||
|
||||
async function create_repository() {
|
||||
await repository.create()
|
||||
.then(async () => {
|
||||
await repository.info()
|
||||
.then(async _repository_info => {
|
||||
repository_info.value = _repository_info;
|
||||
})
|
||||
.catch(err => {
|
||||
error.value = err;
|
||||
});
|
||||
|
||||
const isUploaderOpen = ref(false);
|
||||
is_created.value = true;
|
||||
})
|
||||
.catch(err => {
|
||||
error.value = err;
|
||||
})
|
||||
}
|
||||
|
||||
const openUploader = () => {
|
||||
isUploaderOpen.value = true;
|
||||
};
|
||||
const closeUploader = () => {
|
||||
isUploaderOpen.value = false;
|
||||
function round_size(size: number, mesure: string) {
|
||||
if (mesure == "GB") {
|
||||
return size / 8 / 1024 / 1024;
|
||||
} else if (mesure == "MB") {
|
||||
return size / 8 / 1024;
|
||||
}
|
||||
}
|
||||
|
||||
function size_procent() {
|
||||
return Math.round(repository_info.value.used / repository_info.value.capacity) * 100;
|
||||
}
|
||||
|
||||
function format_creation_time(timestamp: number): string {
|
||||
const date = new Date(timestamp * 1000);
|
||||
|
||||
return `${date.getDate()}-${date.getMonth()}-${date.getFullYear()} ${date.getHours()}:${date.getMinutes()}`;
|
||||
}
|
||||
|
||||
const showMenu = ref(false);
|
||||
const ctxMenuPosX = ref(0);
|
||||
const ctxMenuPosY = ref(0);
|
||||
const targetRow = ref({});
|
||||
const contextMenuActions = ref([
|
||||
{ label: 'Edit', action: 'edit' },
|
||||
{ label: 'Delete', action: 'delete' },
|
||||
]);
|
||||
|
||||
const showContextMenu = (event, user) => {
|
||||
event.preventDefault();
|
||||
showMenu.value = true;
|
||||
targetRow.value = user;
|
||||
ctxMenuPosX.value = event.clientX;
|
||||
ctxMenuPosY.value = event.clientY;
|
||||
};
|
||||
|
||||
const isMakeDirectoryOpen = ref(false);
|
||||
const makeDirectoryName = ref(null);
|
||||
|
||||
const openMakeDirectory = () => {
|
||||
isMakeDirectoryOpen.value = true;
|
||||
const closeContextMenu = () => {
|
||||
showMenu.value = false;
|
||||
};
|
||||
|
||||
const closeMakeDirectory = () => {
|
||||
isMakeDirectoryOpen.value = false;
|
||||
makeDirectoryName.value = null;
|
||||
};
|
||||
function handleActionClick(action) {
|
||||
console.log(action);
|
||||
console.log(targetRow.value);
|
||||
}
|
||||
|
||||
const makeDirectory = async () => {
|
||||
await repoStore.makeDirectory(makeDirectoryName.value.value, error);
|
||||
closeMakeDirectory();
|
||||
};
|
||||
function close_menu() {
|
||||
showMenu.value = false;
|
||||
}
|
||||
|
||||
// repoStore.makeDirectory('test', error)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Base>
|
||||
<Error :value="error" />
|
||||
|
||||
<Uploader v-if="repoStore.isCreated" :isOpen="isUploaderOpen" @close-uploader="closeUploader()" />
|
||||
|
||||
<Modal :isOpen="isMakeDirectoryOpen" @close-modal="closeMakeDirectory()">
|
||||
<template #header>
|
||||
<h2>Create directory</h2>
|
||||
</template>
|
||||
<template #content>
|
||||
<div class="flex mb-8">
|
||||
<input ref="makeDirectoryName" class="input mr-2">
|
||||
<button class="button" @click="makeDirectory()">Create</button>
|
||||
<Error v-if="error">{{ error }}</Error>
|
||||
<section v-if="is_created">
|
||||
<div class="flex items-center">
|
||||
<div class="w-full rounded-full h-2.5 bg-ctp-surface0">
|
||||
<div class="bg-ctp-lavender h-2.5 rounded-full" :style="{ width: size_procent() + '%' }"></div>
|
||||
</div>
|
||||
</template>
|
||||
</Modal>
|
||||
<section v-if="repoStore.isCreated">
|
||||
<div
|
||||
class="flex items-center justify-center fixed bottom-4 bg-ctp-surface0 sm:bg-ctp-crust opacity-90 sm:opacity-100 w-full sm:w-auto sm:relative sm:bottom-auto sm:right-auto my-2">
|
||||
<span class="min-w-48 text-center">{{ filesize(repoStore.info.used)
|
||||
}}
|
||||
/ {{ filesize(repoStore.info.capacity)
|
||||
}}</span>
|
||||
<div class="hidden sm:inline ml-4 mr-4 w-full rounded-full h-2.5 bg-ctp-surface0">
|
||||
<div class="bg-ctp-lavender h-2.5 rounded-full" :style="{ width: repoStore.sizePercent() + '%' }"></div>
|
||||
<span class="min-w-48 text-center">{{ round_size(repository_info.used, "MB").toFixed(2) }} MB / {{
|
||||
round_size(repository_info.capacity, "GB") }} GB</span>
|
||||
|
||||
</div>
|
||||
|
||||
<button @click="openMakeDirectory" class="button justify-end mr-2">Create</button>
|
||||
<button @click="openUploader" class="button justify-end">Upload</button>
|
||||
</div>
|
||||
|
||||
<RepositoryBrowser v-if="!error" v-model="repoStore.content" />
|
||||
<table v-if="repository_content" class="table-auto w-full mt-8 mb-8 pl-8 pr-8 text-ctp-text">
|
||||
<tbody>
|
||||
<tr class="hover:bg-ctp-surface0" v-for="directory in repository_content.directories"
|
||||
@contextmenu.prevent="showContextMenu($event, directory)">
|
||||
<td>
|
||||
<IconDirectory />
|
||||
<div class="inline ml-4">{{ directory.name }}</div>
|
||||
</td>
|
||||
<td class="text-right">-</td>
|
||||
<td class="text-right w-48">
|
||||
{{ format_creation_time(directory.created) }}
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="hover:bg-ctp-surface0" v-for="file in repository_content.files">
|
||||
<td>
|
||||
<IconFile />
|
||||
<div class="inline ml-4">{{ file.name }}</div>
|
||||
</td>
|
||||
<td class="text-right">{{ round_size(file.size, "MB").toFixed(2) }} MB</td>
|
||||
<td class="text-right w-48">
|
||||
{{ format_creation_time(file.created) }}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<ContextMenu v-if="showMenu" :actions="contextMenuActions" @action-clicked="handleActionClick" :x="ctxMenuPosX"
|
||||
:y="ctxMenuPosY" v-click-outside="close_menu" />
|
||||
</section>
|
||||
<section v-else>
|
||||
<p>It looks like you don't have a repository yet...</p>
|
||||
<div class="flex justify-center mt-8">
|
||||
<button @click="repoStore.create(error)" class="button">+ Create repository</button>
|
||||
<button @click="create_repository" class="button">+ Create repository</button>
|
||||
</div>
|
||||
</section>
|
||||
</Base>
|
||||
|
@ -1,30 +1,31 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from "vue";
|
||||
import { router, api, schemas, store } from "@";
|
||||
import { useRoute } from "vue-router";
|
||||
import Base from "@/views/Base.vue";
|
||||
import Error from "@/components/Error.vue";
|
||||
|
||||
import { ref, onMounted } from "vue";
|
||||
|
||||
import router from "@/router";
|
||||
import { api } from "@";
|
||||
import { useUserStore } from "@/stores";
|
||||
|
||||
const email_or_username = defineModel("email_or_username");
|
||||
const password = defineModel("password");
|
||||
|
||||
const route = useRoute();
|
||||
const userStore = store.useUser();
|
||||
const userStore = useUserStore();
|
||||
const error = ref(null);
|
||||
|
||||
onMounted(async () => {
|
||||
if (userStore.current) {
|
||||
router.push({ name: "home" });
|
||||
router.replace({ path: "/" });
|
||||
}
|
||||
});
|
||||
|
||||
const signin = async () => {
|
||||
async function signin() {
|
||||
if (!email_or_username.value || !password.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
const body: schemas.UserCredentialsSchema = {
|
||||
const body: user.UserCredentials = {
|
||||
name: null,
|
||||
password: password.value,
|
||||
email: null
|
||||
@ -37,12 +38,12 @@ const signin = async () => {
|
||||
body.name = email_or_username.value;
|
||||
}
|
||||
|
||||
await api.authSignin({ body: body, throwOnError: true })
|
||||
await api.auth.signin(body)
|
||||
.then(async () => {
|
||||
//userStore.info = user_info;
|
||||
router.push(route.query.redirect ? { path: route.query.redirect } : { name: "home" });
|
||||
router.push({ path: "/" });
|
||||
})
|
||||
.catch(err => { error.value = err; });
|
||||
.catch(err => { error.value = err.message; });
|
||||
};
|
||||
</script>
|
||||
|
||||
@ -64,7 +65,7 @@ const signin = async () => {
|
||||
<button @click="$router.push('/auth/signup')" class="button">Sign Up</button>
|
||||
</div>
|
||||
</form>
|
||||
<Error :value="error" />
|
||||
<Error v-if="error">{{ error }}</Error>
|
||||
</div>
|
||||
</Base>
|
||||
</template>
|
@ -2,30 +2,24 @@
|
||||
import Base from "@/views/Base.vue";
|
||||
import Error from "@/components/Error.vue";
|
||||
|
||||
import { ref, onMounted } from "vue";
|
||||
import { router, api, schemas, store } from "@";
|
||||
import { ref } from "vue";
|
||||
|
||||
import router from "@/router";
|
||||
import { api } from "@";
|
||||
|
||||
const login = defineModel("login");
|
||||
const email = defineModel("email");
|
||||
const password = defineModel("password");
|
||||
|
||||
const userStore = store.useUser();
|
||||
const error = ref(null);
|
||||
|
||||
onMounted(async () => {
|
||||
if (userStore.current) {
|
||||
router.replace({ name: "home" });
|
||||
}
|
||||
});
|
||||
|
||||
const signup = async () => {
|
||||
async function signup() {
|
||||
if (!login.value || !email.value || !password.value) {
|
||||
return;
|
||||
}
|
||||
await api.authSignup({ body: { name: login.value, password: password.value, email: email.value }, throwOnError: true })
|
||||
.then(async () => { router.replace({ name: "signin" }); })
|
||||
.catch(err => { error.value = err; });
|
||||
await api.auth.signup({ name: login.value, password: password.value, email: email.value })
|
||||
.then(async user => { router.push({ path: "/auth/signin" }); })
|
||||
.catch(err => { error.value = err.message; });
|
||||
};
|
||||
</script>
|
||||
|
||||
@ -50,7 +44,7 @@ const signup = async () => {
|
||||
<button @click="signup" class="button">Sign Up</button>
|
||||
</div>
|
||||
</form>
|
||||
<Error :value="error" />
|
||||
<Error v-if="error">{{ error }}</Error>
|
||||
</div>
|
||||
</Base>
|
||||
</template>
|
@ -5,7 +5,7 @@ import Error from "@/components/error/Error.vue";
|
||||
import { ref, onMounted, watch, getCurrentInstance } from "vue";
|
||||
import { onBeforeRouteUpdate, useRoute } from "vue-router"
|
||||
|
||||
import { api } from "@";
|
||||
import { user } from "@/api";
|
||||
import { useUserStore } from "@/stores";
|
||||
|
||||
const route = useRoute();
|
||||
@ -16,11 +16,11 @@ const person = ref<user.User>(null);
|
||||
const avatar = ref<user.Image>(null);
|
||||
|
||||
async function profile(login: string) {
|
||||
await api.user.userInfo()
|
||||
await user.profile(login)
|
||||
.then(async user => { person.value = user; })
|
||||
.then(async () => {
|
||||
if (person.value.avatar?.length) {
|
||||
await api.resources.resourcesAvatars(userStore.info.avatar)
|
||||
await user.get_avatar(person.value.avatar)
|
||||
.then(async _avatar => { avatar.value = _avatar; })
|
||||
}
|
||||
})
|
@ -4,7 +4,7 @@ import Base from "@/views/Base.vue";
|
||||
import { ref, onMounted, watch, getCurrentInstance } from "vue";
|
||||
|
||||
import router from "@/router";
|
||||
import { api } from "@";
|
||||
import { user } from "@/api";
|
||||
import { useUserStore, useMiscStore } from "@/stores";
|
||||
|
||||
const error = ref(null);
|
||||
@ -22,9 +22,7 @@ const avatar_preview = ref(null);
|
||||
onMounted(async () => {
|
||||
miscStore.p_current_tab = 0;
|
||||
|
||||
login.value = userStore.info.name;
|
||||
name.value = userStore.info.full_name;
|
||||
email.value = userStore.info.email;
|
||||
login.value = userStore.current.login;
|
||||
});
|
||||
|
||||
function uploadFile(event) {
|