Move more stuff to Pankow
This commit is contained in:
Generated
+9
@@ -10,6 +10,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"combokeys": "^3.0.1",
|
"combokeys": "^3.0.1",
|
||||||
"filesize": "^10.0.6",
|
"filesize": "^10.0.6",
|
||||||
|
"pankow": "^0.0.1",
|
||||||
"primeicons": "^6.0.1",
|
"primeicons": "^6.0.1",
|
||||||
"primevue": "^3.23.0",
|
"primevue": "^3.23.0",
|
||||||
"safetydance": "^2.2.0",
|
"safetydance": "^2.2.0",
|
||||||
@@ -836,6 +837,14 @@
|
|||||||
"wrappy": "1"
|
"wrappy": "1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/pankow": {
|
||||||
|
"version": "0.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/pankow/-/pankow-0.0.1.tgz",
|
||||||
|
"integrity": "sha512-v3d43trRCqEd63aWPK8c6YhY+7dQ+SiuwZEhbEA6kN4uCNyONf8LA7icBZzXKwEo3P4+Upul7oIBJqV+MEt4BQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"primevue": "^3.23.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/path-parse": {
|
"node_modules/path-parse": {
|
||||||
"version": "1.0.7",
|
"version": "1.0.7",
|
||||||
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
|
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"combokeys": "^3.0.1",
|
"combokeys": "^3.0.1",
|
||||||
"filesize": "^10.0.6",
|
"filesize": "^10.0.6",
|
||||||
|
"pankow": "^0.0.1",
|
||||||
"primeicons": "^6.0.1",
|
"primeicons": "^6.0.1",
|
||||||
"primevue": "^3.23.0",
|
"primevue": "^3.23.0",
|
||||||
"safetydance": "^2.2.0",
|
"safetydance": "^2.2.0",
|
||||||
|
|||||||
@@ -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>
|
|
||||||
@@ -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>
|
|
||||||
@@ -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>
|
|
||||||
@@ -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
@@ -1,15 +1,16 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<CloudronHeader>
|
<NavigationBar>
|
||||||
<template #left>
|
<template #left>
|
||||||
<span>You are logged in</span>
|
<span>You are logged in</span>
|
||||||
</template>
|
</template>
|
||||||
<template #right>
|
<template #right>
|
||||||
<Button label="Logout" @click="onLogout"/>
|
<Button label="Logout" @click="onLogout"/>
|
||||||
</template>
|
</template>
|
||||||
</CloudronHeader>
|
</NavigationBar>
|
||||||
<div class="main-view">
|
<div class="main-view">
|
||||||
<DirectoryView />
|
<DirectoryView @selection-changed="onSelectionChanged"/>
|
||||||
|
<PreviewPanel :item="activeItem"/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -18,20 +19,31 @@
|
|||||||
|
|
||||||
import Button from 'primevue/button';
|
import Button from 'primevue/button';
|
||||||
|
|
||||||
import DirectoryView from '../components/DirectoryView.vue';
|
import { DirectoryView, NavigationBar } from 'pankow';
|
||||||
import CloudronHeader from '../components/CloudronHeader.vue';
|
|
||||||
|
import PreviewPanel from '../components/PreviewPanel.vue';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'Home',
|
name: 'Home',
|
||||||
components: {
|
components: {
|
||||||
DirectoryView,
|
DirectoryView,
|
||||||
CloudronHeader,
|
NavigationBar,
|
||||||
Button
|
Button,
|
||||||
|
PreviewPanel
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
activeItem: {}
|
||||||
|
};
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
onLogout() {
|
onLogout() {
|
||||||
delete localStorage.accessToken;
|
delete localStorage.accessToken;
|
||||||
this.$router.push('/login');
|
this.$router.push('/login');
|
||||||
|
},
|
||||||
|
onSelectionChanged(item) {
|
||||||
|
console.log('also changed here', item);
|
||||||
|
this.activeItem = item;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -41,6 +53,7 @@ export default {
|
|||||||
<style scoped>
|
<style scoped>
|
||||||
|
|
||||||
.main-view {
|
.main-view {
|
||||||
|
display: flex;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 400px;
|
height: 400px;
|
||||||
}
|
}
|
||||||
|
|||||||
+11
-64
@@ -1,91 +1,38 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="container">
|
<LoginView :login-url="loginUrl" @error="onError" @success="onSuccess"/>
|
||||||
<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>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
import superagent from 'superagent';
|
import { LoginView } from 'pankow';
|
||||||
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';
|
|
||||||
|
|
||||||
// can be exposed as env var, see develop.sh
|
// 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 : '';
|
const BASE_URL = import.meta.env.VITE_API_ORIGIN ? 'https://' + import.meta.env.VITE_API_ORIGIN : '';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
Button, InputText, Password, InputMask
|
LoginView
|
||||||
},
|
},
|
||||||
emits: [ 'success', 'error' ],
|
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
username: '',
|
username: '',
|
||||||
password: '',
|
password: '',
|
||||||
totpToken: '',
|
totpToken: '',
|
||||||
error: '',
|
error: '',
|
||||||
busy: false
|
busy: false,
|
||||||
|
loginUrl: `${BASE_URL}/api/v1/cloudron/login`
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
async onLogin() {
|
onError(error) {
|
||||||
this.error = false;
|
console.error('Error loggin in', error);
|
||||||
this.busy = true;
|
},
|
||||||
|
onSuccess(accessToken) {
|
||||||
const [err, res] = await safe(superagent.post(`${BASE_URL}/api/v1/cloudron/login`).send({ username: this.username, password: this.password, totpToken: this.totpToken }));
|
console.log('Success loggin in');
|
||||||
|
|
||||||
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;
|
|
||||||
|
|
||||||
|
localStorage.accessToken = accessToken;
|
||||||
this.$router.push('/home');
|
this.$router.push('/home');
|
||||||
|
|
||||||
this.$emit('success', res.body.accessToken);
|
|
||||||
|
|
||||||
this.username = '';
|
|
||||||
this.password = '';
|
|
||||||
this.totpToken = '';
|
|
||||||
}
|
}
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
setTimeout(function () { document.getElementById('usernameInput').focus(); }, 0);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user