Skip to content

Commit

Permalink
Merge pull request #300 from Security-Onion-Solutions/jertel/ess
Browse files Browse the repository at this point in the history
grid enhancements
  • Loading branch information
jertel authored Dec 8, 2023
2 parents c5518ca + 7a372b2 commit de8b660
Show file tree
Hide file tree
Showing 16 changed files with 1,584 additions and 223 deletions.
13 changes: 13 additions & 0 deletions agent/jobmanager.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@ package agent
import (
"errors"
"io"
"os"
"strconv"
"sync"
"syscall"
"time"

"github.com/apex/log"
Expand Down Expand Up @@ -44,6 +46,7 @@ func NewJobManager(agent *Agent) *JobManager {

func (mgr *JobManager) Start() {
mgr.running = true
mgr.updateOnlineTime("/nsm/pcapout")
for mgr.running {
mgr.updateDataEpoch()
job, err := mgr.PollPendingJobs()
Expand Down Expand Up @@ -116,6 +119,16 @@ func (mgr *JobManager) CleanupJob(job *model.Job) {
}
}

func (mgr *JobManager) updateOnlineTime(src string) {
fi, err := os.Stat(src)
if err != nil {
return
}
stat := fi.Sys().(*syscall.Stat_t)
mgr.node.OnlineTime = time.Unix(int64(stat.Ctim.Sec), int64(stat.Ctim.Nsec))
log.WithField("onlineTime", mgr.node.OnlineTime).Info("Updated online time (node installation time)")
}

func (mgr *JobManager) updateDataEpoch() {
epochHasBeenSet := false
for _, processor := range mgr.jobProcessors {
Expand Down
17 changes: 17 additions & 0 deletions agent/jobmanager_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"errors"
"io"
"net/http"
"os"
"strconv"
"strings"
"testing"
Expand Down Expand Up @@ -99,6 +100,22 @@ func TestUpdateDataEpoch(t *testing.T) {
assert.Equal(t, jm.node.EpochTime, panicProc.GetDataEpoch())
}

func TestOnlineTime(t *testing.T) {
// prep test object
jm := &JobManager{
node: &model.Node{},
}

tmpFile, _ := os.CreateTemp("", "jobmanager_online_time.tmp")

// test
jm.updateOnlineTime(tmpFile.Name())
defer os.Remove(tmpFile.Name())

// verify
assert.GreaterOrEqual(t, jm.node.OnlineTime, time.Now().Add(time.Second*(-2)))
}

type ClientAuthMock struct{}

func (cam *ClientAuthMock) Authorize(*http.Request) error {
Expand Down
4 changes: 3 additions & 1 deletion html/css/app.css
Original file line number Diff line number Diff line change
Expand Up @@ -165,13 +165,15 @@ a#title, a#title:visited, a#title:active, a#title:hover {

.filter.label {
display: inline-block;
padding-right: 10px;
width: 45%;
text-align: right;
white-space: nowrap;
vertical-align: top;
}

.filter.value {
display: inline-block;
width: 55%;
text-align: left;
font-weight: bold;
white-space: nowrap;
Expand Down
246 changes: 224 additions & 22 deletions html/index.html

Large diffs are not rendered by default.

17 changes: 14 additions & 3 deletions html/js/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -508,10 +508,19 @@ $(document).ready(function() {
}
},
formatHours(hours) {
if (!hours) {
hours = 0.0;
return this.formatDecimal2(hours);
},
formatDecimal1(num) {
return this.formatDecimalPlaces(num, 1);
},
formatDecimal2(num) {
return this.formatDecimalPlaces(num, 2);
},
formatDecimalPlaces(num, places) {
if (!num) {
num = 0.0;
}
return hours.toFixed(2);
return num.toFixed(places);
},
formatCount(count) {
return Number(count).toLocaleString();
Expand Down Expand Up @@ -1015,6 +1024,8 @@ $(document).ready(function() {
Vue.filter('formatDateTime', this.formatDateTime);
Vue.filter('formatDuration', this.formatDuration);
Vue.filter('formatHours', this.formatHours);
Vue.filter('formatDecimal1', this.formatDecimal1);
Vue.filter('formatDecimal2', this.formatDecimal2);
Vue.filter('formatCount', this.formatCount);
Vue.filter('formatMarkdown', this.formatMarkdown);
Vue.filter('formatTimestamp', this.formatTimestamp);
Expand Down
19 changes: 19 additions & 0 deletions html/js/app.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,25 @@ test('formatHours', () => {
expect(app.formatHours(10.14)).toBe("10.14");
});

test('formatDecimals', () => {
expect(app.formatDecimal1(null)).toBe("0.0");
expect(app.formatDecimal2(null)).toBe("0.00");
expect(app.formatDecimal1(undefined)).toBe("0.0");
expect(app.formatDecimal2(undefined)).toBe("0.00");
expect(app.formatDecimal1("")).toBe("0.0");
expect(app.formatDecimal2("")).toBe("0.00");
expect(app.formatDecimal1(0)).toBe("0.0");
expect(app.formatDecimal2(0)).toBe("0.00");
expect(app.formatDecimal1(10.1445)).toBe("10.1");
expect(app.formatDecimal2(10.1445)).toBe("10.14");
});

test('formatCount', () => {
expect(app.formatCount(null)).toBe("0");
expect(app.formatCount(123)).toBe("123");
expect(app.formatCount(1234)).toBe("1,234");
});

test('formatStringArray', () => {
expect(app.formatStringArray(['hi','there','foo'])).toBe('hi, there, foo');
expect(app.formatStringArray(['hi','there'])).toBe('hi, there');
Expand Down
57 changes: 53 additions & 4 deletions html/js/i18n.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,13 +46,14 @@ const i18n = {
addObservable: 'Add as new observable...',
addSuccessful: 'Added successfully!',
address: 'Address',
admin: 'Administration',
advanced: 'Temporarily enable advanced interface features',
ago: 'ago',
alertAcknowledge: 'Acknowledge',
alertEscalated: 'This alert has already been escalated',
alertUndoAcknowledge: 'Undo Acknowledge',
alerts: 'Alerts',
all: 'All',
admin: 'Administration',
analyze: 'Analyze',
analyzers: 'Analyzers Processed',
analyzeHelp: 'Enqueues a new analyze job for this observable',
Expand Down Expand Up @@ -110,6 +111,8 @@ const i18n = {
blog: 'Blog',
bytes: 'Bytes',
cancel: 'Cancel',
captureLoss: 'Capture Loss',
captureLossAbbr: 'Cap Loss',
case: 'Case',
cases: 'Cases',
caseAssignee: 'Assignee',
Expand Down Expand Up @@ -205,6 +208,8 @@ const i18n = {
copyFieldToClipboard: 'Copy this value only',
copyFieldValueToClipboard: 'Copy as field:value',
copyToClipboard: 'Copy to clipboard',
cpuUsage: 'CPU Usage',
cpuUsageAbbr: 'CPU',
create: 'Create',
createNewCase: 'Create a new case...',
custom: 'Custom',
Expand All @@ -218,7 +223,7 @@ const i18n = {
dateDataEpoch: 'Earliest PCAP',
dateFailed: 'Date Failed',
dateModified: 'Updated',
dateOnline: 'Online Since:',
dateOnline: 'Date Created',
dateQueued: 'Date Queued',
datePreselectToday: 'Today',
datePreselectYesterday: 'Yesterday',
Expand All @@ -236,7 +241,7 @@ const i18n = {
datePreselect30dToNow: '30 Days ago to Now',
dateTimeFormat: 'lll',
dateUnknown: '',
dateUpdated: 'Date Updated',
dateUpdated: 'Last Heard From',
days: 'days',
defaults: 'Defaults',
delete: 'Delete',
Expand All @@ -248,6 +253,12 @@ const i18n = {
description: 'Description',
details: 'Details',
disconnected: 'Disconnected from manager',
diskUsageElastic: 'Elastic Storage Used',
diskUsageInfluxDb: 'InfluxDB Storage Used',
diskUsageNsm: 'Disk Usage NSM',
diskUsageNsmAbbr: 'NSM',
diskUsageRoot: 'Disk Usage Root',
diskUsageRootAbbr: 'Root',
downloads: 'Downloads',
downloadsFirewallTip: 'When installing the Elastic Agent onto remote systems, be sure to <a href="/#/config?s=firewall.hostgroups.elastic_agent_endpoint">allow network access through the firewall</a>.',
downloadsInfo: 'These <a href="/docs/elastic-agent.html">Elastic Agent</a> installers are customized for this specific <a href="/docs/elastic-fleet.html">Elastic Fleet</a> installation. These files are not signed. If you need signed non-customized Elastic Agent installers, you can get them from <a href="https://www.elastic.co/downloads/elastic-agent">elastic.co</a>.',
Expand Down Expand Up @@ -286,6 +297,7 @@ const i18n = {
eventFetchTook: 'The backend data fetch took ',
eventLookupFailed: 'The event lookup could not be completed.',
eventRoundTripTook: 'The total round trip took ',
eventstoreStatus: 'Elasticsearch Status',
eventTotal: 'Total Found: ',
evidence: 'Observables',
evidenceAdd: 'Add Observable',
Expand Down Expand Up @@ -329,6 +341,7 @@ const i18n = {
fingerprint: 'Fingerprint',
firstName: 'First Name',
flags: 'Flags',
gigabytes: 'GB',
graphs: 'Graphs',
grid: 'Grid',
gridEps: 'Grid EPS:',
Expand Down Expand Up @@ -364,6 +377,9 @@ const i18n = {
gridMemberImportNoChanges: 'A recent import made no changes.',
// note: must replace <[url]> with a link to the results page
gridMemberImportSuccess: 'A recent import has completed and the <a id="view-results" href="<[url]>">results</a> will be available in Dashboards momentarily.',
gridMemberRestartConfirmTitle: 'Reboot Node',
gridMemberRestartConfirmHelp: 'Rebooting a node may be required for various reasons, such as if the node has installed new kernel updates.<p class="my-3"/>The grid may show a fault for several minutes while the node reboots and starts the Security Onion services.<p class="my-3"/>⚠️ Rebooting the manager node will temporarily prevent access to this web application and may show an error similar to <b>502 Bad Gateway</b>. Wait a few minutes and then refresh the browser window to regain access.',
gridMemberRestartSuccess: 'Successfully issued a reboot request to the node.',
groupedBy: 'Group:',
groupByRemove: 'Remove this entire group',
groupByRemoveField: 'Remove this column from the group',
Expand Down Expand Up @@ -407,10 +423,12 @@ const i18n = {
interval10h: "10 hours",
interval24h: "24 hours",
invalid: 'Invalid',
ioWait: 'I/O Wait',
job: 'Job',
jobIncomplete: 'The job was unable to complete and will retry within a few minutes. Details are available below.',
jobInProgress: 'This job is awaiting completion.',
jobs: 'PCAP',
keywords: 'Filter Keywords',
kind: 'Kind',
last: 'Last',
lastName: 'Last Name',
Expand All @@ -426,6 +444,8 @@ const i18n = {
licenseShort: 'ELv2',
licenseStatus: 'Status',
licenseTerms: 'License Terms',
loadAverage: 'Load Average',
loadAverageAbbr: 'Load 1m',
loading: 'Loading, please wait...',
loadMore: 'Load More',
lock: 'Lock User',
Expand All @@ -440,12 +460,17 @@ const i18n = {
logoutFailure: 'Unable to initiate logout. Ensure server is accessible.',
markdownFormattingSupported: 'Markdown formatting supported',
maximize: 'Maximize View (ESC to cancel)',
mbps: 'Mb/s',
md5: 'MD5',
memUsage: 'Memory Usage',
memUsageAbbr: 'Mem',
message: 'Message',
minutes: 'minutes',
model: 'Model',
module: 'Module',
months: 'months',
moreColumns: 'Show additional, sensor-related columns',
moreColumnsHelp: 'This option requires a wide screen and browser window.',
mruCases: 'Recently Viewed Cases',
mruQuery: 'Recently Used',
mruQueryHelp: 'This query is a user-defined query and is only available on this browser.',
Expand Down Expand Up @@ -474,6 +499,7 @@ const i18n = {
notFound: 'The selected item no longer exists',
number: 'Num',
numericOps: 'Numeric Ops',
of: 'of',
oidc: 'Open ID Connect (OIDC)',
oidcHelp: 'Single Sign-On via an external identity provider has been enabled for SOC. Authentication settings, such as password changes, should be performed in the external identity system unless the Security Onion administrators have enabled local password logins concurrently with SSO.',
oidcLinked: 'OIDC Linked',
Expand All @@ -487,6 +513,7 @@ const i18n = {
operation: 'Operation',
options: 'Options',
order: 'Order',
osUptime: 'OS Uptime',
other: 'Other',
owner: 'Owner',
packages: 'Packages',
Expand All @@ -505,6 +532,8 @@ const i18n = {
profileDetails: 'Profile Details',
profileInstructions: 'You may be prompted to login again when updating your profile. This is a security measure to protect your account.',
pcap: 'PCAP',
pcapRetention: 'PCAP Retention',
pcapRetentionAbbr: 'PCAP Avail',
pending: 'Pending',
product: 'Security Onion',
profile: 'Profile',
Expand All @@ -514,6 +543,7 @@ const i18n = {
quickDrilldown: 'Quick Drilldown',
reason: 'Reason',
reconnecting: 'Attempting to connect to manager',
redisQueueSize: 'Redis Queue Size',
refresh: 'Refresh',
refreshAttachmentsHelp: 'Refresh to view all recently added attachments for this case.',
refreshCommentsHelp: 'Refresh to view all recently added comments for this case.',
Expand All @@ -530,6 +560,9 @@ const i18n = {
reset: 'Reset',
resetDefaults: 'Reset Defaults',
resetDefaultsHint: 'Reset all local user SOC settings back to their original default values. This must be done on each browser or device that you have used with SOC.',
restartMinionHelp: 'Reboot this node.',
restartRequired: '(awaiting reboot)',
restartRequiredHelp: 'This node is waiting to be rebooted, typically after a kernel update. Click to reboot.',
results: 'Results',
review: 'Review',
role: 'Role',
Expand Down Expand Up @@ -643,7 +676,12 @@ const i18n = {
startEndNumericErr: 'Start and End values must be numeric.',
startEndOrderErr: 'Start value must come before End value.',
status: 'Status',
stenoLoss: 'Stenographer Loss',
stenoLossAbbr: 'Steno Loss',
summary: 'Summary',
suricataLoss: 'Suricata Loss',
suricataLossAbbr: 'Suri Loss',
swapUsage: 'Swap Usage',
throttledLogin: 'Excessive login requests detected. Login requests can resume momentarily.',
time: 'Time',
timePickerHelp: 'Choose the timespan to search, or click the calendar icon to switch to relative time',
Expand Down Expand Up @@ -680,6 +718,14 @@ const i18n = {
totpDeactivate: 'Deactivate TOTP',
totpSecretInstructions: 'If you are unable to scan the QR code, use the secret provided below instead.',
totpUnlinkInstructions: 'If you no longer have access to your authenticator app, you can deactivate the TOTP.',
trafficMonIn: 'Inbound Monitor Traffic',
trafficMonInAbbr: 'Mon In',
trafficMonInDrops: 'Dropped Monitor Traffic',
trafficMonInDropsAbbr: 'Mon Drops',
trafficManIn: 'Inbound Mgmt Traffic',
trafficManInAbbr: 'Mgmt In',
trafficManOut: 'Outbound Mgmt Traffic',
trafficManOutAbbr: 'Mgmt Out',
transcriptCyberChefHelp: 'Send the transcript to CyberChef',
type: 'Type',
unaccepted: 'Pending',
Expand All @@ -693,10 +739,11 @@ const i18n = {
unwrapHelp: 'Unwrap packets from encapsulation (Ex: VXLAN)',
update: 'Update',
upload: 'Upload',
uploadPcap: 'Upload PCAP/EVTX',
updateProfile: 'Update Profile',
updateSettings: 'Settings',
updateSuccessful: 'Update successful',
uptime: 'Uptime',
uptime: 'Age',
user: 'User Details',
userAdded: 'User added successfully; Users will automatically synchronize across backend apps within 15 minutes.',
userDeleted: 'User deleted successfully; Users will automatically synchronize across backend apps within 15 minutes.',
Expand Down Expand Up @@ -729,6 +776,8 @@ const i18n = {
weeks: 'weeks',
whatsnew: 'What\'s New',
yes: 'Yes',
zeekLoss: 'Zeek Loss',
zeekLossAbbr: 'Zeek Loss',

ERROR_CASE_EVENT_ALREADY_ATTACHED: 'The event is already attached to the selected case.',
ERROR_CASE_MODULE_NOT_ENABLED: 'A case module has not been configured for this installation. Unable to proceed with request.',
Expand Down
Loading

0 comments on commit de8b660

Please sign in to comment.