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"
 | 
			
		||||
  },
 | 
			
		||||
  "dependencies": {
 | 
			
		||||
    "bootstrap": "^5.2.0",
 | 
			
		||||
    "bootstrap-darkmode": "^5.0.1",
 | 
			
		||||
    "bootstrap-icons": "^1.9.1",
 | 
			
		||||
    "core-js": "^3.6.5",
 | 
			
		||||
    "pinia": "^2.0.13",
 | 
			
		||||
    "register-service-worker": "^1.7.1",
 | 
			
		||||
    "typescript-is": "^0.19.0",
 | 
			
		||||
    "vue": "^3.0.0",
 | 
			
		||||
    "vue-router": "^4.0.0-0",
 | 
			
		||||
    "pinia": "^2.0.13",
 | 
			
		||||
    "webdav": "^4.9.0"
 | 
			
		||||
  },
 | 
			
		||||
  "devDependencies": {
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										48
									
								
								src/App.vue
									
									
									
									
									
								
							
							
						
						
									
										48
									
								
								src/App.vue
									
									
									
									
									
								
							@ -1,24 +1,30 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <header
 | 
			
		||||
    class="d-flex justify-content-between p-3 bg-darkmode-dark bg-light shadow"
 | 
			
		||||
  >
 | 
			
		||||
    <div>
 | 
			
		||||
      <b class="mx-2">vuedav</b>
 | 
			
		||||
      <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>
 | 
			
		||||
  <div id="nav">
 | 
			
		||||
    <router-link to="/">Home</router-link> |
 | 
			
		||||
    <router-link to="/about">About</router-link>
 | 
			
		||||
  </div>
 | 
			
		||||
  <router-view />
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup lang="ts">
 | 
			
		||||
import DarkModeToggle from '@/components/DarkmodeToggle.vue';
 | 
			
		||||
</script>
 | 
			
		||||
<style lang="scss">
 | 
			
		||||
#app {
 | 
			
		||||
  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';
 | 
			
		||||
 | 
			
		||||
type Middleware = (context: Context) => void;
 | 
			
		||||
type RunMiddleware = (context: Context) => void;
 | 
			
		||||
function nextFactory(
 | 
			
		||||
  context: Context,
 | 
			
		||||
  middlewares: Array<Middleware>,
 | 
			
		||||
  middlewares: Array<RunMiddleware>,
 | 
			
		||||
  index: number
 | 
			
		||||
) {
 | 
			
		||||
  const subsequentMiddleware = middlewares[index];
 | 
			
		||||
  if (!subsequentMiddleware) return context.next;
 | 
			
		||||
 | 
			
		||||
  return (...args: Array<any>) => {
 | 
			
		||||
  return (...args: any[]) => {
 | 
			
		||||
    // @ts-ignore
 | 
			
		||||
    context.next(...args as Array<any>);
 | 
			
		||||
    context.next(...args);
 | 
			
		||||
    subsequentMiddleware({
 | 
			
		||||
      ...context,
 | 
			
		||||
      next: nextFactory(context, middlewares, index++),
 | 
			
		||||
    });
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
export function runMiddleware(context: Context): void {
 | 
			
		||||
export function runMiddleware(context: Context) {
 | 
			
		||||
  const { to } = context;
 | 
			
		||||
  const middlewares = [
 | 
			
		||||
    ...((Array.isArray(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) });
 | 
			
		||||
 | 
			
		||||
@ -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 App from './App.vue';
 | 
			
		||||
import './registerServiceWorker';
 | 
			
		||||
import router from './router';
 | 
			
		||||
import store from './store';
 | 
			
		||||
import 'bootstrap-icons/font/bootstrap-icons.scss';
 | 
			
		||||
import './main.scss';
 | 
			
		||||
import { ThemeConfig, writeDarkSwitch } from 'bootstrap-darkmode';
 | 
			
		||||
import { createApp } from "vue";
 | 
			
		||||
import App from "./App.vue";
 | 
			
		||||
import "./registerServiceWorker";
 | 
			
		||||
import router from "./router";
 | 
			
		||||
import store from "./store";
 | 
			
		||||
 | 
			
		||||
export const themeConfig = new ThemeConfig();
 | 
			
		||||
themeConfig.initTheme();
 | 
			
		||||
 | 
			
		||||
createApp(App).use(store).use(router).mount('#app');
 | 
			
		||||
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';
 | 
			
		||||
 | 
			
		||||
export const auth = ({ next }: Context) => {
 | 
			
		||||
  if (!useWebdavStore().currentSession?.isActive)
 | 
			
		||||
    return next({ name: 'Login' });
 | 
			
		||||
export default function auth({ next, router }: Context) {
 | 
			
		||||
  console.log('auth');
 | 
			
		||||
  if (!useWebdavStorage().currentSession?.isActive) return router.push({ name: 'Login' });
 | 
			
		||||
  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 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';
 | 
			
		||||
 | 
			
		||||
const routes: Array<RouteRecordRaw> = [
 | 
			
		||||
@ -21,6 +22,9 @@ const routes: Array<RouteRecordRaw> = [
 | 
			
		||||
    path: '/login',
 | 
			
		||||
    name: 'Login',
 | 
			
		||||
    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;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const useWebdavStore = defineStore('auth', {
 | 
			
		||||
export const useWebdavStorage = defineStore('auth', {
 | 
			
		||||
  state: () => ({
 | 
			
		||||
    sessions: [] as Array<Session>,
 | 
			
		||||
    currentSession: null as null | Session,
 | 
			
		||||
  }),
 | 
			
		||||
  actions: {
 | 
			
		||||
    async login({ user, pass }: Auth): Promise<Session> {
 | 
			
		||||
      try {
 | 
			
		||||
        const client = createClient(
 | 
			
		||||
          `${process.env.VUE_APP_ROOT_WEBDAV ?? ''}/api/dav/files/` as string,
 | 
			
		||||
          {
 | 
			
		||||
            authType: AuthType.Password,
 | 
			
		||||
            username: user as string,
 | 
			
		||||
            password: pass as string,
 | 
			
		||||
          }
 | 
			
		||||
        ) as WebDAVClient;
 | 
			
		||||
        const session = { client, isActive: true } as Session;
 | 
			
		||||
        this.sessions.push(session);
 | 
			
		||||
        this.currentSession = session;
 | 
			
		||||
        return session;
 | 
			
		||||
      } catch (e) {
 | 
			
		||||
        throw 'login failed';
 | 
			
		||||
      }
 | 
			
		||||
    login({ user, pass }: Auth): Promise<Session> {
 | 
			
		||||
      return new Promise((resolve, reject) => {
 | 
			
		||||
        try {
 | 
			
		||||
          const client = createClient(
 | 
			
		||||
            process.env.VUE_APP_ROOT_WEBDAV as string,
 | 
			
		||||
            {
 | 
			
		||||
              authType: AuthType.Digest,
 | 
			
		||||
              username: user as string,
 | 
			
		||||
              password: pass as string,
 | 
			
		||||
            }
 | 
			
		||||
          ) as WebDAVClient;
 | 
			
		||||
          const session = { client, isActive: true } as Session;
 | 
			
		||||
          this.sessions.push(session);
 | 
			
		||||
          this.currentSession = session;
 | 
			
		||||
          resolve(session);
 | 
			
		||||
        } catch (e) {
 | 
			
		||||
          reject(e);
 | 
			
		||||
        }
 | 
			
		||||
      });
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
@ -1,20 +1,11 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <div class="row mx-1 justify-content-center">
 | 
			
		||||
    <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>
 | 
			
		||||
  <h1>Files</h1>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup lang="ts">
 | 
			
		||||
import FileList from '@/components/files/FileList.vue';
 | 
			
		||||
import SideMenu from '@/components/SideMenu.vue';
 | 
			
		||||
import { useWebdavStore } from '@/store/webdav';
 | 
			
		||||
 | 
			
		||||
const store = useWebdavStore();
 | 
			
		||||
<script>
 | 
			
		||||
export default {
 | 
			
		||||
  name: 'Files',
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style scoped></style>
 | 
			
		||||
 | 
			
		||||
@ -2,32 +2,22 @@
 | 
			
		||||
  <h1>Login</h1>
 | 
			
		||||
  user: <input type="text" v-model="user" /><br />
 | 
			
		||||
  pass: <input type="password" v-model="pass" /><br />
 | 
			
		||||
  <button class="btn btn-primary" @click="login">Login</button><br />
 | 
			
		||||
  <span class="error">{{ error }}</span>
 | 
			
		||||
  <button @click="login">Login</button>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup lang="ts">
 | 
			
		||||
import { ref } from 'vue';
 | 
			
		||||
import { useWebdavStore } from '@/store/webdav';
 | 
			
		||||
import { useRouter } from 'vue-router';
 | 
			
		||||
import { useWebdavStorage } from '@/store/webdav';
 | 
			
		||||
 | 
			
		||||
const user = ref('');
 | 
			
		||||
const pass = ref('');
 | 
			
		||||
const error = ref('');
 | 
			
		||||
 | 
			
		||||
const router = useRouter();
 | 
			
		||||
const store = useWebdavStore();
 | 
			
		||||
 | 
			
		||||
const login = async () => {
 | 
			
		||||
  try {
 | 
			
		||||
    await store.login({ user: user.value, pass: pass.value });
 | 
			
		||||
    error.value = '';
 | 
			
		||||
    await router.push({ name: 'Files' });
 | 
			
		||||
  } catch (e) {
 | 
			
		||||
    console.error(e);
 | 
			
		||||
    error.value = e as string;
 | 
			
		||||
  }
 | 
			
		||||
  await store.currentSession?.client.getDirectoryContents(`/`);
 | 
			
		||||
  await useWebdavStorage()
 | 
			
		||||
    .login({ user, pass })
 | 
			
		||||
    .then(async session => {
 | 
			
		||||
      console.log(await session.client.getDirectoryContents('/'));
 | 
			
		||||
    });
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user