Move more stuff to Pankow

This commit is contained in:
Johannes Zellner
2023-02-22 19:31:12 +01:00
parent e7b5cf7b23
commit debfd48236
8 changed files with 67 additions and 282 deletions
-41
View File
@@ -1,41 +0,0 @@
<template>
<div class="cloudron-header">
<div class="cloudron-header-left">
<slot name="left"/>
</div>
<div class="cloudron-header-right">
<slot name="right"/>
</div>
</div>
</template>
<script>
export default {
};
</script>
<style>
.cloudron-header {
width: 100%;
background-color: #f8f8f8;
box-shadow: 0 2px 5px rgba(0,0,0,.1);
display: flex;
justify-content: space-between;
margin-bottom: 10px;
padding: 5px 10px;
}
.cloudron-header-left {
display: block;
margin: auto 0px;
}
.cloudron-header-right {
display: block;
margin: auto 0px;
}
</style>
-93
View File
@@ -1,93 +0,0 @@
<template>
<div class="directory-view">
<div class="directory-view-header">
<div class="directory-view-header-icon"></div>
<div class="directory-view-header-name">Name</div>
<div class="directory-view-header-size">Size</div>
<div class="directory-view-header-modified">Modified</div>
</div>
<div class="directory-view-body-container">
<div class="directory-view-body">
<DirectoryViewListItem v-for="item in items" :item="item"/>
</div>
</div>
</div>
</template>
<script>
import { ref } from 'vue';
import Button from 'primevue/button';
import DirectoryViewListItem from './DirectoryViewListItem.vue';
export default {
name: 'DirectoryView',
data() {
return {
items: []
};
},
components: {
DirectoryViewListItem
},
mounted() {
// fill with fake items
for (let i = 0; i < 100; ++i) {
this.items.push({
type: i < 20 ? 'directory' : 'file',
name: 'Entry ' + i,
size: parseInt(Math.random() * 1000000),
modified: new Date(+(new Date()) - Math.floor(Math.random() * 10000000000)),
});
}
}
};
</script>
<style scoped>
.directory-view {
background-color: white;
display: block;
overflow: hidden;
height: 100%;
width: 100%;
}
.directory-view-header {
padding: 10px;
display: flex;
}
.directory-view-body-container {
overflow: hidden;
height: calc(100% - 38px); /* 38px is the header size */
}
.directory-view-body {
padding: 0 10px;
height: 100%;
overflow: scroll;
}
.directory-view-header-icon {
width: 40px;
}
.directory-view-header-name {
padding-left: 10px;
flex-grow: 1;
}
.directory-view-header-size {
width: 100px;
}
.directory-view-header-modified {
width: 100px;
}
</style>
-77
View File
@@ -1,77 +0,0 @@
<template>
<div class="row">
<div class="col icon"><img :src="item.type === 'file' ? '/mime-types/text-x-plain.svg' : '/mime-types/inode-directory.svg'"/></div>
<div class="col label">{{ item.name }}</div>
<div class="col size">{{ prettyFileSize(item.size) }}</div>
<div class="col modified">{{ prettyDate(item.modified) }}</div>
</div>
</template>
<script>
import { prettyDate, prettyFileSize } from '../utils';
export default {
name: 'DirectoryViewListItem',
props: {
item: Object
},
data() {
return {};
},
methods: {
prettyFileSize,
prettyDate
},
mounted() {
}
};
</script>
<style scoped>
.row {
display: flex;
overflow: hidden;
height: 40px;
width: 100%;
line-height: 40px;
}
.row:hover {
background-color: whitesmoke;
}
.col {
white-space: nowrap;
}
.icon {
height: 40px;
width: 40px;
}
.icon > img {
width: 32px;
height: 32px;
object-fit: cover;
vertical-align: middle;
}
.label {
padding-left: 5px;
flex-grow: 1;
overflow: hidden;
text-overflow: ellipsis;
}
.size {
width: 100px;
}
.modified {
width: 100px;
}
</style>
+26
View File
@@ -0,0 +1,26 @@
<template>
<div class="preview-panel">
<span>Preview for {{ item.name }}</span>
</div>
</template>
<script>
export default {
name: 'PreviewPanel',
props: {
item: Object
}
};
</script>
<style scoped>
.preview-panel {
min-width: 260px;
width: 20%;
padding: 0 30px;
}
</style>
+20 -7
View File
@@ -1,15 +1,16 @@
<template>
<div>
<CloudronHeader>
<NavigationBar>
<template #left>
<span>You are logged in</span>
</template>
<template #right>
<Button label="Logout" @click="onLogout"/>
</template>
</CloudronHeader>
</NavigationBar>
<div class="main-view">
<DirectoryView />
<DirectoryView @selection-changed="onSelectionChanged"/>
<PreviewPanel :item="activeItem"/>
</div>
</div>
</template>
@@ -18,20 +19,31 @@
import Button from 'primevue/button';
import DirectoryView from '../components/DirectoryView.vue';
import CloudronHeader from '../components/CloudronHeader.vue';
import { DirectoryView, NavigationBar } from 'pankow';
import PreviewPanel from '../components/PreviewPanel.vue';
export default {
name: 'Home',
components: {
DirectoryView,
CloudronHeader,
Button
NavigationBar,
Button,
PreviewPanel
},
data() {
return {
activeItem: {}
};
},
methods: {
onLogout() {
delete localStorage.accessToken;
this.$router.push('/login');
},
onSelectionChanged(item) {
console.log('also changed here', item);
this.activeItem = item;
}
}
};
@@ -41,6 +53,7 @@ export default {
<style scoped>
.main-view {
display: flex;
width: 100%;
height: 400px;
}
+11 -64
View File
@@ -1,91 +1,38 @@
<template>
<div class="container">
<img style="margin-top: -84px" src="/logo.png" width="128" height="128" />
<form @submit="onLogin" @submit.prevent>
<h1>Login to <b>Cloudron</b></h1>
<div class="field">
<label for="usernameInput">Username</label>
<InputText id="usernameInput" type="username" v-model="username" :disabled="busy" required/>
</div>
<div class="field">
<label for="passwordInput">Password</label>
<Password input-id="passwordInput" v-model="password" :disabled="busy" toggle-mask :feedback="false" required :class="{ 'p-invalid': error }" input-style="width: 100%"/>
<small v-show="error" :class="{ 'p-invalid': error }">Wrong username or password.</small>
</div>
<div class="field">
<label for="totpTokenInput">2FA Token (if enabled)</label>
<InputMask id="totpTokenInput" v-model="totpToken" :disabled="busy" slotChar="." mask="999999" />
</div>
<div class="action-bar">
<a href="">Reset password</a>
<Button class="submit-button" type="submit" label="Sign in" id="loginButton" :loading="busy" :disabled="busy || !username || !password"/>
</div>
</form>
</div>
<LoginView :login-url="loginUrl" @error="onError" @success="onSuccess"/>
</template>
<script>
import superagent from 'superagent';
import safe from 'safetydance';
// if imported in script setup tag they are also registered with the vue app as components to be used in html
import Button from 'primevue/button';
import InputText from 'primevue/inputtext';
import Password from 'primevue/password';
import InputMask from 'primevue/inputmask';
import { LoginView } from 'pankow';
// can be exposed as env var, see develop.sh
const BASE_URL = import.meta.env.VITE_API_ORIGIN ? 'https://' + import.meta.env.VITE_API_ORIGIN : '';
export default {
components: {
Button, InputText, Password, InputMask
LoginView
},
emits: [ 'success', 'error' ],
data() {
return {
username: '',
password: '',
totpToken: '',
error: '',
busy: false
busy: false,
loginUrl: `${BASE_URL}/api/v1/cloudron/login`
};
},
methods: {
async onLogin() {
this.error = false;
this.busy = true;
const [err, res] = await safe(superagent.post(`${BASE_URL}/api/v1/cloudron/login`).send({ username: this.username, password: this.password, totpToken: this.totpToken }));
this.busy = false;
if (err && err.status === 401) {
this.error = 'Invalid username or password';
this.password = '';
return;
}
if (err) return console.error(err);
localStorage.accessToken = res.body.accessToken;
onError(error) {
console.error('Error loggin in', error);
},
onSuccess(accessToken) {
console.log('Success loggin in');
localStorage.accessToken = accessToken;
this.$router.push('/home');
this.$emit('success', res.body.accessToken);
this.username = '';
this.password = '';
this.totpToken = '';
}
},
mounted() {
setTimeout(function () { document.getElementById('usernameInput').focus(); }, 0);
}
};