├── code ├── server │ ├── templates │ │ ├── graph_content.html │ │ ├── devices.html │ │ ├── index.html │ │ ├── events.html │ │ ├── device.html │ │ ├── graph.html │ │ ├── search.html │ │ ├── report.html │ │ ├── settings.html │ │ ├── devices_content.html │ │ ├── about.html │ │ ├── login.html │ │ ├── password.html │ │ ├── user_pass.html │ │ ├── notify.html │ │ ├── user_role.html │ │ ├── users.html │ │ ├── user_add.html │ │ ├── reports.html │ │ ├── base.html │ │ ├── events_content.html │ │ ├── notify_add.html │ │ ├── notify_edit.html │ │ ├── notify_form.html │ │ ├── device_content.html │ │ ├── index_content.html │ │ └── help.html │ ├── database │ │ └── flask.db │ ├── static │ │ ├── favicon.ico │ │ ├── svg │ │ │ ├── bread_chart.svg │ │ │ ├── nav_chart.svg │ │ │ ├── nav_chart_hover.svg │ │ │ ├── bread_user.svg │ │ │ ├── search.svg │ │ │ ├── bread_events.svg │ │ │ ├── nav_events.svg │ │ │ ├── nav_events_hover.svg │ │ │ ├── bread_contact.svg │ │ │ ├── bread_settings.svg │ │ │ ├── nav_settings.svg │ │ │ ├── nav_settings_hover.svg │ │ │ ├── bread_home.svg │ │ │ ├── nav_home.svg │ │ │ ├── nav_home_hover.svg │ │ │ ├── bread_device.svg │ │ │ ├── nav_device.svg │ │ │ └── nav_device_hover.svg │ │ └── style.css │ ├── __pycache__ │ │ ├── app.cpython-312.pyc │ │ ├── model.cpython-312.pyc │ │ ├── views.cpython-312.pyc │ │ └── __init__.cpython-312.pyc │ ├── app.py │ ├── services │ │ ├── settings.ini │ │ ├── collect.py │ │ └── event.py │ ├── certificates │ │ ├── localhost.csr │ │ ├── localhost.crt │ │ ├── localhost.pem │ │ └── localhost.key │ ├── views.py │ └── model.py └── agents │ ├── linux │ ├── agent_sqlite.db │ ├── readme.md │ ├── settings.ini │ ├── magent │ └── agent.py │ └── windows │ ├── settings.ini │ ├── service_agent.py │ └── agent.py ├── LICENSE_Additional ├── images ├── device.png ├── graph.png └── home.png ├── install ├── README.md └── mserver ├── .github └── ISSUE_TEMPLATE │ ├── feature_request.md │ └── bug_report.md ├── README.md └── LICENSE /code/server/templates/graph_content.html: -------------------------------------------------------------------------------- 1 | {{graph|safe}} -------------------------------------------------------------------------------- /LICENSE_Additional: -------------------------------------------------------------------------------- 1 | Font Graphics from FontAwesome.com | https://fontawesome.com/license/free -------------------------------------------------------------------------------- /images/device.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/philipcwhite/Monitoring/HEAD/images/device.png -------------------------------------------------------------------------------- /images/graph.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/philipcwhite/Monitoring/HEAD/images/graph.png -------------------------------------------------------------------------------- /images/home.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/philipcwhite/Monitoring/HEAD/images/home.png -------------------------------------------------------------------------------- /code/server/database/flask.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/philipcwhite/Monitoring/HEAD/code/server/database/flask.db -------------------------------------------------------------------------------- /code/server/static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/philipcwhite/Monitoring/HEAD/code/server/static/favicon.ico -------------------------------------------------------------------------------- /code/agents/linux/agent_sqlite.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/philipcwhite/Monitoring/HEAD/code/agents/linux/agent_sqlite.db -------------------------------------------------------------------------------- /code/server/__pycache__/app.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/philipcwhite/Monitoring/HEAD/code/server/__pycache__/app.cpython-312.pyc -------------------------------------------------------------------------------- /code/server/__pycache__/model.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/philipcwhite/Monitoring/HEAD/code/server/__pycache__/model.cpython-312.pyc -------------------------------------------------------------------------------- /code/server/__pycache__/views.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/philipcwhite/Monitoring/HEAD/code/server/__pycache__/views.cpython-312.pyc -------------------------------------------------------------------------------- /code/server/__pycache__/__init__.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/philipcwhite/Monitoring/HEAD/code/server/__pycache__/__init__.cpython-312.pyc -------------------------------------------------------------------------------- /install/README.md: -------------------------------------------------------------------------------- 1 | # Readme 2 | 3 | Installing the server is fairly straight forward. You can install components as Linux services using the mserver script or just run the Python files manually. The only Python requirement is Flask. Guincorn is optional for the webserver. 4 | -------------------------------------------------------------------------------- /code/server/app.py: -------------------------------------------------------------------------------- 1 | import os 2 | from flask import Flask 3 | 4 | # Initialize Flask 5 | app = Flask(__name__) 6 | 7 | # Add secret key for authentication 8 | app.secret_key = os.urandom(12) 9 | 10 | # Import views 11 | import views 12 | 13 | 14 | # Initialize database 15 | from model import Auth, Data 16 | D = Data() 17 | A = Auth() 18 | D.create_tables() 19 | A.user_initialize() 20 | 21 | if __name__ == '__main__': 22 | app.run() -------------------------------------------------------------------------------- /code/agents/linux/readme.md: -------------------------------------------------------------------------------- 1 | ## Run agent as a service 2 | 3 | The magent shell script will copy the agent.py and settings.ini file to /opt/monitoring/agent. It will create the user monitoring to run the script as a service. And lastly it will create the magent.service file and register it with systemd. The script uses Python 3.7. If your version is different you will have to change the script to your version. I have only tested with 3.7. It will probably work with most Python 3 versions. 4 | -------------------------------------------------------------------------------- /code/server/templates/devices.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block breadcrumbs %} 3 |   Devices  4 | {% endblock %} 5 | {% block body%} 6 | 7 |
8 | 12 | 13 | {% endblock %} 14 | -------------------------------------------------------------------------------- /code/server/templates/index.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block breadcrumbs %} 3 |   Home  4 | {% endblock %} 5 | {% block body%} 6 | 7 |
8 | 12 | 13 | 14 | 15 | {% endblock %} 16 | -------------------------------------------------------------------------------- /code/server/templates/events.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block breadcrumbs %} 3 |   Events  4 | {% endblock %} 5 | {% block body%} 6 | 7 |
8 | 12 | 13 | 14 | 15 | {% endblock %} 16 | -------------------------------------------------------------------------------- /code/server/templates/device.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block breadcrumbs %} 3 |   Devices > {{name}}  4 | {% endblock %} 5 | {% block body%} 6 | 7 |
8 | 12 | 13 | {% endblock %} 14 | -------------------------------------------------------------------------------- /code/server/templates/graph.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block breadcrumbs %} 3 |   Devices >  {{name}}  > {{monitor}}   4 | {% endblock %} 5 | {% block body%} 6 | 7 |
8 | 12 | 13 | {% endblock %} 14 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | 5 | --- 6 | 7 | **Is your feature request related to a problem? Please describe.** 8 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 9 | 10 | **Describe the solution you'd like** 11 | A clear and concise description of what you want to happen. 12 | 13 | **Describe alternatives you've considered** 14 | A clear and concise description of any alternative solutions or features you've considered. 15 | 16 | **Additional context** 17 | Add any other context or screenshots about the feature request here. 18 | -------------------------------------------------------------------------------- /code/server/templates/search.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block breadcrumbs %} 3 |   Settings  4 | {% endblock %} 5 | {% block body%} 6 | 7 |
8 |
Search Results
9 |
10 | Host names containing: {{device}}
11 | {% for i in devices %} 12 | {{i[0]}}
13 | {% endfor %} 14 |
15 |
16 | 17 | 18 | {% endblock %} 19 | -------------------------------------------------------------------------------- /code/server/services/settings.ini: -------------------------------------------------------------------------------- 1 | # Monitoring Server Configuration 2 | 3 | [certificates] 4 | key = localhost.pem 5 | name = localhost.crt 6 | 7 | [database] 8 | host = localhost 9 | name = monitoring 10 | password = monitoring 11 | user = monitoring 12 | 13 | [events] 14 | availability_check = 300 15 | availability_severity = 1 16 | 17 | [mail] 18 | active = 0 19 | admin = admin@monitoring 20 | server = localhost 21 | 22 | [server] 23 | ipaddress = 127.0.0.1 24 | log = False 25 | passphrase = secure_monitoring 26 | port_collect = 8888 27 | port_web = 80 28 | secure = False 29 | session_expire = 3600 30 | 31 | [retention] 32 | agent = 2592000 33 | data = 2592000 34 | event = 2592000 35 | -------------------------------------------------------------------------------- /code/server/templates/report.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block breadcrumbs %} 3 |   Reports  4 | {% endblock %} 5 | {% block body%} 6 | 7 |
8 |
Report
9 | 10 | 11 | 20 | 21 |
12 | 13 | 14 | 17 | 18 |
15 | {{ output|safe }} 16 |
19 |
22 |
23 | 24 | 25 | {% endblock %} -------------------------------------------------------------------------------- /code/agents/linux/settings.ini: -------------------------------------------------------------------------------- 1 | # Monitoring Agent Configuration 2 | 3 | [configuration] 4 | # Set monitoring configuration 5 | log = False 6 | passphrase = secure_monitoring 7 | port = 8888 8 | secure = False 9 | server = 127.0.0.1 10 | 11 | [processes] 12 | # Set processes to be monitored 13 | s00 = systemd 14 | 15 | [thresholds] 16 | # Thresholds (monitor,severity[1-4],value,compare[=,<,>],duration[seconds]) 17 | t00 = perf.memory.percent.used,4,85,>,900 18 | t01 = perf.memory.percent.used,3,90,>,900 19 | t02 = perf.memory.percent.used,2,95,>,900 20 | t03 = perf.memory.percent.used,1,99,>,900 21 | t04 = perf.processor.percent.used,4,0,>,900 22 | t05 = perf.processor.percent.used,3,1,>,900 23 | t06 = perf.processor.percent.used,2,95,>,900 24 | t07 = perf.processor.percent.used,1,99,>,900 25 | -------------------------------------------------------------------------------- /code/server/static/svg/bread_chart.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /code/server/static/svg/nav_chart.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /code/server/static/svg/nav_chart_hover.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /code/server/templates/settings.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block breadcrumbs %} 3 |   Settings  4 | {% endblock %} 5 | {% block body%} 6 | 7 |
8 |
Settings
9 |
10 | Site Settings
11 | Notification Rules
12 |
13 | User and Role Management
14 | Manage Users
15 |
16 | Documentation
17 | User Guide
18 | About
19 |
20 |
21 |
22 | 23 | 24 | {% endblock %} -------------------------------------------------------------------------------- /code/server/static/svg/bread_user.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /code/server/templates/devices_content.html: -------------------------------------------------------------------------------- 1 |
2 |
Devices
3 |
4 |
5 | {% for i in device_list %} 6 |
7 |
8 |
{{i.name}}
9 |
{{i.domain}}
10 |
{{i.ipaddress}}
11 |
{{i.platform}}
12 |
13 | {% endfor %} 14 |
15 |
16 |
17 | -------------------------------------------------------------------------------- /code/server/static/svg/search.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 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 | 24 | 25 |
12 | 13 | 14 | 21 | 22 |
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 |
23 |
26 |
27 | 28 | 29 | {% endblock %} -------------------------------------------------------------------------------- /code/server/templates/login.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Monitoring 4 | 5 | 6 | 7 |
8 |
9 |
10 | 28 | 29 |
11 |
12 |
Monitoring Login
13 | 14 | 15 | 24 | 25 |
16 |
17 | 18 | 19 | 20 | 21 |
User
Password
22 |
23 |
26 |
27 |
30 |
31 | 32 | -------------------------------------------------------------------------------- /code/server/certificates/localhost.csr: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE REQUEST----- 2 | MIICrDCCAZQCAQAwZzELMAkGA1UEBhMCVVMxEjAQBgNVBAgMCU1pbm5lc290YTES 3 | MBAGA1UEBwwJUm9zZW1vdW50MQ0wCwYDVQQKDAROb25lMQ0wCwYDVQQLDAROb25l 4 | MRIwEAYDVQQDDAlsb2NhbGhvc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK 5 | AoIBAQDpLOWOWaeK70QD3Jf6OLgxUF+8B4ikLxx3pOrgbBwi5uINYTuNgn0TEGOV 6 | rjIwgxS1xfhNh73AOlP6W01b6XZssQdfIOsX6daAwk/km5F3RUwqYcjt6lqF+3LP 7 | AWn/oesa07UFH2LQ6lrmAVZdKNnadYV70FmzHZC8XB5i1EDFzlxMwQdYcoUvQZv+ 8 | Yw3+RyN6TACxjvKiFHu8GLvTXmT4sTr0UEwqMzJFVpp3p5S0UNeiJoUWN2e01zys 9 | 2UXNi/wikiWGWbc1odWXk7tRiuBgOh8r7Eiw272yhusm+klDLP3iT1PGvhBk/M3r 10 | pcYLewYMrZ0NU9/gKVvKDJvqq6t5AgMBAAGgADANBgkqhkiG9w0BAQsFAAOCAQEA 11 | QBtP+aieKqPDy6Y6t1YtIrVgXJT43YIrfIEwzOhkv8gRJLNswUwC0pngVfnsgWv8 12 | g7F46XNCYWNhCVxFrMXRu18DXEbWApkY/KKHbwMf/ttTXZ2oYp5KC002t9fkODH6 13 | TGdB/4qx3q5oOqvwIDwTFRfy/8GyfiaQQyavpJnjjtEyH2iKMj9QyAgGU/l9TD1S 14 | qmRF4SjsW7qmzx5783um7Ote2r6WZFoK2O8njNhgEjkqw9kQ9dJ81xFhit4jfwJD 15 | 2q+1c5gkzCQaKJ5hhWUpuoHsB4XvCSWaPwYRKVf09lwoObZ3ojtDJBeZIHW5wha7 16 | LXbmPJDtGy7PLiQ/kEKOlw== 17 | -----END CERTIFICATE REQUEST----- 18 | -------------------------------------------------------------------------------- /code/server/static/svg/bread_events.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /code/server/static/svg/nav_events.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /code/server/static/svg/nav_events_hover.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /code/agents/windows/settings.ini: -------------------------------------------------------------------------------- 1 | # Monitoring Agent Configuration 2 | 3 | [configuration] 4 | # Set monitoring configuration 5 | log = False 6 | passphrase = secure_monitoring 7 | port = 8888 8 | secure = False 9 | server = 127.0.0.1 10 | 11 | [services] 12 | # Set services to be monitored 13 | s00 = Spooler 14 | s01 = LanmanServer 15 | 16 | [thresholds] 17 | # Thresholds (monitor,severity[1-4],value,compare[=,<,>],duration[seconds]) 18 | t00 = perf.filesystem.c.percent.used,4,85,>,900 19 | t01 = perf.filesystem.c.percent.used,3,90,>,900 20 | t02 = perf.filesystem.c.percent.used,2,95,>,900 21 | t03 = perf.filesystem.c.percent.used,1,99,>,900 22 | t04 = perf.memory.percent.used,4,85,>,900 23 | t05 = perf.memory.percent.used,3,90,>,900 24 | t06 = perf.memory.percent.used,2,95,>,900 25 | t07 = perf.memory.percent.used,1,99,>,900 26 | t08 = perf.processor.percent.used,4,0,>,900 27 | t09 = perf.processor.percent.used,3,1,>,900 28 | t10 = perf.processor.percent.used,2,95,>,900 29 | t11 = perf.processor.percent.used,1,99,>,900 30 | t12 = perf.service.spooler.state,3,0,=,300 31 | t13 = perf.service.lanmanserver.state,3,0,=,300 32 | -------------------------------------------------------------------------------- /code/server/templates/password.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block breadcrumbs %} 3 |   Settings  4 | {% endblock %} 5 | {% block body%} 6 | 7 |
8 |
Change Password
9 |
10 |
11 |
12 |
13 |
Old Password
14 |
15 |
16 |
17 |
New Password
18 |
19 |
20 |
21 |
22 | 23 |
24 |
25 |
26 | 27 | 28 | {% endblock %} 29 | -------------------------------------------------------------------------------- /code/server/templates/user_pass.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block breadcrumbs %} 3 |   Settings  4 | {% endblock %} 5 | {% block body%} 6 | 7 |
8 |
Change Password
9 |
10 |
11 | 12 |
13 |
14 |
Enter New Password
15 |
16 |
17 |
18 |
Enter New Password Again
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 | 27 | {% endblock %} 28 | -------------------------------------------------------------------------------- /code/server/templates/notify.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block breadcrumbs %} 3 |   Settings  4 | {% endblock %} 5 | {% block body%} 6 | 7 |
8 |
Notify
9 | 10 | 11 | 25 | 26 |
12 | 13 | {% for i in notify %} 14 | 15 | 16 | 20 | 21 | {% endfor %} 22 |
{{i.notify_name}} 17 | 18 | 19 |
23 |
24 |
27 |
28 | 29 | 30 | {% endblock %} -------------------------------------------------------------------------------- /code/server/certificates/localhost.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDSjCCAjICCQDGVcVl7wOn8zANBgkqhkiG9w0BAQsFADBnMQswCQYDVQQGEwJV 3 | UzESMBAGA1UECAwJTWlubmVzb3RhMRIwEAYDVQQHDAlSb3NlbW91bnQxDTALBgNV 4 | BAoMBE5vbmUxDTALBgNVBAsMBE5vbmUxEjAQBgNVBAMMCWxvY2FsaG9zdDAeFw0x 5 | ODA2MjYxMjA4MTlaFw00NTExMTAxMjA4MTlaMGcxCzAJBgNVBAYTAlVTMRIwEAYD 6 | VQQIDAlNaW5uZXNvdGExEjAQBgNVBAcMCVJvc2Vtb3VudDENMAsGA1UECgwETm9u 7 | ZTENMAsGA1UECwwETm9uZTESMBAGA1UEAwwJbG9jYWxob3N0MIIBIjANBgkqhkiG 8 | 9w0BAQEFAAOCAQ8AMIIBCgKCAQEA6Szljlmniu9EA9yX+ji4MVBfvAeIpC8cd6Tq 9 | 4GwcIubiDWE7jYJ9ExBjla4yMIMUtcX4TYe9wDpT+ltNW+l2bLEHXyDrF+nWgMJP 10 | 5JuRd0VMKmHI7epahftyzwFp/6HrGtO1BR9i0Opa5gFWXSjZ2nWFe9BZsx2QvFwe 11 | YtRAxc5cTMEHWHKFL0Gb/mMN/kcjekwAsY7yohR7vBi7015k+LE69FBMKjMyRVaa 12 | d6eUtFDXoiaFFjdntNc8rNlFzYv8IpIlhlm3NaHVl5O7UYrgYDofK+xIsNu9sobr 13 | JvpJQyz94k9Txr4QZPzN66XGC3sGDK2dDVPf4Clbygyb6qureQIDAQABMA0GCSqG 14 | SIb3DQEBCwUAA4IBAQAjemvss+yvqZZhzB1gdIYvizOSxBwsuUhfX6WOqIiCIIA7 15 | z3PQmLGtkznwvwJpoInPk1+4/wTtnto3XiA3h975zQCUfAQExkioSRZgmpQKEp/n 16 | 591rUbpSwJsI/k85p27FJosOTGv3+caAmflX9dllazgMnt9NbnGPxpD8iR3eMyqn 17 | 8zLvZR8O5/2pI1HfPAKIz++bsIOM5Fnwnum0E2njIr++XZzhpTW5bIHu6wgYlKlm 18 | 4RHRcrdYK71BaR500U5uxg9Qj1cZuiRTuRREIIwRdIt++HS7L1O5IT3qBZaZ7gw/ 19 | VjXoeYOQ8jDzKssIebDF+sWACRK/gcJToejGvgfa 20 | -----END CERTIFICATE----- 21 | -------------------------------------------------------------------------------- /code/server/templates/user_role.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block breadcrumbs %} 3 |   Settings  4 | {% endblock %} 5 | {% block body%} 6 | 7 | 8 |
9 |
Assign role for {{user}}
10 |
11 |
12 | 13 |
14 |
15 |
Role
16 | {% if role == 0 %} 17 |
User Admin
18 | {% elif role == 1 %} 19 |
User Admin
20 | {% endif %} 21 |
22 |
23 | 24 | 25 |
26 |
27 |
28 | 29 | {% endblock %} 30 | -------------------------------------------------------------------------------- /code/server/templates/users.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block breadcrumbs %} 3 |   Settings  4 | {% endblock %} 5 | {% block body%} 6 | 7 | 8 |
9 |
Users
10 |
11 |
12 | {% for i in users %} 13 |
14 |
{{i[1]}}
15 |
16 | 17 | 18 | 19 |
20 |
21 | {% endfor %} 22 |
23 |
24 | 25 |
26 |
27 | 28 | {% endblock %} 29 | -------------------------------------------------------------------------------- /code/server/templates/user_add.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block breadcrumbs %} 3 |   Settings  4 | {% endblock %} 5 | {% block body%} 6 | 7 |
8 |
Users
9 |
10 |
11 |
12 |
13 |
User
14 |
15 |
16 |
17 |
New Password
18 |
19 |
20 |
21 |
Role
22 |
User Admin
23 |
24 |
25 |
26 | 27 |
28 |
29 |
30 | 31 | 32 | {% endblock %} -------------------------------------------------------------------------------- /code/server/static/svg/bread_contact.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /code/agents/windows/service_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 servicemanager 7 | import socket 8 | import sys 9 | import win32event 10 | import win32service 11 | import win32serviceutil 12 | # User classes 13 | import agent 14 | 15 | class AgentService(win32serviceutil.ServiceFramework): 16 | _svc_name_ = "MonitoringAgent" 17 | _svc_display_name_ = "Monitoring Agent" 18 | 19 | def __init__(self, args): 20 | win32serviceutil.ServiceFramework.__init__(self, args) 21 | self.hWaitStop = win32event.CreateEvent(None, 0, 0, None) 22 | socket.setdefaulttimeout(60) 23 | 24 | def SvcStop(self): 25 | agent.AgentSettings.running = False 26 | self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING) 27 | win32event.SetEvent(self.hWaitStop) 28 | 29 | def SvcDoRun(self): 30 | agent.AgentProcess.initialize_agent() 31 | agent.AgentProcess.run_process() 32 | 33 | if __name__ == '__main__': 34 | if len(sys.argv) == 1: 35 | servicemanager.Initialize() 36 | servicemanager.PrepareToHostSingle(AgentService) 37 | servicemanager.StartServiceCtrlDispatcher() 38 | else: 39 | win32serviceutil.HandleCommandLine(AgentService) 40 | -------------------------------------------------------------------------------- /code/server/templates/reports.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block breadcrumbs %} 3 |   Reports  4 | {% endblock %} 5 | {% block body%} 6 | 7 |
8 |
Reports
9 |
10 |
11 |
12 |
Device Report: An overview of all device configuration data.
13 |
14 |   15 | 16 |
17 |
18 |
19 |
Event Report: An export of all open events.
20 |
21 |   22 | 23 |
24 |
25 |
26 |
27 | 28 | {% endblock %} -------------------------------------------------------------------------------- /code/server/templates/base.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Monitoring 5 | 6 | 7 | 8 | 9 |
16 |
17 |
18 | 19 | 20 | 21 |
22 |
23 |
24 |
25 | {% block breadcrumbs %}{% endblock %} |   26 | 27 |   {{session['user']}}   (Log Out) 28 |
29 | {% block body %}{% endblock %} 30 |
31 |
32 | 33 | 34 | -------------------------------------------------------------------------------- /code/server/static/svg/bread_settings.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /code/server/static/svg/nav_settings.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /code/server/static/svg/nav_settings_hover.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /code/server/static/svg/bread_home.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /code/server/static/svg/nav_home.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /code/server/static/svg/nav_home_hover.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /code/server/static/svg/bread_device.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /code/server/static/svg/nav_device.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /code/server/certificates/localhost.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEogIBAAKCAQEA6Szljlmniu9EA9yX+ji4MVBfvAeIpC8cd6Tq4GwcIubiDWE7 3 | jYJ9ExBjla4yMIMUtcX4TYe9wDpT+ltNW+l2bLEHXyDrF+nWgMJP5JuRd0VMKmHI 4 | 7epahftyzwFp/6HrGtO1BR9i0Opa5gFWXSjZ2nWFe9BZsx2QvFweYtRAxc5cTMEH 5 | WHKFL0Gb/mMN/kcjekwAsY7yohR7vBi7015k+LE69FBMKjMyRVaad6eUtFDXoiaF 6 | FjdntNc8rNlFzYv8IpIlhlm3NaHVl5O7UYrgYDofK+xIsNu9sobrJvpJQyz94k9T 7 | xr4QZPzN66XGC3sGDK2dDVPf4Clbygyb6qureQIDAQABAoIBACMYZtSuGdGHga0z 8 | HozCzLZfaolaeO59SMOZRuDQZMu8BHoPEG+UHE8qQFLfDR7QG+XgDiddLuon3Uvp 9 | QhN7aB+j5YAj7D6FK4vlszCtWY+iFnyqixuEEmvoFLejdtjK/h/jjXNf7feTzmqf 10 | +Oe8pk2DMrwX9+hFous20jv9xmH/C7pVudQHki8zPsupHiYmoIBcl6yV6JE0VPiP 11 | 11eYYLSU9Sc9wEd9M+eyz5sopIQ34xSFwqSrclzJnWtiQITi+Hybad7bkbUWWyhv 12 | C9gku9LlrwEbLGj5WQUoJMzrWs5ItJWcTZAesMU/JMbs+g1UmRtEAry+LLqKDVNl 13 | Apc1XSECgYEA/khrroes4Kt2fnF3m8HwnV7AGRgdWCC4VgkSfR4lf2ERaEXMgqgK 14 | 6wQUvlQTq3D5Sgwmk2ds86v7xrY/Zlvcq+wBdAfVoWq7I1urHUFo/+NT6hutiEwY 15 | 7sA6ba5aEYdNcVQrYRaDgRBzuQpvBSSyXVG09o76mUTYqQb5spEgeQsCgYEA6r/8 16 | yow4TBlZY8FzZI0v8No4cGSP/ZCY0huKthssOVfxyg9mUSiHfVFHEY0LTaBzZJHT 17 | U547Aqp+LjQQ8pFcVP/b5IK8IPy6rHOzxR4HMrdo9fouNMNJtkcvyYmln4DRJgho 18 | Yab7oHX3EdDEHrABlquulJ5MkpQUfa02d6uOaAsCgYANcrMPNem0sBzCLyoOOMGK 19 | 8RnjudzDbmeRVUV/DHsbM30CML7SIiUBrOOT9UpdBMqKWf4oSY3/jRbxuW288hrH 20 | lOG2yztHICfphM2V7IGrFtC3TuNsk+m1psDwUAo/ZxJ3beYlalVmHAKixKR+dyRf 21 | vxR8/a7PME7tEYtfIEHBZQKBgFYBjxF6fCnNY8W7m5Zsb/MTUHhFL8gb0RGxi4ma 22 | vQIpaWm5aKg1bCaKv7n7bfe74GspTj6li0M3ydENvGPSw/xUXXEEQP7o+U+zeFqw 23 | 3LxFHEx6o7ErF3eT7zssliSxIm+Hxw54ClrIvYOmdCQ2Ie7c6bKDqNRPMo61Bm6j 24 | ySHvAoGAeFXUJ4fIP8nolGK6Q4SAQ2AQTkXJmUp8+l1dO4OOGKPQjBIAoax7Fvj0 25 | Ut6tYjSp/LG5sHPmbKTtMaS5xrXSyMAKvde/f41NU41I0mTvNIkdPsJ/5eQSqXcN 26 | La9tDfq9aIVZePPQq4kok/NN6QOLIaI6flKeiV0Biff8amAk4QA= 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /code/server/static/svg/nav_device_hover.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /code/server/certificates/localhost.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | Proc-Type: 4,ENCRYPTED 3 | DEK-Info: AES-256-CBC,AD408974A8FFF0801716BE707EA64404 4 | 5 | kKIdNJAPtyHrdOHSVdsb4GIL+dJvtcSJEk5ieTk3MRGJ1bFi1zrBpK6cQcLi1Oaa 6 | 4O3wk7H/MOGHZ13pv8gyg6kYW8zYOJA8iX51/TBGKu3mrT0CiECobPYTMZz7BniF 7 | KLj1QV0n9b6YtLvIYsk+ecObtoq8h2vqm8aQlnldKG2a/i5YkDR736t3K47Mg7o0 8 | QLORMb8YjkweTWSiZIHgzDWj/1Jjo3HQC14f0GIZLQlfKYbH3ITilNsO8sHohevn 9 | GdvLo6qNs4sf9mPenhU5wI9Jquz6vf9RCzrcq0eIMvscku4J3wWAOKZ8eep5xPvA 10 | fNrvLzHghqa7eBaShaMCPJ31Rmlkv/K8sb/JdDBsqhDy4ounykfmQvbJ/KaTy3HZ 11 | uUmwjZk73Vy6CYDoWFV1Mdp//Pn6i++vrAcaICKR6ADhgN73hbT8KYTGw5K1SeFN 12 | tPIAjmDWkwMjeKx7XoENTYDSomkxvfZ1jOXkn1LXPqrBDAPtJvZ0MUjI/B72xaEm 13 | E/MXkbuBUShUAS2T0FrHtwU9d5k/rmejVOcGRqLTpnqE0PdFt8QkchRPHQ6SVrLy 14 | KL+O6c4h1SOIhISREGkajB9xXVyrqSoPzuFAunJNQEcZYAarj6GxbDmIy35KYbml 15 | WQkn4YvLhPmUzqHTJV+AhBBBNWOaO5wt/RFP+zRoxDCSbA+Aav4UJ3wRJ1nrr6ny 16 | 12yofJ4+P2yWIDSSUy6QeVs78TrhzAu933lP5bX0AltIAHzx66Qpl0T1WNhRuLBn 17 | xTgP1oB266QGE9U0Y3iqc4q/ftKN9QGDGr0Yqq6ruTAyQ6s4zcHyma/tTeAUS/jL 18 | Q3e8APjo+jesjAaPRve9nIEoKQoevgmho6j2A/Gbdg3zPy0OII/BtL+Z/GHII3Rm 19 | Kses3kG28WP8CM44yJYbDi+6fuW7sOQgkWGqVIh8tRowRZ+o+vr6uub0hqP30gwM 20 | SVQMqmGzpH8E7ZdfiJV1JGcJ2W8y0gvi37iRg7PFT/nFAY7x/4AWLTgZKaKF4inn 21 | fqvR1QP2HqPt0ohlh/bHtCGeIltUDLJEoqcOKX4MRPReOdrul0dma/LgFfgQgkSe 22 | B+aQqpjX7iAppkkhGoiHZMawgOz0nCWIZHUm7DUHQMWGaNHYb6tvC099xlax91uo 23 | HyNs/cOr5UbT4QJPe2j6tE6KLkl23ddW2kNbq2i2N2zP8zG0Gy/FPaM+ep8Y9CfZ 24 | 2/VSoW7eeJWgAD2rVLUoxItc/SEThI7H6qKScXy/EeJeOWBlYrWatS3eHujLO8nN 25 | VEpImJy/clyynkEE582syTDIrrP3Tyg4HSTLGAyJUMm2iJZ6SzCTzk1adaRc6PYG 26 | L8VyiRd7jRN+P/pYkWvbAdyCfXvtPUe3nrvLdQn0nGgTBMnOpWbRHhVdaK36cmyQ 27 | W85lWk1MVfqRCdNDX3YsKadN4PCjwZcKNdddXnsUOzMHpxE9ev9nOdQFxJixRLw5 28 | MuMvGJFm0wTO+uRyL78Zyd5+Q8OaxpcEg9a8N8QTUZob7sdYRDIyZIu/G/xBLFuj 29 | OMqWEbmXPToE+Ef8A12LXkV3/MSbz21NMkwbpFZwAhbWOz3wm7BOsdu3HmnicS8z 30 | -----END RSA PRIVATE KEY----- 31 | -------------------------------------------------------------------------------- /install/mserver: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # This should install the server components and register them correctly. 4 | # You will still have to run the create database sql script in mysql or mariadb 5 | # To use this file run: 6 | # chmod 755 mserver 7 | # sudo ./mserver 8 | 9 | # Set variables 10 | directory='/opt/monitoring' 11 | py_ver='python' 12 | # py_ver must specify a python 3 executable. For newer Fedora releases 'python' works fine. If you have issues on your distro, try 'python3' 13 | 14 | # Create program directory 15 | mkdir -p $directory 16 | cp -R ../code/server $directory 17 | 18 | # Set up Python 19 | $py_ver -m venv $directory/env 20 | $directory/env/bin/$py_ver -m pip install flask gunicorn 21 | 22 | # Write systemd service files 23 | 24 | cat > /lib/systemd/system/mcserver.service < /lib/systemd/system/meserver.service < /lib/systemd/system/mwserver.service < 3 |
Event Summary
4 |
5 |
6 |
{{status_text}}
7 |
  {{total}}  Total
