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

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
node_modules/

View file

@ -0,0 +1,19 @@
# This file contains information which helps Meteor properly upgrade your
# app when you run 'meteor update'. You should check it into version control
# with your project.
notices-for-0.9.0
notices-for-0.9.1
0.9.4-platform-file
notices-for-facebook-graph-api-2
1.2.0-standard-minifiers-package
1.2.0-meteor-platform-split
1.2.0-cordova-changes
1.2.0-breaking-changes
1.3.0-split-minifiers-package
1.4.0-remove-old-dev-bundle-link
1.4.1-add-shell-server-package
1.4.3-split-account-service-packages
1.5-add-dynamic-import-package
1.7-split-underscore-from-meteor-base
1.8.3-split-jquery-from-blaze

1
.meteor/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
local

7
.meteor/.id Normal file
View file

@ -0,0 +1,7 @@
# This file contains a token that is unique to your project.
# Check it into your repository along with the rest of this directory.
# It can be used for purposes such as:
# - ensuring you don't accidentally deploy one app on top of another
# - providing package authors with aggregated statistics
kbd5rnyj9z6l.ys2ak9q5lu0a

32
.meteor/packages Normal file
View file

@ -0,0 +1,32 @@
# Meteor packages used by this project, one per line.
# Check this file (and the other files in this directory) into your repository.
#
# 'meteor add' and 'meteor remove' will edit this file for you,
# but you can also edit it by hand.
meteor-base@1.5.2 # Packages every Meteor app needs to have
mobile-experience@1.1.2 # Packages for a great mobile UX
mongo@2.1.4 # The database Meteor supports right now
blaze-html-templates # Compile .html files into Meteor Blaze views
jquery # Wrapper package for npm-installed jquery
reactive-var@1.0.13 # Reactive variable for tracker
tracker@1.3.4 # Meteor's client-side reactive programming library
standard-minifier-css@1.9.3 # CSS minifier run for production mode
standard-minifier-js@3.1.1 # JS minifier run for production mode
es5-shim@4.8.1 # ECMAScript 5 compatibility for older browsers
ecmascript@0.16.13 # Enable ECMAScript2015+ syntax in app code
typescript@5.6.6 # Enable TypeScript syntax in .ts and .tsx modules
shell-server@0.6.2 # Server-side component of the `meteor shell` command
hot-module-replacement@0.5.4 # Update code in development without reloading the page
blaze-hot # Update files using Blaze's API with HMR
session@1.2.2
ostrio:flow-router-extra
email@3.1.2
accounts-password@3.2.1
roles@1.0.1
arianjahiri:meteor-handlebars-helpers

2
.meteor/platforms Normal file
View file

@ -0,0 +1,2 @@
server
browser

1
.meteor/release Normal file
View file

@ -0,0 +1 @@
METEOR@3.3.2

94
.meteor/versions Normal file
View file

@ -0,0 +1,94 @@
accounts-base@3.1.2
accounts-password@3.2.1
allow-deny@2.1.0
arianjahiri:meteor-handlebars-helpers@0.0.1
autoupdate@2.0.1
babel-compiler@7.12.2
babel-runtime@1.5.2
base64@1.0.13
binary-heap@1.0.12
blaze@3.0.2
blaze-hot@2.0.0
blaze-html-templates@3.0.0
blaze-tools@2.0.0
boilerplate-generator@2.0.2
caching-compiler@2.0.1
caching-html-compiler@2.0.0
callback-hook@1.6.1
check@1.4.4
core-runtime@1.0.0
ddp@1.4.2
ddp-client@3.1.1
ddp-common@1.4.4
ddp-rate-limiter@1.2.2
ddp-server@3.1.2
deps@1.0.5-pre.1
diff-sequence@1.1.3
dynamic-import@0.7.4
ecmascript@0.16.13
ecmascript-runtime@0.8.3
ecmascript-runtime-client@0.12.3
ecmascript-runtime-server@0.11.1
ejson@1.1.5
email@3.1.2
es5-shim@4.8.1
facts-base@1.0.2
fetch@0.1.6
geojson-utils@1.0.12
hot-code-push@1.0.5
hot-module-replacement@0.5.4
html-tools@2.0.0
htmljs@2.0.1
id-map@1.2.0
inter-process-messaging@0.1.2
jquery@3.0.2
launch-screen@2.0.1
localstorage@1.2.1
logging@1.3.6
meteor@2.1.1
meteor-base@1.5.2
minifier-css@2.0.1
minifier-js@3.0.4
minimongo@2.0.4
mobile-experience@1.1.2
mobile-status-bar@1.1.1
modern-browsers@0.2.3
modules@0.20.3
modules-runtime@0.13.2
modules-runtime-hot@0.14.3
mongo@2.1.4
mongo-decimal@0.2.0
mongo-dev-server@1.1.1
mongo-id@1.0.9
npm-mongo@6.16.1
observe-sequence@2.0.0
ordered-dict@1.2.0
ostrio:flow-router-extra@3.12.1
promise@1.0.0
random@1.2.2
rate-limit@1.1.2
react-fast-refresh@0.2.9
reactive-dict@1.3.2
reactive-var@1.0.13
reload@1.3.2
retry@1.1.1
roles@1.0.1
routepolicy@1.1.2
session@1.2.2
sha@1.0.10
shell-server@0.6.2
socket-stream-client@0.6.1
spacebars@2.0.0
spacebars-compiler@2.0.0
standard-minifier-css@1.9.3
standard-minifier-js@3.1.1
templating@1.4.4
templating-compiler@2.0.0
templating-runtime@2.0.1
templating-tools@2.0.0
tracker@1.3.4
typescript@5.6.6
underscore@1.6.4
url@1.3.5
webapp@2.0.7
webapp-hashing@1.1.2

