WIP reorganization
This commit is contained in:
1 parent
6706dfe634
commit
054e37dfb7
266 files changed
+98
-620
No files matched your search
@@ -0,0 +1 @@
|
||||
.build*
|
||||
@@ -0,0 +1,13 @@
|
||||
language: node_js
|
||||
node_js:
|
||||
- "0.10"
|
||||
#sudo: required
|
||||
|
||||
sudo: required
|
||||
|
||||
env:
|
||||
global:
|
||||
- METEOR_ENV=development
|
||||
|
||||
before_install:
|
||||
- "curl -L http://git.io/ejPSng | /bin/sh"
|
||||
@@ -0,0 +1,9 @@
|
||||
|
||||
#### v2.3.7
|
||||
|
||||
* Updated to use clinical:router
|
||||
|
||||
#### v2.4.0
|
||||
|
||||
* clinical:hipaa-loger extracted from package
|
||||
* HipaaLogger and HipaaLog removed
|
||||
@@ -0,0 +1,71 @@
|
||||
clinical:hipaa-audit-log
|
||||
====================================================
|
||||
|
||||
HIPAA logging and audit features for Meteor Apps built with Clinical UI.
|
||||
|
||||

|
||||
|
||||
====================================================
|
||||
#### Installation
|
||||
|
||||
The HIPAA audit log is now split into two packages: one for the logging, and one for the UI. Please see [``clinical:hipaa-logger``](https://github.com/clinical-meteor/hipaa-logger) for the logging portion.
|
||||
|
||||
````
|
||||
meteor add clinical:hipaa-audit-log
|
||||
meteor add clinical:hipaa-logger
|
||||
````
|
||||
|
||||
====================================================
|
||||
#### URL Routes
|
||||
|
||||
Navigate to the audit log via the default route:
|
||||
|
||||
````js
|
||||
Router.go('/audit');
|
||||
````
|
||||
|
||||
====================================================
|
||||
#### Provided Templates
|
||||
|
||||
Three templates are provided by this package:
|
||||
|
||||
````html
|
||||
{{>hipaaAuditLog}}
|
||||
{{>hipaaRibbon}}
|
||||
{{>hipaaLogPage}}
|
||||
````
|
||||
|
||||
====================================================
|
||||
#### Styling and Classes
|
||||
|
||||
You can adjust the styling of the audit log through the configuration object. The following example shows how to style the audit log with Bootstrap controls.
|
||||
|
||||
````js
|
||||
HipaaAuditLog.configure({
|
||||
classes: {
|
||||
input: "form-control squee",
|
||||
select: "form-control",
|
||||
ribbon: ""
|
||||
},
|
||||
highlightColor: "#006289"
|
||||
});
|
||||
````
|
||||
|
||||
|
||||
====================================================
|
||||
#### StarryNight/Nightwatch API - Provides
|
||||
|
||||
````js
|
||||
// component API calls
|
||||
reviewHipaaAuditLogPage()
|
||||
hipaaLogEntryContains(rowIndex, hipaaEvent)
|
||||
|
||||
// actions
|
||||
logHipaaEvent(hipaaEvent, timeout)
|
||||
````
|
||||
|
||||
|
||||
------------------------
|
||||
### License
|
||||
|
||||

|
||||
@@ -0,0 +1,188 @@
|
||||
<template name="hipaaAuditLog">
|
||||
<ul id="hipaaAuditLog" class="list-group">
|
||||
{{#each hipaaAudit}}
|
||||
{{> hipaaEntry}}
|
||||
{{/each}}
|
||||
</ul>
|
||||
</template>
|
||||
|
||||
<template name="hipaaEntry">
|
||||
{{#if logMessageType 'init'}}
|
||||
<li class="hipaaAuditItem list-group-item">
|
||||
<div class="media">
|
||||
<div class="media-icon">
|
||||
<i class="fa fa-gears fa-2x"></i>
|
||||
</div>
|
||||
<div class="media-body">
|
||||
<span class="small gray">{{entryTimestamp}}</span><br>
|
||||
<span class="userName" style="{{getHighlightColor}}">{{userName}}</span> initialized HIPAA Audit Log.
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
{{/if}}
|
||||
|
||||
|
||||
{{#if logMessageType 'access'}}
|
||||
<li class="hipaaAuditItem list-group-item">
|
||||
<div class="media">
|
||||
<div class="media-icon">
|
||||
<i class="fa fa-database fa-2x"></i>
|
||||
</div>
|
||||
<div class="media-body">
|
||||
<span class="small gray">{{entryTimestamp}}</span><br>
|
||||
<span class="userName" style="{{getHighlightColor}}">{{userName}}</span> accessed record(s) <span class="mongoRecordId" style="{{getHighlightColor}}">{{getRecordId}}</span> in the <span class="collectionName" style="{{getHighlightColor}}">{{getCollectionName}}</span> collection.
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
{{/if}}
|
||||
|
||||
|
||||
{{#if logMessageType 'create'}}
|
||||
<li class="hipaaAuditItem list-group-item">
|
||||
<div class="media">
|
||||
<div class="media-icon">
|
||||
<i class="fa fa-plus-circle fa-2x"></i>
|
||||
</div>
|
||||
<div class="media-body">
|
||||
<span class="small gray">{{entryTimestamp}}</span><br>
|
||||
{{#if hasPatientInfo}}
|
||||
<span class="userName" style="{{getHighlightColor}}">{{userName}}</span> created record <span class="mongoRecordId" style="{{getHighlightColor}}">{{getRecordId}}</span> in the <span class="collectionName" style="{{getHighlightColor}}">{{getCollectionName}}</span> collection, regarding patient <span class="patientName" style="{{getHighlightColor}}">{{getPatientName}}</span>.
|
||||
{{else}}
|
||||
<span class="userName" style="{{getHighlightColor}}">{{userName}}</span> created record <span class="mongoRecordId" style="{{getHighlightColor}}">{{getRecordId}}</span> in the <span class="collectionName" style="{{getHighlightColor}}">{{getCollectionName}}</span> collection.
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
{{/if}}
|
||||
|
||||
|
||||
{{#if logMessageType 'modify'}}
|
||||
<li class="hipaaAuditItem list-group-item">
|
||||
<div class="media">
|
||||
<div class="media-icon">
|
||||
<i class="fa fa-pencil fa-2x"></i>
|
||||
</div>
|
||||
<div class="media-body">
|
||||
<span class="small gray">{{entryTimestamp}}</span><br>
|
||||
{{#if hasPatientInfo}}
|
||||
<span class="userName" style="{{getHighlightColor}}">{{userName}}</span> modified record <span class="mongoRecordId" style="{{getHighlightColor}}">{{getRecordId}}</span> in the <span class="collectionName" style="{{getHighlightColor}}">{{getCollectionName}}</span> collection, regarding patient <span class="patientName" style="{{getHighlightColor}}">{{getPatientName}}</span>.
|
||||
{{else}}
|
||||
<span class="userName" style="{{getHighlightColor}}">{{userName}}</span> modified record <span class="mongoRecordId" style="{{getHighlightColor}}">{{getRecordId}}</span> in the <span class="collectionName" style="{{getHighlightColor}}">{{getCollectionName}}</span> collection.
|
||||
{{/if}}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
{{/if}}
|
||||
|
||||
|
||||
{{#if logMessageType 'clone'}}
|
||||
<li class="hipaaAuditItem list-group-item">
|
||||
<div class="media">
|
||||
<div class="media-icon">
|
||||
<i class="fa fa-code-fork fa-2x"></i>
|
||||
</div>
|
||||
<div class="media-body">
|
||||
<span class="small gray">{{entryTimestamp}}</span><br>
|
||||
<span class="userName" style="{{getHighlightColor}}">{{userName}}</span> cloned record <span class="mongoRecordId" style="{{getHighlightColor}}">{{getRecordId}}</span> in the <span class="collectionName" style="{{getHighlightColor}}">{{getCollectionName}}</span> collection.
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
{{/if}}
|
||||
|
||||
|
||||
{{#if logMessageType 'delete'}}
|
||||
<li class="hipaaAuditItem list-group-item">
|
||||
<div class="media">
|
||||
<div class="media-icon">
|
||||
<i class="fa fa-minus-circle fa-2x"></i>
|
||||
</div>
|
||||
<div class="media-body">
|
||||
<span class="small gray">{{entryTimestamp}}</span><br>
|
||||
<span class="userName" style="{{getHighlightColor}}">{{userName}}</span> deleted record <span class="mongoRecordId" style="{{getHighlightColor}}">{{getRecordId}}</span> in the <span class="collectionName" style="{{getHighlightColor}}">{{getCollectionName}}</span> collection.
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
{{/if}}
|
||||
|
||||
|
||||
{{#if logMessageType 'denied'}}
|
||||
<li class="hipaaAuditItem list-group-item">
|
||||
<div class="media">
|
||||
<div class="media-icon">
|
||||
<i class="fa fa-warning fa-2x"></i>
|
||||
</div>
|
||||
<div class="media-body">
|
||||
<span class="small gray">{{entryTimestamp}}</span><br>
|
||||
<span class="userName" style="{{getHighlightColor}}">{{userName}}</span> was denied access to record <span class="mongoRecordId" style="{{getHighlightColor}}">{{getRecordId}}</span> in the <span class="collectionName" style="{{getHighlightColor}}">{{getCollectionName}}</span> collection.
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
{{/if}}
|
||||
|
||||
|
||||
{{#if logMessageType 'viewed'}}
|
||||
<li class="hipaaAuditItem list-group-item">
|
||||
<div class="media">
|
||||
<div class="media-icon">
|
||||
<i class="fa fa-eye fa-2x"></i>
|
||||
</div>
|
||||
<div class="media-body">
|
||||
<span class="small gray">{{entryTimestamp}}</span><br>
|
||||
{{#if hasPatientInfo}}
|
||||
<span class="userName" style="{{getHighlightColor}}">{{userName}}</span> viewed record <span class="mongoRecordId" style="{{getHighlightColor}}">{{getRecordId}}</span> in the <span class="collectionName" style="{{getHighlightColor}}">{{getCollectionName}}</span> collection, regarding patient <span class="patientName" style="{{getHighlightColor}}">{{getPatientName}}</span>.
|
||||
{{else}}
|
||||
<span class="userName" style="{{getHighlightColor}}">{{userName}}</span> viewed record <span class="mongoRecordId" style="{{getHighlightColor}}">{{getRecordId}}</span> in the <span class="collectionName" style="{{getHighlightColor}}">{{getCollectionName}}</span> collection.
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
{{/if}}
|
||||
|
||||
|
||||
{{#if logMessageType 'publish'}}
|
||||
<li class="hipaaAuditItem list-group-item">
|
||||
<div class="media">
|
||||
<div class="media-icon">
|
||||
<i class="fa fa-rss fa-2x"></i>
|
||||
</div>
|
||||
<div class="media-body">
|
||||
<span class="small gray">{{entryTimestamp}}</span><br>
|
||||
<span class="userName" style="{{getHighlightColor}}">{{userName}}</span> published record <span class="mongoRecordId" style="{{getHighlightColor}}">{{getRecordId}}</span> in the <span class="collectionName" style="{{getHighlightColor}}">{{getCollectionName}}</span> collection.
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
{{/if}}
|
||||
|
||||
|
||||
{{#if logMessageType 'unpublish'}}
|
||||
<li class="hipaaAuditItem list-group-item">
|
||||
<div class="media">
|
||||
<div class="media-icon">
|
||||
<i class="fa fa-circle-o fa-2x"></i>
|
||||
</div>
|
||||
<div class="media-body">
|
||||
<span class="small gray">{{entryTimestamp}}</span><br>
|
||||
<span class="userName" style="{{getHighlightColor}}">{{userName}}</span> unpublished record <span class="mongoRecordId" style="{{getHighlightColor}}">{{getRecordId}}</span> in the <span class="collectionName" style="{{getHighlightColor}}">{{getCollectionName}}</span> collection.
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
{{/if}}
|
||||
|
||||
|
||||
{{#if logMessageType 'error'}}
|
||||
<li class="hipaaAuditItem list-group-item">
|
||||
<div class="media">
|
||||
<div class="media-icon">
|
||||
<i class="fa fa-bomb fa-2x"></i>
|
||||
</div>
|
||||
<div class="media-body">
|
||||
<span class="small gray">{{entryTimestamp}}</span><br>
|
||||
<span class="userName" style="{{getHighlightColor}}">{{userName}}</span> received a data access error while performing an action!<br>
|
||||
<span class="error">{{getErrorMessage}}</span>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
{{/if}}
|
||||
|
||||
</template>
|
||||
@@ -0,0 +1,160 @@
|
||||
//Hipaa = new Meteor.Collection("hipaa");
|
||||
|
||||
Session.setDefault("hipaaSearchFilter", '');
|
||||
Session.setDefault("hipaaTypeFilter", '');
|
||||
|
||||
// search window defaults to seven days in the past and one day in the future
|
||||
Session.setDefault("beginDateFilter", new Date(moment().subtract(7, "days")).toISOString());
|
||||
Session.setDefault("endDateFilter", new Date(moment().add(1, "days")).toISOString());
|
||||
|
||||
|
||||
|
||||
|
||||
Template.hipaaAuditLog.onRendered(function () {
|
||||
Session.set("ribbonWidth", $('#hipaaRibbon').width());
|
||||
});
|
||||
|
||||
Template.hipaaAuditLog.helpers({
|
||||
getHipaaSearchFilter: function () {
|
||||
return Session.get('hipaaSearchFilter');
|
||||
},
|
||||
hipaaAudit: function () {
|
||||
// return HipaaLog.find();
|
||||
return HipaaLog.find({
|
||||
$or: [
|
||||
{
|
||||
userName: {
|
||||
$regex: Session.get('hipaaSearchFilter'),
|
||||
$options: 'i'
|
||||
}
|
||||
},
|
||||
{
|
||||
patientName: {
|
||||
$regex: Session.get('hipaaSearchFilter'),
|
||||
$options: 'i'
|
||||
}
|
||||
},
|
||||
{
|
||||
recordId: {
|
||||
$regex: Session.get('hipaaSearchFilter'),
|
||||
$options: 'i'
|
||||
}
|
||||
},
|
||||
{
|
||||
collectionName: {
|
||||
$regex: Session.get('hipaaSearchFilter'),
|
||||
$options: 'i'
|
||||
}
|
||||
}
|
||||
],
|
||||
eventType: {
|
||||
$regex: Session.get("hipaaTypeFilter"),
|
||||
$options: 'i'
|
||||
},
|
||||
timestamp: {
|
||||
$lte: new Date(Session.get('endDateFilter')),
|
||||
$gte: new Date(Session.get('beginDateFilter'))
|
||||
}
|
||||
}, {
|
||||
sort: {
|
||||
timestamp: -1
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
Template.hipaaAuditLog.events({
|
||||
"keyup #hipaaSearchFilter": function (event, template) {
|
||||
Session.set("hipaaSearchFilter", $('#hipaaSearchFilter').val());
|
||||
},
|
||||
"click .userName": function(event, template) {
|
||||
var userName = $(event.currentTarget).text();
|
||||
Session.set('hipaaSearchFilter', userName);
|
||||
},
|
||||
"click .patientName": function(event, template) {
|
||||
var patientName = $(event.currentTarget).text();
|
||||
var patientNameRegex = patientName.replace(/[!@#$%^&*()+=\-[\]\\';,./{}|":<>?~_]/g, "\\$&");
|
||||
Session.set('hipaaSearchFilter', patientNameRegex);
|
||||
},
|
||||
"click .mongoRecordId": function(event, template) {
|
||||
var mongoRecordId = $(event.currentTarget).text();
|
||||
Session.set('hipaaSearchFilter', mongoRecordId);
|
||||
},
|
||||
"click .collectionName": function(event, template) {
|
||||
var collectionName = $(event.currentTarget).text();
|
||||
Session.set('hipaaSearchFilter', collectionName);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
//==================================================================================================
|
||||
// HIPAA EVENT RECORD
|
||||
|
||||
Template.hipaaEntry.helpers({
|
||||
getHighlightColor: function () {
|
||||
var hipaaAuditLog = Session.get('HipaaAuditLogConfig');
|
||||
if (hipaaAuditLog) {
|
||||
return "color:" + hipaaAuditLog.highlightColor;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
},
|
||||
getUserName: function () {
|
||||
if (this.userName) {
|
||||
return this.userName;
|
||||
} else {
|
||||
return "---";
|
||||
}
|
||||
},
|
||||
getPatientName: function () {
|
||||
if (this.patientName) {
|
||||
return this.patientName;
|
||||
} else {
|
||||
return "---";
|
||||
}
|
||||
},
|
||||
hasPatientInfo: function () {
|
||||
if (this.patientName) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
getErrorMessage: function () {
|
||||
if (this.message) {
|
||||
return this.message;
|
||||
} else {
|
||||
return "---";
|
||||
}
|
||||
},
|
||||
getCollectionName: function () {
|
||||
if (this.collectionName) {
|
||||
return this.collectionName;
|
||||
} else {
|
||||
return "---";
|
||||
}
|
||||
},
|
||||
getRecordId: function () {
|
||||
if (this.recordId) {
|
||||
return this.recordId;
|
||||
} else {
|
||||
return "---";
|
||||
}
|
||||
},
|
||||
entryTimestamp: function () {
|
||||
return moment(this.timestamp).format("YYYY, MMM DD, hh:mm A");
|
||||
},
|
||||
entryTime: function () {
|
||||
return moment(this.timestamp).format("HH:MM A");
|
||||
},
|
||||
entryDate: function () {
|
||||
return moment(this.timestamp).format("YYYY, MMM DD");
|
||||
},
|
||||
logMessageType: function (eventType) {
|
||||
if (this.eventType === eventType) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,54 @@
|
||||
#hipaaAuditLog{
|
||||
color: #fff;
|
||||
width: 100%;
|
||||
|
||||
.hipaaAuditItem {
|
||||
border: none;
|
||||
background-color: black;
|
||||
list-style: none;
|
||||
padding: 10px;
|
||||
line-height: 25px;
|
||||
|
||||
// There's a CSS rule on ohif-user-management that's changing the .hipaaAuditItem border style (w/ !important)
|
||||
&:first-child {
|
||||
border-top: none !important;
|
||||
}
|
||||
|
||||
&:not(:first-child) {
|
||||
border-top: 1px solid #436270;
|
||||
}
|
||||
|
||||
border-bottom: 1px solid #436270;
|
||||
|
||||
&:nth-child(even) {
|
||||
background-color: #151a1f;
|
||||
}
|
||||
|
||||
&:hover, &:active, &.active {
|
||||
background-color: #2c363f;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.item-icon {
|
||||
top: 12px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.media-icon {
|
||||
float: left;
|
||||
top: 12px;
|
||||
width: 40px;
|
||||
text-align: center;
|
||||
position: relative;
|
||||
margin-right: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.gray{
|
||||
color: gray;
|
||||
}
|
||||
|
||||
.userName, .patientName, .mongoRecordId, .collectionName{
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
<template name="hipaaCloseButton">
|
||||
<div class="close-btn">
|
||||
{{#if header}}
|
||||
<button type="button" class="close">
|
||||
<span>×</span>
|
||||
</button>
|
||||
{{else}}
|
||||
<button type="button" class="btn btn-default">
|
||||
Close
|
||||
</button>
|
||||
{{/if}}
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,12 @@
|
||||
Template.hipaaCloseButton.events({
|
||||
'click button': function() {
|
||||
var history = window.history;
|
||||
|
||||
if(history.length > 0) {
|
||||
history.go(-1);
|
||||
}
|
||||
else {
|
||||
window.location = '/'
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,44 @@
|
||||
@tableTextSecondaryColor: #91b9cd;
|
||||
|
||||
.close-btn {
|
||||
text-align: right;
|
||||
padding: 5px 5px 0;
|
||||
|
||||
&:before,
|
||||
&:after {
|
||||
display: table;
|
||||
content: " ";
|
||||
clear: both;
|
||||
}
|
||||
|
||||
.close {
|
||||
color: @tableTextSecondaryColor;
|
||||
opacity: 1;
|
||||
|
||||
&.close:hover, &.close:focus {
|
||||
color: #fff;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.btn-default {
|
||||
margin-bottom: 5px;
|
||||
margin-right: 5%;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 1161px) {
|
||||
.close-btn {
|
||||
.btn-default {
|
||||
margin-right: 3%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 991px) {
|
||||
.close-btn {
|
||||
.btn-default {
|
||||
margin-right: 1%;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
<template name="hipaaLogPage">
|
||||
<div id="hipaaLogPage" class="page">
|
||||
|
||||
<div class="hipaaHeader clearfix">
|
||||
<div class="header pull-left">
|
||||
Audit Log
|
||||
</div>
|
||||
<div class="pull-right">
|
||||
{{> hipaaCloseButton header="true" }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{> hipaaRibbon }}
|
||||
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
{{> hipaaAuditLog}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{#if hasCloseButtons}}
|
||||
{{> hipaaCloseButton }}
|
||||
{{/if}}
|
||||
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,17 @@
|
||||
Router.route("/audit", {
|
||||
name:"hipaaAuditLogRoute",
|
||||
template:"hipaaLogPage"
|
||||
});
|
||||
|
||||
|
||||
Template.hipaaLogPage.helpers({
|
||||
hasCloseButtons: function() {
|
||||
var hipaaAuditLog = Session.get('HipaaAuditLogConfig');
|
||||
return hipaaAuditLog && hipaaAuditLog.closeButton;
|
||||
}
|
||||
});
|
||||
|
||||
// We probably don't need this, but the subscription in subscriptions.js doesn't seem to be working?
|
||||
Template.hipaaLogPage.onCreated(function() {
|
||||
this.subscribe('HipaaLog');
|
||||
})
|
||||
@@ -0,0 +1,52 @@
|
||||
@tableTextSecondaryColor: #91b9cd;
|
||||
|
||||
@hipaaHeaderHeight: 75px;
|
||||
@hipaaHeaderBgColor: #151a1f;
|
||||
|
||||
#hipaaLogPage {
|
||||
background-color: #000;
|
||||
|
||||
.hipaaHeader {
|
||||
background-color: @hipaaHeaderBgColor;
|
||||
height: @hipaaHeaderHeight;
|
||||
margin-bottom: 2px;
|
||||
padding: 0 20px;
|
||||
|
||||
& > div {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.header {
|
||||
font-size: 22px;
|
||||
font-weight: 300;
|
||||
color: @tableTextSecondaryColor;
|
||||
line-height: @hipaaHeaderHeight;
|
||||
}
|
||||
}
|
||||
|
||||
.container {
|
||||
margin: 0;
|
||||
padding: 0 5%;
|
||||
width: 100%;
|
||||
|
||||
& > div.row {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 1161px) {
|
||||
#hipaaLogPage {
|
||||
.container {
|
||||
padding: 0 3%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 991px) {
|
||||
#hipaaLogPage {
|
||||
.container {
|
||||
padding: 0 1%;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
<template name="hipaaRibbon">
|
||||
<div id="hipaaRibbon" class="row {{getRibbonClass}}">
|
||||
<div class="filter col-xs-12 col-sm-4 col-md-6">
|
||||
<span>User</span>
|
||||
<div class="btn-group">
|
||||
<input type="text" name="hipaaSearchFilter" id="hipaaSearchFilter" value="{{getHipaaSearchFilter}}" class="form-control">
|
||||
<span id="searchClear" class="glyphicon glyphicon-remove-circle"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="filter col-xs-4 col-sm-3 col-md-2 text-center">
|
||||
<span>Begin Date</span>
|
||||
<input type="date" id="beginDateInput" class="{{getInputClass}}" value="{{getBeginDate}}">
|
||||
</div>
|
||||
<div class="filter col-xs-4 col-sm-3 col-md-2 text-center">
|
||||
<span>End Date</span>
|
||||
<input type="date" id="endDateInput" class="{{getInputClass}}" value="{{getEndDate}}">
|
||||
</div>
|
||||
<div class="filter col-xs-4 col-sm-2 text-center">
|
||||
<span>Action</span>
|
||||
<select id="actionFilter" name="actionFilter" class="{{getSelectClass}}">
|
||||
<option id="filterCreatedButton" value="create">Created</option>
|
||||
<option id="filterModifiedButton" value="modify">Modified</option>
|
||||
<option id="filterViewedButton" value="viewed">Viewed</option>
|
||||
<option id="filterAllButton" selected value="">All</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,75 @@
|
||||
Template.hipaaRibbon.events({
|
||||
'keyup #hipaaSearchFilter': function () {
|
||||
Session.set("hipaaSearchFilter", $('#hipaaSearchFilter').val());
|
||||
},
|
||||
"change #beginDateInput": function (event, template) {
|
||||
Session.set("beginDateFilter", $('#beginDateInput').val() + "T00:00:00.000Z");
|
||||
},
|
||||
"change #endDateInput": function (event, template) {
|
||||
Session.set("endDateFilter", $('#endDateInput').val() + "T00:00:00.000Z");
|
||||
},
|
||||
'change #actionFilter': function(e) {
|
||||
var actionType = e.currentTarget.value;
|
||||
Session.set("hipaaTypeFilter", actionType);
|
||||
},
|
||||
'click #filterCreatedButton': function () {
|
||||
Session.set("hipaaTypeFilter", 'create');
|
||||
},
|
||||
'click #filterModifiedButton': function () {
|
||||
Session.set("hipaaTypeFilter", 'modify');
|
||||
},
|
||||
'click #filterViewedButton': function () {
|
||||
Session.set("hipaaTypeFilter", 'viewed');
|
||||
},
|
||||
'click #filterAllButton': function () {
|
||||
Session.set("hipaaTypeFilter", '');
|
||||
},
|
||||
'click #searchClear': function() {
|
||||
Session.set("hipaaSearchFilter", '');
|
||||
}
|
||||
});
|
||||
|
||||
var ribbonBreakPoint = 760;
|
||||
|
||||
|
||||
Meteor.startup(function(){
|
||||
$(window).resize(function(evt) {
|
||||
Session.set("ribbonWidth", $('#hipaaRibbon').width());
|
||||
});
|
||||
});
|
||||
|
||||
Template.hipaaRibbon.helpers({
|
||||
getRibbonClass: function () {
|
||||
var hipaaAuditLog = Session.get('HipaaAuditLogConfig');
|
||||
if (hipaaAuditLog && hipaaAuditLog.classes) {
|
||||
return hipaaAuditLog.classes.ribbon;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
},
|
||||
getSelectClass: function () {
|
||||
var hipaaAuditLog = Session.get('HipaaAuditLogConfig');
|
||||
if (hipaaAuditLog && hipaaAuditLog.classes) {
|
||||
return hipaaAuditLog.classes.select;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
},
|
||||
getInputClass: function () {
|
||||
var hipaaAuditLog = Session.get('HipaaAuditLogConfig');
|
||||
if (hipaaAuditLog && hipaaAuditLog.classes) {
|
||||
return hipaaAuditLog.classes.input;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
},
|
||||
getHipaaSearchFilter: function () {
|
||||
return Session.get('hipaaSearchFilter');
|
||||
},
|
||||
getBeginDate: function () {
|
||||
return moment(Session.get("beginDateFilter")).format("YYYY-MM-DD");
|
||||
},
|
||||
getEndDate: function () {
|
||||
return moment(Session.get("endDateFilter")).format("YYYY-MM-DD");
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,113 @@
|
||||
@tableTextPrimaryColor: white;
|
||||
@tableTextSecondaryColor: #91b9cd;
|
||||
|
||||
@inputBackgroundColor: #2c363f;
|
||||
@inputPlaceholderColor: lightgray;
|
||||
|
||||
@hipaaRibbonBgColor: #151a1f;
|
||||
|
||||
#hipaaRibbon {
|
||||
background-color: @hipaaRibbonBgColor;
|
||||
border-bottom: solid 1px #6fbde2;
|
||||
color: @tableTextPrimaryColor;
|
||||
margin: 0 0 2px;
|
||||
padding: 20px 5%;
|
||||
|
||||
.filter {
|
||||
> span {
|
||||
font-size: 15px;
|
||||
display: block;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
min-height: 70px;
|
||||
|
||||
&:first-child {
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
&:not(:last-child) {
|
||||
padding-right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
input[type="date"] {
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
select {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
input, select {
|
||||
height: 40px;
|
||||
padding: 20px;
|
||||
cursor: pointer;
|
||||
border: none;
|
||||
background-color: @inputBackgroundColor;
|
||||
color: @inputPlaceholderColor;
|
||||
font-size: 10pt;
|
||||
font-weight: normal;
|
||||
width: 100%;
|
||||
border-radius: 4px;
|
||||
box-sizing: border-box;
|
||||
|
||||
transition: all 0.15s ease;
|
||||
-webkit-transition: all 0.15s ease;
|
||||
|
||||
&:active, &:hover {
|
||||
background-color: @inputBackgroundColor;
|
||||
}
|
||||
}
|
||||
|
||||
.btn-group {
|
||||
width: 100%;
|
||||
|
||||
#hipaaSearchFilter{
|
||||
padding-right: 35px;
|
||||
}
|
||||
|
||||
#searchClear {
|
||||
position: absolute;
|
||||
right: 14px;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
height: 14px;
|
||||
margin: auto;
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
color: #ccc;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@media only screen and (max-width: 1161px) {
|
||||
#hipaaRibbon {
|
||||
padding: 20px 3%;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 991px) {
|
||||
#hipaaRibbon {
|
||||
padding: 20px 1%;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 767px) {
|
||||
#hipaaRibbon {
|
||||
.filter {
|
||||
&:first-child {
|
||||
padding-bottom: 15px;
|
||||
}
|
||||
|
||||
&:nth-child(2) {
|
||||
padding-left: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
Meteor.subscribe('HipaaLog');
|
||||
@@ -0,0 +1,20 @@
|
||||
HipaaAuditLog = {
|
||||
configure: function (configObject) {
|
||||
if (Meteor.isClient) {
|
||||
Session.set('HipaaAuditLogConfig', configObject);
|
||||
}
|
||||
//console.log("HipaaAuditLogConfig.configObject", configObject);
|
||||
}
|
||||
}
|
||||
|
||||
if (Meteor.isClient) {
|
||||
Session.setDefault('HipaaAuditLogConfig', {
|
||||
classes: {
|
||||
input: "",
|
||||
select: "",
|
||||
ribbon: ""
|
||||
},
|
||||
highlightColor: "",
|
||||
closeButton: false
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
Package.describe({
|
||||
summary: "HIPAA audit log for ClinicalFramework.",
|
||||
version: "2.4.2",
|
||||
git: "http://github.com/clinical-meteor/clinical-hipaa-audit-log.git",
|
||||
name: "clinical:hipaa-audit-log"
|
||||
});
|
||||
|
||||
Package.on_use(function (api) {
|
||||
api.versionsFrom('1.1.0.3');
|
||||
|
||||
api.use('meteor-platform');
|
||||
|
||||
api.use('mrt:moment@2.8.1', 'client');
|
||||
api.use('grove:less@0.1.1', 'client');
|
||||
|
||||
api.use('clinical:router@2.0.19');
|
||||
api.use('clinical:fonts@1.0.0', 'client');
|
||||
api.use('clinical:auto-resizing@0.1.2', 'client');
|
||||
api.use('clinical:hipaa-logger@1.0.0');
|
||||
|
||||
api.imply('clinical:hipaa-logger');
|
||||
|
||||
api.addFiles('lib/HipaaAuditLog.js');
|
||||
|
||||
api.addFiles('server/initialize.js', "server");
|
||||
api.addFiles('server/publication.js', "server");
|
||||
|
||||
api.addFiles('components/hipaaRibbon/hipaaRibbon.html', "client");
|
||||
api.addFiles('components/hipaaRibbon/hipaaRibbon.js', "client");
|
||||
api.addFiles('components/hipaaRibbon/hipaaRibbon.less', "client");
|
||||
|
||||
api.addFiles('components/hipaaAuditLog/hipaaAuditLog.html', "client");
|
||||
api.addFiles('components/hipaaAuditLog/hipaaAuditLog.js', "client");
|
||||
api.addFiles('components/hipaaAuditLog/hipaaAuditLog.less', "client");
|
||||
|
||||
api.addFiles('components/hipaaLogPage/hipaaLogPage.html', "client");
|
||||
api.addFiles('components/hipaaLogPage/hipaaLogPage.js', "client");
|
||||
api.addFiles('components/hipaaLogPage/hipaaLogPage.less', "client");
|
||||
|
||||
api.addFiles('components/hipaaCloseButton/hipaaCloseButton.html', "client");
|
||||
api.addFiles('components/hipaaCloseButton/hipaaCloseButton.js', "client");
|
||||
api.addFiles('components/hipaaCloseButton/hipaaCloseButton.less', "client");
|
||||
|
||||
api.export('HipaaAuditLog');
|
||||
});
|
||||
|
||||
|
||||
|
||||
Package.onTest(function (api) {
|
||||
api.use('tinytest');
|
||||
|
||||
api.use('meteor-platform');
|
||||
api.use('clinical:router@2.0.19', 'client');
|
||||
api.use('mrt:moment@2.8.1', 'client');
|
||||
api.use('grove:less@0.1.1', 'client');
|
||||
|
||||
api.use('clinical:verification');
|
||||
api.use('clinical:fonts@1.0.0');
|
||||
api.use('clinical:hipaa-audit-log');
|
||||
api.use('clinical:hipaa-logger@1.0.0');
|
||||
|
||||
api.addFiles('tests/tinytest/audit-log-tests.js');
|
||||
});
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 304 KiB |
@@ -0,0 +1,26 @@
|
||||
Meteor.methods({
|
||||
initializeDemoLog:function (){
|
||||
console.log('initializeDemoLog');
|
||||
|
||||
// hipaaEvent, userId, userName, collectionName, recordId, patientId, patientName, message
|
||||
HipaaLogger.logEvent("init", Meteor.userId(), "Ada Lovelace");
|
||||
|
||||
HipaaLogger.logEvent("create", Meteor.userId(), "Ada Lovelace", "Users", Random.id(), Random.id(), "John Doe");
|
||||
|
||||
HipaaLogger.logEvent("viewed", Meteor.userId(), "Mary Shelley", "Users", Random.id(), Random.id(), "John Doe");
|
||||
HipaaLogger.logEvent("create", Meteor.userId(), "Florence Nightingale", "Vitals", Random.id(), Random.id(), "John Doe");
|
||||
HipaaLogger.logEvent("viewed", Meteor.userId(), "Florence Nightingale", "Medications", Random.id(), Random.id(), "John Doe");
|
||||
HipaaLogger.logEvent("create", Meteor.userId(), "Florence Nightingale", "Medications", Random.id(), Random.id(), "John Doe");
|
||||
HipaaLogger.logEvent("denied", Meteor.userId(), "Kurt Vonnegut", "MedicationPlans", Random.id(), Random.id(), "John Doe");
|
||||
HipaaLogger.logEvent("create", Meteor.userId(), "Florence Nightingale", "Vitals", Random.id(), Random.id(), "John Doe");
|
||||
HipaaLogger.logEvent("viewed", Meteor.userId(), "Florence Nightingale", "MedicationPlans", Random.id(), Random.id(), "John Doe");
|
||||
HipaaLogger.logEvent("modify", Meteor.userId(), "Florence Nightingale", "MedicationPlans", Random.id(), Random.id(), "John Doe");
|
||||
HipaaLogger.logEvent("viewed", Meteor.userId(), "Edward Doisy", "Users", Random.id(), Random.id(), "John Doe");
|
||||
HipaaLogger.logEvent("clone", Meteor.userId(), "Edward Doisy", "MedicationPlans", Random.id(), Random.id(), "John Doe");
|
||||
HipaaLogger.logEvent("publish", Meteor.userId(), "Edward Doisy", "MedicationPlans", Random.id(), Random.id(), "John Doe");
|
||||
HipaaLogger.logEvent("unpublish", Meteor.userId(), "Edward Doisy", "MedicationPlans", Random.id(), Random.id(), "John Doe");
|
||||
HipaaLogger.logEvent("delete", Meteor.userId(), "Ada Lovelace", "MedicationPlans", Random.id(), Random.id(), "John Doe");
|
||||
HipaaLogger.logEvent("viewed", Meteor.userId(), "Florence Nightingale", "Vitals", Random.id(), Random.id(), "John Doe");
|
||||
HipaaLogger.logEvent("create", Meteor.userId(), "Florence Nightingale", "Vitals", Random.id(), Random.id(), "John Doe");
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,4 @@
|
||||
|
||||
Meteor.publish('HipaaLog', function () {
|
||||
return HipaaLog.find();
|
||||
});
|
||||
@@ -0,0 +1,72 @@
|
||||
|
||||
describe('clinical:hipaa-audit-log', function () {
|
||||
var server = meteor();
|
||||
var client = browser(server);
|
||||
|
||||
// beforeEach(function () {
|
||||
// server.execute(function () {
|
||||
//
|
||||
// }).then(function (value){
|
||||
//
|
||||
// });
|
||||
// });
|
||||
// afterEach(function () {
|
||||
// server.execute(function () {
|
||||
//
|
||||
// });
|
||||
// });
|
||||
|
||||
it('HipaaLogger should exist on the client', function () {
|
||||
return client.execute(function () {
|
||||
expect(HipaaLogger).to.exist;
|
||||
});
|
||||
});
|
||||
|
||||
it('HipaaLogger should exist on the server', function () {
|
||||
return server.execute(function () {
|
||||
expect(HipaaLogger).to.exist;
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it("HipaaLogger can log events on the client", function () {
|
||||
return client.execute(function () {
|
||||
HipaaLogger.logEvent({
|
||||
eventType: "modified",
|
||||
userId: "janedoe",
|
||||
userName: "Jane Doe",
|
||||
collectionName: "Medications",
|
||||
recordId: "123",
|
||||
patientId: "123",
|
||||
patientName: "abc"
|
||||
});
|
||||
return HipaaLog.findOne();
|
||||
}).then(function (eventRecord){
|
||||
server.wait(500, "", function (){
|
||||
expect(eventRecord).to.exist;
|
||||
expect(eventRecord.userId).to.equal("janedoe");
|
||||
expect(eventRecord.userName).to.equal("Jane Doe");
|
||||
});
|
||||
});
|
||||
});
|
||||
it("HipaaLogger can log events on the server", function () {
|
||||
return server.execute(function () {
|
||||
HipaaLogger.logEvent({
|
||||
eventType: "modified",
|
||||
userId: "janedoe",
|
||||
userName: "Jane Doe",
|
||||
collectionName: "Medications",
|
||||
recordId: "123",
|
||||
patientId: "123",
|
||||
patientName: "abc"
|
||||
});
|
||||
|
||||
var eventRecord = HipaaLog.findOne();
|
||||
|
||||
expect(eventRecord).to.exist;
|
||||
expect(eventRecord.userId).to.equal("janedoe");
|
||||
expect(eventRecord.userName).to.equal("Jane Doe");
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
|
||||
|
||||
|
||||
exports.command = function(rowIndex, hipaaEvent) {
|
||||
this
|
||||
.verify.elementPresent("#hipaaAuditLog .hipaaAuditItem:nth-child(" + rowIndex + ")")
|
||||
|
||||
if(hipaaEvent.type === "create"){
|
||||
this
|
||||
.verify.elementPresent("#hipaaAuditLog .hipaaAuditItem:nth-child(" + rowIndex + ") .userName", hipaaEvent.userName)
|
||||
.verify.elementPresent("#hipaaAuditLog .hipaaAuditItem:nth-child(" + rowIndex + ") .recordId", hipaaEvent.recordId)
|
||||
.verify.elementPresent("#hipaaAuditLog .hipaaAuditItem:nth-child(" + rowIndex + ") .collectionName", hipaaEvent.collectionName)
|
||||
|
||||
if(hipaaEvent.patientName){
|
||||
this.verify.elementPresent("#hipaaAuditLog .hipaaAuditItem:nth-child(" + rowIndex + ") .patientName", hipaaEvent.patientName)
|
||||
}
|
||||
}
|
||||
|
||||
return this;
|
||||
};
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
|
||||
|
||||
exports.command = function() {
|
||||
this
|
||||
.verify.elementPresent("#hipaaLogPage")
|
||||
.verify.elementPresent("#hipaaAuditLog")
|
||||
|
||||
return this;
|
||||
};
|
||||
@@ -0,0 +1,41 @@
|
||||
|
||||
|
||||
|
||||
|
||||
// syncrhonous version; doesn't work well
|
||||
/*exports.command = function(hipaaEvent, timeout) {
|
||||
var client = this;
|
||||
this
|
||||
.execute(function(data){
|
||||
return HipaaLogger.logEventObject(data);
|
||||
}, [hipaaEvent], function(result){
|
||||
console.log("result.value", result.value);
|
||||
client.assert.ok(result.value);
|
||||
}).pause(1000)
|
||||
return this;
|
||||
};*/
|
||||
|
||||
|
||||
|
||||
|
||||
// async version calls method on the server
|
||||
exports.command = function(hipaaEvent, timeout) {
|
||||
var client = this;
|
||||
if (!timeout) {
|
||||
timeout = 5000;
|
||||
}
|
||||
|
||||
this
|
||||
.timeoutsAsyncScript(timeout)
|
||||
.executeAsync(function(data, meteorCallback){
|
||||
//return HipaaLogger.logEventObject(data);
|
||||
Meteor.call('logHipaaEvent', data, function(meteorError, meteorResult){
|
||||
var response = (meteorError ? { error: meteorError } : { result: meteorResult });
|
||||
meteorCallback(response);
|
||||
})
|
||||
}, [hipaaEvent], function(result){
|
||||
console.log("result.value", result.value);
|
||||
client.assert.ok(result.value);
|
||||
}).pause(1000)
|
||||
return this;
|
||||
};
|
||||
@@ -0,0 +1,25 @@
|
||||
describe('clinical:hipaa-audit-log', function () {
|
||||
|
||||
describe('user interface', function () {
|
||||
it.client('displays an audit log', function () {
|
||||
|
||||
});
|
||||
it.client('has a search button', function () {
|
||||
|
||||
});
|
||||
it.client('has filter buttons', function () {
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
describe('server functionality', function () {
|
||||
it.client('logs hipaa events to a HIPAA collection', function () {
|
||||
|
||||
});
|
||||
|
||||
it.client('doesnt allow users to delete or modify events', function () {
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user