Initial commit

This commit is contained in:
Brian McGonagill 2025-12-06 15:44:43 -06:00
commit 35745a89ec
44 changed files with 3342 additions and 0 deletions

View file

@ -0,0 +1,40 @@
<template name="login">
{{#if $not currentUser}}
<div id="signInForm">
<div class="container">
<h4>Login</h4>
<article>
<div class="card-content">
<div class="login grid">
<div>
<label for="email">Email *</label>
<input type="email" name="email" id="email" class="email" />
</div>
</div>
<div class="grid">
<div>
<label for="password">Password *</label>
<input type="password" name="password" id="password" class="password" />
</div>
</div>
<div class="grid">
{{#if $eq areFilled false}}
<div>
<span>You must fill all fields to login.</span>
</div>
{{/if}}
<div>
<a id="logmein" class="right btn logmein" role="button">Log In</a>
</div>
</div>
</div>
{{#if $eq canReg true}}
<div>
<a href="#" id="reg">Register</a>
</div>
{{/if}}
</article>
</div>
</div>
{{/if}}
</template>

View file

@ -0,0 +1,44 @@
import { FlowRouter } from 'meteor/ostrio:flow-router-extra';
import { SysConfig } from '../../../imports/api/systemConfig';
Template.login.onCreated(function() {
this.subscribe("SystemConfig");
});
Template.login.onRendered(function() {
});
Template.login.helpers({
areFilled: function() {
return Session.get("filledFields");
},
canReg: function() {
let conf = SysConfig.findOne();
if (typeof conf != 'undefined') {
return conf.allowReg;
} else {
return true;
}
}
});
Template.login.events({
'click #logmein' (event) {
event.preventDefault();
console.log("clicked login");
let email = $("#email").val();
let pass = $("#password").val();
if (email == null || email == "" || pass == "" || pass == null) {
Session.set("filledFields", false);
return;
} else {
Meteor.loginWithPassword(email, pass);
}
},
'click #reg' (event) {
event.preventDefault();
FlowRouter.go('/reg');
},
});

View file

@ -0,0 +1,65 @@
<template name="reg">
{{#if $not currentUser}}
{{#if $eq allowReg true}}
<div id="registrationForm">
<div>
<h4>Register</h4>
<article>
<div class="card-content">
<div class="grid">
<div>
<label for="name">Your Full Name *</label>
<input type="text" name="name" class="name" id="name" style="{{#if $eq misName true}}border: 2px solid red;{{/if}}" />
</div>
</div>
<div class="grid">
<div>
<label for="email">Email *</label>
<input type="email" name="email" id="email" class="email" />
</div>
</div>
{{#if $eq misEmail true}}
<div class="grid">
<div>
<p style="color: red;">This does not appear to be a proper email.</p>
</div>
</div>
{{/if}}
<div class="grid">
<div>
<label for="password">Password *</label>
<input type="password" name="password" id="password" class="password {{#if $eq misPass true}}red lighten-3{{/if}}" />
</div>
</div>
<div class="grid">
<div>
<label for="passwordConfirm">Confirm Password *</label>
<input type="password" name="passwordConfirm" id="passwordConfirm" class="passwordConfirm" style="{{#if $eq passMatch false}}border: 2px solid red !important;{{/if}}" />
</div>
</div>
{{#if $eq passMatch false}}
<div class="grid">
<div>
<p style="color: red;">Passwords do no match!</p>
</div>
</div>
{{/if}}
<div class="grid">
<div>
<a id="registerMe" class="btn right registerMe" role="button">Register</a>
</div>
</div>
</div>
<div class="card-action">
<a href="#" id="login">Sign In</a>
</div>
</article>
</div>
</div>
{{else}}
<h3>Registration Disabled</h3>
<p>The administrator of this system has disabled registration. If you believe you should be allowed to register to use this system, please contact the system administrator for assistance.</p>
{{/if}}
{{/if}}
{{> snackbar}}
</template>

View file

@ -0,0 +1,137 @@
import { Meteor } from 'meteor/meteor';
import { FlowRouter } from 'meteor/ostrio:flow-router-extra';
import { SysConfig } from '../../../imports/api/systemConfig';
Template.reg.onCreated(function() {
this.subscribe("SystemConfig");
});
Template.reg.onRendered(function() {
Session.set("canReg", false);
Session.set("missingReq", false);
Session.set("missingName", false);
Session.set("missingEmail", false);
Session.set("missingPassword", false);
Session.set("passMatch", true);
});
Template.reg.helpers({
canReg: function() {
return Session.get("canReg");
},
misName: function() {
return Session.get("missingName");
},
misEmail: function() {
return Session.get("missingEmail");
},
misPass: function() {
return Session.get("missingPassword");
},
misReq: function() {
return Session.get("missingReq");
},
passMatch: function() {
return Session.get("passMatch");
},
allowReg: async() => {
const conf = await SysConfig.findOneAsync();
try {
if (typeof conf != 'undefined') {
return conf.allowReg;
} else {
return true
}
} catch(error) {
console.log(" ERROR getting registration allowed info: " + error);
}
}
});
Template.reg.events({
'click #registerMe' (event) {
event.preventDefault();
if (Session.get("canreg") == false) {
// console.log("reg disabled.");
} else {
// console.log("Clicked");
let missingName = false;
let missingEmail = false;
let missingPassword = false;
let email = $("#email").val();
let password = $("#password").val();
let name = $("#name").val();
if (name == "" || name == null) {
missingName = true;
Session.set("missingName", true);
}
if (email == "" || email == null) {
missingEmail = true;
Session.set("missingEmail", true);
}
if (password == "" || password == null) {
missingPassword = true;
Session.set("missingPassword", true);
}
let userId;
if (missingName == true || missingEmail == true || missingPassword == true) {
Session.set("missingReq", true);
} else {
Session.set("missingReq", false);
Accounts.createUser({
email: email,
password: password,
profile: {
fullname: name,
}
});
let userId = Meteor.userId();
// console.log("User ID: " + userId);
const addRole = async() => {
let result = await Meteor.callAsync("addToRole", "user");
if (!result) {
console.log(" ERROR: ROLES - Error adding user to role: ");
} else {
// console.log("User should be added to role - teacher.");
FlowRouter.go('/dashboard');
}
}
addRole();
}
}
},
'keyup #passwordConfirm' (event) {
let pwd = $("#password").val();
let pwdconf = $("#passwordConfirm").val();
if (pwd == pwdconf) {
// console.log("passwords match");
Session.set("canreg", true);
} else {
// console.log("passwords don't match");
Session.set("canreg", false);
}
},
'change #email' (event) {
let email = $("#email").val();
var regex = /^([a-zA-Z0-9_.+-])+\@(([a-zA-Z0-9-])+\.)+([a-zA-Z0-9]{2,4})+$/;
let isEmail = regex.test(email);
if (isEmail == false) {
Session.set("missingEmail", true);
} else {
Session.set("missingEmail", false);
}
},
'click #login' (event) {
event.preventDefault();
FlowRouter.go('/login');
},
});

View file

@ -0,0 +1,45 @@
<template name="userInfoModal">
<div id="userInfoModal" class="modal modal-fixed-footer">
<div class="modal-content">
<h4>User Info for: {{userInfo.profile.fullname}}</h4>
<div>
<p class="flow-text">Password Reset</p>
</div>
<form class="row" style="gap: 1em;">
<div class="col s12 m6 l6 input-field outlined">
<input type="password" class="newPass" id="newPass" />
<label for="newPass">Enter New Password</label>
</div>
<div class="col s12 m6 l6 input-field outlined">
<input type="password" class="newPassConf {{#if $eq passMatch false}}red lighten-3{{/if}}" id="newPassConf" />
<label for="newPassConf">Enter New Password</label>
{{#if $eq passMatch false}}<p class="red-text">Passwords do not match!</p>{{/if}}
</div>
<div class="col s12 m6 l6 input-field outlined">
<select name="userRole" id="userRole" class="userRole {{#if $eq roleEmpty true}}red lighten-2 white-text{{/if}}">
<option value="{{userRole}}" selected>{{userRole}}</option>
<option value="admin">Admin</option>
<option value="systemadmin">System Admin</option>
<option value="user">User</option>
</select>
<label for="userRole">User Role</label>
{{#if $eq roleEmpty true}}
<p class="red-text">This field is required.</p>
{{/if}}
</div>
<div class="col s12 m6 l6 input-field outlined">
<input type="text" class="usersEmail" id="usersEmail" value="{{userInfo.emails.[0].address}}"/>
<label for="usersEmail">Email</label>
{{#if $eq emailEmpty true}}
<p class="red-text">This field is required.</p>
{{/if}}
</div>
</form>
</div>
<div class="modal-footer">
<a class="modal-close btn waves-effect waves-light filled orange white-text">Cancel</a>
<a id="saveChanges" class="btn waves-effect waves-light filled green white-text">Save Changes</a>
</div>
</div>
{{> snackbar}}
</template>

View file

@ -0,0 +1,145 @@
Template.userInfoModal.onCreated(function() {
this.subscribe("rolesAvailable");
});
Template.userInfoModal.onRendered(function() {
Session.set("passMatch", true);
Session.set("passErr", false);
Session.set("userEmailErr", false);
Session.set("userRoleErr", false);
});
Template.userInfoModal.helpers({
passMatch: function() {
return Session.get("passMatch");
},
emailEmpty: function() {
return Session.get("userEmailErr");
},
roleEmpty: function() {
return Session.get("userRoleErr");
},
userInfo: function() {
let usersId = Session.get("usersId");
if (usersId != "" && usersId != null) {
let usersInfo = Meteor.users.findOne({ _id: usersId });
// console.dir(usersInfo);
Session.set("usersInfo", usersInfo);
return usersInfo;
} else {
return;
}
},
userRole: function() {
let userRole = Roles.getRolesForUser( Session.get("usersId"));
Session.set("usersRole", userRole);
console.log(userRole);
return userRole;
},
rolesOptions: function() {
return Roles.find();
}
});
Template.userInfoModal.events({
"click #saveChanges" (event) {
event.preventDefault();
let usersId = Session.get("usersId");
let passwd = $("#newPass").val();
let usersEmail = $("#usersEmail").val();
let userRole = $("#userRole").val();
let userInfo = Session.get("usersInfo");
let currEmail = userInfo.emails[0].address;
let userDbRole = Session.get("usersRole");
let currRole = userDbRole[0];
let passMatch = Session.get("passMatch");
if (passMatch == false) {
Session.set("passErr", true);
return;
} else {
Session.set("passErr", false);
}
if (usersEmail == null || usersEmail == "") {
Session.set("userEmailErr", true);
return;
} else {
Session.set("userEmailErr", false);
}
if (userRole == null || userRole == "") {
Session.set("userRoleErr", true);
return;
} else {
Session.set("userRoleErr", false);
}
if (passMatch == true || passMatch == "NA") {
if (passwd != "" && passwd != null) {
// need to account for the admin changing passwords and userRole or Email
changePassword(usersId, passwd);
}
if (usersEmail != null && usersEmail != "" && usersEmail != currEmail) {
changeUserEmail(usersId, usersEmail);
}
if (userRole != null && userRole != "" && userRole != currRole) {
changeUserRole(usersId, userRole);
}
}
},
"click #closePass" (event) {;
},
"keyup #newPassConf" (event) {
let newPass = $("#newPass").val();
let newPassConf = $("#newPassConf").val();
if (newPassConf.length > 2 && (newPass != null || newPass != "")) {
if (newPass != newPassConf) {
Session.set("passMatch", false);
} else {
Session.set("passMatch", true);
}
}
}
});
changePassword = function(userId, passwd) {
console.log("would change password.");
Meteor.call('edit.userPass', userId, passwd, function(err, result) {
if (err) {
console.log(" ERROR changing user passwrod:" + err);
} else {
showSnackbar("Successfully Saved Changes!", "green");
console.log(" Password changed successfully!");
}
});
}
changeUserEmail = function(usersId, usersEmail) {
console.log("Would change user email");
Meteor.call('update.userEmail', usersId, usersEmail, function(err, result) {
if (err) {
console.log(" ERROR updating user email: " + err);
} else {
showSnackbar("Email updated successfully!", "green");
}
});
}
changeUserRole = function(userId, role) {
console.log("Would change user Role.");
Meteor.call('edit.userRole', userId, role, function(err, result) {
if (err) {
console.log(" ERROR updating user role: " + err);
} else {
showSnackbar("Role Successfully Updated!", "green");
}
});
}

View file

@ -0,0 +1,36 @@
<template name="userMgmt">
{{#if isInRole 'systemadmin'}}
<h3>User Management</h3>
<div class="row">
<div class="col s12 m12 l12">
<table class="striped highlight responsive-table">
<thead>
<tr>
<th>Name</th>
<th>Email</th>
<th>Role</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{{#each userInfo}}
<tr>
<td>{{userName}}</td>
<td>{{userEmail}}</td>
<td>{{userRole}}</td>
<td>
<div class="input-field">
<i class="material-icons modal-trigger clickable deleteUser" data-target="modalDelete">delete</i>
<i class="material-icons clickable editUser modal-trigger" data-target="userInfoModal">edit</i>
</div>
</td>
</tr>
{{/each}}
</tbody>
</table>
</div>
</div>
{{/if}}
{{> deleteConfirmationModal}}
{{> userInfoModal}}
</template>

View file

@ -0,0 +1,41 @@
Template.userMgmt.onCreated(function() {
this.subscribe("userList");
});
Template.userMgmt.onRendered(function() {
});
Template.userMgmt.helpers({
userInfo: function() {
return Meteor.users.find({});
},
userName: function() {
return this.profile.fullname;
},
userEmail: function() {
return this.emails[0].address;
},
userRole: function() {
return Roles.getRolesForUser( this._id );
}
});
Template.userMgmt.events({
"click .editUser" (event) {
event.preventDefault();
let userId = this._id;
Session.set("usersId", userId);
},
"click .deleteUser" (event) {
event.preventDefault();
let userId = this._id;
console.log("Delete called on : " + userId);
Session.set("deleteId", userId);
Session.set("item", "User");
Session.set("view", "Users");
Session.set("method", "delete.userFromSys");
}
});

View file

@ -0,0 +1,37 @@
<template name="systemAdmin">
<h2>System Administration</h2>
<div class="grid">
<article>
<h4>Registration Settings</h4>
<div class="grid">
<div class="">
<label for="allowAdmReg">
<input type="checkbox" class="currConfigs" id="allowAdmReg" role="switch">
Allow Admin Registration
</label>
</div>
</div>
<div class="grid">
<div>
<label for="allowGenReg">
<input type="checkbox" class="currConfigs" id="allowGenReg" role="switch">
Allow Registration
</label>
</div>
</div>
</article>
<article>
<h4>Update Notifications</h4>
<p>This option requires the seerver to have an internet connection.</p>
<div class="grid">
<div>
<label for="recvUpdateMsgs">
<input type="checkbox" class="currConfigs" id="recvUpdateMsgs" role="switch">
System Admin will receive information on updates and new featres
</label>
</div>
</div>
</article>
</div>
{{> snackbar}}
</template>

View file

@ -0,0 +1,62 @@
import { SysConfig } from "../../../imports/api/systemConfig";
Template.systemAdmin.onCreated(function() {
this.subscribe("SystemConfig");
this.subscribe("rolesAvailable");
});
Template.systemAdmin.onRendered(function() {
this.autorun (() => {
const curr = SysConfig.findOne({});
if (curr) {
$("#allowGenReg").prop('checked', curr.allowReg);
$("#allowAdmReg").prop('checked', curr.SysAdminReg);
$("#recvUpdateMsgs").prop('checked', curr.allowUpdates);
} else {
console.log(" ---- unable to find current system configuration.");
}
});
});
Template.systemAdmin.helpers({
currConfigs: function() {
},
});
Template.systemAdmin.events({
'change #allowGenReg, change #allowAdmReg' (evnnt) {
let genReg = $("#allowGenReg").prop('checked');
let admReg = $("#allowAdmReg").prop('checked');
console.log("General Reg set to: " + genReg);
const setNoSysAdminReg = async() => {
try {
const result = await Meteor.callAsync("add.noSysAdminReg", admReg, genReg);
if (result) {
console.log("Successfully added reg settings.");
showSnackbar("Updated Registration Settings.", "green");
}
} catch(error) {
console.log(" ERROR setting registration: " + error);
}
}
setNoSysAdminReg();
},
'change #recvUpdateMsgs' (event) {
let updSet = $("#recvUpdateMsgs").prop('checked');
const updateInfo = async() => {
try {
const result = Meteor.callAsync('allow.updateInfo', updSet);
showSnackbar("Update Setting Changed Successfully!", "green");
} catch(error) {
console.log(" ERROR changing update setting: " + err);
}
}
updateInfo();
},
});

View file

@ -0,0 +1,3 @@
<template name="home">
<h1>This is Home.</h1>
</template>

View file

@ -0,0 +1,5 @@
import { FlowRouter } from 'meteor/ostrio:flow-router-extra';
Template.home.events({
});

View file

@ -0,0 +1,3 @@
<template name="snackbar">
<div class="snackbar shadow" id="snackbar"></div>
</template>

View file

@ -0,0 +1,11 @@
// This is called to display a temporary message to the user at the bottom of the screen
showSnackbar = function(snackbarText, snackbarColor) {
var snackbarNotification = document.getElementById("snackbar");
snackbarNotification.innerHTML = snackbarText;
snackbarNotification.style.backgroundColor = snackbarColor;
snackbarNotification.className = "show";
setTimeout(function() {
snackbarNotification.className = snackbarNotification.className.replace("show", "");
}, 4000)
}

View file

@ -0,0 +1,71 @@
#snackbar {
visibility: hidden;
min-width: 250px;
margin-left: -125px;
background-color: #93b7d6;
color: #fff;
text-align: center;
border-radius: 4px;
padding: 16px;
position: fixed;
z-index: 22;
left: 50%;
bottom: 30px;
font-size: 17px;
}
#snackbar.shadow {
min-width: 250px;
padding: 15px;
box-shadow: -5px 7px 8px #2f2f2f;
}
#snackbar.show {
visibility: visible;
-webkit-animation: fadein 0.5s, fadeout 0.5s 4.0s;
animation: fadein 0.5s, fadeout 0.5s 4.0s;
}
@-webkit-keyframes fadein {
from {
bottom: 0;
opacity: 0;
}
to {
bottom: 30px;
opacity: 1;
}
}
@keyframes fadein {
from {
bottom: 0;
opacity: 0;
}
to {
bottom: 30px;
opacity: 1;
}
}
@-webkit-keyframes fadeout {
from {
bottom: 30px;
opacity: 1;
}
to {
bottom: 0;
opacity: 0;
}
}
@keyframes fadeout {
from {
bottom: 30px;
opacity: 1;
}
to {
bottom: 0;
opacity: 0;
}
}

View file

@ -0,0 +1,20 @@
<template name="headerBar">
<nav>
<ul>
<li><h2>[Project Name]</h2></li>
</ul>
<ul>
{{#if currentUser}}
{{#if isInRole "systemadmin"}}
<li><a href="#" id="manage" class="navBtn">Manage</a></li>
{{/if}}
<li class="signOut"><a href="#" class="signOut">Log Out</a></li>
{{#if $eq updateExists true}}
<li><a href="#!"><i class="material-icons" id='dashboard'>notifications</i></a></li>
{{/if}}
{{else}}
<li><a href="#!" id="login" class="navBtn">Login</a></li>
{{/if}}
</ul>
</nav>
</template>

View file

@ -0,0 +1,51 @@
import { Roles } from 'meteor/roles';
import { FlowRouter } from 'meteor/ostrio:flow-router-extra';
import { UpdateInfo } from '../../imports/api/updateInfo.js';
Template.headerBar.onCreated(function() {
this.subscribe("UpdateVersion");
this.subscribe("assignment");
});
Template.headerBar.onRendered(function() {
});
Template.headerBar.helpers({
adminReg: function() {
return Session.get("adminreg");
},
myTheme: function() {
return Session.get("myTheme");
},
updateExists: function() {
let update = UpdateInfo.find({ viewed: false }).fetch();
if (update.length > 0) {
return true;
} else {
return false;
}
},
});
Template.headerBar.events({
'click .navBtn' (event) {
event.preventDefault();
var clickedTarget = event.target.id;
// console.log("clicked " + clickedTarget);
if (clickedTarget == 'mainMenu') {
FlowRouter.go('/');
} else {
// console.log("should be going to /" + clickedTarget);
FlowRouter.go('/' + clickedTarget);
}
},
'click .signOut': () => {
FlowRouter.go('/');
Meteor.logout();
},
'click #brandLogo' (event) {
event.preventDefault();
// FlowRouter.go('/dashboard');
}
});

12
client/MainLayout.html Normal file
View file

@ -0,0 +1,12 @@
<template name="MainLayout">
{{#if Template.subscriptionsReady}}
<div class="container">
{{> headerBar}}
{{#if currentUser}}
{{> Template.dynamic template=main}}
{{else}}
{{> Template.dynamic template=notLoggedIn}}
{{/if}}
</div>
{{/if}}
</template>

5
client/MainLayout.js Normal file
View file

@ -0,0 +1,5 @@
Template.MainLayout.onCreated(function() {
});

4
client/lib/assets/pico.min.css vendored Normal file

File diff suppressed because one or more lines are too long

3
client/main.css Normal file
View file

@ -0,0 +1,3 @@
.right {
float: right;
}

13
client/main.html Normal file
View file

@ -0,0 +1,13 @@
<head>
<title>Meteor 3 Template</title>
<link id="favicon" rel="shortcut icon" type="image/png" href="./lib/assets/icons/Get My Logo no name with transparency.png" />
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="apple-touch-icon" href="">
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
<link rel="stylesheet" href="./lib/assets/pico.min.css">
</head>
<body>
</body>

4
client/main.js Normal file
View file

@ -0,0 +1,4 @@
import { Template } from 'meteor/templating';
import { ReactiveVar } from 'meteor/reactive-var';
import './main.html';