75
OUTLINE.md Normal file
View file

@ -0,0 +1,75 @@
- Super Admin User
- Business (Tenant)
- Name
- Address
- Phone
- Owner Name
- Admin User
- Employees
- Name
- Employee ID No
- Phone
- Address
- Role
- Permissions
- Date Hired
- Hired by
- Date Exited
- Exit Reason
- Active
- Image
- Active
- Business Type
- Fleet
- Vehicle Type
- Year
- Make
- Model
- License Plate
- VIN
- Color
- Style
- Unit Number
- Image(s)
- Assigned Driver
- Employee ID No
- Date Driving
- Starting Miles
- Ending Miles
- Maintenance Needed (yes/no)
- Type of Maintenance
- Mileage
- Maintenance Performed (yes/no)
- Date Performed
- Completed (yes/no)
- Date Completed
- Maintenance Comments
- Comment by
- Date of Comment
- RFS (Request for Service)
- Service Type (e.g. Ride share, delivery, pick-up, messenger, etc)
- Requested by
- Customer Info
- Name
- Address
- Phone
- Customer ID No
- Business Name
- Image of contact person(s)
- Date Requested
- Date / Time for Service
- Date / Time of Completion
- Service Started (yes/no)
- Service Cost
- Starting Mileage
- Ending Mileage
- Mileage Required
- Service Status (based on workflow stages e.g. assigned, en-route, in progress, arrived, delivered, picked up, awaiting vendor, confirmed, pending, etc)
At service request, generate a QR Code which provides a confirmation for servicer to verify upon pickup, acceptance, delivery, etc.
Setup Workflows for different service types.
Setup Dispatch and self-dispatch

1
README.md Normal file
View file

@ -0,0 +1 @@
# Readme

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';

24
imports/api/mScripts.js Normal file
View file

@ -0,0 +1,24 @@
import { Meteor } from 'meteor/meteor';
import { Mongo } from 'meteor/mongo';
import { check } from 'meteor/check';
export const MScripts = new Mongo.Collection('mScripts');
MScripts.allow({
insert: function(userId, doc){
// if use id exists, allow insert
return !!userId;
},
});
Meteor.methods({
'set.ScriptRun' (scriptName) {
check(scriptName, String);
MScripts.insertAsync({
scriptName: scriptName,
hasRun: true,
runOn: new Date(),
});
},
});

View file

@ -0,0 +1,49 @@
import { Meteor } from 'meteor/meteor';
import { Mongo } from 'meteor/mongo';
import { check } from 'meteor/check';
export const SysConfig = new Mongo.Collection('sysConfig');
SysConfig.allow({
insert: function(userId, doc){
// if use id exists, allow insert
return !!userId;
},
});
Meteor.methods({
'add.noSysAdminReg' (admReg, genReg) {
check(admReg, Boolean);
check(genReg, Boolean);
if (!this.userId) {
throw new Meteor.Error('Not able to change registration setting. Make sure you are logged in with valid system administrator credentials.');
}
console.log("Got here...");
console.log("Adding new.");
return SysConfig.upsertAsync({ ruleNo: 1 }, {
$set: {
ruleNo: 1,
SysAdminReg: admReg,
dateAdded: new Date(),
allowReg: genReg,
allowUpdates: true,
}
});
},
'allow.updateInfo' (allowUpdate) {
check(allowUpdate, Boolean);
if (!this.userId) {
throw new Meteor.Error('Not able to change system update notification settings. Make sure you are logged in with valid system administrator credentials.');
}
return SysConfig.updateAsync({ ruleNo: 1 }, {
$set: {
allowUpdates: allowUpdate,
}
});
},
});

