17 |
--------------------------------------------------------------------------------
/code/server/static/svg/search.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 |
5 | ---
6 |
7 | **Describe the bug**
8 | A clear and concise description of what the bug is.
9 |
10 | **To Reproduce**
11 | Steps to reproduce the behavior:
12 | 1. Go to '...'
13 | 2. Click on '....'
14 | 3. Scroll down to '....'
15 | 4. See error
16 |
17 | **Expected behavior**
18 | A clear and concise description of what you expected to happen.
19 |
20 | **Screenshots**
21 | If applicable, add screenshots to help explain your problem.
22 |
23 | **Desktop (please complete the following information):**
24 | - OS: [e.g. iOS]
25 | - Browser [e.g. chrome, safari]
26 | - Version [e.g. 22]
27 |
28 | **Smartphone (please complete the following information):**
29 | - Device: [e.g. iPhone6]
30 | - OS: [e.g. iOS8.1]
31 | - Browser [e.g. stock browser, safari]
32 | - Version [e.g. 22]
33 |
34 | **Additional context**
35 | Add any other context about the problem here.
36 |
--------------------------------------------------------------------------------
/code/agents/linux/magent:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | # Set variables
4 | user='monitoring'
5 | directory='/opt/monitoring'
6 | pythonver='python3.9'
7 |
8 | # Add service account
9 | #useradd -r -s /bin/false $user
10 | # Delete service account
11 | #userdel monitoring
12 |
13 | # Create program directory
14 | mkdir -p $directory/agent
15 | cp agent.py $directory/agent/agent.py
16 | cp settings.ini $directory/agent/settings.ini
17 | #chown -R monitoring $directory
18 | chmod -R 755 $directory/agent
19 |
20 | # Write systemd service file
21 | cat > /lib/systemd/system/magent.service < Settings
4 | {% endblock %}
5 | {% block body%}
6 |
7 |
8 |
Settings
9 |
10 |
11 |
12 |
13 |
14 |
15 | Monitoring 0.04b
16 | Copyright (C) 2018-2021 Phil White - All Rights Reserved
17 | You may use, distribute and modify this application under the terms of the Apache 2 license. You should have received a
18 | copy of the Apache 2 license with this application. If not, please visit:
19 | https://github.com/philipcwhite
20 |
'
159 |
160 | if EventSettings.mailactive == 1:
161 | msg = EmailMessage()
162 | msg['Subject'] = email_subject
163 | msg['From'] = EventSettings.mailadmin
164 | msg['To'] = notify_email
165 | msg.set_content(email_message, subtype='html')
166 | s = smtplib.SMTP(EventSettings.mailserver)
167 | s.send_message(msg)
168 | s.quit()
169 | f = open(EventSettings.app_path + 'output.txt','a')
170 | f.write(str(time.time()).split('.')[0] + ':' + notify_email + ':' + notify_name + ':' + name + ':' + monitor + ':' + message + ':' + severity + ':' +status + ':' + str(date) + '\n')
171 | f.close()
172 |
173 |
174 |
175 |
176 |
177 | def start_server():
178 | EventConfig.load_config()
179 | while EventSettings.running == True:
180 | a = datetime.datetime.now().second
181 | if a == 0:
182 | EventAvailable.check_available()
183 | EventAvailable.check_open()
184 | ServerEvent.process_events()
185 | ED.remove_agents()
186 | ED.remove_data()
187 | ED.remove_events()
188 | time.sleep(1)
189 |
190 | start_server()
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Monitoring ver. 0.05b
2 | Monitoring Server and Agents written in Python
3 |
4 | ## About
5 | The goal of this project is to make a monitoring tool that provides good functionality and is relatively easy to deploy and use. It has evolved from a Windows based VB.Net project to a its current itteration written in Python to support mulitiple platforms. This repository contains the code for the monitoring web server, collect and event engines, and agents. It is a early beta at this point however most of the functionality is working.
6 |
7 | ### The Monitoring Server
8 | The monitoring server is composed of three services: the website, the collect engine, and the event engine. These services all connect to a MySQL (MariaDB) backend. The website coded in pure Python and runs on a custom server. It's main purpose is for viewing the event data although it provides user, notification policy, and event administration. The collect engine mainly acts as a gateway to translate TCP/SSL data from agents into SQL records. It does some event management as well when it is processing incoming events. The event engine takes care of agent down events and processes notifications. Notifications are by default logged and can be sent to a SMTP server.
9 |
10 | ### The Windows Agent
11 | The Windows agent collects data via WMIC and processes events locally. It stores its data in a SQLite database which allows the agent to maintain state even after reboots and system crashes. Data is transferred via TCP (or TCP/SSL) to the Collect Engine on the Monitoring Server. All data transmissions require a response from the Collect Server to assure data has been transferred. If the Agent does not receive a response it will keep trying to send the data until it succeeds. Data is transferred via TCP over port 8888 (non-SSL by default). Agent configuration and thresholds are maintained locally on the agent. The agent receives no configuration or commands from above. This is designed by default to allow agents to function in a secured environment.
12 |
13 | ### The Linux Agent
14 | The Linux agent works very similarly to the Windows version. Instead of WMIC, it uses basic system calls like free, top, df, etc. I have done basic testing for this agent on both elementary OS (Ubuntu) and Centos. There are a couple of differences in the actual monitors but overall it provides about the same level of funtionality.
15 |
16 | ## Screenshots
17 |
18 | 
19 | Home View
20 |
21 | 
22 | Device View
23 |
24 | 
25 | Graph View
26 |
27 | ## Updates
28 | 8/11/2024 - I'm working on updating the application to use sqlite over MySQL. It should simplify installation considerably.
29 |
30 | 8/16/2021 - No updates today. Just wanted to throw out this idea and remind the internet that I'm still writing code. I have been working with OpenTelemetry for the last year or so and I'm contemplating writing a new application based off this one to process OTEL logs, metrics, and traces.
31 |
32 | 4/12/2021 - Updated code on the collector to reduce the number of insert statements.
33 |
34 | 2/8/2021 - I updated the Linux code handling events. I am still testing this as it may break thresholds that use equals.
35 |
36 | 2/4/2021 - I updated a large part of the Linux agent code. Functionality should be identical but I changed how parameters are stored and cleaned up the network monitoring logic.
37 |
38 | 2/3/2021 - The app is now working with Python 3.9.1.
39 |
40 | 1/25/2021 - I started reimplementing roles in Flask. The only change at this point is a new session entry and a rule block on the users page for non-admin roles.
41 |
42 | 1/4/2021 - I cleaned up some of the agent, collector, and event engine code today.
43 |
44 | 12/31/2020 - I finished my table cleanup on the website. I plan on updating some of the documentation today and calling it a year. Next week I'll start on refreshing the agent and backend code. Have a great new year.
45 |
46 | 12/30/2020 - I cleaned up the work log and finished removing all of the HTML I needed to remove from the model. I still have a handful of pages to clean up tables but it should be done soon. I'm looking forward to updating the agents and backend code.
47 |
48 | 12/30/2020 - Still updating templates. Corrected issues with user and notification add pages. I should be done with the UI updates soon. I'll probably put out a release after I finish the majority of the template work. I also plan on doing some cleanup in the agent and collection code. I know this can be improved.
49 |
50 | 12/24/2020 - Happy Holidays. I updated the events template today to divs and moved the HTML code from the backend to the template. Cleanup is going well. About 1/3 of the way through removing tables and moving code into templates.
51 |
52 | 12/15/2020 - I removed all tables from the index display page. Now it is running with divs/css. I also changed the host list display to iterate through a dict in jinja2. I still have quite a bit of clean up to do but it's coming along.
53 |
54 | 12/7/2020 - I'm updating how I'm displaying the SVG icons as part of the CSS refresh.
55 |
56 | 12/2/2020 - I deployed the monitoring server to a hosted VPS today. I did a full deploy with an agent, collector, event engine, DB, and two websites. Worked like a charm on a 1 CPU, 1 GB Ram server. This was a good test because now I see some mobile browser issues that need some TLC. The Flask UI is also missing role based permissions. These are coming.
57 |
58 | 12/1/2020 - I started updating the appearance of the site. I think it looks a bit more professional. I'll update screenshots when done as they are very dated. I still need to do a lot of cleanup especially with the html templates. I tend to be old school and overuse tables.
59 |
60 | 11/28/2020 - I updated the mserver install file for flask/gunicorn. I'll update directions soon. Thanks.
61 |
62 | 11/27/2020 - I finished converting the website to Flask. There is still a little clean up left to do. I did make one minor change to the database changing the username field in users to user. After I do a little cleanup I'm rewrite the mserver install script and post a download. I know this is more of a stability release rather than a feature set but I think it was worth it. Also I'm debating jumping on the open telemetry project. Not sure if I'll integrate it in or build a seperate app for it. Probably too much work for one dev. :)
63 |
64 | 11/25/2020 - I'm about 80% converted to Flask on my test site. I'm going to start uploading changes. These will break alot of things so do not try to run from source. I'm testing the site in a subdirectory call monitoring and lot of the links will be broken. All of this should be fixed in a few days. The install code will have to be updated as well however it shouldn't be too bad to fix. This conversion has been a ton of work but overall this has given me a chance to fix a lot of bad code. There is still quite a bit in there but I'm slowly improving things. Current db structure remains the same.
65 |
66 | 11/24/2020 - I am in the process of switching the web server over to using Flask. While the current web server works fine, I feel this will improve the security and stability of the platform.
67 |
68 | 10/1/2020 - I made some changes to the structure of the app in Github and corrected a few errors. I am no longer going to code/support the Windows server install. The Linux files for the server component should work in Windows with a few minor changes. I'm making this decision so I have more time to work on the app and less time on porting changes between platforms.
69 |
70 | 8/21/2020 - Lots of code updates today. I'm mostly doing code cleanup. I removed about 40 lines of code and changed some of my string concatenations. If all goes well, release 0.4b should be ready in the next month. I have a few more bug fixes, and then I have to test/fix for Windows.
71 |
72 | 7/31/2020 - I templated the index page and added rounded corners to the rectangle status block.
73 |
74 | 7/17/2020 - Updated the Linux install script. Some changes break running the server on Windows. When I finish updating the code, I'll fix these changes for Windows.
75 |
76 | 7/17/2020 - I'm continuing to clean up code. Currently a few things are broken due to this. I've managed to remove several hundred lines of code and have transistioned even more to templates. I changed the order of how the app starts up to using a preload file (start.py). This allows the variables to be populated prior to the database being instantiated. It's not perfect but it's getting better. I'll probably enhance the template engine at some point so even more HTML can be moved out of the backend code.
77 |
78 | 7/15/2020 - I'm currently doing a huge overhaul of the code. The current source code works for the most part, but will not install with the installer. If you want a working release please download one of the release packages. Trying to package some of my modules and clean up code. I also started work on a template engine so I plan on removing a lot of the HTML from code. So far I've been able to remove about 200 lines of code, mostly on the database side.
79 |
80 | 7/13/2020 - Updated the Linux agent. Removed over 50 lines of code. Changed how SQL queries flow. AgentSQL class now takes care of opening and closing connections (__init__/__del__). I'll try to migrate these to the Windows agent next and then update the server which will be a bit different going to MySQL.
81 |
82 | 5/12/2020 - Still continuing to update code. Made minor changes today.
83 |
84 | 1/23/2020 - Sorry for the lack of updates. I took a new job and moved across the country. I noticed I need to make some updates for Python 3.8 and probably put in a setting to allow the version to be a variable in the installs. I should have updates soon. Thanks
85 |
--------------------------------------------------------------------------------
/code/server/templates/help.html:
--------------------------------------------------------------------------------
1 | {% extends "base.html" %}
2 | {% block breadcrumbs %}
3 | Settings
4 | {% endblock %}
5 | {% block body%}
6 |
7 |
24 | The monitoring application consist of five major components (Agent, Collector, Event Engine, Web Server, and Website). These components
25 | work together to perform basic monitoring. This guide is to help you better understand how the components function.
26 |
27 |
Agent
28 | The monitoring agent collects performance statistics using various standard python and system calls. For Windows it calls wmic and for Linux it uses free, top, uptime, df, etc.
29 |
30 | The short system name (non-domain) is used as the primary key for the agent and every piece of data sent to the collector is tagged with it. The following additional system configurations are collected by the agent.
31 |
51 | The agent records statistics locally to a SQLite database at one minute intervals on the minute. It then sends the data via TCP (optionally SSL) to a collection server (collector). If the collector successfully receives the data it responds back and the agent records the data as sent. All data transmissions are initialized from the agent to the collector.
52 |
53 | The agent also generates events based on defined thresholds. The thresholds are defined in settings.ini and stored in SQLite for processing. Agent events are sent on the minute with performance data. The agent will open, close, escalate, or deescalate events based on priority changes. It will also maintain state and not resend on the same alert if that alert has not changed.
54 |
55 |
Settings
56 | The monitoring agent's configuration (settings.ini) is broken down into three sections: configuration, services (Windows) or processes (Linux), and thresholds.
57 |
58 | The main [configuration] section includes five variables. Ex.
64 | The server variable is the ip address of the collection server that you are sending data to. Port is the port that the collection server is listening on. Secure is to set SSL (TLS 1.2) to True or False (default). This is used when sending data from the agent to the collector. The collector must be set the same as the agent. Passphrase is an additional security level. The collector uses this to verify that the message can be accepted. This setting must be the same on the collector and agent. And log is currently not enabled.
65 |
66 | The [services] section is for Windows services. Services can be added like the example below. The utilize the short service name.
67 | s00 = Spooler
68 | s01 = LanmanServer
69 |
70 | The [processes] section is for Linux processes and works similar to the Windows server section. Processes can be added like the example below. The utilize the short service name.
71 | p00 = systemd
72 | p01 =
73 | The thresholds section is a bit more in-depth. Each threshold defined by TXX has five parts (Ex. perf.filesystem.c.percent.free,4,15,<,900). This represents monitor, severity, threshold, operator, and duration. The monitor is predefined and only set monitors can be used. The severity can be 1 - Critical, 2 - Major, 3 - Warning, 4 - Informational. The threshold depends on the monitor although many are percentages 0-100. The operator defines if you want to trigger for greater than or less than the threshold. For services and processes only equals is allowed. And lastly is the duration (in seconds) to check for when creating an event.
74 |
75 | Although the agent is fixed as to its default monitors, it would be very easy to add additional monitors. As long as the threshold rules are respected, the agent can be modified to collect send data and events on any obtainable performance statistic.
76 |
80 | There are three major server components: the web server, collect engine, and event engine. The web server uses the Flask framework and runs by default as a gunicorn service. The collect engine listens for agent traffic and processes it into the database. And the event engine handles simple agent events and maintenance tasks. While these three components run separately, they all use the same configuration file (settings.ini) and certificates.
81 |
82 |
Settings
83 | Like the agent, all setting for the server components are handled using a settings.ini file. Here is a breakdown of the sections and their variables.
84 | The [certificates] section maintains the name of the certificate and the key file. These are required for making SSL/TLS 1.2 connections for web and agent communication.
85 | key = localhost.pem
86 | name = localhost.crt
87 | The [database] section includes all of the database connection information.
88 | host = localhost
89 | name = monitoring
90 | user = monitoring
91 | password = monitoring
92 | The [server] section includes basic port information and settings to enable secure (SSL/TLS 1.2) connections. For secure connections, secure has to be set to True and the port should be changed to 443. This will set the security for both the web server and the collect engine.
100 | The [mail] section is used by the event engine to handle sending emails.
101 | active = 0
102 | admin = philip.c.white@monitoring
103 | server = localhost
104 | The [events] section is for configuring availability checks for agents. If an agent has not reported in a specified number of seconds, then the event engine will create an event and set its severity based on these settings.
109 | The website is the most user interactive component of the monitoring application and consists of several sections. This guide will
110 | briefly cover each section.
111 |
Main
112 | This section includes the main page when you log in. On the main page there is an overview of of the agents, events, and the
113 | monitoring server.
114 |
Devices
115 | The device pages include a device list and individual pages for each device reporting. On these pages you can drill down
116 | an additional level to see performance graphs.
117 |
Events
118 | The events page displays all open and closed events. Events on these pages can be opened or closed as well.
119 |
Reports
120 | The reporting page includes all available reports (currently None)
121 |
Settings
122 | The settings page contains the user guide along with user and notification management.
123 |
User Management
124 | Users can be added, changed, and removed from the user management tool here. To add a user select
125 | Add User. You are required to fully fill out the form including the username, password, and role. You also have the options
126 | to change a user's password or roles in this section. And lastly you can delete a user. There is a confirmation screen
127 | when deleting a user. If the admin user is deleted, the application will create a new one with the default username admin
128 | and password, password. If you are locked out of the system, you can delete the admin user in MySQL from the user's table
129 | and it will recreate the user when the server is restarted.
130 |
Notification Management
131 | You can control who receives notifications here. While internal events are created on the agent or
132 | through the event engine, this section defines who notifications are sent to. The hostname and monitor textboxes can take
133 | wildcards. To use a wildcard simple type %_%. Email notifications have to also be enabled on the event engine to send.
134 |
135 | To send out all critical events on all systems, you can create a rule with your email address, '%_%' for hostname and monitor,
136 | select status open, severity Critical, and set enabled to True.
137 | top
138 |
139 |
140 |
141 |
142 |
143 | {% endblock %}
--------------------------------------------------------------------------------
/code/server/views.py:
--------------------------------------------------------------------------------
1 | from flask import Flask, render_template, request, redirect, session, url_for, make_response
2 | #from app import app
3 | #from app.model import Auth, Code, Data
4 | from app import app
5 | from model import Auth, Code, Data
6 |
7 |
8 |
9 | # Decorators
10 | def authenticate(func):
11 | def wrapper(*args, **kwargs):
12 | if session.get('auth') is None:
13 | return redirect('login')
14 | else:
15 | return func(*args, **kwargs)
16 | wrapper.__name__ = func.__name__
17 | return wrapper
18 |
19 | class Web:
20 | @app.route('/favicon.ico')
21 | def favicon():
22 | return app.send_static_file('favicon.ico')
23 |
24 | @app.route('/login', methods=['GET', 'POST'])
25 | def login():
26 | if request.method == 'GET':
27 | return render_template('login.html')
28 | if request.method == 'POST':
29 | user = request.form['user']
30 | password = request.form['password']
31 | A = Auth()
32 | auth = A.verify(user,password)
33 | if auth[0] == user:
34 | session['user'] = user
35 | session['auth'] = True
36 | session['role'] = auth[1]
37 | return redirect(url_for('index'))
38 |
39 | @app.route('/logoff')
40 | def logoff():
41 | session['auth'] = None
42 | session['user'] = None
43 | session['role'] = None
44 | return redirect(url_for('login'))
45 |
46 | @app.route('/')
47 | @authenticate
48 | def index():
49 | if 'page' in request.args:
50 | page = request.args.get('page')
51 | content = f'index_content?page={page}'
52 | else:
53 | content = 'index_content'
54 | return render_template('index.html', index_content = content)
55 |
56 | @app.route('/index_content')
57 | @authenticate
58 | def index_content():
59 | page = 1
60 | if 'page' in request.args:
61 | page = request.args.get('page')
62 | C = Code()
63 | content = C.index(page)
64 | return render_template('index_content.html', **content)
65 |
66 | @app.route('/password', methods=['GET', 'POST'])
67 | @authenticate
68 | def password():
69 | user = session.get('user')
70 | if request.method == 'GET':
71 | return render_template('password.html', user=user)
72 | if request.method == 'POST':
73 | pass1 = request.form['pass1']
74 | pass2 = request.form['pass2']
75 | A = Auth()
76 | A.user_password_change(user, pass1, pass2)
77 | return redirect(url_for('index'))
78 |
79 | @app.route('/search')
80 | @authenticate
81 | def search():
82 | device = None
83 | if 'device' in request.args:
84 | device = request.args.get('device')
85 | D = Data()
86 | devices = D.search_devices(device)
87 | return render_template('search.html', device = device, devices = devices)
88 |
89 | @app.route('/events')
90 | @authenticate
91 | def events():
92 | if 'status' in request.args:
93 | status = request.args.get('status')
94 | content = f'events_content?status={status}'
95 | else:
96 | content = 'events_content'
97 | return render_template('events.html', events_content = content)
98 |
99 | @app.route('/events_content')
100 | @authenticate
101 | def events_content():
102 | status = '1'
103 | if 'status' in request.args:
104 | status = request.args.get('status')
105 | C = Code()
106 | content = C.events(status)
107 | return render_template('events_content.html', **content)
108 |
109 | @app.route('/event_change//')
110 | @authenticate
111 | def event_change(id, status):
112 | D = Data()
113 | D.event_change_status(id,status)
114 | return redirect(url_for('events'))
115 |
116 | @app.route('/devices')
117 | @authenticate
118 | def devices():
119 | return render_template('devices.html')
120 |
121 | @app.route('/devices_content')
122 | @authenticate
123 | def devices_content():
124 | C = Code()
125 | device_list = C.devices()
126 | return render_template('devices_content.html', device_list=device_list)
127 |
128 | @app.route('/device/')
129 | @authenticate
130 | def device(name):
131 | return render_template('device.html', name = name, device_content='../device_content/' + name)
132 |
133 | @app.route('/device_content/')
134 | @authenticate
135 | def device_content(name):
136 | C = Code()
137 | device, filesystem = C.device(name)
138 | return render_template('device_content.html', name = name, **device, filesystem = filesystem)
139 |
140 | @app.route('/graph//')
141 | @authenticate
142 | def graph(name, monitor):
143 | return render_template('graph.html', name = name, monitor = monitor, graph_content = '/graph_content/' + name + '/' + monitor)
144 |
145 | @app.route('/graph_content//')
146 | @authenticate
147 | def graph_content(name, monitor):
148 | C = Code()
149 | graph = C.device_graph(name, monitor)
150 | return render_template('graph_content.html', graph = graph)
151 |
152 | @app.route('/reports')
153 | @authenticate
154 | def reports():
155 | return render_template('reports.html')
156 |
157 | @app.route('/reports/')
158 | @authenticate
159 | def report(report):
160 | name = report.split('.')[0]
161 | ext = report.split('.')[1]
162 | C = Code()
163 | output = C.report(name, ext)
164 | if ext == 'html':
165 | return render_template('report.html', output = output)
166 | if ext == 'csv':
167 | output_csv = make_response(output) #, 'unicode')
168 | output_csv.headers["Content-Disposition"] = "attachment; filename=export.csv"
169 | output_csv.headers["Content-type"] = "text/csv"
170 | return output_csv
171 |
172 | @app.route('/settings')
173 | @authenticate
174 | def settings():
175 | return render_template('settings.html')
176 |
177 | @app.route('/help')
178 | @authenticate
179 | def help():
180 | return render_template('help.html')
181 |
182 | @app.route('/about')
183 | @authenticate
184 | def about():
185 | return render_template('about.html')
186 |
187 | @app.route('/notify')
188 | @authenticate
189 | def notify():
190 | D = Data()
191 | notify = D.notify_rules()
192 | return render_template('notify.html', user = user, notify = notify)
193 |
194 | @app.route('/notify_add', methods=['GET', 'POST'])
195 | @authenticate
196 | def notify_add():
197 | if request.method == 'GET':
198 | D = Data()
199 | device_names = D.notify_device_names()
200 | return render_template('notify_add.html', device_names = device_names)
201 | if request.method == 'POST':
202 | notify_name = request.form['notify_name']
203 | notify_email = request.form['notify_email']
204 | agent_name = request.form['agent_name']
205 | agent_monitor = request.form['agent_monitor']
206 | agent_status = request.form['agent_status']
207 | agent_severity = request.form['agent_severity']
208 | notify_enabled = request.form['notify_enabled']
209 | D = Data()
210 | D.notify_add(notify_name, notify_email, agent_name, agent_monitor, agent_status, agent_severity, notify_enabled)
211 | return redirect(url_for('notify'))
212 |
213 | @app.route('/notify_edit/', methods=['GET', 'POST'])
214 | @authenticate
215 | def notify_edit(id):
216 | if request.method == 'GET':
217 | D = Data()
218 | device_names = D.notify_device_names()
219 | notify_rule = D.notify_rule(id)
220 | return render_template('notify_edit.html', device_names = device_names, **notify_rule)
221 | if request.method == 'POST':
222 | notify_name = request.form['notify_name']
223 | notify_email = request.form['notify_email']
224 | agent_name = request.form['agent_name']
225 | agent_monitor = request.form['agent_monitor']
226 | agent_status = request.form['agent_status']
227 | agent_severity = request.form['agent_severity']
228 | notify_enabled = request.form['notify_enabled']
229 | D = Data()
230 | D.notify_edit(notify_name, notify_email, agent_name, agent_monitor, agent_status, agent_severity, notify_enabled)
231 | return redirect(url_for('notify'))
232 |
233 | @app.route('/notify_delete/')
234 | @authenticate
235 | def notify_delete(id):
236 | D = Data()
237 | D.notify_delete(id)
238 | return redirect(url_for('notify.html'))
239 |
240 | @app.route('/users')
241 | @authenticate
242 | def users():
243 | role = session.get('role')
244 | if role == 1:
245 | D = Data()
246 | users = D.users_select()
247 | return render_template('users.html', users = users)
248 | else:
249 | return redirect(url_for('settings'))
250 |
251 | @app.route('/user_add', methods=['GET', 'POST'])
252 | @authenticate
253 | def user_add():
254 | if request.method == 'GET':
255 | return render_template('user_add.html')
256 | if request.method == 'POST':
257 | user = request.form['user']
258 | password = request.form['password']
259 | role = request.form['role']
260 | A = Auth()
261 | A.user_add(user, password, role)
262 | return redirect(url_for('users'))
263 |
264 | @app.route('/user_pass/', methods=['GET', 'POST'])
265 | @authenticate
266 | def user_pass(user):
267 | if request.method == 'GET':
268 | return render_template('user_pass.html')
269 | if request.method == 'POST':
270 | user = request.form['user']
271 | pass1 = request.form['pass1']
272 | pass2 = request.form['pass2']
273 | A = Auth()
274 | A.user_password_change_admin(user, pass1, pass2)
275 | return redirect(url_for('users'))
276 |
277 | @app.route('/user_role/', methods=['GET', 'POST'])
278 | @authenticate
279 | def user_role(id):
280 | if request.method == 'GET':
281 | D = Data()
282 | content = D.user_select(id)
283 | return render_template('user_role.html', user=content[1], role=content[2])
284 | if request.method == 'POST':
285 | id = request.form['id']
286 | role = request.form['role']
287 | D = Data()
288 | D.user_edit_role(id, role)
289 | return redirect(url_for('users'))
290 |
291 | @app.route('/user_delete/')
292 | @authenticate
293 | def user_delete(id):
294 | D = Data()
295 | D.user_delete(id)
296 | return redirect(url_for('users'))
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright [yyyy] [name of copyright owner]
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/code/agents/linux/agent.py:
--------------------------------------------------------------------------------
1 | # Copyright (C) 2018-2021 Phil White - All Rights Reserved
2 | #
3 | # You may use, distribute and modify this code under the terms of the Apache 2 license. You should have received a
4 | # copy of the Apache 2 license with this file. If not, please visit: https://github.com/philipcwhite/monitoring
5 |
6 | import configparser, datetime, json, os, platform, socket, sqlite3, ssl, subprocess, time
7 |
8 | session = {}
9 | session['running'] = True
10 | session['path'] = './' #'/opt/monitoring/agent/'
11 | session['passphrase'] = 'secure_monitoring'
12 | session['server'] = '127.0.0.1'
13 | session['port'] = 8888
14 |
15 | class Data():
16 | def __init__(self):
17 | self.con = sqlite3.connect(session['path'] + 'agent_sqlite.db', isolation_level=None)
18 | self.cursor = self.con.cursor()
19 |
20 | def __del__(self):
21 | self.con.close()
22 |
23 | def create_tables(self):
24 | sql = "CREATE TABLE IF NOT EXISTS AgentData (time integer,name text,monitor text,value integer,sent integer);"
25 | sql += "CREATE TABLE IF NOT EXISTS AgentEvents (time integer,name text,monitor text,message text,status integer,severity integer, sent integer);"
26 | sql += "CREATE TABLE IF NOT EXISTS AgentSystem (time integer, name text, ipaddress text, platform text, build text, architecture text, domain text, processors integer, memory integer);"
27 | sql += "CREATE TABLE IF NOT EXISTS AgentThresholds (monitor text,severity integer,threshold integer, compare text,duration integer);"
28 | self.cursor.executescript(sql)
29 | self.con.commit()
30 |
31 | def delete_data_events(self):
32 | agent_time = str(time.time()-604800).split('.')[0]
33 | sql = "DELETE FROM AgentData WHERE time;"
34 | self.cursor.execute(sql, [agent_time])
35 | sql = "DELETE FROM AgentEvents WHERE status=0 AND sent=1;"
36 | self.cursor.execute(sql)
37 | self.con.commit()
38 |
39 | def delete_thresholds(self):
40 | sql = "DELETE FROM AgentThresholds;"
41 | self.cursor.execute(sql)
42 | self.con.commit()
43 |
44 | def insert_data(self, monitor, value):
45 | sql = "INSERT INTO AgentData(time, name, monitor, value, sent) VALUES (?,?,?,?,?);"
46 | self.cursor.execute(sql,(session['time'], session['name'], monitor, value, 0))
47 | self.con.commit()
48 |
49 | def insert_event(self, monitor, message, severity):
50 | sql = "UPDATE AgentEvents SET time=?, message=?, severity=?, sent=0 WHERE monitor=? AND ?> (SELECT MAX(severity) FROM AgentEvents WHERE monitor=? AND status=1);"
51 | self.cursor.execute(sql, (session['time'], message, severity, monitor, severity, monitor))
52 | sql = "INSERT INTO AgentEvents(time, name, monitor, message, status, severity, sent) SELECT ?,?,?,?,1,?,0 WHERE NOT EXISTS(SELECT 1 FROM AgentEvents WHERE monitor=? AND status=1);"
53 | self.cursor.execute(sql, (session['time'], session['name'], monitor, message, severity, monitor))
54 | self.con.commit()
55 |
56 | def insert_system(self, ipaddress, os, build, architecture, domain, processors, memory):
57 | sql = "UPDATE AgentSystem SET time=?, name=?, ipaddress=?, platform=?, architecture=?, domain=?, processors=?, memory=? WHERE name=?;"
58 | self.cursor.execute(sql, (session['time'], session['name'], ipaddress, os, architecture, domain, processors, memory, session['name']))
59 | sql = "INSERT INTO AgentSystem(time, name, ipaddress, platform, build, architecture, domain, processors, memory) SELECT ?,?,?,?,?,?,?,?,? WHERE NOT EXISTS(SELECT 1 FROM AgentSystem WHERE name=?);"
60 | self.cursor.execute(sql, (session['time'], session['name'], ipaddress, os, build, architecture, domain, processors, memory, session['name']))
61 | self.con.commit()
62 |
63 | def insert_thresholds(self, monitor, severity, threshold, compare, duration):
64 | sql = "INSERT INTO AgentThresholds(monitor, severity, threshold, compare, duration) VALUES(?,?,?,?,?);"
65 | self.cursor.execute(sql, (monitor, severity, threshold, compare, duration))
66 | self.con.commit()
67 |
68 | def select_data(self):
69 | sql = "SELECT time, name, monitor, value FROM AgentData WHERE sent=0 AND monitor NOT LIKE '%perf.service%'"
70 | self.cursor.execute(sql)
71 | rows = self.cursor.fetchall()
72 | return rows
73 |
74 | def select_data_events(self, time, monitor):
75 | sql = "SELECT value FROM AgentData WHERE monitor=? AND time > ?;"
76 | self.cursor.execute(sql, (monitor, str(time)))
77 | rows = self.cursor.fetchall()
78 | return rows
79 |
80 | def select_event(self, monitor):
81 | sql = "SELECT monitor FROM AgentEvents WHERE monitor=? AND status=1;"
82 | self.cursor.execute(sql, [monitor])
83 | row = self.cursor.fetchone()
84 | return row
85 |
86 | def select_events(self):
87 | sql = "SELECT time, name, monitor, message, status, severity FROM AgentEvents WHERE sent=0"
88 | self.cursor.execute(sql)
89 | rows = self.cursor.fetchall()
90 | return rows
91 |
92 | def select_system(self):
93 | sql = "SELECT time, name, ipaddress, platform, build, architecture, domain, processors, memory FROM AgentSystem"
94 | self.cursor.execute(sql)
95 | row = self.cursor.fetchone()
96 | return row
97 |
98 | def select_thresholds(self):
99 | sql = "SELECT monitor, severity, threshold, compare, duration FROM AgentThresholds"
100 | self.cursor.execute(sql)
101 | rows = self.cursor.fetchall()
102 | return rows
103 |
104 | def update_close_data_events(self):
105 | sql = "UPDATE AgentData SET sent=1 WHERE sent=0; UPDATE AgentEvents SET sent=1 WHERE sent=0;"
106 | self.cursor.executescript(sql)
107 | self.con.commit()
108 |
109 | def update_event(self, monitor, severity):
110 | sql = "UPDATE AgentEvents SET status=0, sent=0 WHERE monitor=? AND severity=?;"
111 | self.cursor.execute(sql, (monitor, str(severity)))
112 | self.con.commit()
113 |
114 | # Initialize Data Class
115 | SQL = Data()
116 |
117 | class AgentLinux():
118 | def conf_system():
119 | memory = subprocess.run('free -m', shell=True, capture_output=True, text=True).stdout.split('\n')[1].split()[1:]
120 | memory = str(memory[0])
121 | build_name = subprocess.run('cat /etc/os-release|grep -oP "(?<=^NAME=).*"', shell=True, capture_output=True, text=True).stdout.replace('"','').replace('\n','')
122 | build_version = subprocess.run('cat /etc/os-release|grep -oP "(?<=^VERSION_ID=).*"', shell=True, capture_output=True, text=True).stdout.replace('"','').replace('\n','')
123 | osplatform = platform.system()
124 | architecture = platform.architecture()[0]
125 | build = build_name + ' ' + build_version
126 | ipaddress= socket.gethostbyname(socket.gethostname())
127 | processors = str(os.cpu_count())
128 | domain = socket.getfqdn()
129 | if '.' in domain: domain = domain.split('.', 1)[1]
130 | else: domain = 'Stand Alone'
131 | SQL.insert_system(ipaddress, osplatform, build, architecture, domain, processors, memory)
132 |
133 | def perf_filesystem():
134 | output = subprocess.run('df -x tmpfs -x devtmpfs | tail -n +2', shell=True, capture_output=True, text=True).stdout.split('\n')
135 | for i in output:
136 | if '%' in i:
137 | fs = i.split()
138 | fs = i.split()
139 | fs_name = f'perf.filesystem.{fs[0]}.percent.used'
140 | fs_name = fs_name.replace('/','.').replace('..','.')
141 | fs_used = str(fs[4].replace('%',''))
142 | SQL.insert_data(fs_name, fs_used)
143 |
144 | def perf_memory():
145 | output = subprocess.run('free -m', shell=True, capture_output=True, text=True).stdout.split('\n')[1].split()[1:]
146 | memory_used = round((((float(output[0])-float(output[5]))/float(output[0])))*100,0)
147 | SQL.insert_data('perf.memory.percent.used', str(memory_used))
148 |
149 | def perf_network():
150 | br = bs = br_min = bs_min= 0
151 | output = subprocess.run('cat /proc/net/dev | tail -n +3', shell=True, capture_output=True, text=True).stdout.split('\n')
152 | for i in output:
153 | if ':' in i and not 'lo:' in i:
154 | net = i.split()
155 | br += int(net[1])
156 | bs += int(net[9])
157 | if 'bytes_received' in session:
158 | br_min = round((br - session['bytes_received'])/60, 0)
159 | bs_min = round((bs - session['bytes_sent'])/60, 0)
160 | session['bytes_received'] = br
161 | session['bytes_sent'] = bs
162 | SQL.insert_data('perf.network.bytes.received', str(br_min))
163 | SQL.insert_data('perf.network.bytes.sent', str(bs_min))
164 |
165 | def perf_pagefile():
166 | output = subprocess.run('free -m', shell=True, capture_output=True, text=True).stdout.split('\n')[2].split()[1:]
167 | swap_used = round((float(output[1])/float(output[0]))*100,0)
168 | SQL.insert_data('perf.pagefile.percent.used', str(swap_used))
169 |
170 | def perf_processes():
171 | if session['processes']:
172 | for i in session['processes']:
173 | output = subprocess.run('ps -C ' + i + ' >/dev/null && echo 1 || echo 0', shell=True, capture_output=True, text=True).stdout.replace('\n','')
174 | sname = 'perf.process.' + i.replace(' ','').lower() + '.state'
175 | SQL.insert_data(sname, str(output))
176 |
177 | def perf_processor():
178 | output = subprocess.run('top -b -n2 -p1 -d.1| grep -oP "(?<=ni, ).[0-9]*.[0-9]" | tail -1', shell=True, capture_output=True, text=True).stdout
179 | cpu_avg = round(100 - float(output.replace('\n','')),0)
180 | SQL.insert_data('perf.processor.percent.used', str(cpu_avg))
181 |
182 | def perf_uptime():
183 | output = subprocess.run('cat /proc/uptime', shell=True, capture_output=True, text=True).stdout.split()[0]
184 | uptime = int(round(float(output),0))
185 | SQL.insert_data('perf.system.uptime.seconds', str(uptime))
186 |
187 | class AgentProcess():
188 | def initialize_agent():
189 | try:
190 | SQL.create_tables()
191 | SQL.delete_thresholds()
192 | parser = configparser.ConfigParser()
193 | parser.read(session['path'] + 'settings.ini')
194 | config = dict(parser.items('configuration'))
195 | thresholds = list(dict(parser.items('thresholds')).values())
196 | for i in thresholds:
197 | thresh = i.split(',')
198 | SQL.insert_thresholds(thresh[0], thresh[1], thresh[2], thresh[3], thresh[4])
199 | session['name'] = socket.gethostname().lower()
200 | session['server'] = config['server']
201 | session['passphrase'] = config['passphrase']
202 | session['port'] = int(config['port'])
203 | session['secure'] = eval(config['secure'])
204 | session['log'] = eval(config['log'])
205 | session['processes'] = list(dict(parser.items('processes')).values())
206 | except: pass
207 |
208 | def data_process():
209 | try:
210 | AgentLinux.conf_system()
211 | AgentLinux.perf_filesystem()
212 | AgentLinux.perf_memory()
213 | AgentLinux.perf_network()
214 | AgentLinux.perf_pagefile()
215 | AgentLinux.perf_processor()
216 | AgentLinux.perf_uptime()
217 | AgentLinux.perf_processes()
218 | except: pass
219 |
220 | def event_create(monitor, severity, threshold, compare, duration, status):
221 | message = monitor.replace('perf.', '').replace('.', ' ').capitalize()
222 | message = message + ' ' + compare + ' ' + str(threshold) + ' for ' + str(round(duration/60)) + ' minutes'
223 | check_monitor = SQL.select_event(monitor)
224 | if not check_monitor is None: check_monitor=check_monitor[0]
225 | if check_monitor is None and status == 1: SQL.insert_event(monitor, message, severity)
226 | elif check_monitor == monitor and status == 0: SQL.update_event(monitor, severity)
227 | else: pass
228 |
229 | def event_process():
230 | agent_time_int = int(session['time'])
231 | agent_thresholds = SQL.select_thresholds()
232 | status = 0
233 | for i in agent_thresholds:
234 | monitor = i[0]
235 | severity = i[1]
236 | threshold = int(i[2])
237 | compare = i[3]
238 | duration = i[4]
239 | time_window = agent_time_int - duration
240 | agent_data = SQL.select_data_events(time_window, monitor)
241 | a_val = b_val = 0
242 | for i in agent_data:
243 | value = i[0]
244 | if eval(str(value) + compare + str(threshold)) is True:
245 | a_val += 1
246 | b_val += 1
247 | else: b_val += 1
248 | if a_val == b_val and b_val != 0: status = 1
249 | AgentProcess.event_create(monitor, severity, threshold, compare, duration, status)
250 |
251 | def create_packet():
252 | system = SQL.select_system()
253 | events = SQL.select_events()
254 | data = SQL.select_data()
255 | agent_data = []
256 | agent_events = []
257 | for i in events: agent_events.append({"time":i[0],"monitor":i[2],"message":i[3],"status":i[4],"severity":i[5]})
258 | for i in data: agent_data.append({"time":i[0],i[2]:i[3]})
259 | packet = {"time": system[0],
260 | "name": system[1],
261 | "ip": system[2],
262 | "platform": system[3],
263 | "build": system[4],
264 | "arch": system[5],
265 | "domain": system[6],
266 | "procs": system[7],
267 | "memory": system[8],
268 | "passphrase": session['passphrase'],
269 | "data": agent_data,
270 | "events": agent_events}
271 | packet = json.dumps(packet)
272 | return packet
273 |
274 | def send_data(message):
275 | sock = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
276 | try:
277 | if session['secure'] == 1:
278 | context = ssl.create_default_context()
279 | context.options |= ssl.PROTOCOL_TLSv1_2
280 | context.check_hostname = False
281 | context.verify_mode = ssl.CERT_NONE
282 | conn = context.wrap_socket(sock, server_hostname = session['server'])
283 | conn.connect((session['server'], session['port']))
284 | messagebytes=str(message).encode()
285 | conn.sendall(messagebytes)
286 | data = conn.recv(1024).decode()
287 | if data == 'Received': SQL.update_close_data_events()
288 | conn.close()
289 | else:
290 | sock.connect((session['server'], session['port']))
291 | messagebytes=str(message).encode()
292 | sock.sendall(messagebytes)
293 | data = sock.recv(1024).decode()
294 | if data == 'Received': SQL.update_close_data_events()
295 | sock.close()
296 | except: pass
297 |
298 | def run_process():
299 | while session['running'] == True:
300 | a = datetime.datetime.now().second
301 | if a == 0:
302 | session['time'] = str(time.time()).split('.')[0]
303 | AgentProcess.data_process()
304 | AgentProcess.event_process()
305 | AgentProcess.send_data(AgentProcess.create_packet())
306 | SQL.delete_data_events()
307 | time.sleep(1)
308 |
309 | AgentProcess.initialize_agent()
310 | AgentProcess.run_process()
311 |
--------------------------------------------------------------------------------
/code/agents/windows/agent.py:
--------------------------------------------------------------------------------
1 | # Copyright (C) 2018-2019 Phil White - All Rights Reserved
2 | #
3 | # You may use, distribute and modify this code under the terms of the Apache 2 license. You should have received a
4 | # copy of the Apache 2 license with this file. If not, please visit: https://github.com/philipcwhite/monitoring
5 |
6 | import configparser, datetime, json, os, platform, re, socket, sqlite3, ssl, subprocess, time
7 |
8 | class AgentSettings:
9 | log = False
10 | name = None
11 | passphrase = 'secure_monitoring'
12 | path = 'C:\\Progra~1\\monitoring\\agent\\'
13 | port = 8888
14 | running = True
15 | secure = False
16 | server = '127.0.0.1'
17 | services = []
18 | time = None
19 |
20 | class AgentSQL():
21 | def __init__(self):
22 | self.con = sqlite3.connect(AgentSettings.path + 'agent_sqlite.db', isolation_level=None)
23 | self.cursor = self.con.cursor()
24 |
25 | def __del__(self):
26 | self.con.close()
27 |
28 | def create_tables(self):
29 | sql = "CREATE TABLE IF NOT EXISTS AgentData (time integer,name text,monitor text,value integer,sent integer);"
30 | sql += "CREATE TABLE IF NOT EXISTS AgentEvents (time integer,name text,monitor text,message text,status integer,severity integer, sent integer);"
31 | sql += "CREATE TABLE IF NOT EXISTS AgentSystem (time integer, name text, ipaddress text, platform text, build text, architecture text, domain text, processors integer, memory integer);"
32 | sql += "CREATE TABLE IF NOT EXISTS AgentThresholds (monitor text,severity integer,threshold integer, compare text,duration integer);"
33 | self.cursor.executescript(sql)
34 | self.con.commit()
35 |
36 | def delete_data_events(self):
37 | agent_time = str(time.time()-604800).split('.')[0]
38 | sql = "DELETE FROM AgentData WHERE time<" + agent_time + ';'
39 | sql += "DELETE FROM AgentEvents WHERE status=0 AND sent=1;"
40 | self.cursor.executescript(sql)
41 | self.con.commit()
42 |
43 | def delete_thresholds(self):
44 | sql = "DELETE FROM AgentThresholds;"
45 | self.cursor.execute(sql)
46 | self.con.commit()
47 |
48 | def insert_data(self, monitor, value):
49 | sql = "INSERT INTO AgentData(time, name, monitor, value, sent) VALUES (?,?,?,?,?);"
50 | self.cursor.execute(sql,(AgentSettings.time, AgentSettings.name, monitor, value, 0))
51 | self.con.commit()
52 |
53 | def insert_event(self, monitor, message, severity):
54 | sql_update = "UPDATE AgentEvents SET time=?, message=?, severity=?, sent=0 WHERE monitor=? AND ?> (SELECT MAX(severity) FROM AgentEvents WHERE monitor=? AND status=1);"
55 | self.cursor.execute(sql_update, (AgentSettings.time, message, severity, monitor, severity, monitor))
56 | sql_insert = "INSERT INTO AgentEvents(time, name, monitor, message, status, severity, sent) "
57 | sql_insert += "SELECT ?,?,?,?,1,?,0 WHERE NOT EXISTS(SELECT 1 FROM AgentEvents WHERE monitor=? AND status=1);"
58 | self.cursor.execute(sql_insert, (AgentSettings.time, AgentSettings.name, monitor, message, severity, monitor))
59 | self.con.commit()
60 |
61 | def insert_system(self, ipaddress, os, build, architecture, domain, processors, memory):
62 | sql_update = "UPDATE AgentSystem SET time=?, name=?, ipaddress=?, platform=?, architecture=?, domain=?, processors=?, memory=? WHERE name=?;"
63 | self.cursor.execute(sql_update, (AgentSettings.time, AgentSettings.name, ipaddress, os, architecture, domain, processors, memory, AgentSettings.name))
64 | sql_insert = "INSERT INTO AgentSystem(time, name, ipaddress, platform, build, architecture, domain, processors, memory) "
65 | sql_insert += "SELECT ?,?,?,?,?,?,?,?,? WHERE NOT EXISTS(SELECT 1 FROM AgentSystem WHERE name=?);"
66 | self.cursor.execute(sql_insert, (AgentSettings.time, AgentSettings.name, ipaddress, os, build, architecture, domain, processors, memory, AgentSettings.name))
67 | self.con.commit()
68 |
69 | def insert_thresholds(self, monitor, severity, threshold, compare, duration):
70 | sql = "INSERT INTO AgentThresholds(monitor, severity, threshold, compare, duration) VALUES(?,?,?,?,?);"
71 | self.cursor.execute(sql, (monitor, severity, threshold, compare, duration))
72 | self.con.commit()
73 |
74 | def select_data(self):
75 | sql = "SELECT time, name, monitor, value FROM AgentData WHERE sent=0 AND monitor NOT LIKE '%perf.service%'"
76 | self.cursor.execute(sql)
77 | rows = self.cursor.fetchall()
78 | return rows
79 |
80 | def select_data_events(self, time, monitor):
81 | sql = "SELECT value FROM AgentData WHERE monitor='" + monitor + "' AND time > " + str(time) + ";"
82 | self.cursor.execute(sql)
83 | rows = self.cursor.fetchall()
84 | return rows
85 |
86 | def select_event(self, monitor):
87 | sql = "SELECT monitor FROM AgentEvents WHERE monitor='" + monitor + "' AND status=1;"
88 | self.cursor.execute(sql)
89 | row = self.cursor.fetchone()
90 | return row
91 |
92 | def select_events(self):
93 | sql = "SELECT time, name, monitor, message, status, severity FROM AgentEvents WHERE sent=0"
94 | self.cursor.execute(sql)
95 | rows = self.cursor.fetchall()
96 | return rows
97 |
98 | def select_system(self):
99 | sql = "SELECT time, name, ipaddress, platform, build, architecture, domain, processors, memory FROM AgentSystem"
100 | self.cursor.execute(sql)
101 | row = self.cursor.fetchone()
102 | return row
103 |
104 | def select_thresholds(self):
105 | sql = "SELECT monitor, severity, threshold, compare, duration FROM AgentThresholds"
106 | self.cursor.execute(sql)
107 | rows = self.cursor.fetchall()
108 | return rows
109 |
110 | def update_close_data_events(self):
111 | sql = "UPDATE AgentData SET sent=1 WHERE sent=0;"
112 | sql += "UPDATE AgentEvents SET sent=1 WHERE sent=0;"
113 | self.cursor.executescript(sql)
114 | self.con.commit()
115 |
116 | def update_event(self, monitor, severity):
117 | sql = "UPDATE AgentEvents SET status=0, sent=0 WHERE monitor='" + monitor + "' AND severity=" + str(severity) + ";"
118 | self.cursor.execute(sql)
119 | self.con.commit()
120 |
121 | # Initialize AgentSQL Class
122 | ASQL = AgentSQL()
123 |
124 | class AgentWindows():
125 | def conf_system():
126 | memory = subprocess.run('wmic path Win32_ComputerSystem get TotalPhysicalMemory /value', shell=True, capture_output=True, text=True).stdout
127 | memory = re.search(r'(?m)(?<=\bTotalPhysicalMemory=).*$', memory).group()
128 | memory = round(int(memory) / 1048576, 0)
129 | osplatform = platform.system()
130 | architecture = platform.architecture()[0]
131 | build = platform.win32_ver()[1]
132 | ipaddress= socket.gethostbyname(socket.gethostname())
133 | processors = str(os.cpu_count())
134 | domain = socket.getfqdn()
135 | if '.' in domain: domain = domain.split('.', 1)[1]
136 | else: domain = 'Stand Alone'
137 | ASQL.insert_system(ipaddress, osplatform, build, architecture, domain, processors, memory)
138 |
139 | def perf_filesystem():
140 | result = subprocess.run('''wmic path Win32_PerfFormattedData_PerfDisk_LogicalDisk WHERE "Name LIKE '%:'" get Name,PercentFreeSpace,PercentIdleTime /format:csv''', shell=True, capture_output=True, text=True).stdout
141 | result_list = result.split('\n')
142 | for i in result_list:
143 | if not 'PercentFreeSpace' in i and ',' in i:
144 | ld_list = i.split(',')
145 | ld_name = ld_list[1].replace(':','').lower()
146 | ASQL.insert_data('perf.filesystem.' + ld_name + '.percent.used', str(100 - float(ld_list[2])))
147 | ASQL.insert_data('perf.filesystem.' + ld_name + '.percent.active', str(100 - float(ld_list[3])))
148 |
149 | def perf_memory():
150 | result = subprocess.run('wmic path Win32_OperatingSystem get FreePhysicalMemory,TotalVisibleMemorySize /value', shell=True, capture_output=True, text=True).stdout
151 | FreeMem = int(re.search(r'(?m)(?<=\bFreePhysicalMemory=).*$', result).group())
152 | TotalMem = int(re.search(r'(?m)(?<=\bTotalVisibleMemorySize=).*$', result).group())
153 | PercentMem = ((TotalMem-FreeMem)/TotalMem)*100
154 | PercentMem = round(PercentMem, 0)
155 | ASQL.insert_data('perf.memory.percent.used', str(PercentMem))
156 |
157 | def perf_network():
158 | nw_br = 0
159 | nw_bs = 0
160 | result = subprocess.run('wmic path Win32_PerfFormattedData_Tcpip_NetworkInterface get BytesReceivedPersec,BytesSentPersec /format:csv', shell=True, capture_output=True, text=True).stdout
161 | result_list = result.split('\n')
162 | for i in result_list:
163 | if not 'BytesReceivedPersec' in i and ',' in i:
164 | nw_list = i.split(",")
165 | nw_br += int(nw_list[1])
166 | nw_bs += int(nw_list[2])
167 | ASQL.insert_data('perf.network.bytes.received', str(nw_br))
168 | ASQL.insert_data('perf.network.bytes.sent', str(nw_bs))
169 |
170 | def perf_pagefile():
171 | result = subprocess.run('wmic path Win32_PerfFormattedData_PerfOS_PagingFile where name="_Total" get PercentUsage /value', shell=True, capture_output=True, text=True).stdout
172 | result = str(re.search(r'(?m)(?<=\bPercentUsage=).*$', result).group())
173 | ASQL.insert_data('perf.pagefile.percent.used', result)
174 |
175 | def perf_processor():
176 | result = subprocess.run('wmic path Win32_PerfFormattedData_PerfOS_Processor where name="_Total" get PercentProcessorTime /value', shell=True, capture_output=True, text=True).stdout
177 | result = str(re.search(r'(?m)(?<=\bPercentProcessorTime=).*$', result).group())
178 | ASQL.insert_data('perf.processor.percent.used', result)
179 |
180 | def perf_uptime():
181 | result = subprocess.run('wmic path Win32_PerfFormattedData_PerfOS_System get SystemUptime /value', shell=True, capture_output=True, text=True).stdout
182 | result = str(re.search(r'(?m)(?<=\bSystemUpTime=).*$', result).group())
183 | ASQL.insert_data('perf.system.uptime.seconds', result)
184 |
185 | def perf_services():
186 | if AgentSettings.services:
187 | for service in AgentSettings.services:
188 | result = subprocess.run('wmic path Win32_Service where name="' + service + '" get State /value', shell=True, capture_output=True, text=True).stdout
189 | result = str(re.search(r'(?m)(?<=\bState=).*$', result).group())
190 | sname = 'perf.service.' + service.replace(' ','').lower() + '.state'
191 | if result == 'Running': result = 1
192 | else: result = 0
193 | ASQL.insert_data(sname, str(result))
194 |
195 | class AgentProcess():
196 | def initialize_agent():
197 | try:
198 | AgentSettings.name = socket.gethostname().lower()
199 | ASQL.create_tables()
200 | ASQL.delete_thresholds()
201 | parser = configparser.ConfigParser()
202 | parser.read(AgentSettings.path + 'settings.ini')
203 | config = dict(parser.items('configuration'))
204 | services = list(dict(parser.items('services')).values())
205 | thresholds = list(dict(parser.items('thresholds')).values())
206 | AgentSettings.log = eval(config['log'])
207 | AgentSettings.passphrase = config['passphrase']
208 | AgentSettings.port = int(config['port'])
209 | AgentSettings.server = config['server']
210 | AgentSettings.secure = eval(config['secure'])
211 | AgentSettings.services = services
212 | for i in thresholds:
213 | thresh = i.split(',')
214 | ASQL.insert_thresholds(thresh[0], thresh[1], thresh[2], thresh[3], thresh[4])
215 | except: pass
216 |
217 | def data_process():
218 | try:
219 | AgentWindows.conf_system()
220 | AgentWindows.perf_filesystem()
221 | AgentWindows.perf_memory()
222 | AgentWindows.perf_network()
223 | AgentWindows.perf_pagefile()
224 | AgentWindows.perf_processor()
225 | AgentWindows.perf_uptime()
226 | AgentWindows.perf_services()
227 | except: pass
228 |
229 | def event_create(monitor, severity, threshold, compare, duration, status):
230 | message = monitor.replace('perf.', '').replace('.', ' ').capitalize()
231 | message = message + ' ' + compare + ' ' + str(threshold) + ' for ' + str(round(duration/60)) + ' minutes'
232 | check_monitor = ASQL.select_event(monitor)
233 | if not check_monitor is None: check_monitor=check_monitor[0]
234 | if check_monitor is None and status == 1: ASQL.insert_event(monitor, message, severity)
235 | elif check_monitor == monitor and status == 0: ASQL.update_event(monitor, severity)
236 | else: pass
237 |
238 | def event_process():
239 | agent_time_int = int(AgentSettings.time)
240 | agent_thresholds = ASQL.select_thresholds()
241 | a_val = 0
242 | b_val = 0
243 | for i in agent_thresholds:
244 | monitor = i[0]
245 | severity = i[1]
246 | threshold = int(i[2])
247 | compare = i[3]
248 | duration = i[4]
249 | time_window = agent_time_int - duration
250 | agent_data = ASQL.select_data_events(time_window, monitor)
251 | a_val = 0
252 | b_val = 0
253 | for i in agent_data:
254 | value = i[0]
255 | if compare == '>':
256 | if value > threshold:
257 | a_val += 1
258 | b_val += 1
259 | else: b_val += 1
260 | elif compare == '<':
261 | if value < threshold:
262 | a_val += 1
263 | b_val += 1
264 | else: b_val += 1
265 | elif compare == '=':
266 | if value == 0 and threshold == 0:
267 | a_val += 1
268 | b_val += 1
269 | else: b_val += 1
270 | if a_val == b_val and b_val != 0 :
271 | AgentProcess.event_create(monitor, severity, threshold, compare, duration, 1)
272 | else:
273 | AgentProcess.event_create(monitor, severity, threshold, compare, duration, 0)
274 |
275 | def create_packet():
276 | system = ASQL.select_system()
277 | events = ASQL.select_events()
278 | data = ASQL.select_data()
279 | agent_data = []
280 | agent_events = []
281 | for i in events: agent_events.append({"time":i[0],"monitor":i[2],"message":i[3],"status":i[4],"severity":i[5]})
282 | for i in data: agent_data.append({"time":i[0],i[2]:i[3]})
283 | packet = {"time": system[0],
284 | "name": system[1],
285 | "ip": system[2],
286 | "platform": system[3],
287 | "build": system[4],
288 | "arch": system[5],
289 | "domain": system[6],
290 | "procs": system[7],
291 | "memory": system[8],
292 | "passphrase": AgentSettings.passphrase,
293 | "data": agent_data,
294 | "events": agent_events}
295 | packet = json.dumps(packet)
296 | #print(packet)
297 | return packet
298 |
299 | def send_data(message):
300 | sock = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
301 | try:
302 | if AgentSettings.secure == True:
303 | context = ssl.create_default_context()
304 | context.options |= ssl.PROTOCOL_TLSv1_2
305 | context.check_hostname = False
306 | context.verify_mode = ssl.CERT_NONE
307 | conn = context.wrap_socket(sock, server_hostname = AgentSettings.server)
308 | conn.connect((AgentSettings.server, AgentSettings.port))
309 | messagebytes=str(message).encode()
310 | conn.sendall(messagebytes)
311 | data = conn.recv(1024).decode()
312 | if data == 'Received': ASQL.update_close_data_events()
313 | conn.close()
314 | else:
315 | sock.connect((AgentSettings.server, AgentSettings.port))
316 | messagebytes=str(message).encode()
317 | sock.sendall(messagebytes)
318 | data = sock.recv(1024).decode()
319 | if data == 'Received': ASQL.update_close_data_events()
320 | sock.close()
321 | except: pass
322 |
323 | def run_process():
324 | while AgentSettings.running == True:
325 | a = datetime.datetime.now().second
326 | if a == 0:
327 | AgentSettings.time = str(time.time()).split('.')[0]
328 | AgentProcess.data_process()
329 | AgentProcess.event_process()
330 | AgentProcess.create_packet()
331 | AgentProcess.send_data(AgentProcess.create_packet())
332 | ASQL.delete_data_events()
333 | time.sleep(1)
334 |
--------------------------------------------------------------------------------
/code/server/model.py:
--------------------------------------------------------------------------------
1 | import sqlite3
2 | import hashlib, datetime, math, socket, time
3 |
4 | class Auth:
5 | def verify(self, user, password):
6 | D = Data()
7 | encrypt_password = hashlib.sha224(password.encode()).hexdigest()
8 | auth = D.user_auth(user, encrypt_password)
9 | if auth[0] is None: return None
10 | else: return auth
11 |
12 | def user_password_change(self, user, pass1, pass2):
13 | D = Data()
14 | encrypt_password1 = hashlib.sha224(pass1.encode()).hexdigest()
15 | encrypt_password2 = hashlib.sha224(pass2.encode()).hexdigest()
16 | authuser = D.user_auth(user, encrypt_password1)
17 | if not authuser is None:
18 | D.user_password_change(user, encrypt_password2)
19 | return True
20 | return False
21 |
22 | def user_password_change_admin(self, user, pass1, pass2):
23 | D = Data()
24 | encrypt_password1 = hashlib.sha224(pass1.encode()).hexdigest()
25 | encrypt_password2 = hashlib.sha224(pass2.encode()).hexdigest()
26 | if encrypt_password1 == encrypt_password2:
27 | D.user_password_change(user, encrypt_password2)
28 | return True
29 | return False
30 |
31 | def user_password_set(self, pass1, pass2):
32 | if pass1 == pass2:
33 | encrypt_password = hashlib.sha224(pass1.encode()).hexdigest()
34 | return encrypt_password
35 |
36 | def user_initialize(self):
37 | #Check if admin user exists. If not create it
38 | D = Data()
39 | user = 'admin'
40 | password = 'password'
41 | role = 1
42 | encrypt_password = hashlib.sha224(password.encode()).hexdigest()
43 | D.user_create_admin(user, encrypt_password, role)
44 |
45 | def user_add(self, user, password, role):
46 | D = Data()
47 | encrypt_password = hashlib.sha224(password.encode()).hexdigest()
48 | D.user_create(user, encrypt_password, role)
49 |
50 | class Code:
51 | def index(self, page):
52 | index_dict={}
53 | D = Data()
54 | uptime_check = 300
55 | # Block 1
56 | ok = down = 0
57 | total = ok + down
58 | currenttime = time.time()
59 | agentsystem = D.index_availability()
60 | for i in agentsystem:
61 | timestamp = int(i[0])
62 | if (timestamp + uptime_check) >= currenttime: ok += 1
63 | else: down += 1
64 | total = ok + down
65 | if total == 0: total = 1
66 | ok_perc = (ok / total) * 100
67 | down_perc = (down / total) * 100
68 | total_perc = str(ok_perc) + ' ' + str(down_perc)
69 | index_dict['avail_ok'] = str(ok)
70 | index_dict['avail_down'] = str(down)
71 | index_dict['avail_total'] = str(total_perc)
72 | # Block 2
73 | info = warn = majr = crit = 0
74 | agentevents = D.event_totals(1)
75 | for i in agentevents:
76 | sev = int(i[0])
77 | sev_tot = int(i[1])
78 | if sev == 1: crit = sev_tot
79 | elif sev == 2: majr = sev_tot
80 | elif sev == 3: warn = sev_tot
81 | elif sev == 4: info = sev_tot
82 | total = info + warn + majr + crit
83 | if total == 0: total = 1
84 | info_perc = (info / total) * 100
85 | warn_perc = (warn / total) * 100
86 | majr_perc = (majr / total) * 100
87 | crit_perc = (crit / total) * 100
88 | index_dict['event_info'] = str(info)
89 | index_dict['event_warn'] = str(warn)
90 | index_dict['event_majr'] = str(majr)
91 | index_dict['event_crit'] = str(crit)
92 | index_dict['event_info_pts'] = str(info_perc) + ' ' + str(100 - info_perc)
93 | index_dict['event_warn_pts'] = str(warn_perc) + ' ' + str(100 - warn_perc)
94 | index_dict['event_majr_pts'] = str(majr_perc) + ' ' + str(100 - majr_perc)
95 | index_dict['event_crit_pts'] = str(crit_perc) + ' ' + str(100 - crit_perc)
96 | index_dict['event_warn_offset'] = str(100 - info_perc + 25)
97 | index_dict['event_majr_offset'] = str(100 - info_perc - warn_perc + 25)
98 | index_dict['event_crit_offset'] = str(100 - info_perc - warn_perc - majr_perc + 25)
99 | # Block 3
100 | name = socket.gethostname().lower()
101 | currenttime = time.time()
102 | agent_platform = agent_architecture = ''
103 | agent_timestamp = agent_processors = agent_memory = cpu_perc = mem_perc = 0
104 | cpu_color = mem_color = online_color = '93C54B'
105 | agentsystem = D.index_device(name)
106 | agent_platform = agentsystem[3]
107 | agent_architecture = agentsystem[5]
108 | agent_timestamp = agentsystem[0]
109 | agent_processors = agentsystem[8]
110 | agent_memory = str(agentsystem[9])[:-3]
111 | agent_perf = D.device_data_latest(name)
112 | for i in agent_perf:
113 | if i[3] == 'perf.processor.percent.used' : cpu_perc = float(i[4])
114 | if i[3] == 'perf.memory.percent.used' : mem_perc = float(i[4]) #[:-2]
115 | if cpu_perc >= 90 : cpu_color = 'D9534F'
116 | if mem_perc >= 90: mem_color = 'D9534F'
117 | if (agent_timestamp + uptime_check) < currenttime: online_color = 'D9534F'
118 | index_dict['agent_name'] = name
119 | index_dict['agent_processors'] = agent_processors
120 | index_dict['agent_memory'] = agent_memory
121 | index_dict['agent_platform'] = agent_platform
122 | index_dict['agent_architecture'] = agent_architecture
123 | index_dict['agent_cpu_percent'] = str(cpu_perc)[:-2]
124 | index_dict['agent_memory_percent'] = str(mem_perc)[:-2]
125 | index_dict['agent_cpu_color'] = cpu_color
126 | index_dict['agent_memory_color'] = mem_color
127 | index_dict['agent_online_color'] = online_color
128 | # Block 4
129 | page_start = (int(page) * 100) - 100
130 | page_end = page_start + 100
131 | agentsystem = D.index_device_list(page_start, page_end)
132 | currenttime = time.time()
133 | rows = []
134 | for i in agentsystem:
135 | row = {}
136 | row['date'] = str(datetime.datetime.fromtimestamp(int(i[1])))
137 | color = '93C54B'
138 | if (int(i[0]) + uptime_check) < currenttime : color = 'D9534F'
139 | row['color'] = color
140 | row['name']= name
141 | row['ipaddress'] = str(i[2])
142 | row['domain'] = str(i[6]).lower()
143 | row['platform'] = i[3]
144 | row['architecture'] = i[5]
145 | rows.append(row)
146 | index_dict['host_summary'] = rows
147 | # Pager
148 | pager = ''
149 | agent_count = D.index_device_count()
150 | page_count = int(math.ceil(int(agent_count[0]) / 100))
151 | if page_count > 1:
152 | for i in range(1,page_count + 1):
153 | if i == page: pager += str(i)
154 | elif i == 1: pager += f'{str(i)}'
155 | else: pager += f'{str(i)} '
156 | index_dict['pager'] = pager
157 | return index_dict
158 |
159 | def events(self, status):
160 | events_dict = {}
161 | D = Data()
162 | total = info = warn = majr = crit = 0
163 | agentevents = D.event_totals(status)
164 | for i in agentevents:
165 | sev = int(i[0])
166 | sev_tot = int(i[1])
167 | if sev == 1: crit = sev_tot
168 | elif sev == 2: majr = sev_tot
169 | elif sev == 3: warn = sev_tot
170 | elif sev == 4: info = sev_tot
171 | total = info + warn + majr + crit
172 | status_text = ''
173 | change_status = abs(int(status) - 1)
174 | change_status_text = ''
175 | if change_status == 0:
176 | status_text = 'Open Events'
177 | change_status_text = 'Closed Events'
178 | else:
179 | status_text = 'Closed Events'
180 | change_status_text = 'Open Events'
181 | events_dict['info'] = str(info)
182 | events_dict['warn'] = str(warn)
183 | events_dict['majr'] = str(majr)
184 | events_dict['crit'] = str(crit)
185 | events_dict['total'] = str(total)
186 | events_dict['status'] = str(change_status)
187 | events_dict['status_text'] = status_text
188 | events_dict['change_text'] = change_status_text
189 | agentevents = D.events_status(status)
190 | color = '#CCCCCC'
191 | change_status = abs(int(status) - 1)
192 | change_status_text = ""
193 | if change_status == 0: change_status_text = 'Close Event'
194 | else: change_status_text = 'Open Event'
195 | events_dict['change_status'] = change_status
196 | events_dict['change_status_text'] = change_status_text
197 | rows = []
198 | for i in agentevents:
199 | row = {}
200 | row['date'] = str(datetime.datetime.fromtimestamp(int(i[1])))
201 | row['name'] = i[2]
202 | row['message'] = i[4]
203 | row['id'] = str(i[0])
204 | sev_text = ''
205 | if int(i[5]) == 4:
206 | row['color'] = '#29ABE0'
207 | row['severity'] = 'Information'
208 | elif int(i[5]) == 3:
209 | row['color'] = '#FFC107'
210 | row['severity'] = 'Warning'
211 | elif int(i[5]) == 2:
212 | row['color'] = '#F47C3C'
213 | row['severity'] = 'Major'
214 | elif int(i[5]) == 1:
215 | row['color'] = '#D9534F'
216 | row['severity'] = 'Critical'
217 | rows.append(row)
218 | events_dict['event_list'] = rows
219 | return events_dict
220 |
221 | def devices(self):
222 | D = Data()
223 | agentsystem = D.devices_select_all()
224 | uptime_check = 600
225 | currenttime = time.time()
226 | rows = []
227 | for i in agentsystem:
228 | row = {}
229 | if (i[0] + uptime_check) >= currenttime: row['fill'] = '#93C54B'
230 | else: row['fill'] = '#d9534f'
231 | row['name'] = i[2]
232 | row['domain'] = i[7]
233 | row['ipaddress'] = i[3]
234 | row['platform'] = i[4]
235 | rows.append(row)
236 | return rows
237 |
238 | def device(self, name):
239 | device_dict = {}
240 | D = Data()
241 | agentsystem = D.device_system(name)
242 | device_dict['ipaddress'] = agentsystem[3]
243 | device_dict['domain'] = agentsystem[7].lower()
244 | device_dict['platform'] = agentsystem[4]
245 | device_dict['architecture'] = agentsystem[6]
246 | device_dict['build'] = agentsystem[5]
247 | device_dict['processors'] = str(agentsystem[8])
248 | device_dict['memory'] = str(agentsystem[9])[:-3]
249 | agent_query = D.device_data_latest(name)
250 | cpu_perc = mem_perc = pagefile_perc = uptime_days = net_br = net_bs = 0
251 | fs_list = []
252 | for i in agent_query:
253 | if i[3] == 'perf.processor.percent.used': device_dict['cpu_perc'] = str(round(float(i[4]), 0))
254 | if i[3] == 'perf.memory.percent.used': device_dict['mem_perc'] = str(round(float(i[4]), 0))
255 | if i[3] == 'perf.pagefile.percent.used': device_dict['pagefile_perc'] = str(round(float(i[4]), 0))
256 | if i[3] == 'perf.system.uptime.seconds': device_dict['uptime_days'] = str(round(float(i[4]) / 86400, 0))
257 | if i[3] == 'perf.network.bytes.received': device_dict['net_br'] = str(round(float(i[4]), 0))
258 | if i[3] == 'perf.network.bytes.sent': device_dict['net_bs'] = str(round(float(i[4]), 0))
259 | if 'filesystem' in i[3] and 'percent.used' in i[3]:
260 | fs_name = i[3].replace('perf.filesystem.','').replace('.percent.used','')
261 | fs = (fs_name, str(i[4]))
262 | fs_list.append(fs)
263 | return device_dict, fs_list
264 |
265 | def device_graph(self, name, monitor):
266 | D = Data()
267 | device_data = D.device_graph(name, monitor)
268 | data_list = []
269 | max_value = 0
270 | graph_time = datetime.datetime.now() - datetime.timedelta(minutes=60)
271 | for i in range(61):
272 | data_point = [graph_time.strftime('%H:%M'), 0]
273 | data_list.append(data_point)
274 | graph_time = graph_time + datetime.timedelta(minutes=1)
275 | for i in device_data:
276 | if float(i[4]) > max_value:max_value = float(i[4])
277 | for i in device_data:
278 | device_value = float(i[4])
279 | time_short = datetime.datetime.fromtimestamp(int(i[1])).strftime('%H:%M')
280 | for i in data_list:
281 | if i[0] == time_short:
282 | if device_value == 0: i[1] = 0
283 | else: i[1] = (device_value / max_value)*100
284 | device_polyline = ''
285 | device_polyline_data = ''
286 | device_time = ''
287 | xvalue = 55
288 | time_x = 0
289 | for i in data_list:
290 | dvalue = str(round(110 - i[1]))
291 | device_polyline_data += str(xvalue) + ',' + dvalue + ' '
292 | time_x += 1
293 | if time_x == 1:
294 | device_time += f'{str(i[0])}'
295 | if time_x == 5:
296 | time_x = 0
297 | xvalue += 14
298 | device_polyline = f''
299 | html = ''
310 | return html
311 |
312 | def report(self, name, ext):
313 | D = Data()
314 | cr = ''
315 | if ext == 'html': cr = ' '
316 | elif ext == 'csv': cr = '\r\n'
317 | if name == 'devices':
318 | devices = D.device_all()
319 | output = 'Last Reported,Name,IP Address,Platform,Build Number,Architecture,Domain,Processors,Memory' + cr
320 | for i in devices:
321 | last_reported = str(datetime.datetime.fromtimestamp(int(i[0])))
322 | output += last_reported + ',' + str(i[1]) + ',' + str(i[2]) + ',' + str(i[3]) + ',' + str(i[4]) + ',' + str(i[5])
323 | output += ',' + str(i[6]) + ',' + str(i[7]) + ',' + str(i[8]) + cr
324 | if name == 'events':
325 | events = D.events(1)
326 | output = 'Date, Name, Monitor, Message, Severity' + cr
327 | for i in events:
328 | last_reported = str(datetime.datetime.fromtimestamp(int(i[0])))
329 | output += last_reported + ',' + str(i[1]) + ',' + str(i[2]) + ',' + str(i[3]) + ',' + str(i[4]) + cr
330 | return output
331 |
332 | class Data:
333 | def __init__(self):
334 | self.con = sqlite3.connect('database/flask.db')
335 | self.cursor = self.con.cursor()
336 |
337 | def __del__(self):
338 | self.con.close()
339 |
340 | def create_tables(self):
341 | agentdata = 'CREATE TABLE IF NOT EXISTS agentdata(ID INTEGER PRIMARY KEY, timestamp INTERGER, name TEXT, monitor TEXT, value REAL);'
342 | agentevents = 'CREATE TABLE IF NOT EXISTS agentevents(ID INTEGER PRIMARY KEY, timestamp INTERGER, name TEXT, monitor TEXT, message TEXT, status INTEGER, severity TEXT, processed INTERGER);'
343 | agentsystem = 'CREATE TABLE IF NOT EXISTS agentsystem(ID INTEGER PRIMARY KEY, timestamp INTERGER, name TEXT, ipaddress TEXT, platform TEXT, build TEXT, architecture TEXT, domain TEXT, processors INTEGER, memory REAL);'
344 | notifyrule = 'CREATE TABLE IF NOT EXISTS notifyrule(ID INTEGER PRIMARY KEY, name TEXT, email TEXT, agent TEXT, monitor TEXT, status INTEGER, severity TEXT, enabled INTERGER);'
345 | users = 'CREATE TABLE IF NOT EXISTS users (ID INTEGER PRIMARY KEY, user TEXT, password TEXT, role INTERGER);'
346 | self.cursor.executescript(agentdata + agentevents + agentsystem + notifyrule + users)
347 | self.con.commit()
348 |
349 | # User Queries
350 | def user_auth(self, user, encrypt_password):
351 | sql = "SELECT user, role from users where user=? AND password=?"
352 | self.cursor.execute(sql, (user, encrypt_password))
353 | result = self.cursor.fetchone()
354 | if not result is None:
355 | return result # qname
356 |
357 | def user_password_change(self, user, password):
358 | sql = "UPDATE users SET password=? WHERE user=?"
359 | self.cursor.execute(sql, (password, user))
360 | self.con.commit()
361 |
362 | def users_select(self):
363 | sql = "SELECT id, user FROM users order by user"
364 | self.cursor.execute(sql)
365 | result = self.cursor.fetchall()
366 | return result
367 |
368 | def user_select(self, id):
369 | sql = "SELECT id, user, role FROM users WHERE id=?"
370 | self.cursor.execute(sql, (id,))
371 | result = self.cursor.fetchone()
372 | return result
373 |
374 | def user_create_admin(self, user, encrypt_pass, role):
375 | sql = "INSERT INTO users(ID, user, password, role) VALUES (1,?, ?, ?) ON CONFLICT DO NOTHING;" # SELECT ?, ?, ? FROM DUAL WHERE NOT EXISTS (SELECT * from users WHERE user=?) LIMIT 1"
376 | self.cursor.execute(sql, (user, encrypt_pass, role))
377 | self.con.commit()
378 |
379 | def user_create(self, user, encrypt_pass, role):
380 | sql = "INSERT INTO users(user, password, role) VALUES (?, ?, ?) ON CONFLICT DO NOTHING;" # SELECT ?, ?, ? FROM DUAL WHERE NOT EXISTS (SELECT * from users WHERE user=?) LIMIT 1"
381 | self.cursor.execute(sql, (user, encrypt_pass, role))
382 | self.con.commit()
383 |
384 | def user_edit_role(self, id, role):
385 | sql = "UPDATE users set role=? where id=?"
386 | self.cursor.execute(sql, (role, id))
387 | self.con.commit()
388 |
389 | def user_edit_password(self, id, encrypt_pass):
390 | sql = "UPDATE users set password=? where id=?"
391 | self.cursor.execute(sql, (encrypt_pass, id))
392 | self.con.commit()
393 |
394 | def user_delete(self, id):
395 | sql = "DELETE FROM users where id=?"
396 | self.cursor.execute(sql, (id))
397 | self.con.commit()
398 |
399 | def user_add(self, user, password, role):
400 | D = Data()
401 | encrypt_password = hashlib.sha224(password.encode()).hexdigest()
402 | D.create_user(user, encrypt_password, role)
403 |
404 | # Index Queries
405 | def index_availability(self):
406 | sql = "SELECT timestamp from agentsystem"
407 | self.cursor.execute(sql)
408 | result = self.cursor.fetchall()
409 | return result
410 |
411 | def index_device(self, name):
412 | sql = "SELECT id, timestamp, name, ipaddress, platform, build, architecture, domain, processors, memory FROM agentsystem WHERE name=? LIMIT 1"
413 | self.cursor.execute(sql, (name,))
414 | result = self.cursor.fetchone()
415 | return result
416 |
417 | def index_device_list(self, page_start, page_end):
418 | sql = "SELECT id, timestamp, name, ipaddress, platform, build, architecture, domain, processors, memory FROM agentsystem ORDER BY name LIMIT ?,?"
419 | self.cursor.execute(sql, (page_start, page_end))
420 | result = self.cursor.fetchall()
421 | return result
422 |
423 | def index_device_count(self):
424 | sql = "SELECT COUNT(id) as total FROM agentsystem"
425 | self.cursor.execute(sql)
426 | result = self.cursor.fetchone()
427 | return result
428 |
429 | # Event Queries
430 | def event_totals(self, status):
431 | sql = "SELECT severity, count(severity) as total from agentevents WHERE status=? group by severity"
432 | self.cursor.execute(sql, (status,))
433 | result = self.cursor.fetchall()
434 | return result
435 |
436 | def events_status(self, status):
437 | sql = "SELECT id, timestamp, name, monitor, message, severity from agentevents where status=? order by id desc"
438 | self.cursor.execute(sql, (status,))
439 | result = self.cursor.fetchall()
440 | return result
441 |
442 | def event_change_status(self, id, status):
443 | sql = "UPDATE agentevents SET status=? where id=?"
444 | self.cursor.execute(sql, (status, id))
445 | self.con.commit()
446 |
447 | def events(self, status):
448 | sql = "SELECT id, timestamp, name, monitor, message, severity from agentevents where status=? order by id desc"
449 | self.cursor.execute(sql, (status,))
450 | result = self.cursor.fetchall()
451 | return result
452 |
453 | # Device Queries
454 | def search_devices(self, name):
455 | sql = f"SELECT name FROM agentsystem WHERE name LIKE '%{name}%'"
456 | self.cursor.execute(sql)
457 | result = self.cursor.fetchall()
458 | return result
459 |
460 | def device_system(self, name):
461 | sql = "SELECT id, timestamp, name, ipaddress, platform, build, architecture, domain, processors, memory FROM agentsystem WHERE name=? LIMIT 1"
462 | self.cursor.execute(sql, (name,))
463 | result = self.cursor.fetchone()
464 | return result
465 |
466 | def devices_select_all(self):
467 | sql = "SELECT id, timestamp, name, ipaddress, platform, build, architecture, domain, processors, memory FROM agentsystem ORDER BY name"
468 | self.cursor.execute(sql)
469 | result = self.cursor.fetchall()
470 | return result
471 |
472 | def device_data_latest(self, name):
473 | sql = "SELECT id, timestamp, name, monitor, value from agentdata where name=? and timestamp = (SELECT timestamp from agentdata where name=? order by id desc LIMIT 1)"
474 | self.cursor.execute(sql, (name, name))
475 | result = self.cursor.fetchall()
476 | return result
477 |
478 | def device_list(self, page_start, page_end):
479 | sql = "SELECT id, timestamp, name, ipaddress, platform, build, architecture, domain, processors, memory FROM agentsystem ORDER BY name LIMIT ?,?"
480 | self.cursor.execute(sql, (page_start, page_end))
481 | result = self.cursor.fetchall()
482 | return result
483 |
484 | def device_graph(self, name, monitor):
485 | sql = "SELECT id, timestamp, name, monitor, value from agentdata where name=? and monitor=? order by id desc LIMIT 61"
486 | self.cursor.execute(sql, (name, monitor))
487 | result = self.cursor.fetchall()
488 | return result
489 |
490 | def device_all(self):
491 | sql = "SELECT id, timestamp, name, ipaddress, platform, build, architecture, domain, processors, memory FROM agentsystem ORDER BY name"
492 | self.cursor.execute(sql)
493 | result = self.cursor.fetchall()
494 | return result
495 |
496 | # Notify
497 | def notify_rules(self):
498 | sql = "SELECT id, name, email, agent, monitor, status, severity, enabled FROM notifyrule order by name"
499 | self.cursor.execute(sql)
500 | result = self.cursor.fetchall()
501 | return result
502 |
503 | def notify_rule(self, id):
504 | sql = "SELECT id, name, email, agent, monitor, status, severity, enabled FROM notifyrule WHERE id=?"
505 | self.cursor.execute(sql, (id,))
506 | result = self.cursor.fetchone()
507 | return result
508 |
509 | def notify_add(self, notify_name, notify_email, agent_name, agent_monitor, agent_status, agent_severity, notify_enabled):
510 | sql = "INSERT INTO notifyrule (name, email, agent, monitor, status, severity, enabled) VALUES (?,?,?,?,?,?,?)"
511 | self.cursor.execute(sql, (notify_name, notify_email, agent_name, agent_monitor, agent_status, agent_severity, notify_enabled))
512 | self.con.commit()
513 |
514 | def notify_edit(self, notify_name, notify_email, agent_name, agent_monitor, agent_status, agent_severity, notify_enabled):
515 | sql = "UPDATE notifyrule SET name=?, email=?, agent=?, monitor=?, status=?, severity=?, enabled=? WHERE name=?"
516 | self.cursor.execute(sql, (notify_name, notify_email, agent_name, agent_monitor, agent_status, agent_severity, notify_enabled, notify_name))
517 | self.con.commit()
518 |
519 | def notify_delete(self, id):
520 | sql = "DELETE FROM notifyrule where id=?"
521 | self.cursor.execute(sql, (id))
522 | self.con.commit()
523 |
524 | def notify_device_names(self):
525 | sql = "SELECT name FROM agentsystem ORDER by name"
526 | self.cursor.execute(sql)
527 | result = self.cursor.fetchall()
528 | return result
529 |
--------------------------------------------------------------------------------