Added Roles, User Management, fixed bugs, added FlexTable component (should be renamed to GridTable), other table components and test code should be removed down the line, added admin function to fix broken data structures.
This commit is contained in:
@@ -1,269 +1,242 @@
|
||||
|
||||
<script>
|
||||
import {Meteor} from "meteor/meteor";
|
||||
import {Route, router} from 'tinro';
|
||||
import {onMount} from 'svelte';
|
||||
import {useTracker} from 'meteor/rdb:svelte-meteor-data';
|
||||
import {Roles} from 'meteor/alanning:roles';
|
||||
import Chromebooks from './Chromebooks.svelte';
|
||||
import Users from './Users.svelte';
|
||||
import TestTable from './TestTable.svelte';
|
||||
import ListUsers from './ListUsers.svelte';
|
||||
import Admin from './Admin.svelte';
|
||||
import Announcer from './Announcer.svelte';
|
||||
import {BlazeTemplate} from 'meteor/svelte:blaze-integration';
|
||||
import ServiceConfiguration from "meteor/service-configuration";
|
||||
|
||||
// When the URL changes, run the code... in this case to scroll to the top.
|
||||
router.subscribe(_ => window.scrollTo(0, 0));
|
||||
|
||||
// onMount(async () => {
|
||||
// // Meteor.subscribe('records');
|
||||
// });
|
||||
|
||||
// $: incompleteCount = useTracker(() => Tasks.find({checked: {$ne: true}}).count());
|
||||
|
||||
$: currentUser = useTracker(() => Meteor.user());
|
||||
$: canManageLaptops = false;
|
||||
$: isAdmin = false;
|
||||
|
||||
Tracker.autorun(() => {
|
||||
// For some reason currentUser is always null here, and is not reactive (user changes and this does not get re-called).
|
||||
let user = Meteor.user();
|
||||
canManageLaptops = user && Roles.userIsInRole(user._id, 'laptop-management', 'global');
|
||||
isAdmin = user && Roles.userIsInRole(user._id, 'admin', 'global');
|
||||
});
|
||||
|
||||
|
||||
// const taskStore = Tasks.find({}, {sort: {createdAt: -1}});
|
||||
// $: {
|
||||
// tasks = $taskStore;
|
||||
// if (hideCompleted) {
|
||||
// tasks = tasks.filter(task => !task.checked);
|
||||
// }
|
||||
// }
|
||||
|
||||
// function handleSubmit(event) {
|
||||
// Meteor.call("tasks.insert", newTask);
|
||||
// // Clear form
|
||||
// newTask = "";
|
||||
// }
|
||||
|
||||
function performLogin() {
|
||||
//Login style can be "popup" or "redirect". I am not sure we need to request and offline token.
|
||||
Meteor.loginWithGoogle({loginStyle: "popup", requestOfflineToken: true}, (err) => {
|
||||
if (err) {
|
||||
console.log(err);
|
||||
} else {
|
||||
//console.log("Logged in");
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function performLogout() {
|
||||
Meteor.logout();
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<Announcer/>
|
||||
|
||||
<div class="container">
|
||||
<header class="row">
|
||||
<div class="col-12 logoContainer">
|
||||
<img class="logo" src="/images/logo.svg"/>
|
||||
<div class="login">
|
||||
{#if !$currentUser}
|
||||
<button type="button" role="button" on:click={performLogin}>Login</button>
|
||||
{:else}
|
||||
<button type="button" role="button" on:click={performLogout}>Logout</button>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 center" style="margin-bottom: 0"><h1 style="margin-bottom: 0">District Central</h1></div>
|
||||
<div class="col-12 center">
|
||||
<div class="nav-separator"></div>
|
||||
</div>
|
||||
<nav class="col-12 center">
|
||||
<a href="/">Home</a>
|
||||
{#if canManageLaptops}
|
||||
<a href="/chromebooks">Chromebooks</a>
|
||||
{/if}
|
||||
{#if canManageLaptops}
|
||||
<a href="/users">Users</a>
|
||||
{/if}
|
||||
{#if isAdmin}
|
||||
<a href="/admin">Admin</a>
|
||||
{/if}
|
||||
<!-- <a href="/TestTable">Test</a>-->
|
||||
<!-- <a href="/ListUsers">List Users</a>-->
|
||||
</nav>
|
||||
</header>
|
||||
</div>
|
||||
|
||||
<Route path="/">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
TODO: Some statistics and such.
|
||||
</div>
|
||||
</div>
|
||||
</Route>
|
||||
<Route path="/ListUsers">
|
||||
<!-- <ListUsers/>-->
|
||||
</Route>
|
||||
<Route path="/admin">
|
||||
{#if isAdmin}
|
||||
<Admin/>
|
||||
{/if}
|
||||
</Route>
|
||||
<Route path="/TestTable/*">
|
||||
<!-- <TestTable/>-->
|
||||
</Route>
|
||||
<Route path="/chromebooks/*">
|
||||
{#if canManageLaptops}
|
||||
<Chromebooks/>
|
||||
{:else}
|
||||
<!-- User not authorized to use this UI. Don't render anything because it is likely the user is still loading and will have access in a moment. -->
|
||||
{/if}
|
||||
</Route>
|
||||
<Route path="/users/*">
|
||||
{#if isAdmin}
|
||||
<Users/>
|
||||
{:else}
|
||||
<!-- User not authorized to use this UI. Don't render anything because it is likely the user is still loading and will have access in a moment. -->
|
||||
{/if}
|
||||
</Route>
|
||||
|
||||
<style>
|
||||
nav {
|
||||
font-size: 2rem;
|
||||
@font-face {
|
||||
font-family: 'KaushanScript-Regular';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src: url('/fonts/KaushanScript-Regular.ttf') format('truetype');
|
||||
}
|
||||
a {
|
||||
display: block;
|
||||
color: green;
|
||||
|
||||
.nav-separator {
|
||||
height: 0.2rem;
|
||||
width: 40%;
|
||||
margin-left: 30%;
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
nav {
|
||||
font-size: 1.4rem;
|
||||
}
|
||||
|
||||
nav a {
|
||||
display: inline-block;
|
||||
color: #a6a6ea;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
nav a:hover {
|
||||
color: #6363ee;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
nav a + a {
|
||||
margin-left: 2rem;
|
||||
}
|
||||
|
||||
html {
|
||||
background-color: #000121;
|
||||
font-family: 'Roboto', sans-serif;
|
||||
|
||||
}
|
||||
|
||||
/** Forbidden CSS */
|
||||
.maincontainer {
|
||||
position: relative;
|
||||
top: -50px;
|
||||
transform: scale(0.8);
|
||||
background: url("/public/images/forbidden/HauntedHouseBackground.png");
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
background-size: 700px 600px;
|
||||
width: 800px;
|
||||
height: 600px;
|
||||
margin: 0px auto;
|
||||
display: grid;
|
||||
}
|
||||
.foregroundimg {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
top: -230px;
|
||||
z-index: 5;
|
||||
}
|
||||
.errorcode {
|
||||
position: relative;
|
||||
top: -200px;
|
||||
font-family: 'Creepster', cursive;
|
||||
header {
|
||||
background: #2c031c;
|
||||
color: white;
|
||||
text-align: center;
|
||||
font-size: 6em;
|
||||
letter-spacing: 0.1em;
|
||||
}
|
||||
.errortext {
|
||||
/*background-image: linear-gradient(to bottom, #d0edf5, #e1e5f0 100%);*/
|
||||
padding: 20px 15px 15px 15px;
|
||||
position: relative;
|
||||
top: -260px;
|
||||
color: #FBD130;
|
||||
text-align: center;
|
||||
text-transform: uppercase;
|
||||
font-size: 1.8em;
|
||||
}
|
||||
.bat {
|
||||
opacity: 0;
|
||||
position: relative;
|
||||
transform-origin: center;
|
||||
z-index: 3;
|
||||
}
|
||||
.bat:nth-child(1) {
|
||||
top: 380px;
|
||||
left: 120px;
|
||||
transform: scale(0.5);
|
||||
animation: 13s 1s flyBat1 infinite linear;
|
||||
}
|
||||
.bat:nth-child(2) {
|
||||
top: 280px;
|
||||
left: 80px;
|
||||
transform: scale(0.3);
|
||||
animation: 8s 4s flyBat2 infinite linear;
|
||||
}
|
||||
.bat:nth-child(3) {
|
||||
top: 200px;
|
||||
left: 150px;
|
||||
transform: scale(0.4);
|
||||
animation: 12s 2s flyBat3 infinite linear;
|
||||
}
|
||||
.body {
|
||||
position: relative;
|
||||
width: 50px;
|
||||
top: 12px;
|
||||
}
|
||||
.wing {
|
||||
width: 150px;
|
||||
position: relative;
|
||||
transform-origin: right center;
|
||||
}
|
||||
.leftwing {
|
||||
left: 30px;
|
||||
animation: 0.8s flapLeft infinite ease-in-out;
|
||||
}
|
||||
.rightwing {
|
||||
left: -180px;
|
||||
transform: scaleX(-1);
|
||||
animation: 0.8s flapRight infinite ease-in-out;
|
||||
}
|
||||
@keyframes flapLeft {
|
||||
0% { transform: rotateZ(0); }
|
||||
50% { transform: rotateZ(10deg) rotateY(40deg); }
|
||||
100% { transform: rotateZ(0); }
|
||||
}
|
||||
@keyframes flapRight {
|
||||
0% { transform: scaleX(-1) rotateZ(0); }
|
||||
50% { transform: scaleX(-1) rotateZ(10deg) rotateY(40deg); }
|
||||
100% { transform: scaleX(-1) rotateZ(0); }
|
||||
}
|
||||
@keyframes flyBat1 {
|
||||
0% { opacity: 1; transform: scale(0.5)}
|
||||
25% { opacity: 1; transform: scale(0.5) translate(-400px, -330px) }
|
||||
50% { opacity: 1; transform: scale(0.5) translate(400px, -800px) }
|
||||
75% { opacity: 1; transform: scale(0.5) translate(600px, 100px) }
|
||||
100% { opacity: 1; transform: scale(0.5) translate(100px, 300px) }
|
||||
}
|
||||
@keyframes flyBat2 {
|
||||
0% { opacity: 1; transform: scale(0.3)}
|
||||
25% { opacity: 1; transform: scale(0.3) translate(200px, -330px) }
|
||||
50% { opacity: 1; transform: scale(0.3) translate(-300px, -800px) }
|
||||
75% { opacity: 1; transform: scale(0.3) translate(-400px, 100px) }
|
||||
100% { opacity: 1; transform: scale(0.3) translate(100px, 300px) }
|
||||
}
|
||||
@keyframes flyBat3 {
|
||||
0% { opacity: 1; transform: scale(0.4)}
|
||||
25% { opacity: 1; transform: scale(0.4) translate(-350px, -330px) }
|
||||
50% { opacity: 1; transform: scale(0.4) translate(400px, -800px) }
|
||||
75% { opacity: 1; transform: scale(0.4) translate(-600px, 100px) }
|
||||
100% { opacity: 1; transform: scale(0.4) translate(100px, 300px) }
|
||||
}
|
||||
/*@media only screen and (max-width: 850px) {
|
||||
.maincontainer {
|
||||
transform: scale(0.6);
|
||||
width: 600px;
|
||||
height: 400px;
|
||||
background-size: 600px 400px;
|
||||
}
|
||||
|
||||
.errortext {
|
||||
font-size: 1em;
|
||||
}
|
||||
}*/
|
||||
h1 {
|
||||
color: white;
|
||||
font-family: "KaushanScript-Regular", sans-serif;
|
||||
font-size: 4rem;
|
||||
}
|
||||
|
||||
.logo {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 8rem;
|
||||
}
|
||||
|
||||
.logoContainer {
|
||||
height: 0;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.login {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.login button {
|
||||
background: #7171ec;
|
||||
border-radius: 999px;
|
||||
box-shadow: #5E5DF0 0 10px 20px -10px;
|
||||
box-sizing: border-box;
|
||||
color: #FFFFFF;
|
||||
cursor: pointer;
|
||||
font-family: Inter, Helvetica, "Apple Color Emoji", "Segoe UI Emoji", NotoColorEmoji, "Noto Color Emoji", "Segoe UI Symbol", "Android Emoji", EmojiSymbols, -apple-system, system-ui, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", sans-serif;
|
||||
font-weight: 700;
|
||||
line-height: 24px;
|
||||
opacity: 1;
|
||||
outline: 0 solid transparent;
|
||||
padding: 8px 18px;
|
||||
user-select: none;
|
||||
-webkit-user-select: none;
|
||||
touch-action: manipulation;
|
||||
width: fit-content;
|
||||
word-break: break-word;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.logo {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 0;
|
||||
margin: 0 auto;
|
||||
width: 8rem;
|
||||
}
|
||||
.logoContainer {
|
||||
height: 6rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import {Meteor} from "meteor/meteor";
|
||||
import {Route} from 'tinro';
|
||||
import {onMount} from 'svelte';
|
||||
import {useTracker} from 'meteor/rdb:svelte-meteor-data';
|
||||
import {Roles} from 'meteor/alanning:roles';
|
||||
import Chromebooks from './Chromebooks.svelte';
|
||||
import {BlazeTemplate} from 'meteor/svelte:blaze-integration';
|
||||
import {Records} from '../api/records.js'
|
||||
import ServiceConfiguration from "meteor/service-configuration";
|
||||
|
||||
//import './imports/ui/App.css';
|
||||
|
||||
//let currentUser;
|
||||
|
||||
onMount(async () => {
|
||||
// Meteor.subscribe('records');
|
||||
});
|
||||
|
||||
// $: incompleteCount = useTracker(() => Tasks.find({checked: {$ne: true}}).count());
|
||||
|
||||
$: currentUser = useTracker(() => Meteor.user());
|
||||
$: isAdmin = useTracker(() => Roles.userIsInRole(currentUser._id, 'laptop-management', 'global'));
|
||||
|
||||
// Tracker.autorun(() => {
|
||||
// let user = Meteor.user();
|
||||
// let isManagement = user ? Roles.userIsInRole(user._id, 'laptop-management', 'global') : 0;
|
||||
//
|
||||
// if(user && isManagement) {
|
||||
// new ProcessLaptops({
|
||||
// target: document.getElementById('appView')
|
||||
// });
|
||||
// }
|
||||
// });
|
||||
|
||||
// const taskStore = Tasks.find({}, {sort: {createdAt: -1}});
|
||||
// $: {
|
||||
// tasks = $taskStore;
|
||||
// if (hideCompleted) {
|
||||
// tasks = tasks.filter(task => !task.checked);
|
||||
// }
|
||||
// }
|
||||
|
||||
// function handleSubmit(event) {
|
||||
// Meteor.call("tasks.insert", newTask);
|
||||
// // Clear form
|
||||
// newTask = "";
|
||||
// }
|
||||
|
||||
function performLogin() {
|
||||
//Login style can be "popup" or "redirect". I am not sure we need to request and offline token.
|
||||
Meteor.loginWithGoogle({loginStyle: "popup", requestOfflineToken: true}, (err) => {
|
||||
if(err) {
|
||||
console.log(err);
|
||||
}
|
||||
else {
|
||||
//console.log("Logged in");
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function performLogout() {
|
||||
Meteor.logout();
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<Route path="/">
|
||||
<div class="container">
|
||||
<header class="row">
|
||||
<nav class="col-12 center">
|
||||
<h1>AVUSD District Central</h1>
|
||||
<a href="/">Home</a>
|
||||
{#if isAdmin}
|
||||
<a href="/chromebooks">Chromebooks</a>
|
||||
{/if}
|
||||
</nav>
|
||||
{#if !$currentUser}
|
||||
<button type="button" on:click={performLogin}>Login</button>
|
||||
{:else}
|
||||
<button type="button" on:click={performLogout}>Logout</button>
|
||||
{/if}
|
||||
</header>
|
||||
</div>
|
||||
</Route>
|
||||
{#if $currentUser}
|
||||
{#if isAdmin}
|
||||
<Route path="/chromebooks/*">
|
||||
<Chromebooks/>
|
||||
</Route>
|
||||
{:else}
|
||||
<Route fallback redirect="/"/>
|
||||
{/if}
|
||||
{:else}
|
||||
<div className="container" style="background-color: #4d4242">
|
||||
<div class="maincontainer row">
|
||||
<div class="bat">
|
||||
<img class="wing leftwing"
|
||||
src="/images/forbidden/bat-wing.png">
|
||||
<img class="body"
|
||||
src="/images/forbidden/bat-body.png" alt="bat">
|
||||
<img class="wing rightwing"
|
||||
src="/images/forbidden/bat-wing.png">
|
||||
</div>
|
||||
<div class="bat">
|
||||
<img class="wing leftwing"
|
||||
src="/images/forbidden/bat-wing.png">
|
||||
<img class="body"
|
||||
src="/images/forbidden/bat-body.png" alt="bat">
|
||||
<img class="wing rightwing"
|
||||
src="/images/forbidden/bat-wing.png">
|
||||
</div>
|
||||
<div class="bat">
|
||||
<img class="wing leftwing"
|
||||
src="/images/forbidden/bat-wing.png">
|
||||
<img class="body"
|
||||
src="/images/forbidden/bat-body.png" alt="bat">
|
||||
<img class="wing rightwing"
|
||||
src="/images/forbidden/bat-wing.png">
|
||||
</div>
|
||||
<img class="foregroundimg" src="/images/forbidden/HauntedHouseForeground.png" alt="haunted house">
|
||||
|
||||
</div>
|
||||
<h1 class="errorcode">ERROR 403</h1>
|
||||
<div class="errortext">This area is forbidden. Turn back now!</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
Reference in New Issue
Block a user