8 |
  {{info}}  Information
9 |
  {{warn}}  Warning
10 |
  {{majr}}  Major
11 |
  {{crit}}  Critical
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 | Events
20 |
21 | {% for i in event_list %} 22 |
23 |
{{i.date}}
24 |
{{i.severity}}
25 |
{{i.name}}
26 |
{{i.message}}
27 |
28 |
29 | {% endfor %} 30 |
31 |
-------------------------------------------------------------------------------- /code/server/static/style.css: -------------------------------------------------------------------------------- 1 | 2 | html{max-width: 1200px;} 3 | body{margin:0px;padding:0px;height:100%;width:100%;font:12px "Segoe UI", "Helvetica Neue", Helvetica, Arial, sans-serif;color:#8E8C84;} 4 | 5 | 6 | .action-button{border-radius:2px;color:#FFF;font-size:10px;background:#D9534F;padding:3px 5px;text-decoration: none;font-weight:bold;border-style:none;} 7 | .action-button:hover{background-color:#D23430;border-color: #C9302C;} 8 | 9 | .bread-font{color:#8E8C84;height:14px;width:14px;} 10 | 11 | .card-div{background-color: #FFFFFF; border: 1px solid #DFD7CA; border-radius: 3px; min-width: 100px;} 12 | .card-header {padding:3px;margin-bottom: 0;background-color:#F8F5F0;border-bottom: 1px solid #DFD7CA;font-weight: bold; font-size:11pt;} 13 | 14 | .device-stats {font-size:40px;font-weight:bold;color:#29ABE0 !important;height:50px;line-height:50px} 15 | .device-stats a:link {font-size:40px;font-weight:bold;columns: #29abe0 !important;text-decoration:none} 16 | .device-stats a:active {font-size:40px;font-weight:bold;color:#29ABE0 !important;text-decoration:none} 17 | .device-stats a:hover {font-size:40px;font-weight:bold;color:#29ABE0 !important;text-decoration:none} 18 | .device-stats a:visited {font-size:40px;font-weight:bold;color:#29ABE0 !important;text-decoration:none} 19 | 20 | .index-table {display: table; width: 100%;} 21 | .index-row {display: table-row} 22 | .index-cell {display: table-cell; padding:5px} 23 | 24 | .mon-container{padding-left:70px;padding-top:50px;padding-right:20px} 25 | .mon-container a:link {color:#8E8C84;text-decoration:none} 26 | .mon-container a:active{color:#8E8C84;text-decoration:none} 27 | .mon-container a:hover{color:#8E8C84;text-decoration:none} 28 | .mon-container a:visited {color:#8E8C84;text-decoration:none} 29 | 30 | .nav-sidebar{height:100%; width:50px; background-color:#325D88; position:fixed!important;} 31 | .nav-button{display: block; cursor: pointer; height: 48px; background-size: 18px; background-repeat: no-repeat; background-position: center;} 32 | .nav-button:hover{background-color: #EEEEEE!important; background-size: 18px; background-repeat: no-repeat; background-position: center;} 33 | .nav-home{background-image: url(/static/svg/nav_home.svg);} 34 | .nav-home:hover{background-image: url(/static/svg/nav_home_hover.svg);} 35 | .nav-events{background-image: url(/static/svg/nav_events.svg);} 36 | .nav-events:hover{background-image: url(/static/svg/nav_events_hover.svg);} 37 | .nav-devices{background-image: url(/static/svg/nav_device.svg);} 38 | .nav-devices:hover{background-image: url(/static/svg/nav_device_hover.svg);} 39 | .nav-reports{background-image: url(/static/svg/nav_chart.svg);} 40 | .nav-reports:hover{background-image: url(/static/svg/nav_chart_hover.svg);} 41 | .nav-settings{background-image: url(/static/svg/nav_settings.svg);} 42 | .nav-settings:hover{background-image: url(/static/svg/nav_settings_hover.svg);} 43 | 44 | .search-div{float:right;width:200px;padding-top:8px} 45 | .search-svg{color:#8E8C84;height:16px;width:16px;line-height:16px;vertical-align: text-top} 46 | .search-button{border-radius:3px;color:#FFF;font-size:11px;background:#93C54B;padding:5px 5px;text-decoration:none;font-weight:bold;border-style:none;} 47 | .search-button:hover{background-color: #80b139;border-color: #79A736;} 48 | 49 | .text-input{height:18px;border-radius:3px;border: 1px solid #dfd7ca; font-size: 12px; color:#8E8C84;padding-left:5px} 50 | 51 | .user-div{position:absolute;top:14px;left:74px;font-size:12px;padding-bottom: 4px;display: flex;align-items:center;} 52 | .user-font{color:#8E8C84;height:12px;width:12px;} 53 | 54 | -------------------------------------------------------------------------------- /code/server/templates/notify_add.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block breadcrumbs %} 3 |   Settings  4 | {% endblock %} 5 | {% block body%} 6 | 7 |
8 |
Add Notifcation Rule
9 | 10 | 11 | 71 | 72 |
12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | {% if agent_status == 1 %} 39 | 40 | {% else %} 41 | 42 | {% endif %} 43 | 44 | 45 | 46 | {% if agent_severity == '4' %} 47 | 48 | {% elif agent_severity == '3' %} 49 | 50 | {% elif agent_severity == '2' %} 51 | 52 | {% elif agent_severity == '1' %} 53 | 54 | {% endif %} 55 | 56 | 57 | 58 | {% if notify_enabled == 1 %} 59 | 60 | {% else %} 61 | 62 | {% endif %} 63 | 64 | 65 | 66 | 67 | 68 |
Rule Name
Email Address
Agent Name 25 | 30 |
Monitor
Status Open Closed Open Closed
Severity
Enabled True False True False
69 |
70 |
73 |
74 | 75 | {% endblock %} -------------------------------------------------------------------------------- /code/server/templates/notify_edit.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block breadcrumbs %} 3 |   Settings  4 | {% endblock %} 5 | {% block body%} 6 | 7 |
8 |
Add Notifcation Rule
9 | 10 | 11 | 75 | 76 |
12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | {% if agent_status == 1 %} 43 | 44 | {% else %} 45 | 46 | {% endif %} 47 | 48 | 49 | 50 | {% if agent_severity == '4' %} 51 | 52 | {% elif agent_severity == '3' %} 53 | 54 | {% elif agent_severity == '2' %} 55 | 56 | {% elif agent_severity == '1' %} 57 | 58 | {% endif %} 59 | 60 | 61 | 62 | {% if notify_enabled == 1 %} 63 | 64 | {% else %} 65 | 66 | {% endif %} 67 | 68 | 69 | 70 | 71 | 72 |
Rule Name
Email Address
Agent Name 25 | 34 |
Monitor
Status Open Closed Open Closed
Severity
Enabled True False True False
73 |
74 |
77 |
78 | 79 | {% endblock %} -------------------------------------------------------------------------------- /code/server/templates/notify_form.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block breadcrumbs %} 3 |   Settings  4 | {% endblock %} 5 | {% block body%} 6 | 7 |
8 |
Add Notification Rule
9 | 10 | 11 | 75 | 76 |
12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | {% if agent_status == 1 %} 43 | 44 | {% else %} 45 | 46 | {% endif %} 47 | 48 | 49 | 50 | {% if agent_severity == '4' %} 51 | 52 | {% elif agent_severity == '3' %} 53 | 54 | {% elif agent_severity == '2' %} 55 | 56 | {% elif agent_severity == '1' %} 57 | 58 | {% endif %} 59 | 60 | 61 | 62 | {% if notify_enabled == 1 %} 63 | 64 | {% else %} 65 | 66 | {% endif %} 67 | 68 | 69 | 70 | 71 | 72 |
Rule Name
Email Address
Agent Name 25 | 34 |
Monitor
Status Open Closed Open Closed
Severity
Enabled True False True False
73 |
74 |
77 |
78 | 79 | {% endblock %} -------------------------------------------------------------------------------- /code/server/templates/device_content.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 |
6 |
Name: {{name}}
7 |
IP Address: {{ipaddress}}
8 |
Domain: {{domain}}
9 |
Platform: {{platform}} ({{architecture}})
10 |
Build: {{build}}
11 |
Processors: {{processors}}
12 |
Memory: {{memory}} MB
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
Processor (% used)
23 | 24 |
25 |
26 |
27 |
28 |
Memory (% used)
29 | {{mem_perc}} 30 |
31 |
32 |
33 |
34 |
Pagefile (% used)
35 | {{pagefile_perc}} 36 |
37 |
38 |
39 |
40 |
Uptime (days)
41 | {{uptime_days}} 42 |
43 |
44 |
45 | 46 |
47 |
48 |
49 |
50 |
51 |
52 |
Filesystem Monitors
53 |
54 |
55 | {% for i in filesystem %} 56 |
57 |
{{i[0]}}
58 | 59 |
60 | {% endfor %} 61 |
62 |
63 |
64 |
65 |
66 |
67 |
Network Monitors
68 |
69 |
70 |
71 |
Total Network Traffic
72 | 73 |
74 | 78 |
79 |
80 |
81 |
82 |
-------------------------------------------------------------------------------- /code/server/services/collect.py: -------------------------------------------------------------------------------- 1 | import asyncio, configparser, json, os, ssl, time, sqlite3 2 | 3 | class CollectSettings: 4 | server = "0.0.0.0" 5 | port = 8888 6 | secure = False 7 | running = 1 8 | dbhost = 'localhost' 9 | dbuser = 'monitoring' 10 | dbpassword = 'monitoring' 11 | database = 'monitoring' 12 | cert_name = 'localhost.crt' 13 | cert_key = 'localhost.pem' 14 | cert_path = './certificates/' 15 | passphrase = 'secure_monitoring' 16 | 17 | class CollectLoad: 18 | def load_config(): 19 | try: 20 | CollectSettings.running = 1 21 | parser = configparser.ConfigParser() 22 | parser.read(CollectSettings.app_path + 'settings.ini') 23 | certificates = dict(parser.items('certificates')) 24 | database = dict(parser.items('database')) 25 | server = dict(parser.items('server')) 26 | CollectSettings.port = int(server['port_collect']) 27 | CollectSettings.secure = eval(server['secure']) 28 | CollectSettings.passphrase = server['passphrase'] 29 | CollectSettings.cert_key = certificates['key'] 30 | CollectSettings.cert_name = certificates['name'] 31 | except: pass 32 | 33 | class CollectData: 34 | 35 | def __init__(self): 36 | self.con = sqlite3.connect('/opt/monitoring/server/database/flask.db') 37 | self.cursor = self.con.cursor() 38 | 39 | def __del__(self): 40 | self.con.close() 41 | 42 | def agent_system(self, timestamp, name, domain, ipaddress, platform, build, architecture, processors, memory): 43 | print(timestamp, name, domain, ipaddress, platform, build, architecture, processors, memory) 44 | sql = "SELECT name FROM agentsystem where name=?;" 45 | self.cursor.execute(sql, (name,)) 46 | result = self.cursor.fetchone() 47 | qname = "" 48 | print("name=", name, "result=", result) 49 | if result != None: 50 | qname = result[0] 51 | if name == qname: 52 | sql = "UPDATE agentsystem SET timestamp=?, domain=?, ipaddress=?, platform=?, build=?, architecture=?, processors=?, memory=? WHERE name=?" 53 | self.cursor.execute(sql, (timestamp, domain, ipaddress, platform, build, architecture, processors, memory, name)) 54 | self.con.commit() 55 | else: 56 | sql = "INSERT INTO agentsystem (timestamp, name, domain, ipaddress, platform, build, architecture, processors, memory) VALUES(?,?,?,?,?,?,?,?,?)" 57 | self.cursor.execute(sql, (timestamp, name, domain, ipaddress, platform, build, architecture, processors, memory)) 58 | self.con.commit() 59 | 60 | def agent_data(self, values): 61 | sql = f"INSERT INTO agentdata (timestamp, name, monitor, value) VALUES {values}" 62 | self.cursor.execute(sql) 63 | self.con.commit() 64 | 65 | def agent_events_open(self, timestamp, name, monitor, message, status, severity): 66 | sql = "INSERT INTO agentevents (timestamp, name, monitor, message, status, severity, processed) VALUES(?,?,?,?,?,?,?)" 67 | self.cursor.execute(sql, (timestamp, name, monitor, message, status, severity, 0)) 68 | self.con.commit() 69 | 70 | def agent_events_close(self, name, monitor, severity): 71 | sql = "UPDATE agentevents SET status=0 AND processed=0 WHERE name=? AND monitor=? AND severity=? AND status=1" 72 | self.cursor.execute(sql, (name, monitor, severity)) 73 | self.con.commit() 74 | 75 | class CollectParse: 76 | def parse_data(message): 77 | CD = CollectData() 78 | message = json.loads(message) 79 | #print(message) 80 | passphrase = message["passphrase"] 81 | agentdata = '' 82 | if CollectSettings.passphrase == passphrase: 83 | timestamp = message["time"] 84 | name = message["name"] 85 | domain = message["domain"] 86 | ipaddress = message["ip"] 87 | platform = message["platform"] 88 | build = message["build"] 89 | architecture = message["arch"] 90 | processors = message["procs"] 91 | memory = message["memory"] 92 | CD.agent_system(timestamp, name, domain, ipaddress, platform, build, architecture, int(processors), float(memory)) 93 | for i in message["data"]: 94 | agentdata += f"({str(i[list(i)[0]])},'{name}','{list(i)[1]}',{str(i[list(i)[1]])})," 95 | for i in message["events"]: 96 | timestamp = i["time"] 97 | name = name 98 | monitor = i["monitor"] 99 | message = i["message"] 100 | status = i["status"] 101 | severity = i["severity"] 102 | if int(status) == 1: 103 | CD.agent_events_open(timestamp, name, monitor, message, status, severity) 104 | elif int(status) == 0: 105 | CD.agent_events_close(name, monitor, severity) 106 | CD.agent_data(agentdata[:-1]) 107 | 108 | 109 | class EchoServerProtocol(asyncio.Protocol): 110 | def connection_made(self, transport): 111 | self.transport = transport 112 | 113 | def data_received(self, data): 114 | if CollectSettings.running == 0: 115 | loop = asyncio.get_running_loop() 116 | loop.call_soon_threadsafe(loop.stop) 117 | message = data.decode() 118 | CollectParse.parse_data(message) 119 | self.transport.write(b'Received') 120 | self.transport.close() 121 | 122 | class CollectServer(): 123 | async def connection_loop(): 124 | loop = asyncio.get_running_loop() 125 | if CollectSettings.secure == True: 126 | ssl_context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH) 127 | ssl_context.options |= ssl.PROTOCOL_TLSv1_2 128 | ssl_context.load_cert_chain(certfile = CollectSettings.cert_path + CollectSettings.cert_name, keyfile = CollectSettings.cert_path + CollectSettings.cert_key) 129 | server = await loop.create_server(lambda: EchoServerProtocol(), CollectSettings.server, CollectSettings.port, ssl=ssl_context) 130 | else: server = await loop.create_server(lambda: EchoServerProtocol(), CollectSettings.server, CollectSettings.port) 131 | async with server: await server.serve_forever() 132 | 133 | def start_server(): 134 | CollectLoad.load_config() 135 | try: asyncio.run(CollectServer.connection_loop()) 136 | except: pass 137 | 138 | start_server() 139 | -------------------------------------------------------------------------------- /code/server/templates/index_content.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 |
6 | Host Availability 7 |
8 |
9 |
10 | 11 | 12 | 13 | 14 |
15 |
16 | 17 | 18 | 19 | {{avail_ok}} Hosts Up
20 | 21 | 22 | 23 | {{avail_down}} Hosts Down 24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 | Open Events 33 |
34 |
35 |
36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 |
44 |
45 | 46 | 47 | 48 | {{event_info}} Information
49 | 50 | 51 | 52 | {{event_warn}} Warning
53 | 54 | 55 | 56 | {{event_majr}} Major
57 | 58 | 59 | 60 | {{event_crit}} Critical 61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 | Monitoring Server 70 |
71 |
72 |
73 | Name: {{agent_name}}
Processors: {{agent_processors}}
Memory: {{agent_memory}} MB
Platform: {{agent_platform}} ({{agent_architecture}})
74 |
75 |
76 | 77 | 78 | 79 | {{agent_cpu_percent}}% CPU
80 | 81 | 82 | 83 | {{agent_memory_percent}}% Memory
84 | 85 | 86 | 87 | Agent Status
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 | Host Summary 99 |
100 | {% for i in host_summary %} 101 |
102 |
103 | 104 | 105 | 106 |  {{i.name}} 107 |
108 |
 IP Address: {{i.ipaddress}}
