This commit is contained in:
j3d1 2023-05-10 23:28:55 +02:00
parent ff372205d9
commit e89c261f56
9 changed files with 550 additions and 7 deletions

29
frontend/README.md Normal file
View file

@ -0,0 +1,29 @@
# frontend
This template should help get you started developing with Vue 3 in Vite.
## Recommended IDE Setup
[VSCode](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur) + [TypeScript Vue Plugin (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin).
## Customize configuration
See [Vite Configuration Reference](https://vitejs.dev/config/).
## Project Setup
```sh
npm install
```
### Compile and Hot-Reload for Development
```sh
npm run dev
```
### Compile and Minify for Production
```sh
npm run build
```

View file

@ -3,13 +3,23 @@
<template>
<router-view></router-view>
<!-- TODO UI für Freunde liste, add, remove -->
</template>
<script>
import {mapMutations} from 'vuex';
import store from '@/store';
export default {
name: 'App'
name: 'App',
methods: {
...mapMutations(['init'])
},
beforeCreate () {
store.commit('init')
}
}
</script>

View file

@ -35,6 +35,7 @@ class FallBackResolver {
const key = domain + ':' + type;
if (key in this._cache && this._cache[key].time > Date.now() - 1000 * 60 * 60) {
const age_seconds = Math.ceil(Date.now() / 1000 - this._cache[key].time / 1000);
console.log('cache hit', key, this._cache[key].ttl - age_seconds);
return [this._cache[key].data];
}
const result = await query(
@ -47,6 +48,7 @@ class FallBackResolver {
const first = result.answers[0];
this._cache[key] = {time: Date.now(), ...first}; // TODO hadle multiple answers
localStorage.setItem('dns-cache', JSON.stringify(this._cache));
console.log('cache miss', key, first.ttl);
return [first.data];
}
}

View file

@ -98,7 +98,9 @@ export default createStore({
this.commit('setToken', token);
if (keypair) {
this.commit('setKey', keypair)
} else {
}
router.push('/');
}
state.cache_loaded = true;
}
@ -118,9 +120,16 @@ export default createStore({
} else {
return false;
}
},
async lookupServer({state}, {username}) {
const domain = username.split('@')[1]
if (domain === 'example.eleon')
return ['10.23.42.186:8000'];
if (domain === 'localhost')
return ['127.0.0.1:8000'];
if (domain === 'example.com')
return ['10.23.42.128:8000'];
const request = '_toolshed-server._tcp.' + domain + '.'
return await state.resolver.query(request, 'SRV').then(
(result) => result.map(

View file

@ -5,7 +5,16 @@
<h1 class="h3 mb-3">Dashboard</h1>
<div class="row">
<div class="col-12">
<div class="card">
<div class="card-header">
<h5 class="card-title mb-0">Empty card</h5>
</div>
<div class="card-body">
<div class="logo">
<img src="/src/assets/icons/toolshed-48x48.png" alt="Toolshed logo">
</div>
</div>
</div>
</div>
</div>
</div>

View file

@ -2,6 +2,7 @@
<BaseLayout>
<main class="content">
<div class="container-fluid p-0">
<h1 class="h3 mb-3">Inventory Own & Friends"</h1>
<div class="row">
<div class="col-12 col-xl-6">
<div class="card">
@ -28,6 +29,7 @@
<td>
<router-link :to="`/inventory/${item.id}`">{{ item.name }}</router-link>
</td>
<td>{{ item.owner }}</td>
<td class="d-none d-md-table-cell">
<span class="badge bg-secondary text-white">{{ item.availability_policy }}</span>
</td>
@ -75,7 +77,7 @@
</div>
<div class="card">
<button class="btn" @click="fetchInventoryItems">Refresh</button>
<button class="btn" @click="getInventoryItems">Refresh</button>
<router-link to="/inventory/new" class="btn btn-primary">Add</router-link>
</div>
</div>
@ -102,14 +104,29 @@ export default {
...BIcons
},
computed: {
...mapGetters(["inventory_items", "loaded_items"]),
...mapState(["user"]),
...mapGetters(["inventory_items"]),
username() {
return this.$route.params.username
}
},
methods: {
...mapActions(["fetchInventoryItems", "deleteInventoryItem"]),
...mapActions(["apiFederatedGet", "getFriends", "getFriendServer"]),
...mapMutations(["setInventoryItems"]),
async getInventoryItems() {
try {
const servers = await this.getFriends().then(friends => friends.map(friend => this.getFriendServer({username: friend})))
const urls = servers.map(server => server.then(s => `http://${s}/api/inventory_items/`))
urls.map(url => url.then(u => this.apiFederatedGet(u).then(items => {
this.setInventoryItems({url: u, items})
}).catch(e => {
}))) // TODO: handle error
} catch (e) {
console.error(e)
}
},
},
async mounted() {
await this.fetchInventoryItems()
await this.getInventoryItems()
}
}
</script>

View file

@ -0,0 +1,257 @@
<template>
<BaseLayout>
<main class="content">
<div class="container-fluid p-0">
<h1 class="h3 mb-3">Profile</h1>
<div class="row">
<div class="col-md-4 col-xl-3">
<div class="card mb-3">
<div class="card-header">
<h5 class="card-title mb-0">Profile Details</h5>
</div>
<div class="card-body text-center">
<!--<img src="/static/assets/img/avatars/avatar.png"
alt="Christina Mason" class="img-fluid rounded-circle mb-2" width="128"
height="128"/>-->
<h5 class="card-title mb-0">
{{ user.username }}
</h5>
<div class="text-muted mb-2">
{{ user.email }}
</div>
<div>
<a class="btn btn-primary btn-sm" href="#">Follow</a>
<a class="btn btn-primary btn-sm" href="#"><span
data-feather="message-square"></span> Message</a>
</div>
</div>
<!--{% if user.bio %}-->
<hr class="my-0"/>
<div class="card-body">
<h5 class="h6 card-title">Bio</h5>
<div class="text-muted mb-2">
{{ user.bio }}
</div>
</div>
<!--{% endif %}-->
<hr class="my-0"/>
<div class="card-body">
<h5 class="h6 card-title">Skills</h5>
<a href="#" class="badge bg-primary mr-1 my-1">HTML</a>
<a href="#" class="badge bg-primary mr-1 my-1">JavaScript</a>
<a href="#" class="badge bg-primary mr-1 my-1">Sass</a>
<a href="#" class="badge bg-primary mr-1 my-1">Angular</a>
<a href="#" class="badge bg-primary mr-1 my-1">Vue</a>
<a href="#" class="badge bg-primary mr-1 my-1">React</a>
<a href="#" class="badge bg-primary mr-1 my-1">Redux</a>
<a href="#" class="badge bg-primary mr-1 my-1">UI</a>
<a href="#" class="badge bg-primary mr-1 my-1">UX</a>
</div>
<hr class="my-0"/>
<div class="card-body">
<h5 class="h6 card-title">About</h5>
<ul class="list-unstyled mb-0">
<!--{% if user.location %}-->
<li class="mb-1"><span data-feather="home" class="feather-sm mr-1"></span> Lives
in <a href="#">{{ user.location }}</a></li>
<!--{% endif %}-->
<li class="mb-1"><span data-feather="briefcase" class="feather-sm mr-1"></span>
Works at <a href="#">GitHub</a></li>
<li class="mb-1"><span data-feather="map-pin" class="feather-sm mr-1"></span>
From <a href="#">Boston</a></li>
</ul>
</div>
<hr class="my-0"/>
<div class="card-body">
<h5 class="h6 card-title">Elsewhere</h5>
<ul class="list-unstyled mb-0">
<li class="mb-1"><span class="fas fa-globe fa-fw mr-1"></span> <a href="#">staciehall.co</a>
</li>
<li class="mb-1"><span class="fab fa-twitter fa-fw mr-1"></span> <a
href="#">Twitter</a>
</li>
<li class="mb-1"><span class="fab fa-facebook fa-fw mr-1"></span> <a href="#">Facebook</a>
</li>
<li class="mb-1"><span class="fab fa-instagram fa-fw mr-1"></span> <a href="#">Instagram</a>
</li>
<li class="mb-1"><span class="fab fa-linkedin fa-fw mr-1"></span> <a href="#">LinkedIn</a>
</li>
</ul>
</div>
</div>
</div>
<div class="col-md-8 col-xl-9">
<div class="card">
<div class="card-header">
<h5 class="card-title mb-0">Activities</h5>
</div>
<div class="card-body h-100">
<div class="d-flex align-items-start">
<!--<img src="/static/assets/img/avatars/avatar-5.png" width="36" height="36"
class="rounded-circle mr-2" alt="Vanessa Tucker">-->
<div class="flex-grow-1">
<small class="float-right text-navy">5m ago</small>
<strong>Vanessa Tucker</strong> started following <strong>Christina
Mason</strong><br/>
<small class="text-muted">Today 7:51 pm</small><br/>
</div>
</div>
<hr/>
<div class="d-flex align-items-start">
<!--<img src="/static/assets/img/avatars/avatar.png" width="36" height="36"
class="rounded-circle mr-2" alt="Charles Hall">-->
<div class="flex-grow-1">
<small class="float-right text-navy">30m ago</small>
<strong>Charles Hall</strong> posted something on <strong>Christina
Mason</strong>'s timeline<br/>
<small class="text-muted">Today 7:21 pm</small>
<div class="border text-sm text-muted p-2 mt-1">
Etiam rhoncus. Maecenas tempus, tellus eget condimentum rhoncus, sem
quam semper libero, sit amet adipiscing sem neque sed ipsum. Nam quam
nunc, blandit vel, luctus
pulvinar, hendrerit id, lorem. Maecenas nec odio et ante tincidunt
tempus. Donec vitae sapien ut libero venenatis faucibus. Nullam quis
ante.
</div>
<a href="#" class="btn btn-sm btn-danger mt-1"><i class="feather-sm"
data-feather="heart"></i>
Like</a>
</div>
</div>
<hr/>
<div class="d-flex align-items-start">
<!--<img src="/static/assets/img/avatars/avatar-4.png" width="36" height="36"
class="rounded-circle mr-2" alt="Christina Mason">-->
<div class="flex-grow-1">
<small class="float-right text-navy">1h ago</small>
<strong>Christina Mason</strong> posted a new blog<br/>
<small class="text-muted">Today 6:35 pm</small>
</div>
</div>
<hr/>
<div class="d-flex align-items-start">
<!--<img src="/static/assets/img/avatars/avatar-2.png" width="36" height="36"
class="rounded-circle mr-2" alt="William Harris">-->
<div class="flex-grow-1">
<small class="float-right text-navy">3h ago</small>
<strong>William Harris</strong> posted two photos on <strong>Christina
Mason</strong>'s timeline<br/>
<small class="text-muted">Today 5:12 pm</small>
<div class="row g-0 mt-1">
<div class="col-6 col-md-4 col-lg-4 col-xl-3">
<!--<img src="/static/assets/img/photos/unsplash-1.jpg"
class="img-fluid pr-2" alt="Unsplash">-->
</div>
<div class="col-6 col-md-4 col-lg-4 col-xl-3">
<!--<img src="/static/assets/img/photos/unsplash-2.jpg"
class="img-fluid pr-2" alt="Unsplash">-->
</div>
</div>
<a href="#" class="btn btn-sm btn-danger mt-1"><i class="feather-sm"
data-feather="heart"></i>
Like</a>
</div>
</div>
<hr/>
<div class="d-flex align-items-start">
<!--<img src="/static/assets/img/avatars/avatar-2.png" width="36" height="36"
class="rounded-circle mr-2" alt="William Harris">-->
<div class="flex-grow-1">
<small class="float-right text-navy">1d ago</small>
<strong>William Harris</strong> started following <strong>Christina
Mason</strong><br/>
<small class="text-muted">Yesterday 3:12 pm</small>
<div class="d-flex align-items-start mt-1">
<a class="pr-3" href="#">
<!--<img src="/static/assets/img/avatars/avatar-4.png" width="36"
height="36" class="rounded-circle mr-2" alt="Christina Mason">-->
</a>
<div class="flex-grow-1">
<div class="border text-sm text-muted p-2 mt-1">
Nam quam nunc, blandit vel, luctus pulvinar, hendrerit id,
lorem. Maecenas nec odio et ante tincidunt tempus.
</div>
</div>
</div>
</div>
</div>
<hr/>
<div class="d-flex align-items-start">
<!--<img src="/static/assets/img/avatars/avatar-4.png" width="36" height="36"
class="rounded-circle mr-2" alt="Christina Mason">-->
<div class="flex-grow-1">
<small class="float-right text-navy">1d ago</small>
<strong>Christina Mason</strong> posted a new blog<br/>
<small class="text-muted">Yesterday 2:43 pm</small>
</div>
</div>
<hr/>
<div class="d-flex align-items-start">
<!--<img src="/static/assets/img/avatars/avatar.png" width="36" height="36"
class="rounded-circle mr-2" alt="Charles Hall">-->
<div class="flex-grow-1">
<small class="float-right text-navy">1d ago</small>
<strong>Charles Hall</strong> started following <strong>Christina
Mason</strong><br/>
<small class="text-muted">Yesterdag 1:51 pm</small>
</div>
</div>
<hr/>
<a href="#" class="btn btn-primary btn-block">Load more</a>
</div>
</div>
</div>
</div>
</div>
</main>
</BaseLayout>
</template>
<script>
import BaseLayout from "@/components/BaseLayout.vue";
export default {
name: 'Profile',
data() {
return {
user: {
name: 'John Doe',
avatar: '/static/assets/img/avatars/avatar.png',
cover: '/static/assets/img/photos/unsplash-1.jpg',
occupation: 'Frontend Developer',
company: 'Facebook Inc.',
email: 'foo@bar.com',
phone: '+12 345 678 001',
address: 'Boulevard of Broken Dreams, 1234',
}
}
},
components: {BaseLayout},
}
</script>
<style scoped>
</style>

View file

@ -0,0 +1,209 @@
<template>
<BaseLayout>
<main class="content">
<div class="container-fluid p-0">
<h1 class="h3 mb-3">Settings</h1>
<div class="row">
<div class="col-md-3 col-xl-2">
<div class="card">
<div class="card-header">
<h5 class="card-title mb-0">Profile Settings</h5>
</div>
<div class="list-group list-group-flush" role="tablist">
<a class="list-group-item list-group-item-action active" data-toggle="list"
href="#account" role="tab">
Account
</a>
<a class="list-group-item list-group-item-action" data-toggle="list"
href="#password" role="tab">
Password
</a>
<a class="list-group-item list-group-item-action" data-toggle="list" href="#"
role="tab">
Privacy and safety
</a>
<a class="list-group-item list-group-item-action" data-toggle="list" href="#"
role="tab">
Email notifications
</a>
<a class="list-group-item list-group-item-action" data-toggle="list" href="#"
role="tab">
Web notifications
</a>
<a class="list-group-item list-group-item-action" data-toggle="list" href="#"
role="tab">
Widgets
</a>
<a class="list-group-item list-group-item-action" data-toggle="list" href="#"
role="tab">
Your data
</a>
<a class="list-group-item list-group-item-action" data-toggle="list" href="#"
role="tab">
Delete account
</a>
</div>
</div>
</div>
<div class="col-md-9 col-xl-10">
<div class="tab-content">
<div class="tab-pane fade show active" id="account" role="tabpanel">
<div class="card">
<div class="card-header">
<h5 class="card-title mb-0">Public info</h5>
</div>
<div class="card-body">
<form>
<div class="row">
<div class="col-md-8">
<div class="mb-3">
<label class="form-label"
for="inputUsername">Username</label>
<input type="text" class="form-control" id="inputUsername"
placeholder="Username">
</div>
<div class="mb-3">
<label class="form-label"
for="inputUsername">Biography</label>
<textarea rows="2" class="form-control" id="inputBio"
placeholder="Tell something about yourself"></textarea>
</div>
</div>
<div class="col-md-4">
<div class="text-center">
<img alt="Charles Hall"
src="/static/assets/img/avatars/avatar.png"
class="rounded-circle img-responsive mt-2" width="128"
height="128"/>
<div class="mt-2">
<span class="btn btn-primary"><i
class="fas fa-upload"></i> Upload</span>
</div>
<small>For best results, use an image at least 128px by
128px in .jpg format</small>
</div>
</div>
</div>
<button type="submit" class="btn btn-primary">Save changes</button>
</form>
</div>
</div>
<div class="card">
<div class="card-header">
<h5 class="card-title mb-0">Private info</h5>
</div>
<div class="card-body">
<form>
<div class="row">
<div class="mb-3 col-md-6">
<label class="form-label" for="inputFirstName">First
name</label>
<input type="text" class="form-control" id="inputFirstName"
placeholder="First name">
</div>
<div class="mb-3 col-md-6">
<label class="form-label" for="inputLastName">Last name</label>
<input type="text" class="form-control" id="inputLastName"
placeholder="Last name">
</div>
</div>
<div class="mb-3">
<label class="form-label" for="inputEmail4">Email</label>
<input type="email" class="form-control" id="inputEmail4"
placeholder="Email">
</div>
<div class="mb-3">
<label class="form-label" for="inputAddress">Address</label>
<input type="text" class="form-control" id="inputAddress"
placeholder="1234 Main St">
</div>
<div class="mb-3">
<label class="form-label" for="inputAddress2">Address 2</label>
<input type="text" class="form-control" id="inputAddress2"
placeholder="Apartment, studio, or floor">
</div>
<div class="row">
<div class="mb-3 col-md-6">
<label class="form-label" for="inputCity">City</label>
<input type="text" class="form-control" id="inputCity">
</div>
<div class="mb-3 col-md-4">
<label class="form-label" for="inputState">State</label>
<select id="inputState" class="form-control">
<option selected>Choose...</option>
<option>...</option>
</select>
</div>
<div class="mb-3 col-md-2">
<label class="form-label" for="inputZip">Zip</label>
<input type="text" class="form-control" id="inputZip">
</div>
</div>
<button type="submit" class="btn btn-primary">Save changes</button>
</form>
</div>
</div>
</div>
<div class="tab-pane fade" id="password" role="tabpanel">
<div class="card">
<div class="card-body">
<h5 class="card-title">Password</h5>
<form>
<div class="mb-3">
<label class="form-label" for="inputPasswordCurrent">Current
password</label>
<input type="password" class="form-control"
id="inputPasswordCurrent">
<small><a href="#">Forgot your password?</a></small>
</div>
<div class="mb-3">
<label class="form-label" for="inputPasswordNew">New
password</label>
<input type="password" class="form-control" id="inputPasswordNew">
</div>
<div class="mb-3">
<label class="form-label" for="inputPasswordNew2">Verify
password</label>
<input type="password" class="form-control" id="inputPasswordNew2">
</div>
<button type="submit" class="btn btn-primary">Save changes</button>
</form>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</main>
</BaseLayout>
</template>
<script>
import BaseLayout from "@/components/BaseLayout.vue";
export default {
name: 'Settings',
components: {BaseLayout},
}
</script>
<style scoped>
</style>

View file

@ -3,6 +3,7 @@ import {fileURLToPath, URL} from 'node:url'
import {defineConfig} from 'vite'
import vue from '@vitejs/plugin-vue'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [vue()],
resolve: {