38
imports/api/updateInfo.js Normal file
View file

@ -0,0 +1,38 @@
import { Meteor } from 'meteor/meteor';
import { Mongo } from 'meteor/mongo';
import { check } from 'meteor/check';
export const UpdateInfo = new Mongo.Collection('updateInfo');
UpdateInfo.allow({
insert: function(userId, doc){
// if use id exists, allow insert
return !!userId;
},
});
Meteor.methods({
'add.updateInfo' (updateObject) {
check(updateObject, Object);
UpdateInfo.insertAsync({
title: updateObject.title,
description: updateObject.description,
dateRelease: updateObject.date,
releaseLink: updateObject.link
});
},
'markUpdate.read' (updateId) {
check(updateId, String);
if (!this.userId) {
throw new Meteor.Error('You are not allowed to mark updates as read. Make sure you are logged in with valid user credentials.');
}
return UpdateInfo.updateAsync({ _id: updateId }, {
$set: {
viewed: true
}
});
}
});

16
imports/api/userInfo.js Normal file
View file

@ -0,0 +1,16 @@
import { Meteor } from 'meteor/meteor';
import { Mongo } from 'meteor/mongo';
import { check } from 'meteor/check';
export const UserInfo = new Mongo.Collection('userInfo');
UserInfo.allow({
insert: function(userId, doc){
// if use id exists, allow insert
return !!userId;
},
});
Meteor.methods({
});

50
lib/route.js Normal file
View file

@ -0,0 +1,50 @@
import { FlowRouter } from 'meteor/ostrio:flow-router-extra';
FlowRouter.route('/', {
name: 'home',
action() {
this.render('MainLayout', { main: "home" });
}
});
FlowRouter.route('/home', {
name: 'home',
action() {
this.render('MainLayout', { main: "home" });
}
});
FlowRouter.route('/', {
name: 'login',
action() {
this.render('MainLayout', { notLoggedIn: "login" });
}
});
FlowRouter.route('/login', {
name: 'login',
action() {
this.render('MainLayout', { notLoggedIn: "login" });
}
});
FlowRouter.route('/reg', {
name: 'registration',
action() {
this.render('MainLayout', { notLoggedIn: 'reg' });
}
});
FlowRouter.route('/systemAdmin', {
name: 'systemAdmin',
action() {
this.render('MainLayout', { main: "systemAdmin" });
}
});
FlowRouter.route('/userMgmt', {
name: 'userManagement',
action() {
this.render('MainLayout', { main: "userMgmt" });
}
});

1836
package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

17
package.json Normal file
View file

@ -0,0 +1,17 @@
{
"name": "meteor-app",
"private": true,
"scripts": {
"start": "meteor run",
"test": "meteor test --once --driver-package meteortesting:mocha",
"test-app": "TEST_WATCH=1 meteor test --full-app --driver-package meteortesting:mocha",
"visualize": "meteor --production --extra-packages bundle-visualizer"
},
"dependencies": {
"@babel/runtime": "^7.17.9",
"jquery": "^3.6.0",
"meteor-node-stubs": "^1.2.1",
"node-cron": "^3.0.3",
"rss-url-parser": "^1.1.0"
}
}

95
server/main.js Normal file
View file

