Allow exporting all data in bson, json, or csv formats.

This commit is contained in:
MarkBryanMilligan 2022-01-29 18:25:19 -06:00
parent eeec6cc697
commit eaf1e4504f
117 changed files with 41205 additions and 10527 deletions

Binary file not shown.

View File

@ -28,11 +28,15 @@ public class LEDFlasher implements Runnable {
public static void setLEDOn(boolean _on) {
try {
if (_on)
if (_on) {
Runtime.getRuntime().exec(new String[]{"sh", "-c", "echo default-on > /sys/class/leds/led0/trigger"});
Runtime.getRuntime().exec(new String[]{"sh", "-c", "echo default-on > /sys/class/leds/led1/trigger"});
else
}
else {
Runtime.getRuntime().exec(new String[]{"sh", "-c", "echo none > /sys/class/leds/led0/trigger"});
Runtime.getRuntime().exec(new String[]{"sh", "-c", "echo none > /sys/class/leds/led1/trigger"});
}
}
catch (Exception _e) {
LOG.error("Failed to change LED state", _e);
}

View File

@ -3,7 +3,7 @@ package com.lanternsoftware.currentmonitor;
import com.lanternsoftware.datamodel.currentmonitor.Breaker;
import com.lanternsoftware.util.CollectionUtils;
import com.lanternsoftware.util.LanternFiles;
import com.lanternsoftware.util.external.LanternFiles;
import com.lanternsoftware.util.ResourceLoader;
import com.lanternsoftware.util.dao.DaoSerializer;
@ -23,6 +23,6 @@ public class CreateConfig {
b1.setPort(1);
b1.setSizeAmps(20);
c.setMqttBreakers(CollectionUtils.asArrayList(b1));
ResourceLoader.writeFile(LanternFiles.OPS_PATH + "mqtt1.json", DaoSerializer.toJson(c));
ResourceLoader.writeFile(LanternFiles.CONFIG_PATH + "mqtt1.json", DaoSerializer.toJson(c));
}
}

View File

@ -1,11 +1,11 @@
package com.lanternsoftware.currentmonitor;
import com.lanternsoftware.util.LanternFiles;
import com.lanternsoftware.util.external.LanternFiles;
import com.lanternsoftware.util.dao.generator.DaoSerializerGenerator;
public class CurrentMonitorAppSerializers {
public static void main(String[] args) {
DaoSerializerGenerator.generateSerializers(LanternFiles.SOURCE_PATH + "currentmonitor", true, null);
DaoSerializerGenerator.generateSerializers(LanternFiles.SOURCE_CODE_PATH + "currentmonitor", true, null);
}
}

View File

@ -1,6 +1,6 @@
package com.lanternsoftware.currentmonitor;
import com.lanternsoftware.util.LanternFiles;
import com.lanternsoftware.util.external.LanternFiles;
import com.lanternsoftware.util.ResourceLoader;
import com.lanternsoftware.util.dao.DaoEntity;
import com.lanternsoftware.util.dao.DaoSerializer;
@ -9,18 +9,17 @@ import com.lanternsoftware.util.xml.XmlParser;
import org.apache.commons.codec.digest.DigestUtils;
import java.io.File;
import java.util.Arrays;
import java.util.Collections;
public class ReleaseCurrentMonitor {
public static void main(String[] args) {
XmlNode pom = XmlParser.loadXmlFile(LanternFiles.SOURCE_PATH + "currentmonitor" + File.separator + "lantern-currentmonitor" + File.separator + "pom.xml");
XmlNode pom = XmlParser.loadXmlFile(LanternFiles.SOURCE_CODE_PATH + "currentmonitor" + File.separator + "lantern-currentmonitor" + File.separator + "pom.xml");
if (pom == null)
return;
XmlNode versionNode = pom.getChild(Collections.singletonList("version"));
String version = versionNode.getContent();
ProcessBuilder builder = new ProcessBuilder();
builder.directory(new File(LanternFiles.SOURCE_PATH));
builder.directory(new File(LanternFiles.SOURCE_CODE_PATH));
builder.command("cmd.exe", "/c", "mvn clean install");
builder.redirectOutput(ProcessBuilder.Redirect.INHERIT);
try {
@ -30,9 +29,9 @@ public class ReleaseCurrentMonitor {
} catch (Exception _e) {
_e.printStackTrace();
}
byte[] jar = ResourceLoader.loadFile(LanternFiles.SOURCE_PATH + "currentmonitor" + File.separator + "lantern-currentmonitor" + File.separator + "target" + File.separator + "lantern-currentmonitor.jar");
byte[] jar = ResourceLoader.loadFile(LanternFiles.SOURCE_CODE_PATH + "currentmonitor" + File.separator + "lantern-currentmonitor" + File.separator + "target" + File.separator + "lantern-currentmonitor.jar");
DaoEntity meta = new DaoEntity("version", version).and("size", jar.length).and("checksum", DigestUtils.md5Hex(jar));
ResourceLoader.writeFile(LanternFiles.OPS_PATH + "release" + File.separator + "lantern-currentmonitor.jar", jar);
ResourceLoader.writeFile(LanternFiles.OPS_PATH + "release" + File.separator + "version.json", DaoSerializer.toJson(meta));
ResourceLoader.writeFile(LanternFiles.CONFIG_PATH + "release" + File.separator + "lantern-currentmonitor.jar", jar);
ResourceLoader.writeFile(LanternFiles.CONFIG_PATH + "release" + File.separator + "version.json", DaoSerializer.toJson(meta));
}
}

View File

@ -11,7 +11,7 @@ import com.lanternsoftware.datamodel.rules.Event;
import com.lanternsoftware.datamodel.rules.FcmDevice;
import com.lanternsoftware.datamodel.rules.Rule;
import com.lanternsoftware.util.DebugTimer;
import com.lanternsoftware.util.LanternFiles;
import com.lanternsoftware.util.external.LanternFiles;
import com.lanternsoftware.util.dao.DaoQuery;
import com.lanternsoftware.util.dao.mongo.MongoConfig;
@ -19,8 +19,8 @@ import java.util.List;
public class Backup {
public static void main(String[] args) {
CurrentMonitorDao dao = new MongoCurrentMonitorDao(MongoConfig.fromDisk(LanternFiles.BACKUP_SOURCE + "mongo.cfg"));
CurrentMonitorDao backupDao = new MongoCurrentMonitorDao(MongoConfig.fromDisk(LanternFiles.BACKUP_DEST + "mongo.cfg"));
CurrentMonitorDao dao = new MongoCurrentMonitorDao(MongoConfig.fromDisk(LanternFiles.CONFIG_PATH + "mongo.cfg"));
CurrentMonitorDao backupDao = new MongoCurrentMonitorDao(MongoConfig.fromDisk(LanternFiles.BACKUP_DEST_PATH + "mongo.cfg"));
DebugTimer t1 = new DebugTimer("Query Accounts");
List<Account> accounts = dao.getProxy().queryAll(Account.class);

View File

@ -4,7 +4,7 @@ import com.lanternsoftware.datamodel.currentmonitor.Account;
import com.lanternsoftware.datamodel.currentmonitor.HubPowerMinute;
import com.lanternsoftware.util.DateUtils;
import com.lanternsoftware.util.DebugTimer;
import com.lanternsoftware.util.LanternFiles;
import com.lanternsoftware.util.external.LanternFiles;
import com.lanternsoftware.util.NullUtils;
import com.lanternsoftware.util.dao.DaoQuery;
import com.lanternsoftware.util.dao.DaoSort;
@ -16,8 +16,8 @@ import java.util.TimeZone;
public class BackupMinutes {
public static void main(String[] args) {
CurrentMonitorDao dao = new MongoCurrentMonitorDao(MongoConfig.fromDisk(LanternFiles.BACKUP_SOURCE + "mongo.cfg"));
CurrentMonitorDao backupDao = new MongoCurrentMonitorDao(MongoConfig.fromDisk(LanternFiles.BACKUP_DEST + "mongo.cfg"));
CurrentMonitorDao dao = new MongoCurrentMonitorDao(MongoConfig.fromDisk(LanternFiles.CONFIG_PATH + "mongo.cfg"));
CurrentMonitorDao backupDao = new MongoCurrentMonitorDao(MongoConfig.fromDisk(LanternFiles.BACKUP_DEST_PATH + "mongo.cfg"));
Date now = new Date();
for (Account a : dao.getProxy().queryAll(Account.class)) {
if (a.getId() == 0)

View File

@ -7,9 +7,12 @@ import com.lanternsoftware.datamodel.currentmonitor.EnergySummary;
import com.lanternsoftware.datamodel.currentmonitor.EnergyViewMode;
import com.lanternsoftware.datamodel.currentmonitor.HubCommand;
import com.lanternsoftware.datamodel.currentmonitor.HubPowerMinute;
import com.lanternsoftware.datamodel.currentmonitor.archive.ArchiveStatus;
import com.lanternsoftware.util.DateRange;
import com.lanternsoftware.util.dao.auth.AuthCode;
import com.lanternsoftware.util.dao.mongo.MongoProxy;
import java.io.InputStream;
import java.util.Date;
import java.util.List;
import java.util.TimeZone;
@ -26,6 +29,15 @@ public interface CurrentMonitorDao {
void putEnergySummary(EnergySummary _energy);
void putHubPowerMinute(HubPowerMinute _power);
Iterable<HubPowerMinute> streamHubPowerMinutes(int _accountId, Date _start, Date _end);
void archiveMonth(int _accountId, Date _month);
InputStream streamArchive(int _accountId, Date _month);
void putArchiveStatus(ArchiveStatus _status);
void deleteArchiveStatus(int _accountId, Date _month);
List<ArchiveStatus> getArchiveStatus(int _accountId);
DateRange getMonitoredDateRange(int _accountId);
BreakerConfig getConfig(int _accountId);
BreakerConfig getMergedConfig(AuthCode _authCode);

View File

@ -2,12 +2,13 @@ package com.lanternsoftware.dataaccess.currentmonitor;
import com.lanternsoftware.datamodel.currentmonitor.Account;
import com.lanternsoftware.datamodel.currentmonitor.BillingPlan;
import com.lanternsoftware.datamodel.currentmonitor.BillingRate;
import com.lanternsoftware.datamodel.currentmonitor.Breaker;
import com.lanternsoftware.datamodel.currentmonitor.BreakerConfig;
import com.lanternsoftware.datamodel.currentmonitor.BreakerGroup;
import com.lanternsoftware.datamodel.currentmonitor.BreakerHub;
import com.lanternsoftware.datamodel.currentmonitor.BreakerPower;
import com.lanternsoftware.datamodel.currentmonitor.BreakerPowerMinute;
import com.lanternsoftware.datamodel.currentmonitor.BreakerType;
import com.lanternsoftware.datamodel.currentmonitor.ChargeSummary;
import com.lanternsoftware.datamodel.currentmonitor.ChargeTotal;
import com.lanternsoftware.datamodel.currentmonitor.EnergySummary;
@ -16,7 +17,12 @@ import com.lanternsoftware.datamodel.currentmonitor.EnergyViewMode;
import com.lanternsoftware.datamodel.currentmonitor.HubCommand;
import com.lanternsoftware.datamodel.currentmonitor.HubPowerMinute;
import com.lanternsoftware.datamodel.currentmonitor.Sequence;
import com.lanternsoftware.datamodel.currentmonitor.archive.ArchiveStatus;
import com.lanternsoftware.datamodel.currentmonitor.archive.BreakerEnergyArchive;
import com.lanternsoftware.datamodel.currentmonitor.archive.DailyEnergyArchive;
import com.lanternsoftware.datamodel.currentmonitor.archive.MonthlyEnergyArchive;
import com.lanternsoftware.util.CollectionUtils;
import com.lanternsoftware.util.DateRange;
import com.lanternsoftware.util.DateUtils;
import com.lanternsoftware.util.DebugTimer;
import com.lanternsoftware.util.NullUtils;
@ -28,24 +34,41 @@ import com.lanternsoftware.util.dao.DaoSort;
import com.lanternsoftware.util.dao.auth.AuthCode;
import com.lanternsoftware.util.dao.mongo.MongoConfig;
import com.lanternsoftware.util.dao.mongo.MongoProxy;
import com.lanternsoftware.util.external.LanternFiles;
import com.lanternsoftware.util.mutable.MutableDouble;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.mindrot.jbcrypt.BCrypt;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TimeZone;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.zip.Deflater;
import java.util.zip.GZIPOutputStream;
public class MongoCurrentMonitorDao implements CurrentMonitorDao {
private static final Logger logger = LoggerFactory.getLogger(MongoCurrentMonitorDao.class);
@ -65,6 +88,7 @@ public class MongoCurrentMonitorDao implements CurrentMonitorDao {
proxy.ensureIndex(ChargeSummary.class, DaoSort.sort("account_id").then("plan_id").then("group_id").then("view_mode"));
proxy.ensureIndex(ChargeTotal.class, DaoSort.sort("account_id").then("plan_id").then("group_id").then("view_mode").then("start"));
proxy.ensureIndex(DirtyMinute.class, DaoSort.sort("posted"));
proxy.ensureIndex(ArchiveStatus.class, DaoSort.sort("account_id"));
for (DirtyMinute minute : proxy.queryAll(DirtyMinute.class)) {
updateEnergySummaries(minute);
}
@ -100,6 +124,196 @@ public class MongoCurrentMonitorDao implements CurrentMonitorDao {
}, 10000);
}
@Override
public Iterable<HubPowerMinute> streamHubPowerMinutes(int _accountId, Date _start, Date _end) {
return proxy.queryIterator(HubPowerMinute.class, new DaoQuery("account_id", _accountId).andBetweenInclusiveExclusive("minute", DateUtils.toLong(_start)/60000, DateUtils.toLong(_end)/60000), null, DaoSort.sort("start"), 0, 0);
}
@Override
public void archiveMonth(int _accountId, Date _month) {
ArchiveStatus status = new ArchiveStatus();
status.setAccountId(_accountId);
status.setMonth(_month);
status.setProgress(1);
putArchiveStatus(status);
executor.submit(()->{
synchronized (MongoCurrentMonitorDao.this) {
TimeZone tz = getTimeZoneForAccount(_accountId);
DebugTimer timer = new DebugTimer("Monthly Archive Generation for account " + _accountId + " month " + DateUtils.format("MMMM yyyy", tz, _month));
Date start = _month;
Date end = DateUtils.getEndOfMonth(_month, tz);
BreakerConfig config = getConfig(_accountId); //TODO: get historical config for archive month in case it's changed since then.
List<Breaker> breakers = CollectionUtils.filter(config.getAllBreakers(), _b -> !NullUtils.isOneOf(_b.getType(), BreakerType.DOUBLE_POLE_BOTTOM, BreakerType.EMPTY));
breakers.sort(Comparator.comparing(Breaker::getPanel).thenComparing(Breaker::getSpace));
Map<Integer, Integer> breakerKeys = CollectionUtils.transformToMap(config.getAllBreakers(), Breaker::getIntKey, _b -> Breaker.intKey(_b.getPanel(), _b.getType() == BreakerType.DOUBLE_POLE_BOTTOM ? _b.getSpace() - 2 : _b.getSpace()));
Map<Integer, List<Float>> minuteReadings = new HashMap<>();
MonthlyEnergyArchive archive = new MonthlyEnergyArchive();
archive.setAccountId(_accountId);
archive.setMonth(start);
List<DailyEnergyArchive> days = new ArrayList<>();
archive.setDays(days);
while (start.before(end)) {
Map<Integer, byte[]> dayReadings = new HashMap<>();
Date dayEnd = DateUtils.addDays(start, 1, tz);
int minute = 0;
int bytesInDay = (int) (4 * DateUtils.diffInSeconds(start, dayEnd));
Iterator<HubPowerMinute> i = streamHubPowerMinutes(_accountId, start, dayEnd).iterator();
HubPowerMinute m = null;
if (i.hasNext())
m = i.next();
DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
df.setTimeZone(TimeZone.getTimeZone("UTC"));
while (i.hasNext()) {
if (m == null)
break;
for (BreakerPowerMinute breaker : CollectionUtils.makeNotNull(m.getBreakers())) {
if (!breakerKeys.containsKey(breaker.breakerIntKey()))
continue;
int key = breakerKeys.get(breaker.breakerIntKey());
List<Float> r = minuteReadings.get(key);
if (r == null)
minuteReadings.put(key, breaker.getReadings());
else {
for (int idx = 0; idx < minuteReadings.size(); idx++) {
r.set(idx, r.get(idx) + breaker.getReadings().get(idx));
}
}
}
HubPowerMinute cur = i.next();
if (cur.getMinute() != m.getMinute()) {
addReadings(minute, bytesInDay, minuteReadings, dayReadings);
minute++;
}
m = cur;
}
if (m != null)
addReadings(minute, bytesInDay, minuteReadings, dayReadings);
List<BreakerEnergyArchive> breakerEnergies = new ArrayList<>();
for (Entry<Integer, byte[]> be : dayReadings.entrySet()) {
BreakerEnergyArchive breakerEnergy = new BreakerEnergyArchive();
breakerEnergy.setPanel(Breaker.intKeyToPanel(be.getKey()));
breakerEnergy.setSpace(Breaker.intKeyToSpace(be.getKey()));
breakerEnergy.setReadings(be.getValue());
breakerEnergies.add(breakerEnergy);
}
DailyEnergyArchive day = new DailyEnergyArchive();
day.setBreakers(breakerEnergies);
days.add(day);
start = dayEnd;
status.setProgress(50f * (start.getTime() - _month.getTime()) / (end.getTime() - _month.getTime()));
putArchiveStatus(status);
}
timer.stop();
DebugTimer t = new DebugTimer("Convert Archive to bson for account " + archive.getAccountId());
byte[] bson = DaoSerializer.toBson(archive);
t.stop();
DebugTimer t2 = new DebugTimer("Zip Archive and write to disk for account" + archive.getAccountId());
OutputStream os = null;
try {
File partialPath = new File(LanternFiles.BACKUP_DEST_PATH + archive.getAccountId()+File.separator + "partial");
FileUtils.deleteDirectory(partialPath);
partialPath.mkdirs();
String backupPath = LanternFiles.BACKUP_DEST_PATH + archive.getAccountId() + File.separator;
if (!archive.isComplete(tz))
backupPath += "partial" + File.separator;
os = new GZIPOutputStream(new FileOutputStream(backupPath + archive.getMonth().getTime() + ".zip")) {{def.setLevel(Deflater.BEST_SPEED);}};
int batchSize = bson.length / 50;
for (int offset = 0; offset < bson.length; offset += batchSize) {
os.write(bson, offset, Math.min(batchSize, bson.length - offset));
status.setProgress(50 + (50f * offset / bson.length));
putArchiveStatus(status);
}
} catch (Exception _e) {
logger.error("Failed to write export file", _e);
} finally {
IOUtils.closeQuietly(os);
}
t2.stop();
deleteArchiveStatus(_accountId, _month);
}
});
}
private void addReadings(int _minuteInDay, int _bytesInDay, Map<Integer, List<Float>> _minuteReadings, Map<Integer, byte[]> _dayReadings) {
for (Entry<Integer, List<Float>> entry : _minuteReadings.entrySet()) {
byte[] dayBytes = _dayReadings.computeIfAbsent(entry.getKey(), _r->new byte[_bytesInDay]);
ByteBuffer bb = ByteBuffer.wrap(dayBytes);
for (int fl = 0; fl < CollectionUtils.size(entry.getValue()); fl++) {
bb.putFloat(_minuteInDay*240 + (fl*4), CollectionUtils.get(entry.getValue(), fl));
}
}
_minuteReadings.clear();
}
@Override
public InputStream streamArchive(int _accountId, Date _month) {
try {
String complete = LanternFiles.BACKUP_DEST_PATH + _accountId + File.separator + _month.getTime() + ".zip";
if (new File(complete).exists())
return new FileInputStream(complete);
String partial = LanternFiles.BACKUP_DEST_PATH + _accountId + File.separator + "partial" + File.separator + _month.getTime() + ".zip";
if (new File(partial).exists())
return new FileInputStream(partial);
}
catch (Exception _e) {
logger.error("Failed to load archive", _e);
}
return null;
}
@Override
public void putArchiveStatus(ArchiveStatus _status) {
proxy.save(_status);
}
@Override
public void deleteArchiveStatus(int _accountId, Date _month) {
proxy.delete(ArchiveStatus.class, new DaoQuery("_id", MonthlyEnergyArchive.toId(_accountId, _month)));
}
@Override
public List<ArchiveStatus> getArchiveStatus(int _accountId) {
Map<Date, ArchiveStatus> statuses = CollectionUtils.transformToSortedMap(proxy.query(ArchiveStatus.class, new DaoQuery("account_id", _accountId)), ArchiveStatus::getMonth);
File folder = new File(LanternFiles.BACKUP_DEST_PATH + _accountId);
if (folder.exists()) {
for (File file : CollectionUtils.asArrayList(folder.listFiles())) {
if (file.isFile()) {
Date month = new Date(DaoSerializer.toLong(file.getName().replace(".zip", "")));
statuses.computeIfAbsent(month, _m -> new ArchiveStatus(_accountId, _m, 100));
}
}
}
File partial = new File(LanternFiles.BACKUP_DEST_PATH + _accountId + File.separator + "partial");
if (partial.exists()) {
for (File file : CollectionUtils.asArrayList(partial.listFiles())) {
if (file.isFile() && (new Date().getTime() - file.lastModified() < 86400000)) {
Date month = new Date(DaoSerializer.toLong(file.getName().replace(".zip", "")));
statuses.computeIfAbsent(month, _m -> new ArchiveStatus(_accountId, _m, 100));
}
}
}
DateRange range = getMonitoredDateRange(_accountId);
TimeZone tz = getTimeZoneForAccount(_accountId);
Date month = DateUtils.getStartOfMonth(range.getStart(), tz);
Date end = DateUtils.getEndOfMonth(range.getEnd(), tz);
while (month.before(end)) {
statuses.computeIfAbsent(month, _m->new ArchiveStatus(_accountId, _m, 0));
month = DateUtils.addMonths(month, 1, tz);
}
return new ArrayList<>(statuses.values());
}
@Override
public DateRange getMonitoredDateRange(int _accountId) {
DaoQuery query = new DaoQuery("account_id", _accountId).and("view_mode", EnergyViewMode.MONTH.name());
EnergySummary first = proxy.queryOne(EnergySummary.class, query, DaoSort.sort("start"));
EnergySummary last = proxy.queryOne(EnergySummary.class, query, DaoSort.sortDesc("start"));
if ((first != null) && (last != null))
return new DateRange(first.getStart(), last.getStart());
return null;
}
private void updateEnergySummaries(DirtyMinute _minute) {
DebugTimer timer = new DebugTimer("Updating summaries", logger);
List<HubPowerMinute> minutes = proxy.query(HubPowerMinute.class, new DaoQuery("account_id", _minute.getAccountId()).and("minute", _minute.getMinute()));
@ -262,32 +476,6 @@ public class MongoCurrentMonitorDao implements CurrentMonitorDao {
}
}
private void rebuildSummaries(int _accountId, Collection<BillingRate> _rates) {
logger.info("Rebuilding summaries due to a change in {} rates", CollectionUtils.size(_rates));
HubPowerMinute firstMinute = proxy.queryOne(HubPowerMinute.class, new DaoQuery("account_id", _accountId), DaoSort.sort("minute"));
if (firstMinute == null)
return;
TimeZone tz = getTimeZoneForAccount(_accountId);
Map<String, BillingRate> rates = CollectionUtils.transformToMap(_rates, _r -> String.format("%d%d", DaoSerializer.toLong(_r.getBeginEffective()), DaoSerializer.toLong(_r.getEndEffective())));
for (BillingRate rate : rates.values()) {
Date start = rate.getBeginEffective();
Date end = rate.getEndEffective();
Date now = new Date();
if ((start == null) || start.before(firstMinute.getMinuteAsDate()))
start = firstMinute.getMinuteAsDate();
if ((end == null) || end.after(now))
end = now;
rebuildSummaries(_accountId, start, end);
if (rate.isRecursAnnually()) {
while (end.before(now)) {
start = DateUtils.addYears(start, 1, tz);
end = DateUtils.addYears(end, 1, tz);
rebuildSummaries(_accountId, start, end);
}
}
}
}
@Override
public void rebuildSummariesAsync(int _accountId) {
executor.submit(() -> rebuildSummaries(_accountId));
@ -488,13 +676,13 @@ public class MongoCurrentMonitorDao implements CurrentMonitorDao {
public String getAuthCodeForEmail(String _email, TimeZone _tz) {
_email = _email.toLowerCase().trim();
Account account = getAccountByUsername(_email);
if (account == null) {
if ((account == null) && (_tz != null)) {
account = new Account();
account.setUsername(_email);
account.setTimezone(_tz.getID());
putAccount(account);
}
return toAuthCode(account.getId(), account.getAuxiliaryAccountIds());
return (account == null)?null:toAuthCode(account.getId(), account.getAuxiliaryAccountIds());
}
public String toAuthCode(int _acctId, List<Integer> _auxAcctIds) {

View File

@ -194,10 +194,26 @@ public class Breaker implements IIdentical<Breaker> {
return key;
}
public int getIntKey() {
return intKey(panel, space);
}
public static int intKeyToPanel(int _intKey) {
return _intKey/10000;
}
public static int intKeyToSpace(int _intKey) {
return _intKey%10000;
}
public static String key(int _panel, int _space) {
return String.format("%d-%d", _panel, _space);
}
public static int intKey(int _panel, int _space) {
return 10000*_panel + _space;
}
public static int portToChip(int _port) {
return (_port < 9) ? 1 : 0;
}

View File

@ -30,6 +30,10 @@ public class BreakerPowerMinute {
return Breaker.key(panel, space);
}
public int breakerIntKey() {
return Breaker.intKey(panel, space);
}
public List<Float> getReadings() {
return readings;
}

View File

@ -0,0 +1,50 @@
package com.lanternsoftware.datamodel.currentmonitor.archive;
import com.lanternsoftware.util.DateUtils;
import com.lanternsoftware.util.dao.annotations.DBSerializable;
import java.util.Date;
@DBSerializable(autogen = false)
public class ArchiveStatus {
private int accountId;
private Date month;
private float progress;
public ArchiveStatus() {
}
public ArchiveStatus(int _accountId, Date _month, float _progress) {
accountId = _accountId;
month = _month;
progress = _progress;
}
public String getId() {
return String.format("%d-%d", accountId, DateUtils.toLong(month));
}
public int getAccountId() {
return accountId;
}
public void setAccountId(int _accountId) {
accountId = _accountId;
}
public Date getMonth() {
return month;
}
public void setMonth(Date _month) {
month = _month;
}
public float getProgress() {
return progress;
}
public void setProgress(float _progress) {
progress = _progress;
}
}

View File

@ -0,0 +1,34 @@
package com.lanternsoftware.datamodel.currentmonitor.archive;
import com.lanternsoftware.util.dao.annotations.DBSerializable;
@DBSerializable
public class BreakerEnergyArchive {
private int panel;
private int space;
private byte[] readings;
public int getPanel() {
return panel;
}
public void setPanel(int _panel) {
panel = _panel;
}
public int getSpace() {
return space;
}
public void setSpace(int _space) {
space = _space;
}
public byte[] getReadings() {
return readings;
}
public void setReadings(byte[] _readings) {
readings = _readings;
}
}

View File

@ -0,0 +1,18 @@
package com.lanternsoftware.datamodel.currentmonitor.archive;
import com.lanternsoftware.util.dao.annotations.DBSerializable;
import java.util.List;
@DBSerializable
public class DailyEnergyArchive {
private List<BreakerEnergyArchive> breakers;
public List<BreakerEnergyArchive> getBreakers() {
return breakers;
}
public void setBreakers(List<BreakerEnergyArchive> _breakers) {
breakers = _breakers;
}
}

View File

@ -0,0 +1,53 @@
package com.lanternsoftware.datamodel.currentmonitor.archive;
import com.lanternsoftware.util.DateUtils;
import com.lanternsoftware.util.dao.annotations.DBSerializable;
import java.util.Date;
import java.util.List;
import java.util.TimeZone;
@DBSerializable(autogen = false)
public class MonthlyEnergyArchive {
private int accountId;
private Date month;
private List<DailyEnergyArchive> days;
public String getId() {
return toId(accountId, month);
}
public static String toId(int _accountId, Date _month) {
return String.format("%d-%d", _accountId, DateUtils.toLong(_month));
}
public int getAccountId() {
return accountId;
}
public void setAccountId(int _accountId) {
accountId = _accountId;
}
public Date getMonth() {
return month;
}
public void setMonth(Date _month) {
month = _month;
}
public List<DailyEnergyArchive> getDays() {
return days;
}
public void setDays(List<DailyEnergyArchive> _days) {
days = _days;
}
public boolean isComplete(TimeZone _tz) {
Date valid = DateUtils.addDays(new Date(), -7, _tz);
valid = DateUtils.addMonths(valid, -1, _tz);
return month.before(valid);
}
}

View File

@ -0,0 +1,44 @@
package com.lanternsoftware.datamodel.currentmonitor.archive.dao;
import com.lanternsoftware.datamodel.currentmonitor.archive.ArchiveStatus;
import com.lanternsoftware.util.dao.AbstractDaoSerializer;
import com.lanternsoftware.util.dao.DaoEntity;
import com.lanternsoftware.util.dao.DaoProxyType;
import com.lanternsoftware.util.dao.DaoSerializer;
import java.util.Collections;
import java.util.List;
public class ArchiveStatusSerializer extends AbstractDaoSerializer<ArchiveStatus>
{
@Override
public Class<ArchiveStatus> getSupportedClass()
{
return ArchiveStatus.class;
}
@Override
public List<DaoProxyType> getSupportedProxies() {
return Collections.singletonList(DaoProxyType.MONGO);
}
@Override
public DaoEntity toDaoEntity(ArchiveStatus _o)
{
DaoEntity d = new DaoEntity();
d.put("_id", _o.getId());
d.put("account_id", _o.getAccountId());
d.put("month", DaoSerializer.toLong(_o.getMonth()));
d.put("progress", _o.getProgress());
return d;
}
@Override
public ArchiveStatus fromDaoEntity(DaoEntity _d)
{
ArchiveStatus o = new ArchiveStatus();
o.setAccountId(DaoSerializer.getInteger(_d, "account_id"));
o.setMonth(DaoSerializer.getDate(_d, "month"));
o.setProgress(DaoSerializer.getFloat(_d, "progress"));
return o;
}
}

View File

@ -0,0 +1,43 @@
package com.lanternsoftware.datamodel.currentmonitor.archive.dao;
import com.lanternsoftware.datamodel.currentmonitor.archive.BreakerEnergyArchive;
import com.lanternsoftware.util.dao.AbstractDaoSerializer;
import com.lanternsoftware.util.dao.DaoEntity;
import com.lanternsoftware.util.dao.DaoProxyType;
import com.lanternsoftware.util.dao.DaoSerializer;
import java.util.Collections;
import java.util.List;
public class BreakerEnergyArchiveSerializer extends AbstractDaoSerializer<BreakerEnergyArchive>
{
@Override
public Class<BreakerEnergyArchive> getSupportedClass()
{
return BreakerEnergyArchive.class;
}
@Override
public List<DaoProxyType> getSupportedProxies() {
return Collections.singletonList(DaoProxyType.MONGO);
}
@Override
public DaoEntity toDaoEntity(BreakerEnergyArchive _o)
{
DaoEntity d = new DaoEntity();
d.put("panel", _o.getPanel());
d.put("space", _o.getSpace());
d.put("readings", _o.getReadings());
return d;
}
@Override
public BreakerEnergyArchive fromDaoEntity(DaoEntity _d)
{
BreakerEnergyArchive o = new BreakerEnergyArchive();
o.setPanel(DaoSerializer.getInteger(_d, "panel"));
o.setSpace(DaoSerializer.getInteger(_d, "space"));
o.setReadings(DaoSerializer.getByteArray(_d, "readings"));
return o;
}
}

View File

@ -0,0 +1,40 @@
package com.lanternsoftware.datamodel.currentmonitor.archive.dao;
import com.lanternsoftware.datamodel.currentmonitor.archive.BreakerEnergyArchive;
import com.lanternsoftware.datamodel.currentmonitor.archive.DailyEnergyArchive;
import com.lanternsoftware.util.dao.AbstractDaoSerializer;
import com.lanternsoftware.util.dao.DaoEntity;
import com.lanternsoftware.util.dao.DaoProxyType;
import com.lanternsoftware.util.dao.DaoSerializer;
import java.util.Collections;
import java.util.List;
public class DailyEnergyArchiveSerializer extends AbstractDaoSerializer<DailyEnergyArchive>
{
@Override
public Class<DailyEnergyArchive> getSupportedClass()
{
return DailyEnergyArchive.class;
}
@Override
public List<DaoProxyType> getSupportedProxies() {
return Collections.singletonList(DaoProxyType.MONGO);
}
@Override
public DaoEntity toDaoEntity(DailyEnergyArchive _o)
{
DaoEntity d = new DaoEntity();
d.put("breakers", DaoSerializer.toDaoEntities(_o.getBreakers(), DaoProxyType.MONGO));
return d;
}
@Override
public DailyEnergyArchive fromDaoEntity(DaoEntity _d)
{
DailyEnergyArchive o = new DailyEnergyArchive();
o.setBreakers(DaoSerializer.getList(_d, "breakers", BreakerEnergyArchive.class));
return o;
}
}

View File

@ -0,0 +1,45 @@
package com.lanternsoftware.datamodel.currentmonitor.archive.dao;
import com.lanternsoftware.datamodel.currentmonitor.archive.DailyEnergyArchive;
import com.lanternsoftware.datamodel.currentmonitor.archive.MonthlyEnergyArchive;
import com.lanternsoftware.util.dao.AbstractDaoSerializer;
import com.lanternsoftware.util.dao.DaoEntity;
import com.lanternsoftware.util.dao.DaoProxyType;
import com.lanternsoftware.util.dao.DaoSerializer;
import java.util.Collections;
import java.util.List;
public class MonthlyEnergyArchiveSerializer extends AbstractDaoSerializer<MonthlyEnergyArchive>
{
@Override
public Class<MonthlyEnergyArchive> getSupportedClass()
{
return MonthlyEnergyArchive.class;
}
@Override
public List<DaoProxyType> getSupportedProxies() {
return Collections.singletonList(DaoProxyType.MONGO);
}
@Override
public DaoEntity toDaoEntity(MonthlyEnergyArchive _o)
{
DaoEntity d = new DaoEntity();
d.put("_id", _o.getId());
d.put("account_id", _o.getAccountId());
d.put("month", DaoSerializer.toLong(_o.getMonth()));
d.put("days", DaoSerializer.toDaoEntities(_o.getDays(), DaoProxyType.MONGO));
return d;
}
@Override
public MonthlyEnergyArchive fromDaoEntity(DaoEntity _d)
{
MonthlyEnergyArchive o = new MonthlyEnergyArchive();
o.setAccountId(DaoSerializer.getInteger(_d, "account_id"));
o.setMonth(DaoSerializer.getDate(_d, "month"));
o.setDays(DaoSerializer.getList(_d, "days", DailyEnergyArchive.class));
return o;
}
}

View File

@ -1,3 +1,7 @@
com.lanternsoftware.datamodel.currentmonitor.archive.dao.ArchiveStatusSerializer
com.lanternsoftware.datamodel.currentmonitor.archive.dao.BreakerEnergyArchiveSerializer
com.lanternsoftware.datamodel.currentmonitor.archive.dao.DailyEnergyArchiveSerializer
com.lanternsoftware.datamodel.currentmonitor.archive.dao.MonthlyEnergyArchiveSerializer
com.lanternsoftware.datamodel.currentmonitor.dao.AccountSerializer
com.lanternsoftware.datamodel.currentmonitor.dao.BillingPlanSerializer
com.lanternsoftware.datamodel.currentmonitor.dao.BillingRateSerializer

View File

@ -6,7 +6,7 @@ import com.lanternsoftware.datamodel.currentmonitor.HubCommand;
import com.lanternsoftware.datamodel.currentmonitor.HubCommands;
import com.lanternsoftware.rules.RulesEngine;
import com.lanternsoftware.util.DateUtils;
import com.lanternsoftware.util.LanternFiles;
import com.lanternsoftware.util.external.LanternFiles;
import com.lanternsoftware.util.dao.mongo.MongoConfig;
import javax.servlet.ServletContextEvent;
@ -24,7 +24,7 @@ public class Globals implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent sce) {
dao = new MongoCurrentMonitorDao(MongoConfig.fromDisk(LanternFiles.OPS_PATH + "mongo.cfg"));
dao = new MongoCurrentMonitorDao(MongoConfig.fromDisk(LanternFiles.CONFIG_PATH + "mongo.cfg"));
RulesEngine.instance().start();
RulesEngine.instance().schedule(new CommandTask(), 0);
}

View File

@ -1,17 +1,10 @@
package com.lanternsoftware.currentmonitor.servlet;
import com.google.api.client.googleapis.auth.oauth2.GoogleAuthorizationCodeTokenRequest;
import com.google.api.client.googleapis.auth.oauth2.GoogleIdToken;
import com.google.api.client.googleapis.auth.oauth2.GoogleTokenResponse;
import com.google.api.client.http.javanet.NetHttpTransport;
import com.google.api.client.json.gson.GsonFactory;
import com.lanternsoftware.currentmonitor.context.Globals;
import com.lanternsoftware.currentmonitor.util.GoogleAuthHelper;
import com.lanternsoftware.util.DateUtils;
import com.lanternsoftware.util.LanternFiles;
import com.lanternsoftware.util.NullUtils;
import com.lanternsoftware.util.ResourceLoader;
import com.lanternsoftware.util.dao.DaoEntity;
import com.lanternsoftware.util.dao.DaoSerializer;
import com.lanternsoftware.util.servlet.BasicAuth;
import com.lanternsoftware.util.servlet.LanternServlet;
import org.slf4j.Logger;
@ -23,15 +16,7 @@ import javax.servlet.http.HttpServletResponse;
@WebServlet("/auth/*")
public class AuthServlet extends LanternServlet {
private static final NetHttpTransport transport = new NetHttpTransport();
private static final Logger logger = LoggerFactory.getLogger(AuthServlet.class);
private static final String googleClientId;
private static final String googleClientSecret;
static {
DaoEntity google = DaoSerializer.parse(ResourceLoader.loadFileAsString(LanternFiles.OPS_PATH + "google_sso.txt"));
googleClientId = DaoSerializer.getString(google, "id");
googleClientSecret = DaoSerializer.getString(google, "secret");
}
@Override
protected void doGet(HttpServletRequest _req, HttpServletResponse _rep) {
@ -40,16 +25,7 @@ public class AuthServlet extends LanternServlet {
BasicAuth auth = new BasicAuth(_req);
if (NullUtils.isEqual(auth.getUsername(), "googlesso")) {
logger.info("Attempting google SSO");
try {
GoogleTokenResponse tokenResponse = new GoogleAuthorizationCodeTokenRequest(transport, new GsonFactory(), "https://oauth2.googleapis.com/token", googleClientId, googleClientSecret, auth.getPassword(), "").execute();
if (tokenResponse != null) {
GoogleIdToken idToken = tokenResponse.parseIdToken();
if (idToken != null)
authCode = Globals.dao.getAuthCodeForEmail(idToken.getPayload().getEmail(), DateUtils.fromTimeZoneId(_req.getHeader("timezone")));
}
} catch (Exception _e) {
logger.error("Failed to validate google auth code", _e);
}
authCode = GoogleAuthHelper.signin(auth.getPassword(), DateUtils.fromTimeZoneId(_req.getHeader("timezone")));
} else
authCode = Globals.dao.authenticateAccount(auth.getUsername(), auth.getPassword());
}

View File

@ -14,7 +14,7 @@ import javax.ws.rs.core.MediaType;
import java.util.Date;
@WebServlet("/charge/*")
public class ChargeServlet extends SecureServlet {
public class ChargeServlet extends SecureServiceServlet {
@Override
protected void get(AuthCode _authCode, HttpServletRequest _req, HttpServletResponse _rep) {
String[] path = path(_req);

View File

@ -7,7 +7,7 @@ import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@WebServlet("/command")
public class CommandServlet extends SecureServlet {
public class CommandServlet extends SecureServiceServlet {
@Override
protected void get(AuthCode _authCode, HttpServletRequest _req, HttpServletResponse _rep) {

View File

@ -13,7 +13,7 @@ import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@WebServlet("/config/*")
public class ConfigServlet extends SecureServlet {
public class ConfigServlet extends SecureServiceServlet {
private static final Logger logger = LoggerFactory.getLogger(ConfigServlet.class);
@Override

View File

@ -14,7 +14,7 @@ import javax.ws.rs.core.MediaType;
import java.util.Date;
@WebServlet("/energy/*")
public class EnergyServlet extends SecureServlet {
public class EnergyServlet extends SecureServiceServlet {
@Override
protected void get(AuthCode _authCode, HttpServletRequest _req, HttpServletResponse _rep) {
String[] path = path(_req);

View File

@ -0,0 +1,23 @@
package com.lanternsoftware.currentmonitor.servlet;
import com.lanternsoftware.util.servlet.FreemarkerConfigUtil;
import com.lanternsoftware.util.servlet.FreemarkerServlet;
import com.lanternsoftware.util.servlet.FreemarkerUtil;
import freemarker.template.Configuration;
import javax.servlet.http.HttpServletResponse;
import java.util.Map;
public abstract class FreemarkerCMServlet extends FreemarkerServlet {
protected static final Configuration CONFIG = FreemarkerConfigUtil.createConfig(FreemarkerCMServlet.class, "/templates", 100);
@Override
protected Configuration getFreemarkerConfig() {
return CONFIG;
}
public void renderBody(HttpServletResponse _rep, String _sHtmlResourceKey, Map<String, Object> _mapModel) {
_mapModel.put("body", FreemarkerUtil.render(getFreemarkerConfig(), _sHtmlResourceKey, _mapModel));
render(_rep, "frame.ftl", _mapModel);
}
}

View File

@ -9,7 +9,7 @@ import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@WebServlet("/generateBom")
public class GenerateBomServlet extends SecureServlet {
public class GenerateBomServlet extends SecureServiceServlet {
@Override
protected void get(AuthCode _authCode, HttpServletRequest _req, HttpServletResponse _rep) {
AuthCode authCode = Globals.dao.decryptAuthCode(_req.getHeader("auth_code"));

View File

@ -15,7 +15,7 @@ import javax.ws.rs.core.MediaType;
import java.util.*;
@WebServlet("/energy/group/*")
public class GroupEnergyServlet extends SecureServlet {
public class GroupEnergyServlet extends SecureServiceServlet {
@Override
protected void get(AuthCode _authCode, HttpServletRequest _req, HttpServletResponse _rep) {
String[] path = path(_req);

View File

@ -10,7 +10,7 @@ import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@WebServlet("/power/group/*")
public class GroupPowerServlet extends SecureServlet {
public class GroupPowerServlet extends SecureServiceServlet {
@Override
protected void get(AuthCode _authCode, HttpServletRequest _req, HttpServletResponse _rep) {
String[] path = path(_req);

View File

@ -19,7 +19,7 @@ import javax.servlet.http.HttpServletResponse;
import java.util.List;
@WebServlet("/power/*")
public class PowerServlet extends SecureServlet {
public class PowerServlet extends SecureServiceServlet {
private static final Logger logger = LoggerFactory.getLogger(MongoCurrentMonitorDao.class);
@Override

View File

@ -3,7 +3,6 @@ package com.lanternsoftware.currentmonitor.servlet;
import com.lanternsoftware.currentmonitor.context.Globals;
import com.lanternsoftware.datamodel.currentmonitor.Account;
import com.lanternsoftware.util.CollectionUtils;
import com.lanternsoftware.util.NullUtils;
import com.lanternsoftware.util.dao.DaoSerializer;
import com.lanternsoftware.util.dao.auth.AuthCode;
@ -12,7 +11,7 @@ import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@WebServlet("/rebuildSummaries/*")
public class RebuildSummariesServlet extends SecureServlet {
public class RebuildSummariesServlet extends SecureServiceServlet {
@Override
protected void get(AuthCode _authCode, HttpServletRequest _req, HttpServletResponse _rep) {
if (_authCode.getAccountId() == 100) {

View File

@ -1,7 +1,8 @@
package com.lanternsoftware.currentmonitor.servlet;
import com.lanternsoftware.currentmonitor.context.Globals;
import com.lanternsoftware.util.LanternFiles;
import com.lanternsoftware.util.CollectionUtils;
import com.lanternsoftware.util.external.LanternFiles;
import com.lanternsoftware.util.NullUtils;
import com.lanternsoftware.util.ResourceLoader;
import com.lanternsoftware.util.dao.DaoEntity;
@ -30,7 +31,7 @@ import java.io.IOException;
public class ResetPasswordServlet extends FreemarkerServlet {
protected static final Logger LOG = LoggerFactory.getLogger(ResetPasswordServlet.class);
protected static final Configuration CONFIG = FreemarkerConfigUtil.createConfig(ResetPasswordServlet.class, "/templates", 100);
protected static final String api_key = ResourceLoader.loadFileAsString(LanternFiles.OPS_PATH + "sendgrid.txt");
protected static final String api_key = ResourceLoader.loadFileAsString(LanternFiles.CONFIG_PATH + "sendgrid.txt");
@Override
protected Configuration getFreemarkerConfig() {
@ -40,7 +41,7 @@ public class ResetPasswordServlet extends FreemarkerServlet {
@Override
protected void doGet(HttpServletRequest _req, HttpServletResponse _resp) {
String[] path = getPath(_req);
String email = Globals.dao.getEmailForResetKey(path[1]);
String email = Globals.dao.getEmailForResetKey(CollectionUtils.get(path, 1));
if (EmailValidator.getInstance().isValid(email)) {
render(_resp, "passwordReset.ftl", model(_req, "key", path[1]));
} else {

View File

@ -7,7 +7,7 @@ import com.lanternsoftware.util.servlet.LanternServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public abstract class SecureServlet extends LanternServlet {
public abstract class SecureServiceServlet extends LanternServlet {
@Override
protected void doGet(HttpServletRequest _req, HttpServletResponse _rep) {
AuthCode authCode = Globals.dao.decryptAuthCode(_req.getHeader("auth_code"));

View File

@ -1,6 +1,6 @@
package com.lanternsoftware.currentmonitor.servlet;
import com.lanternsoftware.util.LanternFiles;
import com.lanternsoftware.util.external.LanternFiles;
import com.lanternsoftware.util.ResourceLoader;
import com.lanternsoftware.util.dao.DaoSerializer;
import com.lanternsoftware.util.servlet.LanternServlet;
@ -16,8 +16,8 @@ public class UpdateServlet extends LanternServlet {
@Override
protected void doGet(HttpServletRequest _req, HttpServletResponse _rep) {
if (isPath(_req, 0, "version"))
setResponseEntity(_rep, MediaType.APPLICATION_OCTET_STREAM, DaoSerializer.toZipBson(DaoSerializer.parse(ResourceLoader.loadFileAsString(LanternFiles.OPS_PATH + "release" + File.separator + "version.json"))));
setResponseEntity(_rep, MediaType.APPLICATION_OCTET_STREAM, DaoSerializer.toZipBson(DaoSerializer.parse(ResourceLoader.loadFileAsString(LanternFiles.CONFIG_PATH + "release" + File.separator + "version.json"))));
else
setResponseEntity(_rep, MediaType.APPLICATION_OCTET_STREAM, ResourceLoader.loadFile(LanternFiles.OPS_PATH + "release" + File.separator + "lantern-currentmonitor.jar"));
setResponseEntity(_rep, MediaType.APPLICATION_OCTET_STREAM, ResourceLoader.loadFile(LanternFiles.CONFIG_PATH + "release" + File.separator + "lantern-currentmonitor.jar"));
}
}

View File

@ -0,0 +1,19 @@
package com.lanternsoftware.currentmonitor.servlet.console;
import com.lanternsoftware.util.dao.auth.AuthCode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@WebServlet("")
public class ConsoleServlet extends SecureConsoleServlet {
private static final Logger logger = LoggerFactory.getLogger(ConsoleServlet.class);
@Override
protected void get(AuthCode _authCode, HttpServletRequest _req, HttpServletResponse _rep) {
redirect(_rep, "export");
}
}

View File

@ -0,0 +1,140 @@
package com.lanternsoftware.currentmonitor.servlet.console;
import com.lanternsoftware.currentmonitor.context.Globals;
import com.lanternsoftware.datamodel.currentmonitor.Breaker;
import com.lanternsoftware.datamodel.currentmonitor.BreakerConfig;
import com.lanternsoftware.datamodel.currentmonitor.archive.ArchiveStatus;
import com.lanternsoftware.datamodel.currentmonitor.archive.BreakerEnergyArchive;
import com.lanternsoftware.datamodel.currentmonitor.archive.DailyEnergyArchive;
import com.lanternsoftware.datamodel.currentmonitor.archive.MonthlyEnergyArchive;
import com.lanternsoftware.util.CollectionUtils;
import com.lanternsoftware.util.DateUtils;
import com.lanternsoftware.util.NullUtils;
import com.lanternsoftware.util.dao.DaoEntity;
import com.lanternsoftware.util.dao.DaoSerializer;
import com.lanternsoftware.util.dao.auth.AuthCode;
import org.apache.commons.io.IOUtils;
import org.bson.codecs.DocumentCodec;
import org.bson.codecs.EncoderContext;
import org.bson.json.JsonWriter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.TimeZone;
import java.util.zip.Deflater;
import java.util.zip.GZIPOutputStream;
@WebServlet("/export/*")
public class ExportServlet extends SecureConsoleServlet {
private static final Logger logger = LoggerFactory.getLogger(ExportServlet.class);
@Override
protected void get(AuthCode _authCode, HttpServletRequest _req, HttpServletResponse _rep) {
TimeZone tz = Globals.dao.getTimeZoneForAccount(_authCode.getAccountId());
String[] path = path(_req);
if (path.length > 1) {
synchronized (this) {
InputStream is = Globals.dao.streamArchive(_authCode.getAccountId(), new Date(DaoSerializer.toLong(path[0])));
if (is == null) {
redirect(_rep, _req.getContextPath() + "/export");
return;
}
OutputStream os = null;
GZIPOutputStream gout = null;
JsonWriter jsonWriter = null;
try {
os = _rep.getOutputStream();
if (NullUtils.makeNotNull(path[1]).contains("csv")) {
BreakerConfig config = Globals.dao.getConfig(_authCode.getAccountId()); //TODO: get historical config for this month in case it's changed since then.
Map<Integer, Breaker> breakers = CollectionUtils.transformToMap(config.getAllBreakers(), Breaker::getIntKey);
os = new GZIPOutputStream(os) {{def.setLevel(Deflater.BEST_SPEED);}};
MonthlyEnergyArchive archive = DaoSerializer.fromZipBson(IOUtils.toByteArray(is), MonthlyEnergyArchive.class);
DailyEnergyArchive fday = CollectionUtils.getFirst(archive.getDays());
if (fday == null) {
redirect(_rep, _req.getContextPath() + "/export");
return;
}
StringBuilder header = new StringBuilder("Timestamp");
for (BreakerEnergyArchive ba : CollectionUtils.makeNotNull(fday.getBreakers())) {
Breaker b = breakers.get(Breaker.intKey(ba.getPanel(), ba.getSpace()));
header.append(",");
if (b != null) {
header.append(b.getKey());
header.append("-");
header.append(b.getName());
}
}
header.append("\n");
DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
df.setTimeZone(TimeZone.getTimeZone("UTC"));
os.write(NullUtils.toByteArray(header.toString()));
Date dayStart = archive.getMonth();
for (DailyEnergyArchive day : CollectionUtils.makeNotNull(archive.getDays())) {
Date dayEnd = DateUtils.addDays(dayStart, 1, tz);
int secondsInDay = (int) ((dayEnd.getTime() - dayStart.getTime()) / 1000);
for (int sec = 0; sec < secondsInDay; sec++) {
StringBuilder line = new StringBuilder();
line.append(df.format(new Date(dayStart.getTime() + ((long) sec * 1000))));
for (BreakerEnergyArchive b : CollectionUtils.makeNotNull(day.getBreakers())) {
line.append(",");
if ((b.getReadings() == null) || (sec * 4 >= b.getReadings().length))
line.append("NaN");
else {
ByteBuffer readings = ByteBuffer.wrap(b.getReadings());
line.append(readings.getFloat(sec * 4));
}
}
line.append("\n");
os.write(NullUtils.toByteArray(line.toString()));
}
}
return;
}
if (NullUtils.makeNotNull(path[1]).contains("json")) {
DaoEntity archive = DaoSerializer.fromZipBson(IOUtils.toByteArray(is));
gout = new GZIPOutputStream(os) {{def.setLevel(Deflater.BEST_SPEED);}};
jsonWriter = new JsonWriter(new OutputStreamWriter(gout, StandardCharsets.UTF_8), DaoSerializer.JSON_COMPACT_SETTINGS);
new DocumentCodec().encode(jsonWriter, archive.toDocument(), EncoderContext.builder().build());
return;
}
IOUtils.copy(is, os);
return;
} catch (Exception _e) {
logger.error("Failed to send archive to browser", _e);
redirect(_rep, _req.getContextPath() + "/export");
return;
} finally {
IOUtils.closeQuietly(is);
IOUtils.closeQuietly(jsonWriter);
IOUtils.closeQuietly(gout);
IOUtils.closeQuietly(os);
}
}
}
List<ArchiveStatus> status = Globals.dao.getArchiveStatus(_authCode.getAccountId());
List<MonthDisplay> months = CollectionUtils.transform(status, _s->new MonthDisplay(DateUtils.format("MMMM yyyy", tz, _s.getMonth()), _s.getMonth().getTime(), (int)_s.getProgress()));
Map<String, Object> model = model(_req, "months", months);
model.put("inprogress", CollectionUtils.anyQualify(months, _m->_m.getProgress() > 0 && _m.getProgress() < 100));
renderBody(_rep, "export.ftl", model);
}
@Override
protected void post(AuthCode _authCode, HttpServletRequest _req, HttpServletResponse _rep) {
Date month = new Date(DaoSerializer.toLong(_req.getParameter("month")));
Globals.dao.archiveMonth(_authCode.getAccountId(), month);
redirect(_rep, ".");
}
}

View File

@ -0,0 +1,33 @@
package com.lanternsoftware.currentmonitor.servlet.console;
import com.lanternsoftware.currentmonitor.servlet.FreemarkerCMServlet;
import com.lanternsoftware.currentmonitor.util.GoogleAuthHelper;
import com.lanternsoftware.util.NullUtils;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@WebServlet("/gso")
public class GsoServlet extends FreemarkerCMServlet {
@Override
protected void doGet(HttpServletRequest _req, HttpServletResponse _rep) {
render(_rep, "login.ftl", model(_req));
}
@Override
protected void doPost(HttpServletRequest _req, HttpServletResponse _rep) {
String code = getRequestPayloadAsString(_req);
if (NullUtils.isNotEmpty(code)) {
String authCode = GoogleAuthHelper.signin(code, null);
if (NullUtils.isNotEmpty(authCode)) {
Cookie authCookie = new Cookie("auth_code", authCode);
authCookie.setMaxAge(157680000);
authCookie.setSecure(true);
_rep.addCookie(authCookie);
_req.getSession().setAttribute("auth_code", authCode);
}
}
}
}

View File

@ -0,0 +1,42 @@
package com.lanternsoftware.currentmonitor.servlet.console;
import com.lanternsoftware.currentmonitor.context.Globals;
import com.lanternsoftware.currentmonitor.servlet.FreemarkerCMServlet;
import com.lanternsoftware.currentmonitor.util.GoogleAuthHelper;
import com.lanternsoftware.util.DateUtils;
import com.lanternsoftware.util.NullUtils;
import com.lanternsoftware.util.dao.DaoEntity;
import com.lanternsoftware.util.dao.DaoSerializer;
import com.lanternsoftware.util.dao.auth.AuthCode;
import com.lanternsoftware.util.servlet.LanternServlet;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@WebServlet("/login")
public class LoginServlet extends FreemarkerCMServlet {
@Override
protected void doGet(HttpServletRequest _req, HttpServletResponse _rep) {
render(_rep, "login.ftl", model(_req));
}
@Override
protected void doPost(HttpServletRequest _req, HttpServletResponse _rep) {
String username = _req.getParameter("username");
String password = _req.getParameter("password");
String authCode = Globals.dao.authenticateAccount(username, password);
if (NullUtils.isNotEmpty(authCode)) {
Cookie authCookie = new Cookie("auth_code", authCode);
authCookie.setMaxAge(157680000);
authCookie.setSecure(true);
_rep.addCookie(authCookie);
_req.getSession().setAttribute("auth_code", authCode);
redirect(_rep, _req.getContextPath());
}
render(_rep, "login.ftl", model(_req, "error", "Invalid Credentials"));
}
}

View File

@ -0,0 +1,27 @@
package com.lanternsoftware.currentmonitor.servlet.console;
import com.lanternsoftware.currentmonitor.servlet.FreemarkerCMServlet;
import com.lanternsoftware.currentmonitor.util.GoogleAuthHelper;
import com.lanternsoftware.util.NullUtils;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@WebServlet("/logout")
public class LogoutServlet extends FreemarkerCMServlet {
@Override
protected void doGet(HttpServletRequest _req, HttpServletResponse _rep) {
_req.getSession().removeAttribute("auth_code");
Cookie authCookie = new Cookie("auth_code", "");
authCookie.setMaxAge(0);
authCookie.setSecure(true);
_rep.addCookie(authCookie);
redirect(_rep, _req.getContextPath());
}
@Override
protected void doPost(HttpServletRequest _req, HttpServletResponse _rep) {
}
}

View File

@ -0,0 +1,29 @@
package com.lanternsoftware.currentmonitor.servlet.console;
public class MonthDisplay {
public final String name;
public final long date;
public final int progress;
public MonthDisplay(String _name, long _date, int _progress) {
name = _name;
date = _date;
progress = _progress;
}
public String getName() {
return name;
}
public String getFileName() {
return name.replace(" ", "-");
}
public String getDate() {
return String.valueOf(date);
}
public int getProgress() {
return progress;
}
}

View File

@ -0,0 +1,48 @@
package com.lanternsoftware.currentmonitor.servlet.console;
import com.lanternsoftware.currentmonitor.context.Globals;
import com.lanternsoftware.currentmonitor.servlet.FreemarkerCMServlet;
import com.lanternsoftware.util.CollectionUtils;
import com.lanternsoftware.util.NullUtils;
import com.lanternsoftware.util.dao.DaoSerializer;
import com.lanternsoftware.util.dao.auth.AuthCode;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public abstract class SecureConsoleServlet extends FreemarkerCMServlet {
@Override
protected void doGet(HttpServletRequest _req, HttpServletResponse _rep) {
AuthCode code = getAuthCode(_req, _rep);
if (code != null)
get(code, _req, _rep);
}
protected void get(AuthCode _authCode, HttpServletRequest _req, HttpServletResponse _rep) {
}
@Override
protected void doPost(HttpServletRequest _req, HttpServletResponse _rep) {
AuthCode code = getAuthCode(_req, _rep);
if (code != null)
post(code, _req, _rep);
}
private AuthCode getAuthCode(HttpServletRequest _req, HttpServletResponse _rep) {
AuthCode authCode = Globals.dao.decryptAuthCode(DaoSerializer.toString(_req.getSession().getAttribute("auth_code")));
if (authCode == null) {
Cookie authCookie = CollectionUtils.filterOne(CollectionUtils.asArrayList(_req.getCookies()), _c-> NullUtils.isEqual(_c.getName(), "auth_code"));
if (authCookie != null)
authCode = Globals.dao.decryptAuthCode(authCookie.getValue());
}
if (authCode == null) {
redirect(_rep, _req.getContextPath() + "/login");
return null;
}
return authCode;
}
protected void post(AuthCode _authCode, HttpServletRequest _req, HttpServletResponse _rep) {
}
}

View File

@ -0,0 +1,42 @@
package com.lanternsoftware.currentmonitor.util;
import com.google.api.client.googleapis.auth.oauth2.GoogleAuthorizationCodeTokenRequest;
import com.google.api.client.googleapis.auth.oauth2.GoogleIdToken;
import com.google.api.client.googleapis.auth.oauth2.GoogleTokenResponse;
import com.google.api.client.http.javanet.NetHttpTransport;
import com.google.api.client.json.gson.GsonFactory;
import com.lanternsoftware.currentmonitor.context.Globals;
import com.lanternsoftware.util.external.LanternFiles;
import com.lanternsoftware.util.ResourceLoader;
import com.lanternsoftware.util.dao.DaoEntity;
import com.lanternsoftware.util.dao.DaoSerializer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.TimeZone;
public class GoogleAuthHelper {
private static final Logger logger = LoggerFactory.getLogger(GoogleAuthHelper.class);
private static final NetHttpTransport transport = new NetHttpTransport();
private static final String googleClientId;
private static final String googleClientSecret;
static {
DaoEntity google = DaoSerializer.parse(ResourceLoader.loadFileAsString(LanternFiles.CONFIG_PATH + "google_sso.txt"));
googleClientId = DaoSerializer.getString(google, "id");
googleClientSecret = DaoSerializer.getString(google, "secret");
}
public static String signin(String _code, TimeZone _tz) {
try {
GoogleTokenResponse tokenResponse = new GoogleAuthorizationCodeTokenRequest(transport, new GsonFactory(), "https://oauth2.googleapis.com/token", googleClientId, googleClientSecret, _code, "postmessage").execute();
if (tokenResponse != null) {
GoogleIdToken idToken = tokenResponse.parseIdToken();
if (idToken != null)
return Globals.dao.getAuthCodeForEmail(idToken.getPayload().getEmail(), _tz);
}
} catch (Exception _e) {
logger.error("Failed to validate google auth code", _e);
}
return null;
}
}

View File

@ -0,0 +1,50 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<#if inprogress><meta http-equiv="refresh" content="1"></#if>
<title>Lantern Console</title>
<link href="${link_prefix}/bootstrap/css/bootstrap.min.css" rel="stylesheet">
<link href="${link_prefix}bootstrap/css/style.min.css" rel="stylesheet">
</head>
<body>
<div class="container-fluid login-container">
<div class="row">
<div class="col-1 col-lg-3 col-4k-4"></div>
<div class="col-10 col-lg-6 col-4k-4">
<div class="row mt-3">
<table class="table">
<tbody>
<#list months as month>
<#if month.progress == 0>
<form method="POST">
</#if>
<tr>
<td>${month.name!}</td>
<#if month.progress == 0>
<td><input type="hidden" name="month" value="${month.date}"/><input type="submit" class="btn-primary border-none px-2 py-1" value="Export"/></td>
<#elseif month.progress == 1>
<td>Queued</td>
<#elseif month.progress < 100>
<td>Progress ${month.progress!}%</td>
<#else>
<td>
<a href="export/${month.date}/${month.fileName!}.bson.zip" class="btn-primary border-none text-decoration-none p-2" download>BSON</a>
<a href="export/${month.date}/${month.fileName!}.json.zip" class="btn-primary border-none text-decoration-none p-2" download>JSON</a>
<a href="export/${month.date}/${month.fileName!}.csv.zip" class="btn-primary border-none text-decoration-none p-2" download>CSV</a>
</td>
</#if>
</tr>
<#if month.progress == 0>
</form>
</#if>
</#list>
</tbody>
</table>
</div>
<div class="col-1 col-lg-3 col-4k-4"></div>
</div>
</body>
</html>

View File

@ -0,0 +1,34 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="">
<meta name="author" content="">
<title>Lantern Power Monitor</title>
<link rel="icon" type="image/png" href="${link_prefix}img/favicon.png">
<link href="${link_prefix}bootstrap/css/bootstrap.min.css" rel="stylesheet">
<link href="${link_prefix}bootstrap/css/style.min.css" rel="stylesheet">
</head>
<body>
<main>
<div class="container-fluid">
<div class="row header-menu py-2">
<div class="col-2"></div>
<div class="col-auto"><img class="img-fluid" alt="Logo" src="${link_prefix}img/logo_40.png"/></div>
<div class="col"></div>
<div class="col-auto mx-2"><a href="${link_prefix}" class="text-decoration-none header-link fmnu1">DATA EXPORT</a></div>
<div class="col-auto mx-2"><a href="${link_prefix}logout" class="text-decoration-none header-link fmnu1">LOGOUT</a></div>
<div class="col-2"></div>
</div>
<div class="row">
<div class="col-12">
${body}
</div>
</div>
</div>
</main>
</body>
</html>

View File

@ -0,0 +1,97 @@
<!DOCTYPE html>
<html lang="en">
<head>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.8.2/jquery.min.js"></script>
<script src="https://apis.google.com/js/client:platform.js?onload=start" async defer></script>
<script>
function start() {
gapi.load('auth2', function() {
auth2 = gapi.auth2.init({client_id: '412929846491-r3uh0t67mpeouicjvlara580i9cfchol.apps.googleusercontent.com', cookie_policy: 'none', redirect_uri: 'postmessage'});
});
}
</script>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="">
<meta name="author" content="">
<title>Lantern Power Monitor</title>
<link rel="icon" type="image/png" href="${link_prefix}img/favicon.png">
<link href="${link_prefix}bootstrap/css/bootstrap.min.css" rel="stylesheet">
<link href="${link_prefix}bootstrap/css/style.min.css" rel="stylesheet">
</head>
<body>
<main>
<div class="container-fluid login-container">
<form method="POST">
<div class="row">
<div class="col-1 col-lg-3 col-4k-4"></div>
<div class="col-10 col-lg-6 col-4k-4">
<div class="row pt-5">
<div class="col-12">
<img class="img-fluid" alt='Lantern Logo' src='${link_prefix}img/lantern_cm.png'/>
</div>
</div>
<div class="row pt-5">
<div class="col-1 col-lg-2"></div>
<div class="col-10 col-lg-8">
<input type="email" name="username" class="login-input" placeholder="email"/>
</div>
<div class="col-1 col-lg-2"></div>
</div>
<div class="row pt-2">
<div class="col-1 col-lg-2"></div>
<div class="col-10 col-lg-8">
<input type="password" name="password" class="login-input" placeholder="password"/>
</div>
<div class="col-1 col-lg-2"></div>
</div>
<div class="row pt-2">
<div class="col-1 col-lg-2"></div>
<div class="col-8 col-lg-6 d-flex"><button type="button" id="signinButton" class="gso"></button></div>
<div class="col-2 d-flex justify-content-end">
<input type="submit" class="btn-primary border-none px-3" value="Login"/>
</div>
<div class="col-1 col-lg-2"></div>
</div>
<div class="row pt-5">
<div class="col-1 col-lg-2"></div>
<div class="col-10 col-lg-8 error">
${error!}
</div>
<div class="col-1 col-lg-2"></div>
</div>
</div>
<div class="col-1 col-lg-3 col-4k-4"></div>
</div>
</form>
</div>
<div class="login-bkgnd"></div>
<script>
$('#signinButton').click(function() {
auth2.grantOfflineAccess().then(signInCallback);
});
function signInCallback(authResult) {
if (authResult['code']) {
$.ajax({
type: 'POST',
url: 'gso',
headers: {
'X-Requested-With': 'XMLHttpRequest'
},
contentType: 'application/octet-stream; charset=utf-8',
success: function(result) {
window.location.replace('./');
},
processData: false,
data: authResult['code']
});
} else {
// There was an error.
}
}
</script>
</main>
</body>
</html>

View File

@ -15,7 +15,7 @@
<div class="navy-line"></div>
<h4><img class="mr-1" src="${context}/img/logo_40.png">Reset Password</h4>
<form class="ml-2" method="POST">
<input type="hidden" name="reset_key" value="${key}"/>
<input type="hidden" name="reset_key" value="${key!}"/>
<div>New Password:</div>
<input type="password" name="password"/>
<input type="submit" class="btn-primary" value="Submit"/>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,51 +1,150 @@
/*!
* Bootstrap Reboot v4.5.0 (https://getbootstrap.com/)
* Copyright 2011-2020 The Bootstrap Authors
* Copyright 2011-2020 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
* Bootstrap Reboot v5.1.1 (https://getbootstrap.com/)
* Copyright 2011-2021 The Bootstrap Authors
* Copyright 2011-2021 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
* Forked from Normalize.css, licensed MIT (https://github.com/necolas/normalize.css/blob/master/LICENSE.md)
*/
:root {
--bs-blue: #0d6efd;
--bs-indigo: #6610f2;
--bs-purple: #6f42c1;
--bs-pink: #d63384;
--bs-red: #dc3545;
--bs-orange: #fd7e14;
--bs-yellow: #ffc107;
--bs-green: #198754;
--bs-teal: #20c997;
--bs-cyan: #0dcaf0;
--bs-white: #fff;
--bs-gray: #6c757d;
--bs-gray-dark: #343a40;
--bs-gray-100: #f8f9fa;
--bs-gray-200: #e9ecef;
--bs-gray-300: #dee2e6;
--bs-gray-400: #ced4da;
--bs-gray-500: #adb5bd;
--bs-gray-600: #6c757d;
--bs-gray-700: #495057;
--bs-gray-800: #343a40;
--bs-gray-900: #212529;
--bs-primary: #0d6efd;
--bs-secondary: #6c757d;
--bs-success: #198754;
--bs-info: #0dcaf0;
--bs-warning: #ffc107;
--bs-danger: #dc3545;
--bs-light: #f8f9fa;
--bs-dark: #212529;
--bs-primary-rgb: 13, 110, 253;
--bs-secondary-rgb: 108, 117, 125;
--bs-success-rgb: 25, 135, 84;
--bs-info-rgb: 13, 202, 240;
--bs-warning-rgb: 255, 193, 7;
--bs-danger-rgb: 220, 53, 69;
--bs-light-rgb: 248, 249, 250;
--bs-dark-rgb: 33, 37, 41;
--bs-white-rgb: 255, 255, 255;
--bs-black-rgb: 0, 0, 0;
--bs-body-color-rgb: 33, 37, 41;
--bs-body-bg-rgb: 255, 255, 255;
--bs-font-sans-serif: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", "Liberation Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
--bs-font-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
--bs-gradient: linear-gradient(180deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0));
--bs-body-font-family: var(--bs-font-sans-serif);
--bs-body-font-size: 1rem;
--bs-body-font-weight: 400;
--bs-body-line-height: 1.5;
--bs-body-color: #212529;
--bs-body-bg: #fff;
}
*,
*::before,
*::after {
box-sizing: border-box;
}
html {
font-family: sans-serif;
line-height: 1.15;
-webkit-text-size-adjust: 100%;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
@media (prefers-reduced-motion: no-preference) {
:root {
scroll-behavior: smooth;
}
article, aside, figcaption, figure, footer, header, hgroup, main, nav, section {
display: block;
}
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
font-size: 1rem;
font-weight: 400;
line-height: 1.5;
color: #212529;
text-align: left;
background-color: #fff;
}
[tabindex="-1"]:focus:not(:focus-visible) {
outline: 0 !important;
font-family: var(--bs-body-font-family);
font-size: var(--bs-body-font-size);
font-weight: var(--bs-body-font-weight);
line-height: var(--bs-body-line-height);
color: var(--bs-body-color);
text-align: var(--bs-body-text-align);
background-color: var(--bs-body-bg);
-webkit-text-size-adjust: 100%;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
}
hr {
box-sizing: content-box;
height: 0;
overflow: visible;
margin: 1rem 0;
color: inherit;
background-color: currentColor;
border: 0;
opacity: 0.25;
}
h1, h2, h3, h4, h5, h6 {
hr:not([size]) {
height: 1px;
}
h6, h5, h4, h3, h2, h1 {
margin-top: 0;
margin-bottom: 0.5rem;
font-weight: 500;
line-height: 1.2;
}
h1 {
font-size: calc(1.375rem + 1.5vw);
}
@media (min-width: 1200px) {
h1 {
font-size: 2.5rem;
}
}
h2 {
font-size: calc(1.325rem + 0.9vw);
}
@media (min-width: 1200px) {
h2 {
font-size: 2rem;
}
}
h3 {
font-size: calc(1.3rem + 0.6vw);
}
@media (min-width: 1200px) {
h3 {
font-size: 1.75rem;
}
}
h4 {
font-size: calc(1.275rem + 0.3vw);
}
@media (min-width: 1200px) {
h4 {
font-size: 1.5rem;
}
}
h5 {
font-size: 1.25rem;
}
h6 {
font-size: 1rem;
}
p {
@ -54,12 +153,10 @@ p {
}
abbr[title],
abbr[data-original-title] {
text-decoration: underline;
abbr[data-bs-original-title] {
-webkit-text-decoration: underline dotted;
text-decoration: underline dotted;
cursor: help;
border-bottom: 0;
-webkit-text-decoration-skip-ink: none;
text-decoration-skip-ink: none;
}
@ -70,6 +167,11 @@ address {
line-height: inherit;
}
ol,
ul {
padding-left: 2rem;
}
ol,
ul,
dl {
@ -89,7 +191,7 @@ dt {
}
dd {
margin-bottom: .5rem;
margin-bottom: 0.5rem;
margin-left: 0;
}
@ -103,42 +205,39 @@ strong {
}
small {
font-size: 80%;
font-size: 0.875em;
}
mark {
padding: 0.2em;
background-color: #fcf8e3;
}
sub,
sup {
position: relative;
font-size: 75%;
font-size: 0.75em;
line-height: 0;
vertical-align: baseline;
}
sub {
bottom: -.25em;
bottom: -0.25em;
}
sup {
top: -.5em;
top: -0.5em;
}
a {
color: #007bff;
text-decoration: none;
background-color: transparent;
}
a:hover {
color: #0056b3;
color: #0d6efd;
text-decoration: underline;
}
a:not([href]) {
color: inherit;
text-decoration: none;
a:hover {
color: #0a58ca;
}
a:not([href]):hover {
a:not([href]):not([class]), a:not([href]):not([class]):hover {
color: inherit;
text-decoration: none;
}
@ -147,59 +246,94 @@ pre,
code,
kbd,
samp {
font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
font-family: var(--bs-font-monospace);
font-size: 1em;
direction: ltr /* rtl:ignore */;
unicode-bidi: bidi-override;
}
pre {
display: block;
margin-top: 0;
margin-bottom: 1rem;
overflow: auto;
-ms-overflow-style: scrollbar;
font-size: 0.875em;
}
pre code {
font-size: inherit;
color: inherit;
word-break: normal;
}
code {
font-size: 0.875em;
color: #d63384;
word-wrap: break-word;
}
a > code {
color: inherit;
}
kbd {
padding: 0.2rem 0.4rem;
font-size: 0.875em;
color: #fff;
background-color: #212529;
border-radius: 0.2rem;
}
kbd kbd {
padding: 0;
font-size: 1em;
font-weight: 700;
}
figure {
margin: 0 0 1rem;
}
img {
vertical-align: middle;
border-style: none;
}
img,
svg {
overflow: hidden;
vertical-align: middle;
}
table {
caption-side: bottom;
border-collapse: collapse;
}
caption {
padding-top: 0.75rem;
padding-bottom: 0.75rem;
padding-top: 0.5rem;
padding-bottom: 0.5rem;
color: #6c757d;
text-align: left;
caption-side: bottom;
}
th {
text-align: inherit;
text-align: -webkit-match-parent;
}
thead,
tbody,
tfoot,
tr,
td,
th {
border-color: inherit;
border-style: solid;
border-width: 0;
}
label {
display: inline-block;
margin-bottom: 0.5rem;
}
button {
border-radius: 0;
}
button:focus {
outline: 1px dotted;
outline: 5px auto -webkit-focus-ring-color;
button:focus:not(:focus-visible) {
outline: 0;
}
input,
@ -213,54 +347,45 @@ textarea {
line-height: inherit;
}
button,
input {
overflow: visible;
}
button,
select {
text-transform: none;
}
[role="button"] {
[role=button] {
cursor: pointer;
}
select {
word-wrap: normal;
}
button,
[type="button"],
[type="reset"],
[type="submit"] {
-webkit-appearance: button;
select:disabled {
opacity: 1;
}
[list]::-webkit-calendar-picker-indicator {
display: none;
}
button,
[type=button],
[type=reset],
[type=submit] {
-webkit-appearance: button;
}
button:not(:disabled),
[type="button"]:not(:disabled),
[type="reset"]:not(:disabled),
[type="submit"]:not(:disabled) {
[type=button]:not(:disabled),
[type=reset]:not(:disabled),
[type=submit]:not(:disabled) {
cursor: pointer;
}
button::-moz-focus-inner,
[type="button"]::-moz-focus-inner,
[type="reset"]::-moz-focus-inner,
[type="submit"]::-moz-focus-inner {
::-moz-focus-inner {
padding: 0;
border-style: none;
}
input[type="radio"],
input[type="checkbox"] {
box-sizing: border-box;
padding: 0;
}
textarea {
overflow: auto;
resize: vertical;
}
@ -272,33 +397,59 @@ fieldset {
}
legend {
display: block;
float: left;
width: 100%;
max-width: 100%;
padding: 0;
margin-bottom: .5rem;
font-size: 1.5rem;
margin-bottom: 0.5rem;
font-size: calc(1.275rem + 0.3vw);
line-height: inherit;
color: inherit;
white-space: normal;
}
@media (min-width: 1200px) {
legend {
font-size: 1.5rem;
}
}
legend + * {
clear: left;
}
progress {
vertical-align: baseline;
::-webkit-datetime-edit-fields-wrapper,
::-webkit-datetime-edit-text,
::-webkit-datetime-edit-minute,
::-webkit-datetime-edit-hour-field,
::-webkit-datetime-edit-day-field,
::-webkit-datetime-edit-month-field,
::-webkit-datetime-edit-year-field {
padding: 0;
}
[type="number"]::-webkit-inner-spin-button,
[type="number"]::-webkit-outer-spin-button {
::-webkit-inner-spin-button {
height: auto;
}
[type="search"] {
[type=search] {
outline-offset: -2px;
-webkit-appearance: textfield;
}
/* rtl:raw:
[type="tel"],
[type="url"],
[type="email"],
[type="number"] {
direction: ltr;
}
*/
::-webkit-search-decoration {
-webkit-appearance: none;
}
[type="search"]::-webkit-search-decoration {
-webkit-appearance: none;
::-webkit-color-swatch-wrapper {
padding: 0;
}
::file-selector-button {
font: inherit;
}
::-webkit-file-upload-button {
@ -310,16 +461,21 @@ output {
display: inline-block;
}
iframe {
border: 0;
}
summary {
display: list-item;
cursor: pointer;
}
template {
display: none;
progress {
vertical-align: baseline;
}
[hidden] {
display: none !important;
}
/*# sourceMappingURL=bootstrap-reboot.css.map */

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,478 @@
/*!
* Bootstrap Reboot v5.1.1 (https://getbootstrap.com/)
* Copyright 2011-2021 The Bootstrap Authors
* Copyright 2011-2021 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
* Forked from Normalize.css, licensed MIT (https://github.com/necolas/normalize.css/blob/master/LICENSE.md)
*/
:root {
--bs-blue: #0d6efd;
--bs-indigo: #6610f2;
--bs-purple: #6f42c1;
--bs-pink: #d63384;
--bs-red: #dc3545;
--bs-orange: #fd7e14;
--bs-yellow: #ffc107;
--bs-green: #198754;
--bs-teal: #20c997;
--bs-cyan: #0dcaf0;
--bs-white: #fff;
--bs-gray: #6c757d;
--bs-gray-dark: #343a40;
--bs-gray-100: #f8f9fa;
--bs-gray-200: #e9ecef;
--bs-gray-300: #dee2e6;
--bs-gray-400: #ced4da;
--bs-gray-500: #adb5bd;
--bs-gray-600: #6c757d;
--bs-gray-700: #495057;
--bs-gray-800: #343a40;
--bs-gray-900: #212529;
--bs-primary: #0d6efd;
--bs-secondary: #6c757d;
--bs-success: #198754;
--bs-info: #0dcaf0;
--bs-warning: #ffc107;
--bs-danger: #dc3545;
--bs-light: #f8f9fa;
--bs-dark: #212529;
--bs-primary-rgb: 13, 110, 253;
--bs-secondary-rgb: 108, 117, 125;
--bs-success-rgb: 25, 135, 84;
--bs-info-rgb: 13, 202, 240;
--bs-warning-rgb: 255, 193, 7;
--bs-danger-rgb: 220, 53, 69;
--bs-light-rgb: 248, 249, 250;
--bs-dark-rgb: 33, 37, 41;
--bs-white-rgb: 255, 255, 255;
--bs-black-rgb: 0, 0, 0;
--bs-body-color-rgb: 33, 37, 41;
--bs-body-bg-rgb: 255, 255, 255;
--bs-font-sans-serif: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", "Liberation Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
--bs-font-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
--bs-gradient: linear-gradient(180deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0));
--bs-body-font-family: var(--bs-font-sans-serif);
--bs-body-font-size: 1rem;
--bs-body-font-weight: 400;
--bs-body-line-height: 1.5;
--bs-body-color: #212529;
--bs-body-bg: #fff;
}
*,
*::before,
*::after {
box-sizing: border-box;
}
@media (prefers-reduced-motion: no-preference) {
:root {
scroll-behavior: smooth;
}
}
body {
margin: 0;
font-family: var(--bs-body-font-family);
font-size: var(--bs-body-font-size);
font-weight: var(--bs-body-font-weight);
line-height: var(--bs-body-line-height);
color: var(--bs-body-color);
text-align: var(--bs-body-text-align);
background-color: var(--bs-body-bg);
-webkit-text-size-adjust: 100%;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
}
hr {
margin: 1rem 0;
color: inherit;
background-color: currentColor;
border: 0;
opacity: 0.25;
}
hr:not([size]) {
height: 1px;
}
h6, h5, h4, h3, h2, h1 {
margin-top: 0;
margin-bottom: 0.5rem;
font-weight: 500;
line-height: 1.2;
}
h1 {
font-size: calc(1.375rem + 1.5vw);
}
@media (min-width: 1200px) {
h1 {
font-size: 2.5rem;
}
}
h2 {
font-size: calc(1.325rem + 0.9vw);
}
@media (min-width: 1200px) {
h2 {
font-size: 2rem;
}
}
h3 {
font-size: calc(1.3rem + 0.6vw);
}
@media (min-width: 1200px) {
h3 {
font-size: 1.75rem;
}
}
h4 {
font-size: calc(1.275rem + 0.3vw);
}
@media (min-width: 1200px) {
h4 {
font-size: 1.5rem;
}
}
h5 {
font-size: 1.25rem;
}
h6 {
font-size: 1rem;
}
p {
margin-top: 0;
margin-bottom: 1rem;
}
abbr[title],
abbr[data-bs-original-title] {
-webkit-text-decoration: underline dotted;
text-decoration: underline dotted;
cursor: help;
-webkit-text-decoration-skip-ink: none;
text-decoration-skip-ink: none;
}
address {
margin-bottom: 1rem;
font-style: normal;
line-height: inherit;
}
ol,
ul {
padding-right: 2rem;
}
ol,
ul,
dl {
margin-top: 0;
margin-bottom: 1rem;
}
ol ol,
ul ul,
ol ul,
ul ol {
margin-bottom: 0;
}
dt {
font-weight: 700;
}
dd {
margin-bottom: 0.5rem;
margin-right: 0;
}
blockquote {
margin: 0 0 1rem;
}
b,
strong {
font-weight: bolder;
}
small {
font-size: 0.875em;
}
mark {
padding: 0.2em;
background-color: #fcf8e3;
}
sub,
sup {
position: relative;
font-size: 0.75em;
line-height: 0;
vertical-align: baseline;
}
sub {
bottom: -0.25em;
}
sup {
top: -0.5em;
}
a {
color: #0d6efd;
text-decoration: underline;
}
a:hover {
color: #0a58ca;
}
a:not([href]):not([class]), a:not([href]):not([class]):hover {
color: inherit;
text-decoration: none;
}
pre,
code,
kbd,
samp {
font-family: var(--bs-font-monospace);
font-size: 1em;
direction: ltr ;
unicode-bidi: bidi-override;
}
pre {
display: block;
margin-top: 0;
margin-bottom: 1rem;
overflow: auto;
font-size: 0.875em;
}
pre code {
font-size: inherit;
color: inherit;
word-break: normal;
}
code {
font-size: 0.875em;
color: #d63384;
word-wrap: break-word;
}
a > code {
color: inherit;
}
kbd {
padding: 0.2rem 0.4rem;
font-size: 0.875em;
color: #fff;
background-color: #212529;
border-radius: 0.2rem;
}
kbd kbd {
padding: 0;
font-size: 1em;
font-weight: 700;
}
figure {
margin: 0 0 1rem;
}
img,
svg {
vertical-align: middle;
}
table {
caption-side: bottom;
border-collapse: collapse;
}
caption {
padding-top: 0.5rem;
padding-bottom: 0.5rem;
color: #6c757d;
text-align: right;
}
th {
text-align: inherit;
text-align: -webkit-match-parent;
}
thead,
tbody,
tfoot,
tr,
td,
th {
border-color: inherit;
border-style: solid;
border-width: 0;
}
label {
display: inline-block;
}
button {
border-radius: 0;
}
button:focus:not(:focus-visible) {
outline: 0;
}
input,
button,
select,
optgroup,
textarea {
margin: 0;
font-family: inherit;
font-size: inherit;
line-height: inherit;
}
button,
select {
text-transform: none;
}
[role=button] {
cursor: pointer;
}
select {
word-wrap: normal;
}
select:disabled {
opacity: 1;
}
[list]::-webkit-calendar-picker-indicator {
display: none;
}
button,
[type=button],
[type=reset],
[type=submit] {
-webkit-appearance: button;
}
button:not(:disabled),
[type=button]:not(:disabled),
[type=reset]:not(:disabled),
[type=submit]:not(:disabled) {
cursor: pointer;
}
::-moz-focus-inner {
padding: 0;
border-style: none;
}
textarea {
resize: vertical;
}
fieldset {
min-width: 0;
padding: 0;
margin: 0;
border: 0;
}
legend {
float: right;
width: 100%;
padding: 0;
margin-bottom: 0.5rem;
font-size: calc(1.275rem + 0.3vw);
line-height: inherit;
}
@media (min-width: 1200px) {
legend {
font-size: 1.5rem;
}
}
legend + * {
clear: right;
}
::-webkit-datetime-edit-fields-wrapper,
::-webkit-datetime-edit-text,
::-webkit-datetime-edit-minute,
::-webkit-datetime-edit-hour-field,
::-webkit-datetime-edit-day-field,
::-webkit-datetime-edit-month-field,
::-webkit-datetime-edit-year-field {
padding: 0;
}
::-webkit-inner-spin-button {
height: auto;
}
[type=search] {
outline-offset: -2px;
-webkit-appearance: textfield;
}
[type="tel"],
[type="url"],
[type="email"],
[type="number"] {
direction: ltr;
}
::-webkit-search-decoration {
-webkit-appearance: none;
}
::-webkit-color-swatch-wrapper {
padding: 0;
}
::file-selector-button {
font: inherit;
}
::-webkit-file-upload-button {
font: inherit;
-webkit-appearance: button;
}
output {
display: inline-block;
}
iframe {
border: 0;
}
summary {
display: list-item;
cursor: pointer;
}
progress {
vertical-align: baseline;
}
[hidden] {
display: none !important;
}
/*# sourceMappingURL=bootstrap-reboot.rtl.css.map */

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,229 @@
.scrollarea {
overflow-y: auto;
}
.fw-semibold { font-weight: 600; }
.lh-tight { line-height: 1.25; }
.menu_link {
text-decoration:none!important;
}
.menu_subitem {
color:#404040;
}
.menu_item, .menu_subitem:hover, .menu_subitem:focus {
background-color: #1a9acc;
color:#f0f0f0;
}
.lpm_menu {
z-index:1;
background:#ffffff;
min-height:100%;
max-height:100%;
}
#toggle_label {
margin-top:0.5rem;
display: block;
width: 2rem;
position:absolute;
z-index: 10;
}
.blue, .title {
color: #1a9acc;
}
.kill {
color: #ff0000;
}
.max-w-300 {
max-width:min(100%,300px);
}
.max-w-400 {
max-width:min(100%,400px);
}
.max-w-500 {
max-width:min(100%,500px);
}
.max-w-600 {
max-width:min(100%,600px);
}
.max-w-700 {
max-width:min(100%,700px);
}
.max-w-800 {
max-width:min(100%,800px);
}
.max-w-900 {
max-width:min(100%,900px);
}
.max-w-1000 {
max-width:min(100%,1000px);
}
.max-w-1200 {
max-width:min(100%,1200px);
}
.align-col-right {
display: flex !important;
justify-content: flex-end !important;
padding-right: 1vw !important;
}
.align-col-left {
display: flex !important;
justify-content: flex-start !important;
padding-left: 1vw !important;
}
.title {
font-size: 2em;
}
.sub-title {
font-size: 1.1em;
}
@media (max-width: 991px) {
#menu_toggle:checked ~ #toggle_label {
position:fixed!important;
}
.lpm_menu {
display:none!important;
}
#menu_toggle:checked ~ .lpm_menu {
display:block!important;
}
#menu_toggle:checked ~ .lpm_body {
display: block!important;
width: 50%;
}
}
@media (min-width: 992px) {
.hsy {
margin-top: 2.3rem !important;
}
}
@media (min-width: 3000px) {
#menu_toggle:checked ~ .lpm_menu {
display:block!important;
}
#menu_toggle:checked ~ .lpm_body {
display: block!important;
width: 50%;
}
}
.fm1 {
font-size: calc(1rem + 1.25vw);
}
.fm2 {
font-size: calc(0.7rem + 1vw);
}
.fm3 {
font-size: calc(0.8rem + 0.5vw);
}
.fmnu1 {
font-size: calc(1rem + 0.5vw);
}
.fmnu2 {
font-size: calc(0.8rem + 0.5vw);
}
.login-container {
z-index:2;
position:absolute;
}
.login-bkgnd {
z-index:1;
position:absolute;
width: 100%;
height: 100%;
top:0;
left:0;
background-image:url('../../img/pcb.png');
background-size:cover;
background-position: center;
}
.login-input {
width: 100%;
}
::placeholder {
color: #a0a0a0;
opacity: 1;
}
.btn-primary {
background-color: #1a9acc;
border-color: #1a9acc;
}
.btn-primary:hover, .btn-primary:focus{
background-color: #20c0ff;
border-color: #20c0ff;
}
.header-menu {
background-color: #1a9acc;
}
.header-link {
color: #f0f0f0;
}
.header-link:hover {
color: #d0d0d0;
}
.gso {
border-style: none;
width: 10rem;
height: 2.5rem;
background-image: url("../../img/gso.png");
background-position: center;
background-size: cover;
background-color: #0000;
}
.gso:hover {
background-color: #0000;
}
.gso:focus {
background-image: url("../../img/gso_pressed.png");
}
.border-none {
border-style: none;
}
.error {
color: darkred;
}

View File

@ -0,0 +1 @@
.scrollarea{overflow-y:auto}.fw-semibold{font-weight:600}.lh-tight{line-height:1.25}.menu_link{text-decoration:none!important}.menu_subitem{color:#404040}.menu_item,.menu_subitem:focus,.menu_subitem:hover{background-color:#1a9acc;color:#f0f0f0}.lpm_menu{z-index:1;background:#fff;min-height:100%;max-height:100%}#toggle_label{margin-top:.5rem;display:block;width:2rem;position:absolute;z-index:10}.blue,.title{color:#1a9acc}.kill{color:red}.max-w-300{max-width:min(100%,300px)}.max-w-400{max-width:min(100%,400px)}.max-w-500{max-width:min(100%,500px)}.max-w-600{max-width:min(100%,600px)}.max-w-700{max-width:min(100%,700px)}.max-w-800{max-width:min(100%,800px)}.max-w-900{max-width:min(100%,900px)}.max-w-1000{max-width:min(100%,1000px)}.max-w-1200{max-width:min(100%,1200px)}.align-col-right{display:flex!important;justify-content:flex-end!important;padding-right:1vw!important}.align-col-left{display:flex!important;justify-content:flex-start!important;padding-left:1vw!important}.title{font-size:2em}.sub-title{font-size:1.1em}@media (max-width:991px){#menu_toggle:checked~#toggle_label{position:fixed!important}.lpm_menu{display:none!important}#menu_toggle:checked~.lpm_menu{display:block!important}#menu_toggle:checked~.lpm_body{display:block!important;width:50%}}@media (min-width:992px){.hsy{margin-top:2.3rem!important}}@media (min-width:3000px){#menu_toggle:checked~.lpm_menu{display:block!important}#menu_toggle:checked~.lpm_body{display:block!important;width:50%}}.fm1{font-size:calc(1rem + 1.25vw)}.fm2{font-size:calc(.7rem + 1vw)}.fm3{font-size:calc(.8rem + .5vw)}.fmnu1{font-size:calc(1rem + .5vw)}.fmnu2{font-size:calc(.8rem + .5vw)}.login-bkgnd,.login-container{z-index:2;position:absolute}.login-bkgnd{z-index:1;width:100%;height:100%;top:0;left:0;background-image:url(../../img/pcb.png);background-size:cover;background-position:center}.login-input{width:100%}::placeholder{color:#a0a0a0;opacity:1}.btn-primary{background-color:#1a9acc;border-color:#1a9acc}.btn-primary:focus,.btn-primary:hover{background-color:#20c0ff;border-color:#20c0ff}.header-menu{background-color:#1a9acc}.header-link{color:#f0f0f0}.header-link:hover{color:#d0d0d0}.gso{border-style:none;width:10rem;height:2.5rem;background-image:url(../../img/gso.png);background-position:center;background-size:cover}.gso,.gso:hover{background-color:#0000}.gso:focus{background-image:url(../../img/gso_pressed.png)}.border-none{border-style:none}.error{color:#8b0000}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 167 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 295 KiB

View File

@ -3,14 +3,12 @@ package com.lanternsoftware.currentmonitor;
import com.lanternsoftware.dataaccess.currentmonitor.CurrentMonitorDao;
import com.lanternsoftware.dataaccess.currentmonitor.MongoCurrentMonitorDao;
import com.lanternsoftware.datamodel.currentmonitor.Account;
import com.lanternsoftware.util.LanternFiles;
import com.lanternsoftware.util.external.LanternFiles;
import com.lanternsoftware.util.dao.mongo.MongoConfig;
import java.util.Arrays;
public class CreateAccount {
public static void main(String[] args) {
CurrentMonitorDao dao = new MongoCurrentMonitorDao(MongoConfig.fromDisk(LanternFiles.OPS_PATH + "mongo.cfg"));
CurrentMonitorDao dao = new MongoCurrentMonitorDao(MongoConfig.fromDisk(LanternFiles.CONFIG_PATH + "mongo.cfg"));
Account account = new Account();
account.setId(1);
account.setPassword("*redacted*");

View File

@ -1,13 +1,13 @@
package com.lanternsoftware.currentmonitor;
import com.lanternsoftware.util.LanternFiles;
import com.lanternsoftware.util.external.LanternFiles;
import com.lanternsoftware.util.ResourceLoader;
import com.lanternsoftware.util.cryptography.AESTool;
import com.lanternsoftware.util.dao.DaoSerializer;
import com.lanternsoftware.util.dao.auth.AuthCode;
public class CreateAuthCode {
private static final AESTool aes = new AESTool(ResourceLoader.loadFile(LanternFiles.OPS_PATH + "authKey.dat"));
private static final AESTool aes = new AESTool(ResourceLoader.loadFile(LanternFiles.CONFIG_PATH + "authKey.dat"));
public static void main(String[] args) {
System.out.println(aes.encryptToBase64(DaoSerializer.toZipBson(new AuthCode(100, null))));

View File

@ -1,11 +1,11 @@
package com.lanternsoftware.currentmonitor;
import com.lanternsoftware.util.LanternFiles;
import com.lanternsoftware.util.external.LanternFiles;
import com.lanternsoftware.util.ResourceLoader;
import com.lanternsoftware.util.cryptography.AESTool;
public class CreateAuthKey {
public static void main(String[] args) {
ResourceLoader.writeFile(LanternFiles.OPS_PATH + "authKey.dat", AESTool.generateRandomSecretKey().getEncoded());
ResourceLoader.writeFile(LanternFiles.CONFIG_PATH + "authKey.dat", AESTool.generateRandomSecretKey().getEncoded());
}
}

View File

@ -4,26 +4,14 @@ import com.lanternsoftware.dataaccess.currentmonitor.CurrentMonitorDao;
import com.lanternsoftware.dataaccess.currentmonitor.MongoCurrentMonitorDao;
import com.lanternsoftware.datamodel.currentmonitor.Breaker;
import com.lanternsoftware.datamodel.currentmonitor.BreakerConfig;
import com.lanternsoftware.datamodel.currentmonitor.BreakerGroup;
import com.lanternsoftware.datamodel.currentmonitor.BreakerHub;
import com.lanternsoftware.datamodel.currentmonitor.BreakerPanel;
import com.lanternsoftware.datamodel.currentmonitor.BreakerPolarity;
import com.lanternsoftware.datamodel.currentmonitor.Meter;
import com.lanternsoftware.util.CollectionUtils;
import com.lanternsoftware.util.LanternFiles;
import com.lanternsoftware.util.NullUtils;
import com.lanternsoftware.util.ResourceLoader;
import com.lanternsoftware.util.dao.DaoSerializer;
import com.lanternsoftware.util.IFilter;
import com.lanternsoftware.util.external.LanternFiles;
import com.lanternsoftware.util.dao.mongo.MongoConfig;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
public class CreateBreakers {
public static void main(String[] args) {
CurrentMonitorDao dao = new MongoCurrentMonitorDao(MongoConfig.fromDisk(LanternFiles.OPS_PATH + "mongo.cfg"));
CurrentMonitorDao dao = new MongoCurrentMonitorDao(MongoConfig.fromDisk(LanternFiles.CONFIG_PATH + "mongo.cfg"));
/* Breaker bf1 = new Breaker("Solar A", 2, 20, 0, 1, 50, 1.6);
bf1.setPolarity(BreakerPolarity.SOLAR);
@ -212,7 +200,6 @@ public class CreateBreakers {
Map<Integer, Integer> panelToMeter = CollectionUtils.transformToMap(config.getPanels(), BreakerPanel::getIndex, BreakerPanel::getMeter);
CollectionUtils.edit(config.getAllBreakers(), _b->_b.setMeter(DaoSerializer.toInteger(panelToMeter.get(_b.getPanel()))));*/
BreakerConfig config = dao.getConfig(1);
// BreakerConfig config = DaoSerializer.parse(ResourceLoader.loadFileAsString(LanternFiles.OPS_PATH + "breakerconfig_backup_210107.json"), BreakerConfig.class);
// ResourceLoader.writeFile(LanternFiles.OPS_PATH + "breakerconfig_backup_210107.json", DaoSerializer.toJson(config));
// CollectionUtils.edit(config.getAllBreakerGroups(), _g->{
@ -235,6 +222,34 @@ public class CreateBreakers {
// group.setId(String.valueOf(CollectionUtils.getLargest(ids) + 1));
// }
// }
BreakerConfig config = dao.getConfig(100);
/* TimeZone tz = TimeZone.getTimeZone("America/Chicago");
BillingRate summer0 = new BillingRate();
summer0.setMeter(-1);
summer0.setDayBillingCycleStart(13);
summer0.setFlow(GridFlow.BOTH);
summer0.setRate(0.13806);
summer0.setCurrency(BillingCurrency.DOLLAR);
summer0.setBeginEffective(DateUtils.date(5, 16, 2021, tz));
summer0.setEndEffective(DateUtils.date(9, 16, 2021, tz));
summer0.setRecursAnnually(true);
BillingRate winter0 = summer0.duplicate();
winter0.setMonthKWhEnd(1000);
winter0.setRate(0.09703);
winter0.setBeginEffective(DateUtils.date(9, 16, 2021, tz));
winter0.setEndEffective(DateUtils.date(5, 16, 2022, tz));
BillingRate winter1 = winter0.duplicate();
winter1.setMonthKWhStart(1000);
winter1.setMonthKWhEnd(0);
winter1.setRate(0.063);
config.setBillingRates(CollectionUtils.asArrayList(summer0, winter0, winter1));*/
IFilter<Breaker> filter = _b->_b.getName().contains("Water") || _b.getName().contains("Furnace") || _b.getName().contains("Basement HP");
CollectionUtils.filter(config.getAllBreakers(), filter).forEach(_b->_b.setMeter(0));
CollectionUtils.filter(config.getAllBreakers(), _b->!filter.isFiltered(_b)).forEach(_b->_b.setMeter(1));
dao.putConfig(config);
dao.shutdown();
}

View File

@ -1,10 +1,10 @@
package com.lanternsoftware.currentmonitor;
import com.lanternsoftware.util.LanternFiles;
import com.lanternsoftware.util.external.LanternFiles;
import com.lanternsoftware.util.dao.mongo.MongoConfig;
public class CreateMongoConfig {
public static void main(String[] args) {
new MongoConfig("localhost", "*redacted*", "*redacted*", "CURRENT_MONITOR").saveToDisk(LanternFiles.OPS_PATH + "mongo.cfg");
new MongoConfig("lanternsoftware.com", "*redacted*", "*redacted*", "CURRENT_MONITOR").saveToDisk(LanternFiles.CONFIG_PATH + "mongo.cfg");
}
}

View File

@ -1,13 +1,13 @@
package com.lanternsoftware.currentmonitor;
import com.lanternsoftware.util.LanternFiles;
import com.lanternsoftware.util.external.LanternFiles;
import com.lanternsoftware.util.dao.generator.DaoSerializerGenerator;
import com.lanternsoftware.util.dao.generator.SwiftModelGenerator;
public class CurrentMonitorSerializers {
public static void main(String[] args) {
DaoSerializerGenerator.generateSerializers(LanternFiles.SOURCE_PATH + "currentmonitor", true, null);
SwiftModelGenerator.generateModel(LanternFiles.SOURCE_PATH + "currentmonitor", LanternFiles.SOURCE_PATH + "iOS");
DaoSerializerGenerator.generateSerializers(LanternFiles.SOURCE_CODE_PATH + "currentmonitor", true, null);
SwiftModelGenerator.generateModel(LanternFiles.SOURCE_CODE_PATH + "currentmonitor", LanternFiles.SOURCE_CODE_PATH + "iOS");
}
}

View File

@ -2,21 +2,21 @@ package com.lanternsoftware.currentmonitor;
import com.lanternsoftware.dataaccess.currentmonitor.CurrentMonitorDao;
import com.lanternsoftware.dataaccess.currentmonitor.MongoCurrentMonitorDao;
import com.lanternsoftware.datamodel.currentmonitor.BreakerGroupEnergy;
import com.lanternsoftware.datamodel.currentmonitor.BreakerGroupSummary;
import com.lanternsoftware.datamodel.currentmonitor.EnergySummary;
import com.lanternsoftware.datamodel.currentmonitor.EnergyTotal;
import com.lanternsoftware.util.CollectionUtils;
import com.lanternsoftware.util.LanternFiles;
import com.lanternsoftware.util.external.LanternFiles;
import com.lanternsoftware.util.dao.mongo.MongoConfig;
public class MigrateSummaries {
public static void main(String[] args) {
CurrentMonitorDao dao = new MongoCurrentMonitorDao(MongoConfig.fromDisk(LanternFiles.OPS_PATH + "mongo.cfg"));
CurrentMonitorDao dao = new MongoCurrentMonitorDao(MongoConfig.fromDisk(LanternFiles.CONFIG_PATH + "mongo.cfg"));
// TimeZone tz = TimeZone.getTimeZone("America/Chicago");
// List<BreakerGroupEnergy> summaries = dao.getProxy().query(BreakerGroupEnergy.class, null);
// CollectionUtils.edit(summaries, _s->CollectionUtils.edit(_s.getAllGroups(), _t->_t.setAccountId(1)));
// dao.getProxy().save(summaries);
dao.getProxy().save(CollectionUtils.transform(dao.getProxy().queryAll(BreakerGroupEnergy.class), BreakerGroupSummary::new));
dao.getProxy().save(CollectionUtils.transform(dao.getProxy().queryAll(EnergySummary.class), EnergyTotal::new));
// List<BreakerPower> readings = null;
// while ((readings == null) || !readings.isEmpty()) {

View File

@ -2,24 +2,28 @@ package com.lanternsoftware.currentmonitor;
import com.lanternsoftware.dataaccess.currentmonitor.CurrentMonitorDao;
import com.lanternsoftware.dataaccess.currentmonitor.MongoCurrentMonitorDao;
import com.lanternsoftware.datamodel.currentmonitor.Account;
import com.lanternsoftware.util.DateUtils;
import com.lanternsoftware.util.LanternFiles;
import com.lanternsoftware.util.external.LanternFiles;
import com.lanternsoftware.util.dao.mongo.MongoConfig;
import java.util.TimeZone;
public class RebuildSummaries {
public static void main(String[] args) {
CurrentMonitorDao dao = new MongoCurrentMonitorDao(MongoConfig.fromDisk(LanternFiles.OPS_PATH + "mongo.cfg"));
TimeZone tz = TimeZone.getTimeZone("America/Chicago");
dao.rebuildSummaries(100, DateUtils.date(8,3,2021, tz), DateUtils.date(8,5,2021, tz));
/* List<Account> accounts = dao.getProxy().queryAll(Account.class);
for (int accountId : CollectionUtils.transform(accounts, Account::getId)) {
if (accountId != 100)
continue;
dao.rebuildSummaries(accountId, DateUtils.date(4,21,2021, tz), DateUtils.date(4,22,2021, tz));
CurrentMonitorDao dao = new MongoCurrentMonitorDao(MongoConfig.fromDisk(LanternFiles.CONFIG_PATH + "mongo.cfg"));
for (Account a : dao.getProxy().queryAll(Account.class)) {
TimeZone tz = DateUtils.fromTimeZoneId(a.getTimezone());
// BreakerConfig config = dao.getConfig(a.getId());
// BillingPlan plan = new BillingPlan();
// plan.setPlanId(1);
// plan.setBillingDay(1);
// plan.setName("Standard");
// plan.setRates(config.getBillingRates());
// config.setBillingPlans(CollectionUtils.asArrayList(plan));
// dao.putConfig(config);
dao.rebuildSummaries(a.getId(), DateUtils.date(12,18,2021,tz), DateUtils.date(12,19,2021,tz));
}
*/
dao.shutdown();
}
}

View File

@ -15,7 +15,7 @@ import com.lanternsoftware.datamodel.rules.Rule;
import com.lanternsoftware.rules.actions.ActionImpl;
import com.lanternsoftware.util.CollectionUtils;
import com.lanternsoftware.util.DateUtils;
import com.lanternsoftware.util.LanternFiles;
import com.lanternsoftware.util.external.LanternFiles;
import com.lanternsoftware.util.dao.DaoSerializer;
import com.lanternsoftware.util.dao.mongo.MongoConfig;
import org.slf4j.Logger;
@ -54,8 +54,8 @@ public class RulesEngine {
public RulesEngine() {
ServiceLoader.load(ActionImpl.class).forEach(_action->actions.put(_action.getType(), _action));
dao = new MongoRulesDataAccess(MongoConfig.fromDisk(LanternFiles.OPS_PATH + "mongo.cfg"));
cmDao = new MongoCurrentMonitorDao(MongoConfig.fromDisk(LanternFiles.OPS_PATH + "mongo.cfg"));
dao = new MongoRulesDataAccess(MongoConfig.fromDisk(LanternFiles.CONFIG_PATH + "mongo.cfg"));
cmDao = new MongoCurrentMonitorDao(MongoConfig.fromDisk(LanternFiles.CONFIG_PATH + "mongo.cfg"));
timer = new Timer("RulesEngine Timer");
}

View File

@ -10,7 +10,7 @@ import com.lanternsoftware.datamodel.rules.Alert;
import com.lanternsoftware.datamodel.rules.FcmDevice;
import com.lanternsoftware.datamodel.rules.Rule;
import com.lanternsoftware.rules.RulesEngine;
import com.lanternsoftware.util.LanternFiles;
import com.lanternsoftware.util.external.LanternFiles;
import com.lanternsoftware.util.dao.DaoSerializer;
import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
@ -25,7 +25,7 @@ public abstract class AbstractAlertAction implements ActionImpl {
static {
FirebaseMessaging m = null;
try {
FileInputStream is = new FileInputStream(LanternFiles.OPS_PATH + "google_account_key.json");
FileInputStream is = new FileInputStream(LanternFiles.CONFIG_PATH + "google_account_key.json");
FirebaseOptions options = FirebaseOptions.builder().setCredentials(GoogleCredentials.fromStream(is)).build();
m = FirebaseMessaging.getInstance(FirebaseApp.initializeApp(options));
IOUtils.closeQuietly(is);

View File

@ -9,7 +9,7 @@ import com.lanternsoftware.dataaccess.rules.MongoRulesDataAccess;
import com.lanternsoftware.dataaccess.rules.RulesDataAccess;
import com.lanternsoftware.datamodel.rules.Alert;
import com.lanternsoftware.datamodel.rules.FcmDevice;
import com.lanternsoftware.util.LanternFiles;
import com.lanternsoftware.util.external.LanternFiles;
import com.lanternsoftware.util.dao.DaoSerializer;
import com.lanternsoftware.util.dao.mongo.MongoConfig;
import org.apache.commons.io.IOUtils;
@ -18,10 +18,12 @@ import java.io.FileInputStream;
public class TestSendAlert {
public static void main(String[] args) {
RulesDataAccess dao = new MongoRulesDataAccess(MongoConfig.fromDisk(LanternFiles.OPS_PATH + "mongo.cfg"));
RulesDataAccess dao = new MongoRulesDataAccess(MongoConfig.fromDisk(LanternFiles.CONFIG_PATH + "mongo.cfg"));
for (FcmDevice d : dao.getFcmDevicesForAccount(100)) {
if (!d.getName().contains("Sony"))
continue;
Alert alert = new Alert();
alert.setMessage("Test Alert");
alert.setMessage("Garage Door 1 is still open");
Message msg = Message.builder().setToken(d.getToken()).putData("payload", DaoSerializer.toBase64ZipBson(alert)).putData("payloadClass", Alert.class.getCanonicalName()).build();
try {
FileInputStream is = new FileInputStream("d:\\zwave\\firebase\\account_key.json");

View File

@ -607,29 +607,43 @@ public class CollectionUtils {
return ret;
}
public static <T, V extends Comparable<V>> Map<V, T> transformToSortedMap(Collection<T> _coll, ITransformer<? super T, V> _transformer) {
return transformToMap(_coll, _transformer, new TreeMap<>());
}
public static <T, V> Map<V, T> transformToMap(Collection<T> _coll, ITransformer<? super T, V> _transformer) {
Map<V, T> mapValues = new HashMap<>();
return transformToMap(_coll, _transformer, new HashMap<>());
}
public static <T, V> Map<V, T> transformToMap(Collection<T> _coll, ITransformer<? super T, V> _transformer, Map<V, T> _map) {
if ((_coll == null) || (_transformer == null))
return mapValues;
return _map;
for (T t : _coll) {
V v = _transformer.transform(t);
if (v != null)
mapValues.put(v, t);
_map.put(v, t);
}
return mapValues;
return _map;
}
public static <T, V extends Comparable<V>, U> Map<V, U> transformToSortedMap(Collection<T> _coll, ITransformer<? super T, V> _keyTrans, ITransformer<? super T, U> _valTrans) {
return transformToMap(_coll, _keyTrans, _valTrans, new TreeMap<>());
}
public static <T, V, U> Map<V, U> transformToMap(Collection<T> _coll, ITransformer<? super T, V> _keyTrans, ITransformer<? super T, U> _valTrans) {
Map<V, U> mapValues = new HashMap<>();
return transformToMap(_coll, _keyTrans, _valTrans, new HashMap<>());
}
public static <T, V, U> Map<V, U> transformToMap(Collection<T> _coll, ITransformer<? super T, V> _keyTrans, ITransformer<? super T, U> _valTrans, Map<V, U> _map) {
if ((_coll == null) || (_keyTrans == null) || (_valTrans == null))
return mapValues;
return _map;
for (T t : _coll) {
V v = _keyTrans.transform(t);
U u = _valTrans.transform(t);
if ((v != null) && (u != null))
mapValues.put(v, u);
_map.put(v, u);
}
return mapValues;
return _map;
}
public static <T, V> Map<V, List<T>> transformToMultiMap(Collection<T> _coll, ITransformer<? super T, V> _transformer) {

View File

@ -0,0 +1,32 @@
package com.lanternsoftware.util;
import java.util.Date;
public class DateRange {
private Date start;
private Date end;
public DateRange() {
}
public DateRange(Date _start, Date _end) {
start = _start;
end = _end;
}
public Date getStart() {
return start;
}
public void setStart(Date _start) {
start = _start;
}
public Date getEnd() {
return end;
}
public void setEnd(Date _end) {
end = _end;
}
}

Some files were not shown because too many files have changed in this diff Show More