Compare commits
	
		
			No commits in common. "01a6481d6223c73e9b232853629f1cd3e6b41b70" and "7bedf7c13fea66fde709bbdaad0475edb7f1f609" have entirely different histories.
		
	
	
		
			01a6481d62
			...
			7bedf7c13f
		
	
		
@ -1 +0,0 @@
 | 
				
			|||||||
VUE_APP_ROOT_WEBDAV="http://127.0.0.1:8080"
 | 
					 | 
				
			||||||
@ -1,64 +0,0 @@
 | 
				
			|||||||
version: '3'
 | 
					 | 
				
			||||||
services:
 | 
					 | 
				
			||||||
  webdav:
 | 
					 | 
				
			||||||
    build:
 | 
					 | 
				
			||||||
      context: ./docker/webdav
 | 
					 | 
				
			||||||
      dockerfile: ./Dockerfile
 | 
					 | 
				
			||||||
    restart: always
 | 
					 | 
				
			||||||
    volumes:
 | 
					 | 
				
			||||||
      - ./nginx.conf:/etc/nginx/nginx.conf
 | 
					 | 
				
			||||||
      - ./htpasswd:/etc/nginx/htpasswd
 | 
					 | 
				
			||||||
      - ./dist/:/var/www/html/
 | 
					 | 
				
			||||||
      - ./media/:/media/
 | 
					 | 
				
			||||||
    ports:
 | 
					 | 
				
			||||||
        - "8080:8080"
 | 
					 | 
				
			||||||
    links:
 | 
					 | 
				
			||||||
      - ldap
 | 
					 | 
				
			||||||
      - nginx-ldap-auth
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  nginx-ldap-auth:
 | 
					 | 
				
			||||||
    image: bitnami/nginx-ldap-auth-daemon
 | 
					 | 
				
			||||||
    restart: always
 | 
					 | 
				
			||||||
    links:
 | 
					 | 
				
			||||||
      - ldap
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  ldap:
 | 
					 | 
				
			||||||
    image: mwaeckerlin/openldap
 | 
					 | 
				
			||||||
    ports:
 | 
					 | 
				
			||||||
      - "389:389"
 | 
					 | 
				
			||||||
    volumes:
 | 
					 | 
				
			||||||
      - ./docker./ldap/:/var/restore/
 | 
					 | 
				
			||||||
    environment:
 | 
					 | 
				
			||||||
      DOMAIN: example.com
 | 
					 | 
				
			||||||
      DEBUG: 256
 | 
					 | 
				
			||||||
      ACCESS_RULES: |
 | 
					 | 
				
			||||||
        access to attrs=userPassword
 | 
					 | 
				
			||||||
            by anonymous auth
 | 
					 | 
				
			||||||
            by self write
 | 
					 | 
				
			||||||
            by * none
 | 
					 | 
				
			||||||
        access to *
 | 
					 | 
				
			||||||
            by * read
 | 
					 | 
				
			||||||
    restart: always
 | 
					 | 
				
			||||||
    healthcheck:
 | 
					 | 
				
			||||||
      test: "ldapsearch -x -b dc=example,dc=com cn > /dev/null"
 | 
					 | 
				
			||||||
      interval: 30s
 | 
					 | 
				
			||||||
      retries: 2
 | 
					 | 
				
			||||||
      timeout: 2s
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  ldap-ui:
 | 
					 | 
				
			||||||
    image: dnknth/ldap-ui
 | 
					 | 
				
			||||||
    ports:
 | 
					 | 
				
			||||||
      - "5000:5000"
 | 
					 | 
				
			||||||
    links:
 | 
					 | 
				
			||||||
      - ldap
 | 
					 | 
				
			||||||
    environment:
 | 
					 | 
				
			||||||
      LDAP_URL: "ldap://ldap/"
 | 
					 | 
				
			||||||
      BASE_DN: "dc=example,dc=com"
 | 
					 | 
				
			||||||
      BIND_DN: "cn=admin,dc=example,dc=com"
 | 
					 | 
				
			||||||
      BIND_PASSWORD: "admin"
 | 
					 | 
				
			||||||
    restart: always
 | 
					 | 
				
			||||||
    healthcheck:
 | 
					 | 
				
			||||||
      test: "wget -q -O /dev/null http://localhost:5000"
 | 
					 | 
				
			||||||
      interval: 30s
 | 
					 | 
				
			||||||
      retries: 2
 | 
					 | 
				
			||||||
      timeout: 2s
 | 
					 | 
				
			||||||
@ -1,32 +0,0 @@
 | 
				
			|||||||
# Entry 1: dc=example,dc=com
 | 
					 | 
				
			||||||
dn: dc=example,dc=com
 | 
					 | 
				
			||||||
dc: example
 | 
					 | 
				
			||||||
o: Example
 | 
					 | 
				
			||||||
objectclass: dcObject
 | 
					 | 
				
			||||||
objectclass: top
 | 
					 | 
				
			||||||
objectclass: organization
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
dn: cn=admin,dc=example,dc=com
 | 
					 | 
				
			||||||
cn: admin
 | 
					 | 
				
			||||||
uid: admin
 | 
					 | 
				
			||||||
userpassword: admin
 | 
					 | 
				
			||||||
objectclass: organizationalRole
 | 
					 | 
				
			||||||
objectclass: simpleSecurityObject
 | 
					 | 
				
			||||||
objectclass: uidObject
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# Entry 2: ou=users,dc=example,dc=com
 | 
					 | 
				
			||||||
dn: ou=users,dc=example,dc=com
 | 
					 | 
				
			||||||
objectclass: organizationalUnit
 | 
					 | 
				
			||||||
objectclass: top
 | 
					 | 
				
			||||||
ou: users
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# Entry 3: cn=Test User,ou=users,dc=example,dc=com
 | 
					 | 
				
			||||||
dn: cn=Test User,ou=users,dc=example,dc=com
 | 
					 | 
				
			||||||
cn: Test User
 | 
					 | 
				
			||||||
givenname: Test User
 | 
					 | 
				
			||||||
objectclass: inetOrgPerson
 | 
					 | 
				
			||||||
objectclass: uidObject
 | 
					 | 
				
			||||||
objectclass: simpleSecurityObject
 | 
					 | 
				
			||||||
sn: User
 | 
					 | 
				
			||||||
uid: test
 | 
					 | 
				
			||||||
userPassword: test
 | 
					 | 
				
			||||||
@ -1,29 +0,0 @@
 | 
				
			|||||||