@ -0,0 +1,95 @@
import { Meteor } from 'meteor/meteor';
import { Roles } from 'meteor/roles';
import { SysConfig } from '../imports/api/systemConfig.js';
import { MScripts } from '../imports/api/mScripts.js';
import { UpdateInfo } from '../imports/api/updateInfo.js'
Meteor.startup(async () => {
// code to run on server at startup
await Roles.createRoleAsync("user", { unlessExists: true });
await Roles.createRoleAsync("systemadmin", { unlessExists: true });
await Roles.createRoleAsync("tenantadmin", { unlessExists: true });
await Roles.createRoleAsync("shareadmin", { unlessExists: true });
// set the systemconfig defaults for registration
// check if this has already been run
let regPolRun = await MScripts.findOneAsync({ scriptName: "DefaultRegPolicy", scriptRun: true });
if (!regPolRun) {
try {
let regPolicy = await SysConfig.findOneAsync({});
if (!regPolicy) {
SysConfig.insertAsync({
SysAdminReg: false,
dateAdded: new Date(),
allowReg: true,
allowUpdates: true,
});
markScriptRun("DefaultRegPolicy");
} else {
// console.log("Registration policy already set.");
markScriptRun("DefaultRegPolicy");
}
} catch(error) {
console.log(" ERROR trying to check if registration policy was set.");
console.log(error.message);
console.log(error.stack);
}
}
});
var startCronForUpdates = function(feedurl) {
var cron = require('node-cron');
cron.schedule('*/30 * * * *', () => {
getUpdateInfoNow(feedurl);
});
}
var markScriptRun = async function(scriptName) {
// check if this is already set
let scriptRun = await MScripts.findOneAsync({ scriptName: scriptName });
if (!scriptRun) {
try {
return MScripts.insertAsync({
scriptName: scriptName,
scriptRun: true,
runOn: new Date()
});
} catch(error) {
console.log(" ERROR inserting the script run log: " + error);
console.log(error.message);
console.log(error.stack);
}
} else {
// console.log(scriptName + " already set as run on " + scriptRun.runOn);
}
}
var getUpdateInfoNow = async function(feedurl) {
const parser = require('rss-url-parser')
const data = await parser(feedurl)
let dataLength = data.length;
// console.dir(data[0].title);
// check if this title already exists in db
let updatesExist = await UpdateInfo.findOneAsync({ title: data[0].title });
try {
if (!updatesExist) {
UpdateInfo.insertAsync({
title: data[0].title,
description: data[0].description,
dateRelease: data[0].date,
releaseLink: data[0].link,
viewed: false
});
} else {
console.log("No new updates available at this time.");
}
} catch(error) {
console.log(" ERROR checking for update: " + error);
console.log(error.message);
console.log(error.stack);
}
}

71
server/methods.js Normal file
View file

@ -0,0 +1,71 @@
import { Meteor } from 'meteor/meteor';
import { Mongo } from 'meteor/mongo';
import { check } from 'meteor/check';
import { Roles } from 'meteor/roles';
Meteor.methods({
'addToRole' (role) {
const getUserInfo = async() => {
try {
let countOfUsers = await Meteor.users.find().countAsync();
const user = await Meteor.userAsync();
if (user) {
let userId = user._id;
if (countOfUsers > 1) {
Roles.addUsersToRolesAysnc(userId, role);
} else if (countOfUsers == 1) {
Roles.addUsersToRolesAsync(userId, "systemadmin");
} else {
console.log("The count of users didn't seem to work when adding a new user.");
}
} else {
console.log(" ---- No user info found.")
}
} catch(error) {
console.log(" ERROR getting user info on server: " + error);
}
}
getUserInfo();
},
'edit.userPass' (userId, newPassword) {
check(userId, String);
check(newPassword, String);
const setUsersPassword = async() => {
try {
const setPass = await Accounts.setPasswordAsync(userId, newPassword);
if (setPass) {
return setPass;
} else {
console.log(" Error seeting user password, await returned nothing.");
}
} catch(error) {
console.log(" ERROR setting user's password: " + error);
}
}
},
'delete.userFromSys' (userId) {
check(userId, String);
return Meteor.users.remove({ _id: userId });
},
'update.userEmail' (userId, email) {
check(userId, String);
check(email, String);
return Meteor.users.update({ _id: userId }, {
$set: {
'emails.0.address': email,
}
});
},
'edit.userRole' (userId, role) {
check(userId, String);
check(role, String);
return Roles.setUserRoles(userId, role);
},
});

36
server/publish.js Normal file
View file

@ -0,0 +1,36 @@
import { SysConfig } from "../imports/api/systemConfig";
import { UpdateInfo } from "../imports/api/updateInfo";
import { MScripts } from "../imports/api/mScripts";
import { UserInfo } from "../imports/api/userInfo";
import { Roels } from "meteor/roles";
Meteor.publish("SystemConfig", function() {
try {
return SysConfig.find({});
} catch (error) {
console.log(" ERROR pulling system config data: " + error);
}
});
Meteor.publish("UpdateVersion", function() {
try {
return UpdateInfo.find({ viewed: false });
} catch(error) {
console.log(" ERROR pulling updated version info: " + error);
}
});
Meteor.publish("UserInfo", function() {
try {
return this.userId;
} catch(error) {
console.log(" ERROR getting user Id: " + error);
}
});
Meteor.publish(null, function () {
if (this.userId) {
return Meteor.roleAssignment.find({ "user._id": this.userId });
}
this.ready();
});

20
tests/main.js Normal file
View file

@ -0,0 +1,20 @@
import assert from "assert";
describe("meteor-app", function () {
it("package.json has correct name", async function () {
const { name } = await import("../package.json");
assert.strictEqual(name, "meteor-app");
});
if (Meteor.isClient) {
it("client is not server", function () {
assert.strictEqual(Meteor.isServer, false);
});
}
if (Meteor.isServer) {
it("server is not client", function () {
assert.strictEqual(Meteor.isClient, false);
});
}
});