109 |
 Domain: {{i.domain}}
110 |
 Platform: {{i.platform}} ({{i.architecture}})
111 |
 {{i.date}}
112 |
113 | {% endfor %} 114 |
115 | {{pager|safe}} 116 |
117 |
-------------------------------------------------------------------------------- /code/server/services/event.py: -------------------------------------------------------------------------------- 1 | import datetime, configparser, os, smtplib, time, sqlite3 2 | from email.message import EmailMessage 3 | 4 | class EventSettings: 5 | app_path = './' 6 | availability_check = 300 7 | availability_severity = 1 8 | agent_retention = 2592000 9 | data_retention = 2592000 10 | event_retention = 2592000 11 | mailactive = 0 12 | mailadmin = 'monitoring@monitoring' 13 | mailserver = 'localhost' 14 | running = True 15 | 16 | class EventConfig: 17 | def load_config(): 18 | try: 19 | EventSettings.running = True 20 | parser = configparser.ConfigParser() 21 | parser.read(EventSettings.app_path + 'settings.ini') 22 | events = dict(parser.items('events')) 23 | mail = dict(parser.items('mail')) 24 | retention = dict(parser.items('retention')) 25 | EventSettings.agent_retention = int(retention['agent']) 26 | EventSettings.data_retention = int(retention['data']) 27 | EventSettings.event_retention = int(retention['event']) 28 | EventSettings.mailactive = int(mail['active']) 29 | EventSettings.mailserver = mail['server'] 30 | EventSettings.mailadmin = mail['admin'] 31 | EventSettings.availability_check = int(events['availability_check']) 32 | EventSettings.availability_severity = int(events['availability_severity']) 33 | except: pass 34 | 35 | class EventData: 36 | def __init__(self): 37 | self.con = sqlite3.connect('/opt/monitoring/server/database/flask.db') 38 | self.cursor = self.con.cursor() 39 | 40 | def __del__(self): 41 | self.con.close() 42 | 43 | def agent_select_id(self): 44 | sql = 'SELECT id from agentevents ORDER BY id DESC LIMIT 1' 45 | self.cursor.execute(sql) 46 | result = self.cursor.fetchone() 47 | result = str(result[0]) 48 | return result 49 | 50 | def agent_events_processed(self, id): 51 | sql = 'UPDATE agentevents SET processed=1 WHERE id<=?' 52 | self.cursor.execute(sql, str(id)) 53 | self.con.commit() 54 | 55 | def agent_filter_select(self, id): 56 | sql = '''select t1.email, t1.name, t2.id, t2.timestamp, t2.name, t2.monitor, t2.message, t2.severity, t2.status FROM notifyrule as t1 57 | INNER JOIN agentevents as t2 on 58 | t2.name LIKE t1.agent AND t2.monitor LIKE t1.monitor 59 | AND t2.status LIKE t1.status AND t2.severity LIKE t1.severity AND t2.processed=0 AND T2.id<=? AND t1.enabled=1''' 60 | self.cursor.execute(sql, (str(id),)) 61 | result = self.cursor.fetchall() 62 | self.agent_events_processed(id) 63 | return result 64 | 65 | def agent_avail_select(self, timestamp): 66 | sql = 'SELECT name FROM agentsystem WHERE timestamp < ?' 67 | self.cursor.execute(sql, (str(timestamp),)) 68 | result = self.cursor.fetchall() 69 | return result 70 | 71 | def agent_avail_event_open(self, timestamp, name, message, severity): 72 | sql = """INSERT INTO agentevents (timestamp, name, monitor, message, status, severity, processed) 73 | SELECT ?, ?, 'perf.system.availability.seconds', ?, 1, ?, 0 NOT IN (SELECT name FROM agentevents WHERE name=? AND monitor='perf.system.availability.seconds' AND status=1)""" 74 | self.cursor.execute(sql, (str(timestamp), name, message, str(severity), name)) 75 | self.con.commit() 76 | 77 | def agent_avail_select_event_open(self, timestamp): 78 | sql = """SELECT DISTINCT t1.name FROM agentevents as t1 79 | INNER JOIN agentdata as t2 on t1.name = t2.name 80 | WHERE t1.monitor='perf.system.availability.seconds' AND t1.status=1 AND t2.timestamp >=?""" 81 | self.cursor.execute(sql, (str(timestamp),)) 82 | result = self.cursor.fetchall() 83 | if not result is None: 84 | for i in result: 85 | name = i['name'] 86 | sql = "UPDATE agentevents SET status=0 WHERE name=?" 87 | self.cursor.execute(sql, name) 88 | self.con.commit() 89 | 90 | def remove_agents(self): 91 | sql = 'DELETE FROM agentsystem WHERE timestamp < ' + str(time.time() - EventSettings.agent_retention) 92 | self.cursor.execute(sql) 93 | self.con.commit() 94 | 95 | def remove_events(self): 96 | sql = 'DELETE FROM agentevents WHERE timestamp < ' + str(time.time() - EventSettings.event_retention) 97 | self.cursor.execute(sql) 98 | self.con.commit() 99 | 100 | def remove_data(self): 101 | sql = 'DELETE FROM agentdata WHERE timestamp < ' + str(time.time() - EventSettings.data_retention) 102 | self.cursor.execute(sql) 103 | self.con.commit() 104 | 105 | ED = EventData() 106 | 107 | class EventAvailable: 108 | def check_available(): 109 | '''try: 110 | check_time = str(time.time() - EventSettings.availability_check).split('.')[0] 111 | cur_time = str(time.time()).split('.')[0] 112 | hosts = ED.agent_avail_select(str(check_time)) 113 | for i in hosts: 114 | name = i[0] 115 | message = 'Agent not responding for ' + str(int(round(EventSettings.availability_check / 60,0))) + ' minutes' 116 | ED.agent_avail_event_open(cur_time, name, message, str(EventSettings.availability_severity)) 117 | except: pass''' 118 | 119 | check_time = str(time.time() - EventSettings.availability_check).split('.')[0] 120 | cur_time = str(time.time()).split('.')[0] 121 | hosts = ED.agent_avail_select(str(check_time)) 122 | for i in hosts: 123 | name = i[0] 124 | message = 'Agent not responding for ' + str(int(round(EventSettings.availability_check / 60,0))) + ' minutes' 125 | ED.agent_avail_event_open(cur_time, name, message, str(EventSettings.availability_severity)) 126 | 127 | 128 | def check_open(): 129 | check_time = str(time.time() - EventSettings.availability_check).split('.')[0] 130 | ED.agent_avail_select_event_open(check_time) 131 | 132 | '''try: 133 | check_time = str(time.time() - EventSettings.availability_check).split('.')[0] 134 | ED.agent_avail_select_event_open(check_time) 135 | except: pass''' 136 | 137 | class ServerEvent: 138 | def process_events(): 139 | id = ED.agent_select_id() 140 | output = ED.agent_filter_select(id) 141 | for i in output: 142 | notify_email = i[0] 143 | notify_name = i[1] 144 | name = i[4] 145 | monitor = i[5] 146 | message = i[6] 147 | severity = '' 148 | if i[7] == '1': severity = 'critical' 149 | if i[7] == '2': severity = 'major' 150 | if i[7] == '3': severity = 'warning' 151 | if i[7] == '4': severity = 'info' 152 | status = '' 153 | if i[8] == '0': status = 'closed' 154 | else: status = 'open' 155 | timestamp = int(i[3]) 156 | date = datetime.datetime.fromtimestamp(timestamp) 157 | email_subject = name + ':' + monitor + ':' + severity + ':' + status 158 | email_message = '''
message: ''' + message + '
name: ' + name + '
monitor: ' + monitor + '
severity: ' + severity + '
status: ' + status + '
time opened: ' + str(date) + '
policy: ' + notify_name + '
' 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 | ![WebSite](https://raw.githubusercontent.com/philipcwhite/Monitoring/master/images/home.png) 19 | Home View 20 | 21 | ![WebSite](https://raw.githubusercontent.com/philipcwhite/Monitoring/master/images/device.png) 22 | Device View 23 | 24 | ![WebSite](https://raw.githubusercontent.com/philipcwhite/Monitoring/master/images/graph.png) 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 |
8 |
Help
9 |
10 |