FROM alpine
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
RUN apk update && \
 | 
					 | 
				
			||||||
    apk add --no-cache pcre libxml2 libxslt && \
 | 
					 | 
				
			||||||
    apk add --no-cache apache2-utils && \
 | 
					 | 
				
			||||||
    apk add --no-cache gcc make libc-dev pcre-dev zlib-dev libxml2-dev libxslt-dev && \
 | 
					 | 
				
			||||||
    cd /tmp && \
 | 
					 | 
				
			||||||
    wget https://github.com/nginx/nginx/archive/master.zip -O nginx.zip && \
 | 
					 | 
				
			||||||
    unzip nginx.zip && \
 | 
					 | 
				
			||||||
    wget https://github.com/arut/nginx-dav-ext-module/archive/master.zip -O dav-ext-module.zip && \
 | 
					 | 
				
			||||||
    unzip dav-ext-module.zip && \
 | 
					 | 
				
			||||||
    cd nginx-master && \
 | 
					 | 
				
			||||||
    ./auto/configure --prefix=/opt/nginx --with-http_dav_module --with-http_auth_request_module --add-module=/tmp/nginx-dav-ext-module-master && \
 | 
					 | 
				
			||||||
    make && make install && \
 | 
					 | 
				
			||||||
    cd /root && \
 | 
					 | 
				
			||||||
    apk del gcc make libc-dev pcre-dev zlib-dev libxml2-dev libxslt-dev && \
 | 
					 | 
				
			||||||
    rm -rf /var/cache/apk/* && \
 | 
					 | 
				
			||||||
    rm -rf /tmp/*
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
RUN mkdir -p /tmp/nginx/client-body
 | 
					 | 
				
			||||||
COPY nginx.conf /opt/nginx/conf/nginx.conf
 | 
					 | 
				
			||||||
COPY htpasswd /opt/nginx/htpasswd
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#RUN apk update && \
 | 
					 | 
				
			||||||
#    apk add nginx nginx-extras
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
EXPOSE 8080
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
CMD /bin/echo "starting nginx webdav server" && /opt/nginx/sbin/nginx -g "daemon off;"
 | 
					 | 
				
			||||||
@ -1 +0,0 @@
 | 
				
			|||||||
test2:$apr1$zSKjrvfS$r6itS4PfhS2QicesM70Ks/
 | 
					 | 
				
			||||||
@ -1,97 +0,0 @@
 | 
				
			|||||||
worker_processes  auto;
 | 
					 | 
				
			||||||
worker_cpu_affinity auto;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#pid        /var/run/nginx.pid;
 | 
					 | 
				
			||||||
error_log  /dev/stderr warn;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
events {
 | 
					 | 
				
			||||||
    worker_connections  1024;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
http {
 | 
					 | 
				
			||||||
#   rewrite_log on;
 | 
					 | 
				
			||||||
    include            mime.types;
 | 
					 | 
				
			||||||
    default_type       application/json;
 | 
					 | 
				
			||||||
    access_log         /dev/stdout;
 | 
					 | 
				
			||||||
    sendfile           on;
 | 
					 | 
				
			||||||
#   tcp_nopush         on;
 | 
					 | 
				
			||||||
    keepalive_timeout  3;
 | 
					 | 
				
			||||||
#   tcp_nodelay        on;
 | 
					 | 
				
			||||||
    gzip               on;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    proxy_cache_path ./cache/ keys_zone=auth_cache:5m;
 | 
					 | 
				
			||||||
    client_max_body_size 1M;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    server {
 | 
					 | 
				
			||||||
        listen 8080 default_server;
 | 
					 | 
				
			||||||
        server_name _;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        absolute_redirect off;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        location / {
 | 
					 | 
				
			||||||
            root /var/www/html;
 | 
					 | 
				
			||||||
            index index.html;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            try_files $uri $uri/ /index.html;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        location = /ldap-auth {
 | 
					 | 
				
			||||||
            internal;
 | 
					 | 
				
			||||||
            proxy_pass_request_body off;
 | 
					 | 
				
			||||||
            client_max_body_size 0; # has to be set even tho the body is not passed
 | 
					 | 
				
			||||||
            proxy_set_header Content-Length "";
 | 
					 | 
				
			||||||
            #proxy_cache auth_cache;
 | 
					 | 
				
			||||||
            #proxy_cache_valid 200 5m;
 | 
					 | 
				
			||||||
            #proxy_cache_key $scheme$proxy_host$request_uri$remote_user;
 | 
					 | 
				
			||||||
            proxy_pass http://nginx-ldap-auth:8888;
 | 
					 | 
				
			||||||
            proxy_set_header X-Ldap-URL "ldap://ldap/";
 | 
					 | 
				
			||||||
            proxy_set_header X-Ldap-Template "(uid=%(username)s)";
 | 
					 | 
				
			||||||
            proxy_set_header X-Ldap-BaseDN "ou=users,dc=example,dc=com";
 | 
					 | 
				
			||||||
            #proxy_set_header X-Ldap-BindDN "cn=test,dc=example,dc=com";
 | 
					 | 
				
			||||||
            #proxy_set_header X-Ldap-BindPass "test";
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        #location ~ ^/api/dav/files/(?<userpath>(\w+))(|(?<filename>/.*))$ {
 | 
					 | 
				
			||||||
        location ~ ^/api/dav/files(?<filename>.*)$ {
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            if ( $request_method = OPTIONS ) {
 | 
					 | 
				
			||||||
                add_header "Access-Control-Allow-Origin" *;
 | 
					 | 
				
			||||||
                add_header "Access-Control-Allow-Methods" *;
 | 
					 | 
				
			||||||
                add_header "Access-Control-Allow-Headers" "Authorization, Origin, X-Requested-With, Content-Type, Accept";
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            if ($remote_user = "") {
 | 
					 | 
				
			||||||
                add_header "WWW-Authenticate" "Basic realm=\"Restricted\"";
 | 
					 | 
				
			||||||
                return 401;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            proxy_set_header X-Auth "nginxauth";
 | 
					 | 
				
			||||||
            proxy_set_header Cookie nginxauth=$cookie_nginxauth;
 | 
					 | 
				
			||||||
            proxy_set_header Authorization $http_authorization;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            auth_request /ldap-auth;
 | 
					 | 
				
			||||||
            auth_request_set $new_cookie $sent_http_set_cookie;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            add_header "Set-Cookie" $new_cookie;
 | 
					 | 
				
			||||||
            add_header "X-Auth" $sent_http_set_cookie;
 | 
					 | 
				
			||||||
            auth_basic "Restricted";
 | 
					 | 
				
			||||||
            #auth_basic_user_file /opt/nginx/htpasswd;
 | 
					 | 
				
			||||||
            satisfy any;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            alias /media/$remote_user$filename;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            client_max_body_size 120G;
 | 
					 | 
				
			||||||
            client_body_temp_path /tmp/nginx/client-body;
 | 
					 | 
				
			||||||
            create_full_put_path on;
 | 
					 | 
				
			||||||
            autoindex on;
 | 
					 | 
				
			||||||
            autoindex_exact_size off;
 | 
					 | 
				
			||||||
            autoindex_localtime on;
 | 
					 | 
				
			||||||
            autoindex_format html;
 | 
					 | 
				
			||||||
            charset utf-8;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            dav_methods PUT DELETE MKCOL COPY MOVE;
 | 
					 | 
				
			||||||
            dav_ext_methods PROPFIND OPTIONS;
 | 
					 | 
				
			||||||
            dav_access user:rw group:rw all:rw;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@ -9,15 +9,11 @@
 | 
				
			|||||||
    "lint": "vue-cli-service lint"
 | 
					    "lint": "vue-cli-service lint"
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  "dependencies": {
 | 
					  "dependencies": {
 | 
				
			||||||
    "bootstrap": "^5.2.0",
 | 
					 | 
				
			||||||
    "bootstrap-darkmode": "^5.0.1",
 | 
					 | 
				
			||||||
    "bootstrap-icons": "^1.9.1",
 | 
					 | 
				
			||||||
    "core-js": "^3.6.5",
 | 
					    "core-js": "^3.6.5",
 | 
				
			||||||
    "pinia": "^2.0.13",
 | 
					 | 
				
			||||||
    "register-service-worker": "^1.7.1",
 | 
					    "register-service-worker": "^1.7.1",
 | 
				
			||||||
    "typescript-is": "^0.19.0",
 | 
					 | 
				
			||||||
    "vue": "^3.0.0",
 | 
					    "vue": "^3.0.0",
 | 
				
			||||||
    "vue-router": "^4.0.0-0",
 | 
					    "vue-router": "^4.0.0-0",
 | 
				
			||||||
 | 
					    "pinia": "^2.0.13",
 | 
				
			||||||
    "webdav": "^4.9.0"
 | 
					    "webdav": "^4.9.0"
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  "devDependencies": {
 | 
					  "devDependencies": {
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										48
									
								
								src/App.vue
									
									
									
									
									
								
							
							
						
						
									
										48
									
								
								src/App.vue
									
									
									
									
									
								
							@ -1,24 +1,30 @@
 | 
				
			|||||||
<template>
 | 
					<template>
 | 
				
			||||||
  <header
 | 
					  <div id="nav">
 | 
				
			||||||
    class="d-flex justify-content-between p-3 bg-darkmode-dark bg-light shadow"
 | 
					    <router-link to="/">Home</router-link> |
 | 
				
			||||||
  >
 | 
					    <router-link to="/about">About</router-link>
 | 
				
			||||||
    <div>
 | 
					  </div>
 | 
				
			||||||
      <b class="mx-2">vuedav</b>
 | 
					  <router-view />
 | 
				
			||||||
      <router-link class="mx-2" to="/files">Files</router-link>
 | 
					 | 
				
			||||||
    </div>
 | 
					 | 
				
			||||||
    <div class="d-flex">
 | 
					 | 
				
			||||||
      <DarkModeToggle class="mx-2" v-slot="{ state }">
 | 
					 | 
				
			||||||
        <i v-if="state" class="bi-moon"></i>
 | 
					 | 
				
			||||||
        <i v-else class="bi-sun"></i>
 | 
					 | 
				
			||||||
      </DarkModeToggle>
 | 
					 | 
				
			||||||
    </div>
 | 
					 | 
				
			||||||
  </header>
 | 
					 | 
				
			||||||
  <main class="container my-5">
 | 
					 | 
				
			||||||
    <router-view />
 | 
					 | 
				
			||||||
  </main>
 | 
					 | 
				
			||||||
  <footer></footer>
 | 
					 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<script setup lang="ts">
 | 
					<style lang="scss">
 | 
				
			||||||
import DarkModeToggle from '@/components/DarkmodeToggle.vue';
 | 
					#app {
 | 
				
			||||||
</script>
 | 
					  font-family: Avenir, Helvetica, Arial, sans-serif;
 | 
				
			||||||
 | 
					  -webkit-font-smoothing: antialiased;
 | 
				
			||||||
 | 
					  -moz-osx-font-smoothing: grayscale;
 | 
				
			||||||
 | 
					  text-align: center;
 | 
				
			||||||
 | 
					  color: #2c3e50;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#nav {
 | 
				
			||||||
 | 
					  padding: 30px;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  a {
 | 
				
			||||||
 | 
					    font-weight: bold;
 | 
				
			||||||
 | 
					    color: #2c3e50;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    &.router-link-exact-active {
 | 
				
			||||||
 | 
					      color: #42b983;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					</style>
 | 
				
			||||||
 | 
				
			|||||||
@ -1,23 +0,0 @@
 | 
				
			|||||||
<template>
 | 
					 | 
				
			||||||
  <div class="form-check form-switch">
 | 
					 | 
				
			||||||
    <label class="custom-control-label" for="darkSwitch">
 | 
					 | 
				
			||||||
      <slot :state="sliderState" />
 | 
					 | 
				
			||||||
    </label>
 | 
					 | 
				
			||||||
    <input v-model="sliderState" type="checkbox" class="form-check-input" />
 | 
					 | 
				
			||||||
  </div>
 | 
					 | 
				
			||||||
</template>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<script setup>
 | 
					 | 
				
			||||||
import { ref, watch } from 'vue';
 | 
					 | 
				
			||||||
import { themeConfig } from '@/main';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const sliderState = ref(themeConfig.getTheme() === 'dark');
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
watch(sliderState, (state) => {
 | 
					 | 
				
			||||||
  themeConfig.setTheme(state ? 'dark' : 'light');
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
themeConfig.themeChangeHandlers.push((newTheme) => {
 | 
					 | 
				
			||||||
  sliderState.value = newTheme === 'dark';
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
</script>
 | 
					 | 
				
			||||||
@ -1,19 +0,0 @@
 | 
				
			|||||||
<template>
 | 
					 | 
				
			||||||
  <div class="card sideMenu sticky-top">
 | 
					 | 
				
			||||||
    <div class="card-header">Menu</div>
 | 
					 | 
				
			||||||
    <div class="card-body">
 | 
					 | 
				
			||||||
      <router-link class="py-2" to="/files">
 | 
					 | 
				
			||||||
        <i class="bi-archive"></i> My Files
 | 
					 | 
				
			||||||
      </router-link>
 | 
					 | 
				
			||||||
    </div>
 | 
					 | 
				
			||||||
  </div>
 | 
					 | 
				
			||||||
</template>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<style scoped lang="scss">
 | 
					 | 
				
			||||||
.sideMenu {
 | 
					 | 
				
			||||||
  a {
 | 
					 | 
				
			||||||
    color: inherit;
 | 
					 | 
				
			||||||
    text-decoration: none;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
</style>
 | 
					 | 
				
			||||||
@ -1,100 +0,0 @@
 | 
				
			|||||||
<template>
 | 
					 | 
				
			||||||
  <div class="px-0">
 | 
					 | 
				
			||||||
    <div class="files card">
 | 
					 | 
				
			||||||
      <div class="card-header sticky-top bg-light bg-darkmode-dark">
 | 
					 | 
				
			||||||
        <div class="row">
 | 
					 | 
				
			||||||
          <i
 | 
					 | 
				
			||||||
            v-if="path && path !== '/'"
 | 
					 | 
				
			||||||
            @click="navigateUp"
 | 
					 | 
				
			||||||
            class="col-1 px-0 btn btn-default bi-arrow-90deg-left"
 | 
					 | 
				
			||||||
          ></i>
 | 
					 | 
				
			||||||
          <PathSegments
 | 
					 | 
				
			||||||
            class="col my-2"
 | 
					 | 
				
			||||||
            :path="path"
 | 
					 | 
				
			||||||
            @newPath="(newPath) => emit('newPath', newPath)"
 | 
					 | 
				
			||||||
          />
 | 
					 | 
				
			||||||
          <div class="col-auto px-0">
 | 
					 | 
				
			||||||
            <FileUpload :path="path" @finished="fetchLocation" />
 | 
					 | 
				
			||||||
          </div>
 | 
					 | 
				
			||||||
        </div>
 | 
					 | 
				
			||||||
      </div>
 | 
					 | 
				
			||||||
      <div class="card-body">
 | 
					 | 
				
			||||||
        <div v-if="loading">loading...</div>
 | 
					 | 
				
			||||||
        <div v-else>
 | 
					 | 
				
			||||||
          <table class="table table-hover">
 | 
					 | 
				
			||||||
            <thead>
 | 
					 | 
				
			||||||
              <tr>
 | 
					 | 
				
			||||||
                <th></th>
 | 
					 | 
				
			||||||
                <th>Name</th>
 | 
					 | 
				
			||||||
                <th class="d-none d-md-table-cell">Modified</th>
 | 
					 | 
				
			||||||
                <th class="d-none d-sm-table-cell">Size</th>
 | 
					 | 
				
			||||||
              </tr>
 | 
					 | 
				
			||||||
            </thead>
 | 
					 | 
				
			||||||
            <tbody>
 | 
					 | 
				
			||||||
              <component
 | 
					 | 
				
			||||||
                v-for="file in files"
 | 
					 | 
				
			||||||
                :key="file"
 | 
					 | 
				
			||||||
                :is="file.type === 'file' ? FileListElement : FolderListElement"
 | 
					 | 
				
			||||||
                :file="file"
 | 
					 | 
				
			||||||
                :client="client"
 | 
					 | 
				
			||||||
              />
 | 
					 | 
				
			||||||
            </tbody>
 | 
					 | 
				
			||||||
          </table>
 | 
					 | 
				
			||||||
        </div>
 | 
					 | 
				
			||||||
        {{ error }}
 | 
					 | 
				
			||||||
      </div>
 | 
					 | 
				
			||||||
    </div>
 | 
					 | 
				
			||||||
  </div>
 | 
					 | 
				
			||||||
</template>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<script setup lang="ts">
 | 
					 | 
				
			||||||
import FileListElement from './FileListElement.vue';
 | 
					 | 
				
			||||||
import FolderListElement from './FolderListElement.vue';
 | 
					 | 
				
			||||||
import FileUpload from '@/components/helpers/FileUpload.vue';
 | 
					 | 
				
			||||||
import PathSegments from '@/components/files/PathSegments.vue';
 | 
					 | 
				
			||||||
import { useWebdavStore } from '@/store/webdav';
 | 
					 | 
				
			||||||
import { ref, defineProps, watch, defineEmits } from 'vue';
 | 
					 | 
				
			||||||
import type { File } from '@/models';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const props = defineProps({
 | 
					 | 
				
			||||||
  path: String,
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
const emit = defineEmits(['newPath']);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const files = ref([] as Array<File>);
 | 
					 | 
				
			||||||
const loading = ref(true as boolean);
 | 
					 | 
				
			||||||
const error = ref('' as string);
 | 
					 | 
				
			||||||
const store = useWebdavStore();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const client = store.currentSession?.client;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const fetchLocation = async () => {
 | 
					 | 
				
			||||||
  try {
 | 
					 | 
				
			||||||
    loading.value = true;
 | 
					 | 
				
			||||||
    files.value = (await client?.getDirectoryContents(
 | 
					 | 
				
			||||||
      props.path ?? '/'
 | 
					 | 
				
			||||||
    )) as Array<File>;
 | 
					 | 
				
			||||||
    loading.value = false;
 | 
					 | 
				
			||||||
    error.value = '';
 | 
					 | 
				
			||||||
  } catch (e) {
 | 
					 | 
				
			||||||
    files.value = [];
 | 
					 | 
				
			||||||
    loading.value = false;
 | 
					 | 
				
			||||||
    error.value = e as string;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const navigateUp = () => {
 | 
					 | 
				
			||||||
  const newPath = (props.path ?? '/').split('/');
 | 
					 | 
				
			||||||
  newPath.pop();
 | 
					 | 
				
			||||||
  emit('newPath', newPath.join('/') || '/');
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
watch(
 | 
					 | 
				
			||||||
  () => props.path,
 | 
					 | 
				
			||||||
  () => {
 | 
					 | 
				
			||||||
    fetchLocation();
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
fetchLocation();
 | 
					 | 
				
			||||||
</script>
 | 
					 | 
				
			||||||
@ -1,32 +0,0 @@
 | 
				
			|||||||
<template>
 | 
					 | 
				
			||||||
  <tr>
 | 
					 | 
				
			||||||
    <td><i class="icon col-1" :class="`bi-${getIconFromFile(file)}`"></i></td>
 | 
					 | 
				
			||||||
    <td>{{ file.basename }}</td>
 | 
					 | 
				
			||||||
    <td class="d-none d-md-table-cell">
 | 
					 | 
				
			||||||
      {{ new Date(file.lastmod).toLocaleDateString() }}
 | 
					 | 
				
			||||||
    </td>
 | 
					 | 
				
			||||||
    <td class="d-none d-sm-table-cell"><ByteCalc :bytes="file.size" /></td>
 | 
					 | 
				
			||||||
  </tr>
 | 
					 | 
				
			||||||
</template>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<script setup lang="ts">
 | 
					 | 
				
			||||||
import ByteCalc from '@/components/helpers/ByteCalc.vue';
 | 
					 | 
				
			||||||
import { defineProps } from 'vue';
 | 
					 | 
				
			||||||
import { fileExtensions, defaultIcon } from '@/lib/fileTypeToIconMappings';
 | 
					 | 
				
			||||||
import type { File } from '@/models';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const props = defineProps({
 | 
					 | 
				
			||||||
  file: Object,
 | 
					 | 
				
			||||||
  client: Object,
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const getIconFromFile = (file: File): string => {
 | 
					 | 
				
			||||||
  const segments = file.basename.split('.');
 | 
					 | 
				
			||||||
  if (segments.length < 2) return defaultIcon;
 | 
					 | 
				
			||||||
  return fileExtensions.get(segments.pop() as string) ?? defaultIcon;
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const downloadFile = async (file: File) => {
 | 
					 | 
				
			||||||
  await props.client?.getFileContents(file.filename);
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
</script>
 | 
					 | 
				
			||||||
@ -1,27 +0,0 @@
 | 
				
			|||||||
<template>
 | 
					 | 
				
			||||||
  <tr @click="$router.push(`#${file.filename}`)" class="file">
 | 
					 | 
				
			||||||
    <td><i class="icon bi-folder"></i></td>
 | 
					 | 
				
			||||||
    <td>{{ file.basename }}</td>
 | 
					 | 
				
			||||||
    <td class="d-none d-md-table-cell">{{ new Date(file.lastmod).toLocaleDateString() }}</td>
 | 
					 | 
				
			||||||
    <td class="d-none d-sm-table-cell"></td>
 | 
					 | 
				
			||||||
  </tr>
 | 
					 | 
				
			||||||
</template>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<script setup lang="ts">
 | 
					 | 
				
			||||||
import { defineProps } from 'vue';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
defineProps({
 | 
					 | 
				
			||||||
  file: Object,
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
</script>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<style lang="scss">
 | 
					 | 
				
			||||||
.file {
 | 
					 | 
				
			||||||
  color: inherit !important;
 | 
					 | 
				
			||||||
  text-decoration: inherit !important;
 | 
					 | 
				
			||||||
  cursor: pointer;
 | 
					 | 
				
			||||||
  &:hover{
 | 
					 | 
				
			||||||
    color: inherit !important;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
</style>
 | 
					 | 
				
			||||||
@ -1,46 +0,0 @@
 | 
				
			|||||||
<template>
 | 
					 | 
				
			||||||
  <div>
 | 
					 | 
				
			||||||
    <span v-for="(segment, index) of getSegments().slice(0, -1)" :key="segment">
 | 
					 | 
				
			||||||
      <span
 | 
					 | 
				
			||||||
        class="segment p-1"
 | 
					 | 
				
			||||||
        @click="
 | 
					 | 
				
			||||||
          emit(
 | 
					 | 
				
			||||||
            'newPath',
 | 
					 | 
				
			||||||
            `/${getSegments()
 | 
					 | 
				
			||||||
              .slice(1, index + 1)
 | 
					 | 
				
			||||||
              .join('/')}`
 | 
					 | 
				
			||||||
          )
 | 
					 | 
				
			||||||
        "
 | 
					 | 
				
			||||||
        >{{ segment }}</span
 | 
					 | 
				
			||||||
      ><i class="bi-caret-right mx-1"></i>
 | 
					 | 
				
			||||||
    </span>
 | 
					 | 
				
			||||||
    <span class="p-1">
 | 
					 | 
				
			||||||
      <b>{{ getSegments()?.pop() }}</b>
 | 
					 | 
				
			||||||
    </span>
 | 
					 | 
				
			||||||
  </div>
 | 
					 | 
				
			||||||
</template>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<script setup lang="ts">
 | 
					 | 
				
			||||||
import { defineProps, defineEmits } from 'vue';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const props = defineProps({
 | 
					 | 
				
			||||||
  path: String,
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
const emit = defineEmits(['newPath']);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const getSegments = (): Array<string> => {
 | 
					 | 
				
			||||||
  const segments = `My Files${props.path ?? ''}`.split('/');
 | 
					 | 
				
			||||||
  if (segments[segments.length - 1] === '') segments?.pop();
 | 
					 | 
				
			||||||
  return segments;
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
</script>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<style scoped lang="scss">
 | 
					 | 
				
			||||||
.segment {
 | 
					 | 
				
			||||||
  border-radius: .2rem;
 | 
					 | 
				
			||||||
  cursor: pointer;
 | 
					 | 
				
			||||||
  &:hover {
 | 
					 | 
				
			||||||
    background-color: #aaa6;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
</style>
 | 
					 | 
				
			||||||
@ -1,23 +0,0 @@
 | 
				
			|||||||
<template>
 | 
					 | 
				
			||||||
  <span>{{ getByteString(bytes) }}</span>
 | 
					 | 
				
			||||||
</template>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<script setup lang="ts">
 | 
					 | 
				
			||||||
import { defineProps } from 'vue';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const props = defineProps({
 | 
					 | 
				
			||||||
  bytes: Number,
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const getByteString = (bytes: number): string => {
 | 
					 | 
				
			||||||
  const unit = ['B', 'kB', 'MB', 'GB', 'TB', 'PB']
 | 
					 | 
				
			||||||
    .map((symbol, index) => ({
 | 
					 | 
				
			||||||
      symbol,
 | 
					 | 
				
			||||||
      index,
 | 
					 | 
				
			||||||
      breakpoint: 10 ** ((index + 1) * 3),
 | 
					 | 
				
			||||||
    }))
 | 
					 | 
				
			||||||
    .find((unit) => bytes < unit.breakpoint);
 | 
					 | 
				
			||||||
  if (!unit) return 'wtf';
 | 
					 | 
				
			||||||
  return `${(bytes / 10 ** (unit.index * 3)).toFixed(2)} ${unit.symbol}`;
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
</script>
 | 
					 | 
				
			||||||
@ -1,61 +0,0 @@
 | 
				
			|||||||
<template>
 | 
					 | 
				
			||||||
  <button class="btn btn-secondary" @click.prevent="$refs.fileInput.click()">
 | 
					 | 
				
			||||||
    <slot><i class="bi-plus"></i></slot>
 | 
					 | 
				
			||||||
  </button>
 | 
					 | 
				
			||||||
  <input
 | 
					 | 
				
			||||||
    type="file"
 | 
					 | 
				
			||||||
    ref="fileInput"
 | 
					 | 
				
			||||||
    class="d-none"
 | 
					 | 
				
			||||||
    @change="setFile($refs.fileInput.files[0])"
 | 
					 | 
				
			||||||
  />
 | 
					 | 
				
			||||||
  <div v-if="info">{{ info }}</div>
 | 
					 | 
				
			||||||
  <div v-if="progressRef">
 | 
					 | 
				
			||||||
    <ByteCalc :bytes="progressRef.loaded" /><span> of </span
 | 
					 | 
				
			||||||
    ><ByteCalc :bytes="progressRef.total" />
 | 
					 | 
				
			||||||
  </div>
 | 
					 | 
				
			||||||
</template>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<script setup lang="ts">
 | 
					 | 
				
			||||||
import ByteCalc from '@/components/helpers/ByteCalc.vue';
 | 
					 | 
				
			||||||
import { defineEmits, defineProps, ref } from 'vue';
 | 
					 | 
				
			||||||
import { readFileBuffer } from '@/lib/readFileBlob';
 | 
					 | 
				
			||||||
import { useWebdavStore } from '@/store/webdav';
 | 
					 | 
				
			||||||
import type { ProgressEvent } from 'webdav';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const props = defineProps({ path: String });
 | 
					 | 
				
			||||||
const emit = defineEmits(['started', 'finished', 'failed', 'progress']);
 | 
					 | 
				
			||||||
const store = useWebdavStore();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const progressRef = ref(null as null | ProgressEvent);
 | 
					 | 
				
			||||||
const info = ref('');
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const setFile = async (file: File) => {
 | 
					 | 
				
			||||||
  emit('started');
 | 
					 | 
				
			||||||
  const buffer = await readFileBuffer(file);
 | 
					 | 
				
			||||||
  try {
 | 
					 | 
				
			||||||
    await store.currentSession?.client.putFileContents(
 | 
					 | 
				
			||||||
      `${props.path}/${file.name}`,
 | 
					 | 
				
			||||||
      buffer,
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        onUploadProgress: (progress) => {
 | 
					 | 
				
			||||||
          progressRef.value = progress;
 | 
					 | 
				
			||||||
          emit('progress', progress);
 | 
					 | 
				
			||||||
          console.log(`Uploaded ${progress.loaded} bytes of ${progress.total}`);
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
    info.value = 'upload completed';
 | 
					 | 
				
			||||||
    emit('finished');
 | 
					 | 
				
			||||||
  } catch (e) {
 | 
					 | 
				
			||||||
    info.value = 'upload failed';
 | 
					 | 
				
			||||||
    emit('failed', e);
 | 
					 | 
				
			||||||
    console.error(e);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  setTimeout(() => {
 | 
					 | 
				
			||||||
    progressRef.value = null;
 | 
					 | 
				
			||||||
    info.value = '';
 | 
					 | 
				
			||||||
  }, 2000);
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
</script>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<style scoped></style>
 | 
					 | 
				
			||||||
@ -1,41 +0,0 @@
 | 
				
			|||||||
export const defaultIcon = 'file-earmark';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export const fileExtensions = new Map([
 | 
					 | 
				
			||||||
  // Images
 | 
					 | 
				
			||||||
  ['png', 'file-earmark-image'],
 | 
					 | 
				
			||||||
  ['jpg', 'file-earmark-image'],
 | 
					 | 
				
			||||||
  ['tiff', 'file-earmark-image'],
 | 
					 | 
				
			||||||
  // Music
 | 
					 | 
				
			||||||
  ['mp3', 'file-earmark-music'],
 | 
					 | 
				
			||||||
  ['m4a', 'file-earmark-music'],
 | 
					 | 
				
			||||||
  ['aac', 'file-earmark-music'],
 | 
					 | 
				
			||||||
  ['aiff', 'file-earmark-music'],
 | 
					 | 
				
			||||||
  ['wav', 'file-earmark-music'],
 | 
					 | 
				
			||||||
  ['wma', 'file-earmark-music'],
 | 
					 | 
				
			||||||
  // Code
 | 
					 | 
				
			||||||
  ['html', 'file-earmark-code'],
 | 
					 | 
				
			||||||
  ['htm', 'file-earmark-code'],
 | 
					 | 
				
			||||||
  ['xml', 'file-earmark-code'],
 | 
					 | 
				
			||||||
  ['js', 'file-earmark-code'],
 | 
					 | 
				
			||||||
  ['mjs', 'file-earmark-code'],
 | 
					 | 
				
			||||||
  ['py', 'file-earmark-code'],
 | 
					 | 
				
			||||||
  ['sh', 'file-earmark-code'],
 | 
					 | 
				
			||||||
  ['ts', 'file-earmark-code'],
 | 
					 | 
				
			||||||
  ['go', 'file-earmark-code'],
 | 
					 | 
				
			||||||
  ['rs', 'file-earmark-code'],
 | 
					 | 
				
			||||||
  ['java', 'file-earmark-code'],
 | 
					 | 
				
			||||||
  // Binaries
 | 
					 | 
				
			||||||
  ['jar', 'file-earmark-binary'],
 | 
					 | 
				
			||||||
  ['exe', 'file-earmark-binary'],
 | 
					 | 
				
			||||||
  ['iso', 'file-earmark-binary'],
 | 
					 | 
				
			||||||
  // etc...
 | 
					 | 
				
			||||||
  ['pdf', 'file-earmark-pdf'],
 | 
					 | 
				
			||||||
  ['txt', 'filetype-txt'],
 | 
					 | 
				
			||||||
  ['zip', 'file-earmark-zip'],
 | 
					 | 
				
			||||||
  ['gz', 'file-earmark-zip'],
 | 
					 | 
				
			||||||
  ['xz', 'file-earmark-zip'],
 | 
					 | 
				
			||||||
]);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export const mimeTypes = new Map([
 | 
					 | 
				
			||||||
  ['application/pdf', 'file-earmark-pdf'],
 | 
					 | 
				
			||||||
]);
 | 
					 | 
				
			||||||
@ -1,41 +0,0 @@
 | 
				
			|||||||
import { is } from 'typescript-is';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export const readFileAs = <T>(
 | 
					 | 
				
			||||||
  file: File,
 | 
					 | 
				
			||||||
  getReaderMethod = (reader: FileReader) => reader.readAsDataURL
 | 
					 | 
				
			||||||
): Promise<T> => {
 | 
					 | 
				
			||||||
  return new Promise((resolve, reject) => {
 | 
					 | 
				
			||||||
    const reader = new FileReader();
 | 
					 | 
				
			||||||
    reader.onerror = reject;
 | 
					 | 
				
			||||||
    reader.onload = async (event) => {
 | 
					 | 
				
			||||||
      const buffer = event.target?.result;
 | 
					 | 
				
			||||||
      if (!buffer) reject('failed to read file');
 | 
					 | 
				
			||||||
      if (!is<T>(buffer)) reject('wrong type');
 | 
					 | 
				
			||||||
      else resolve(buffer as T);
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
    getReaderMethod(reader as FileReader)(file);
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export const readFileBuffer = async (file: File): Promise<ArrayBuffer> => {
 | 
					 | 
				
			||||||
  return new Promise((resolve, reject) => {
 | 
					 | 
				
			||||||
    const reader = new FileReader();
 | 
					 | 
				
			||||||
    reader.onerror = reject;
 | 
					 | 
				
			||||||
    reader.onload = async (event) => {
 | 
					 | 
				
			||||||
      const buffer = event.target?.result;
 | 
					 | 
				
			||||||
      if (buffer === null || buffer === undefined || typeof buffer === 'string')
 | 
					 | 
				
			||||||
        reject('failed to read file');
 | 
					 | 
				
			||||||
      else resolve(buffer);
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
    reader.readAsArrayBuffer(file);
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
  /*return readFileAs<ArrayBuffer>(
 | 
					 | 
				
			||||||
    file,
 | 
					 | 
				
			||||||
    (reader: FileReader) => reader.readAsArrayBuffer
 | 
					 | 
				
			||||||
  );*/
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export const readFileBlob = async (file: File): Promise<Blob> => {
 | 
					 | 
				
			||||||
  const data = await readFileAs<string>(file);
 | 
					 | 
				
			||||||
  return await (await fetch(data)).blob();
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
@ -1,29 +1,29 @@
 | 
				
			|||||||
import { Context } from '@/middleware/Context';
 | 
					import { Context } from '@/middleware/Context';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type Middleware = (context: Context) => void;
 | 
					type RunMiddleware = (context: Context) => void;
 | 
				
			||||||
function nextFactory(
 | 
					function nextFactory(
 | 
				
			||||||
  context: Context,
 | 
					  context: Context,
 | 
				
			||||||
  middlewares: Array<Middleware>,
 | 
					  middlewares: Array<RunMiddleware>,
 | 
				
			||||||
  index: number
 | 
					  index: number
 | 
				
			||||||
) {
 | 
					) {
 | 
				
			||||||
  const subsequentMiddleware = middlewares[index];
 | 
					  const subsequentMiddleware = middlewares[index];
 | 
				
			||||||
  if (!subsequentMiddleware) return context.next;
 | 
					  if (!subsequentMiddleware) return context.next;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  return (...args: Array<any>) => {
 | 
					  return (...args: any[]) => {
 | 
				
			||||||
    // @ts-ignore
 | 
					    // @ts-ignore
 | 
				
			||||||
    context.next(...args as Array<any>);
 | 
					    context.next(...args);
 | 
				
			||||||
    subsequentMiddleware({
 | 
					    subsequentMiddleware({
 | 
				
			||||||
      ...context,
 | 
					      ...context,
 | 
				
			||||||
      next: nextFactory(context, middlewares, index++),
 | 
					      next: nextFactory(context, middlewares, index++),
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
export function runMiddleware(context: Context): void {
 | 
					export function runMiddleware(context: Context) {
 | 
				
			||||||
  const { to } = context;
 | 
					  const { to } = context;
 | 
				
			||||||
  const middlewares = [
 | 
					  const middlewares = [
 | 
				
			||||||
    ...((Array.isArray(to.meta.middleware)
 | 
					    ...((Array.isArray(to.meta.middleware)
 | 
				
			||||||
      ? to.meta.middleware
 | 
					      ? to.meta.middleware
 | 
				
			||||||
      : [to.meta.middleware]) as Array<Middleware>),
 | 
					      : [to.meta.middleware]) as Array<RunMiddleware>),
 | 
				
			||||||
  ];
 | 
					  ];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  middlewares[0]({ ...context, next: nextFactory(context, middlewares, 1) });
 | 
					  middlewares[0]({ ...context, next: nextFactory(context, middlewares, 1) });
 | 
				
			||||||
 | 
				
			|||||||
@ -1,16 +0,0 @@
 | 
				
			|||||||
// Your variable overrides
 | 
					 | 
				
			||||||
//$body-bg: #222;
 | 
					 | 
				
			||||||
//$body-color: #222;
 | 
					 | 
				
			||||||
$dark-body-bg: #222;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// Bootstrap and its default variables
 | 
					 | 
				
			||||||
@import "~bootstrap/scss/bootstrap";
 | 
					 | 
				
			||||||
@import "~bootstrap-darkmode/css/darktheme";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
html, body {
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#app{
 | 
					 | 
				
			||||||
  font-family: monospace;
 | 
					 | 
				
			||||||
  //color: #fff;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
							
								
								
									
										18
									
								
								src/main.ts
									
									
									
									
									
								
							
							
						
						
									
										18
									
								
								src/main.ts
									
									
									
									
									
								
							@ -1,13 +1,7 @@
 | 
				
			|||||||
import { createApp } from 'vue';
 | 
					import { createApp } from "vue";
 | 
				
			||||||
import App from './App.vue';
 | 
					import App from "./App.vue";
 | 
				
			||||||
import './registerServiceWorker';
 | 
					import "./registerServiceWorker";
 | 
				
			||||||
import router from './router';
 | 
					import router from "./router";
 | 
				
			||||||
import store from './store';
 | 
					import store from "./store";
 | 
				
			||||||
import 'bootstrap-icons/font/bootstrap-icons.scss';
 | 
					 | 
				
			||||||
import './main.scss';
 | 
					 | 
				
			||||||
import { ThemeConfig, writeDarkSwitch } from 'bootstrap-darkmode';
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const themeConfig = new ThemeConfig();
 | 
					createApp(App).use(store).use(router).mount("#app");
 | 
				
			||||||
themeConfig.initTheme();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
createApp(App).use(store).use(router).mount('#app');
 | 
					 | 
				
			||||||
 | 
				
			|||||||
@ -1,8 +1,8 @@
 | 
				
			|||||||
import { useWebdavStore } from '@/store/webdav';
 | 
					import { useWebdavStorage } from '@/store/webdav';
 | 
				
			||||||
import { Context } from '@/middleware/Context';
 | 
					import { Context } from '@/middleware/Context';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const auth = ({ next }: Context) => {
 | 
					export default function auth({ next, router }: Context) {
 | 
				
			||||||
  if (!useWebdavStore().currentSession?.isActive)
 | 
					  console.log('auth');
 | 
				
			||||||
    return next({ name: 'Login' });
 | 
					  if (!useWebdavStorage().currentSession?.isActive) return router.push({ name: 'Login' });
 | 
				
			||||||
  return next();
 | 
					  return next();
 | 
				
			||||||
};
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -1,9 +0,0 @@
 | 
				
			|||||||
export type File = {
 | 
					 | 
				
			||||||
  basename: string;
 | 
					 | 
				
			||||||
  etag: string;
 | 
					 | 
				
			||||||
  filename: string;
 | 
					 | 
				
			||||||
  lastmod: string;
 | 
					 | 
				
			||||||
  mime: string;
 | 
					 | 
				
			||||||
  size: number;
 | 
					 | 
				
			||||||
  type: string;
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
@ -1,6 +1,7 @@
 | 
				
			|||||||
import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router';
 | 
					import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router';
 | 
				
			||||||
import Home from '../views/Home.vue';
 | 
					import Home from '../views/Home.vue';
 | 
				
			||||||
import { auth } from '@/middleware/auth';
 | 
					import auth from '@/middleware/auth';
 | 
				
			||||||
 | 
					import log from '@/middleware/log';
 | 
				
			||||||
import { runMiddleware } from '@/lib/runMiddleware';
 | 
					import { runMiddleware } from '@/lib/runMiddleware';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const routes: Array<RouteRecordRaw> = [
 | 
					const routes: Array<RouteRecordRaw> = [
 | 
				
			||||||
@ -21,6 +22,9 @@ const routes: Array<RouteRecordRaw> = [
 | 
				
			|||||||
    path: '/login',
 | 
					    path: '/login',
 | 
				
			||||||
    name: 'Login',
 | 
					    name: 'Login',
 | 
				
			||||||
    component: () => import('../views/Login.vue'),
 | 
					    component: () => import('../views/Login.vue'),
 | 
				
			||||||
 | 
					    meta: {
 | 
				
			||||||
 | 
					      middleware: [log],
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
];
 | 
					];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										13
									
								
								src/store/auth.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								src/store/auth.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,13 @@
 | 
				
			|||||||
 | 
					import {defineStore} from "pinia";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const useAuthStorage = defineStore('auth', {
 | 
				
			||||||
 | 
					  state: () => ({
 | 
				
			||||||
 | 
					    user: '',
 | 
				
			||||||
 | 
					    password: ''
 | 
				
			||||||
 | 
					  }),
 | 
				
			||||||
 | 
					  actions: {
 | 
				
			||||||
 | 
					    login(){
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
@ -11,29 +11,31 @@ export type Session = {
 | 
				
			|||||||
  isActive: boolean;
 | 
					  isActive: boolean;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const useWebdavStore = defineStore('auth', {
 | 
					export const useWebdavStorage = defineStore('auth', {
 | 
				
			||||||
  state: () => ({
 | 
					  state: () => ({
 | 
				
			||||||
    sessions: [] as Array<Session>,
 | 
					    sessions: [] as Array<Session>,
 | 
				
			||||||
    currentSession: null as null | Session,
 | 
					    currentSession: null as null | Session,
 | 
				
			||||||
  }),
 | 
					  }),
 | 
				
			||||||
  actions: {
 | 
					  actions: {
 | 
				
			||||||
    async login({ user, pass }: Auth): Promise<Session> {
 | 
					    login({ user, pass }: Auth): Promise<Session> {
 | 
				
			||||||
      try {
 | 
					      return new Promise((resolve, reject) => {
 | 
				
			||||||
        const client = createClient(
 | 
					        try {
 | 
				
			||||||
          `${process.env.VUE_APP_ROOT_WEBDAV ?? ''}/api/dav/files/` as string,
 | 
					          const client = createClient(
 | 
				
			||||||
          {
 | 
					            process.env.VUE_APP_ROOT_WEBDAV as string,
 | 
				
			||||||
            authType: AuthType.Password,
 | 
					            {
 | 
				
			||||||
            username: user as string,
 | 
					              authType: AuthType.Digest,
 | 
				
			||||||
            password: pass as string,
 | 
					              username: user as string,
 | 
				
			||||||
          }
 | 
					              password: pass as string,
 | 
				
			||||||
        ) as WebDAVClient;
 | 
					            }
 | 
				
			||||||
        const session = { client, isActive: true } as Session;
 | 
					          ) as WebDAVClient;
 | 
				
			||||||
        this.sessions.push(session);
 | 
					          const session = { client, isActive: true } as Session;
 | 
				
			||||||
        this.currentSession = session;
 | 
					          this.sessions.push(session);
 | 
				
			||||||
        return session;
 | 
					          this.currentSession = session;
 | 
				
			||||||
      } catch (e) {
 | 
					          resolve(session);
 | 
				
			||||||
        throw 'login failed';
 | 
					        } catch (e) {
 | 
				
			||||||
      }
 | 
					          reject(e);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
				
			|||||||
@ -1,20 +1,11 @@
 | 
				
			|||||||
<template>
 | 
					<template>
 | 
				
			||||||
  <div class="row mx-1 justify-content-center">
 | 
					  <h1>Files</h1>
 | 
				
			||||||
    <div class="d-none d-lg-block col-3">
 | 
					 | 
				
			||||||
      <SideMenu />
 | 
					 | 
				
			||||||
    </div>
 | 
					 | 
				
			||||||
    <FileList
 | 
					 | 
				
			||||||
      class="col"
 | 
					 | 
				
			||||||
      :path="$route.hash.slice(1)"
 | 
					 | 
				
			||||||
      @newPath="(path) => $router.push(`#${path}`)"
 | 
					 | 
				
			||||||
    />
 | 
					 | 
				
			||||||
  </div>
 | 
					 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<script setup lang="ts">
 | 
					<script>
 | 
				
			||||||
import FileList from '@/components/files/FileList.vue';
 | 
					export default {
 | 
				
			||||||
import SideMenu from '@/components/SideMenu.vue';
 | 
					  name: 'Files',
 | 
				
			||||||
import { useWebdavStore } from '@/store/webdav';
 | 
					};
 | 
				
			||||||
 | 
					 | 
				
			||||||
const store = useWebdavStore();
 | 
					 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<style scoped></style>
 | 
				
			||||||
 | 
				
			|||||||
@ -2,32 +2,22 @@
 | 
				
			|||||||
  <h1>Login</h1>
 | 
					  <h1>Login</h1>
 | 
				
			||||||
  user: <input type="text" v-model="user" /><br />
 | 
					  user: <input type="text" v-model="user" /><br />
 | 
				
			||||||
  pass: <input type="password" v-model="pass" /><br />
 | 
					  pass: <input type="password" v-model="pass" /><br />
 | 
				
			||||||
  <button class="btn btn-primary" @click="login">Login</button><br />
 | 
					  <button @click="login">Login</button>
 | 
				
			||||||
  <span class="error">{{ error }}</span>
 | 
					 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<script setup lang="ts">
 | 
					<script setup lang="ts">
 | 
				
			||||||
import { ref } from 'vue';
 | 
					import { ref } from 'vue';
 | 
				
			||||||
import { useWebdavStore } from '@/store/webdav';
 | 
					import { useWebdavStorage } from '@/store/webdav';
 | 
				
			||||||
import { useRouter } from 'vue-router';
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
const user = ref('');
 | 
					const user = ref('');
 | 
				
			||||||
const pass = ref('');
 | 
					const pass = ref('');
 | 
				
			||||||
const error = ref('');
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const router = useRouter();
 | 
					 | 
				
			||||||
const store = useWebdavStore();
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
const login = async () => {
 | 
					const login = async () => {
 | 
				
			||||||
  try {
 | 
					  await useWebdavStorage()
 | 
				
			||||||
    await store.login({ user: user.value, pass: pass.value });
 | 
					    .login({ user, pass })
 | 
				
			||||||
    error.value = '';
 | 
					    .then(async session => {
 | 
				
			||||||
    await router.push({ name: 'Files' });
 | 
					      console.log(await session.client.getDirectoryContents('/'));
 | 
				
			||||||
  } catch (e) {
 | 
					    });
 | 
				
			||||||
    console.error(e);
 | 
					 | 
				
			||||||
    error.value = e as string;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  await store.currentSession?.client.getDirectoryContents(`/`);
 | 
					 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
		Reference in New Issue
	
	Block a user