Compare commits
2 commits
stable
...
jedi/dev/f
Author | SHA1 | Date | |
---|---|---|---|
a10eb089b4 | |||
4bb75c095e |
7 changed files with 146 additions and 65 deletions
|
@ -8,41 +8,32 @@ import dotenv
|
|||
from django.db import transaction, IntegrityError
|
||||
|
||||
|
||||
class CmdCtx:
|
||||
def yesno(prompt, default=False):
|
||||
if not sys.stdin.isatty():
|
||||
return default
|
||||
yes = {'yes', 'y', 'ye'}
|
||||
no = {'no', 'n'}
|
||||
|
||||
def __init__(self, args):
|
||||
self.args = args
|
||||
if default:
|
||||
yes.add('')
|
||||
else:
|
||||
no.add('')
|
||||
|
||||
def yesno(self, prompt, default=False):
|
||||
if not sys.stdin.isatty() or self.args.noninteractive:
|
||||
return default
|
||||
elif self.args.yes:
|
||||
hint = ' [Y/n] ' if default else ' [y/N] '
|
||||
|
||||
while True:
|
||||
choice = input(prompt + hint).lower()
|
||||
if choice in yes:
|
||||
return True
|
||||
elif self.args.no:
|
||||
elif choice in no:
|
||||
return False
|
||||
yes = {'yes', 'y', 'ye'}
|
||||
no = {'no', 'n'}
|
||||
|
||||
if default:
|
||||
yes.add('')
|
||||
else:
|
||||
no.add('')
|
||||
|
||||
hint = ' [Y/n] ' if default else ' [y/N] '
|
||||
|
||||
while True:
|
||||
choice = input(prompt + hint).lower()
|
||||
if choice in yes:
|
||||
return True
|
||||
elif choice in no:
|
||||
return False
|
||||
else:
|
||||
print('Please respond with "yes" or "no"')
|
||||
print('Please respond with "yes" or "no"')
|
||||
|
||||
|
||||
def configure(ctx):
|
||||
def configure():
|
||||
if not os.path.exists('.env'):
|
||||
if not ctx.yesno("the .env file does not exist, do you want to create it?", default=True):
|
||||
if not yesno("the .env file does not exist, do you want to create it?", default=True):
|
||||
print('Aborting')
|
||||
exit(0)
|
||||
if not os.path.exists('.env.dist'):
|
||||
|
@ -65,7 +56,7 @@ def configure(ctx):
|
|||
current_hosts = os.getenv('ALLOWED_HOSTS')
|
||||
print('Current ALLOWED_HOSTS: {}'.format(current_hosts))
|
||||
|
||||
if ctx.yesno("Do you want to add ALLOWED_HOSTS?"):
|
||||
if yesno("Do you want to add ALLOWED_HOSTS?"):
|
||||
hosts = input("Enter a comma-separated list of allowed hosts: ")
|
||||
joined_hosts = current_hosts + ',' + hosts if current_hosts else hosts
|
||||
dotenv.set_key('.env', 'ALLOWED_HOSTS', joined_hosts)
|
||||
|
@ -76,21 +67,20 @@ def configure(ctx):
|
|||
django.setup()
|
||||
|
||||
if not os.path.exists('db.sqlite3'):
|
||||
if not ctx.yesno("No database found, do you want to create one?", default=True):
|
||||
if not yesno("No database found, do you want to create one?", default=True):
|
||||
print('Aborting')
|
||||
exit(0)
|
||||
|
||||
from django.core.management import call_command
|
||||
call_command('migrate')
|
||||
|
||||
if ctx.yesno("Do you want to create a superuser?"):
|
||||
if yesno("Do you want to create a superuser?"):
|
||||
from django.core.management import call_command
|
||||
call_command('createsuperuser')
|
||||
|
||||
call_command('collectstatic', '--no-input')
|
||||
|
||||
if ctx.yesno("Do you want to import all categories, properties and tags contained in this repository?",
|
||||
default=True):
|
||||
if yesno("Do you want to import all categories, properties and tags contained in this repository?", default=True):
|
||||
from hostadmin.serializers import CategorySerializer, PropertySerializer, TagSerializer
|
||||
from hostadmin.models import ImportedIdentifierSets
|
||||
from hashlib import sha256
|
||||
|
@ -206,7 +196,6 @@ def main():
|
|||
parser = ArgumentParser(description='Toolshed Server Configuration')
|
||||
parser.add_argument('--yes', '-y', help='Answer yes to all questions', action='store_true')
|
||||
parser.add_argument('--no', '-n', help='Answer no to all questions', action='store_true')
|
||||
parser.add_argument('--noninteractive', '-x', help="Run in noninteractive mode", action='store_true')
|
||||
parser.add_argument('cmd', help='Command', default='configure', nargs='?')
|
||||
args = parser.parse_args()
|
||||
|
||||
|
@ -214,10 +203,8 @@ def main():
|
|||
print('Error: --yes and --no are mutually exclusive')
|
||||
exit(1)
|
||||
|
||||
ctx = CmdCtx(args)
|
||||
|
||||
if args.cmd == 'configure':
|
||||
configure(ctx)
|
||||
configure()
|
||||
elif args.cmd == 'reset':
|
||||
reset()
|
||||
elif args.cmd == 'testdata':
|
||||
|
|
|
@ -6,6 +6,11 @@
|
|||
<a class="sidebar-toggle d-flex" @click="toggleSidebar">
|
||||
<i class="hamburger align-self-center"></i>
|
||||
</a>
|
||||
<div class="navbar-collapse collapse">
|
||||
<ul class="navbar-nav navbar-align">
|
||||
<UserDropdown/>
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
<slot></slot>
|
||||
<Footer/>
|
||||
|
@ -14,12 +19,15 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
/* global closeAllDropdowns */
|
||||
import Footer from "@/components/Footer.vue";
|
||||
import Sidebar from "@/components/Sidebar.vue";
|
||||
import UserDropdown from "@/components/UserDropdown.vue";
|
||||
|
||||
export default {
|
||||
name: 'BaseLayout',
|
||||
components: {
|
||||
UserDropdown,
|
||||
Footer,
|
||||
Sidebar
|
||||
},
|
||||
|
@ -68,6 +76,10 @@ export default {
|
|||
box-shadow: 0 0 2rem var(--bs-shadow)
|
||||
}
|
||||
|
||||
.navbar-align {
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.navbar-bg {
|
||||
background: var(--bs-white);
|
||||
}
|
||||
|
|
47
frontend/src/components/UserDropdown.vue
Normal file
47
frontend/src/components/UserDropdown.vue
Normal file
|
@ -0,0 +1,47 @@
|
|||
<template>
|
||||
<li class="nav-item dropdown">
|
||||
<a class="nav-icon dropdown-toggle d-inline-block d-sm-none" href="#" @click="toggleDropdown">
|
||||
<i class="align-middle" data-feather="settings"></i>
|
||||
<b-icon-person class="bi-valign-middle"></b-icon-person>
|
||||
</a>
|
||||
|
||||
<a class="nav-link dropdown-toggle d-none d-sm-inline-block" href="#" @click="toggleDropdown">
|
||||
<span class="text-dark">
|
||||
{{ username }}
|
||||
</span>
|
||||
</a>
|
||||
|
||||
<div class="dropdown-menu dropdown-menu-right" id="userDropdown">
|
||||
<a class="dropdown-item" href="#" @click="logout"> Log out</a>
|
||||
</div>
|
||||
</li>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import * as BIcons from 'bootstrap-icons-vue';
|
||||
import {mapMutations, mapState} from "vuex";
|
||||
|
||||
export default {
|
||||
name: "UserDropdown",
|
||||
components: {
|
||||
...BIcons
|
||||
},
|
||||
methods: {
|
||||
...mapMutations(['logout']),
|
||||
toggleDropdown() {
|
||||
closeAllDropdowns();
|
||||
document.getElementById("userDropdown").classList.toggle("show");
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState(['user']),
|
||||
username() {
|
||||
return this.user;
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
|
@ -2,6 +2,7 @@ import {createRouter, createWebHistory} from 'vue-router'
|
|||
import Index from '@/views/Index.vue';
|
||||
import Login from '@/views/Login.vue';
|
||||
import Register from '@/views/Register.vue';
|
||||
import store from '@/store';
|
||||
|
||||
|
||||
const routes = [
|
||||
|
@ -20,7 +21,7 @@ const router = createRouter({
|
|||
router.beforeEach((to/*, from*/) => {
|
||||
// instead of having to check every route record with
|
||||
// to.matched.some(record => record.meta.requiresAuth)
|
||||
if (to.meta.requiresAuth && false) {
|
||||
if (to.meta.requiresAuth && !store.getters.isLoggedIn) {
|
||||
// this route requires auth, check if logged in
|
||||
// if not, redirect to login page.
|
||||
console.log("Not logged in, redirecting to login page")
|
||||
|
|
|
@ -76,6 +76,12 @@ export default createStore({
|
|||
commit('setRemember', remember);
|
||||
const data = await dispatch('lookupServer', {username}).then(servers => new ServerSet(servers, state.unreachable_neighbors))
|
||||
.then(set => set.post(getters.nullAuth, '/auth/token/', {username, password}))
|
||||
//const data = await fetch('/auth/token/', {
|
||||
// method: 'POST',
|
||||
// headers: {'Content-Type': 'application/json'},
|
||||
// body: JSON.stringify({username: username, password: password}),
|
||||
// credentials: 'omit'
|
||||
//}).then(r => r.json())
|
||||
if (data.token && data.key) {
|
||||
commit('setToken', data.token);
|
||||
commit('setUser', username);
|
||||
|
@ -89,6 +95,12 @@ export default createStore({
|
|||
},
|
||||
async lookupServer({state}, {username}) {
|
||||
const domain = username.split('@')[1]
|
||||
if (domain === 'localhost')
|
||||
return ['localhost:5173'];
|
||||
if (domain === 'example.com')
|
||||
return ['localhost:5173'];
|
||||
if (domain === 'example.jedi')
|
||||
return ['localhost:5173'];
|
||||
const request = '_toolshed-server._tcp.' + domain + '.'
|
||||
return await state.resolver.query(request, 'SRV').then(
|
||||
(result) => result.map(
|
||||
|
|
|
@ -24,7 +24,9 @@
|
|||
<label class="form-label">Username</label>
|
||||
<input class="form-control form-control-lg" type="text"
|
||||
name="username" placeholder="Enter your username"
|
||||
:class="errors.username?['is-invalid']:[]"
|
||||
v-model="username"/>
|
||||
<div class="invalid-feedback">{{ errors.username }}</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Password</label>
|
||||
|
@ -65,6 +67,7 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import {mapActions, mapMutations} from 'vuex';
|
||||
import router from "@/router";
|
||||
|
||||
export default {
|
||||
|
@ -74,15 +77,15 @@ export default {
|
|||
msg: 'Welcome to ' + location.hostname,
|
||||
username: '',
|
||||
password: '',
|
||||
remember: false
|
||||
remember: false,
|
||||
errors: {
|
||||
username: '',
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
setRemember(remember) {
|
||||
},
|
||||
login(data) {
|
||||
return true;
|
||||
},
|
||||
...mapActions(['login']),
|
||||
...mapMutations(['setRemember']),
|
||||
async do_login(e) {
|
||||
e.preventDefault();
|
||||
if (await this.login({username: this.username, password: this.password, remember: this.remember})) {
|
||||
|
@ -92,11 +95,24 @@ export default {
|
|||
await router.push({path: '/'});
|
||||
}
|
||||
} else {
|
||||
if (!this.username.includes('@')) {
|
||||
this.errors.username = 'Username should be in the form of user@domain';
|
||||
}
|
||||
this.msg = 'Invalid username or password';
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
},
|
||||
mounted() {
|
||||
if (this.$route.query.redirect) {
|
||||
this.msg = 'You must be logged in to access that page';
|
||||
}
|
||||
const user = localStorage.getItem('user');
|
||||
if (user) {
|
||||
this.username = user;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -44,6 +44,7 @@
|
|||
<div :class="errors.email?['mb-3','is-invalid']:['mb-3']">
|
||||
<label class="form-label">Email</label>
|
||||
<input class="form-control form-control-lg" type="email"
|
||||
:class="errors.email?['is-invalid']:[]"
|
||||
v-model="form.email" placeholder="Enter your email"/>
|
||||
<div class="invalid-feedback">{{ errors.email }}</div>
|
||||
</div>
|
||||
|
@ -51,6 +52,7 @@
|
|||
<div :class="errors.password?['mb-3','is-invalid']:['mb-3']">
|
||||
<label class="form-label">Password</label>
|
||||
<input class="form-control form-control-lg" type="password"
|
||||
:class="errors.password?['is-invalid']:[]"
|
||||
v-model="form.password" placeholder="Enter your password"/>
|
||||
<div class="invalid-feedback">{{ errors.password }}</div>
|
||||
</div>
|
||||
|
@ -58,6 +60,7 @@
|
|||
<div :class="errors.password2?['mb-3','is-invalid']:['mb-3']">
|
||||
<label class="form-label">Password Check</label>
|
||||
<input class="form-control form-control-lg" type="password"
|
||||
:class="errors.password2?['is-invalid']:[]"
|
||||
v-model="password2" placeholder="Enter your password again"/>
|
||||
<div class="invalid-feedback">{{ errors.password2 }}</div>
|
||||
</div>
|
||||
|
@ -86,6 +89,10 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import {mapActions} from 'vuex';
|
||||
import {ServerSet, createNullAuth} from '../federation';
|
||||
import NeighborsCache from '../neigbors';
|
||||
|
||||
export default {
|
||||
name: 'Register',
|
||||
data() {
|
||||
|
@ -109,6 +116,7 @@ export default {
|
|||
}
|
||||
},
|
||||
methods: {
|
||||
...mapActions(['lookupServer']),
|
||||
do_register() {
|
||||
console.log('do_register');
|
||||
console.log(this.form);
|
||||
|
@ -118,36 +126,34 @@ export default {
|
|||
} else {
|
||||
this.errors.password2 = null;
|
||||
}
|
||||
fetch('/auth/register/', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(this.form)
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.errors) {
|
||||
console.error('Error:', data.errors);
|
||||
this.errors = data.errors;
|
||||
const user = this.form.username + '@' + this.form.domain;
|
||||
var unreachable = new NeighborsCache();
|
||||
var nullAuth = createNullAuth({})
|
||||
this.lookupServer({username: user}).then(servers => new ServerSet(servers, unreachable))
|
||||
.then(set => set.post(nullAuth, '/auth/register/', this.form))
|
||||
.then(response => {
|
||||
if (response.errors) {
|
||||
console.error('Error:', response.errors);
|
||||
this.errors = response.errors;
|
||||
return;
|
||||
}
|
||||
console.log('Success:', data);
|
||||
console.log('Success:', response);
|
||||
this.msg = 'Success';
|
||||
localStorage.setItem('user', user);
|
||||
this.$router.push('/login');
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('Error:', error);
|
||||
this.msg = 'Error';
|
||||
});
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
fetch('/api/domains/')
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
this.domains = data;
|
||||
});
|
||||
try {
|
||||
fetch('/local/domains')
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
this.domains = data;
|
||||
});
|
||||
} catch (e) {
|
||||
console.error('Error:', e);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
Loading…
Reference in a new issue