Table of Contents

11 | Introduction
12 | Agent
13 |  -Settings
14 | Server
15 |  -Settings
16 | Website
17 |  -Main
18 |  -Devices
19 |  -Events
20 |  -Reports
21 |  -Settings
22 |
23 |

Introduction

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 |

32 | conf.domain
33 | conf.ipaddress
34 | conf.memory.total
35 | conf.os.architecture
36 | conf.os.build
37 | conf.os.name
38 | conf.processors
39 |

40 | The agent also collect a variety of performance data. Some is OS specific and some can be configured on the agent.

41 | perf.memory.percent.used
42 | perf.filesystem.c.percent.used
43 | perf.network.bytes.received
44 | perf.network.bytes.sent
45 | perf.pagefile.percent.used
46 | perf.process.processname.state
47 | perf.processor.percent.used
48 | perf.service.spooler.state
49 | perf.system.uptime.seconds
50 |

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.

59 | log = False
60 | secure = True
61 | port = 8888
62 | secure = False
63 | server = 127.0.0.1

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 |

77 | top
78 | 79 |

Server

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.

93 | log = False
94 | ipaddress = 127.0.0.1
95 | passphrase = secure_monitoring
96 | port_collect = 8888
97 | port_web = 443
98 | secure = True
99 | session_expire = 3600

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.

105 | availability_check = 300
106 | availability_severity = 1
107 |
top
108 |

Website

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 (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 = '' 300 | html += '' 301 | html += '' 302 | html += '' 303 | html += '' 304 | html += '' 305 | html += '' 306 | html += f'{str(int(max_value))}' 307 | html += f'{str(int(max_value / 2))}' 308 | html += '0' 309 | html += f'{device_polyline}{device_time}' 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 | --------------------------------------------------------------------------------