From 78d46662b9748299ec600a4f5e9ddb74b904e994 Mon Sep 17 00:00:00 2001
From: maryamsaleem <maryamsaleem874@gmail.com>
Date: Thu, 6 Feb 2025 14:30:09 +0500
Subject: [PATCH] Sip module code added

---
 db/old_migrations/patch-add-sip-fields.sql |  67 ++++++
 db/old_migrations/patch-sip-auth.sql       |  11 +
 package-lock.json                          |  69 +++++-
 package.json                               |   2 +
 server/database.js                         |   2 +
 server/model/monitor.js                    | 238 ++++++++++++++++++++-
 server/notification-providers/sip.js       |  64 ++++++
 server/server.js                           |   7 +
 server/util-server.js                      | 210 ++++++++++++++++++
 src/lang/en.json                           |   7 +-
 src/pages/EditMonitor.vue                  | 131 +++++++++++-
 11 files changed, 789 insertions(+), 19 deletions(-)
 create mode 100644 db/old_migrations/patch-add-sip-fields.sql
 create mode 100644 db/old_migrations/patch-sip-auth.sql
 create mode 100644 server/notification-providers/sip.js

diff --git a/db/old_migrations/patch-add-sip-fields.sql b/db/old_migrations/patch-add-sip-fields.sql
new file mode 100644
index 000000000..25959e771
--- /dev/null
+++ b/db/old_migrations/patch-add-sip-fields.sql
@@ -0,0 +1,67 @@
+BEGIN TRANSACTION;
+
+
+
+ALTER TABLE monitor
+
+    ADD sip_auth_method VARCHAR(10) default null;  
+
+COMMIT;
+
+
+
+
+
+BEGIN TRANSACTION;
+
+
+
+ALTER TABLE monitor
+
+    ADD sip_protocol VARCHAR(10);  
+
+COMMIT;
+
+
+
+BEGIN TRANSACTION;
+
+
+
+ALTER TABLE monitor
+
+    ADD sip_port INT;  
+
+
+
+ALTER TABLE monitor
+
+    ADD sip_url VARCHAR(255);  
+
+
+
+COMMIT;
+
+BEGIN TRANSACTION;
+
+
+
+ALTER TABLE monitor
+
+    ADD sip_maintainence BOOLEAN;
+
+
+
+COMMIT;
+
+BEGIN TRANSACTION;
+
+
+
+ALTER TABLE monitor
+
+  ADD COLUMN sip_method VARCHAR(250) NULL;
+
+  
+
+COMMIT;
\ No newline at end of file
diff --git a/db/old_migrations/patch-sip-auth.sql b/db/old_migrations/patch-sip-auth.sql
new file mode 100644
index 000000000..0d3651f88
--- /dev/null
+++ b/db/old_migrations/patch-sip-auth.sql
@@ -0,0 +1,11 @@
+-- You should not modify if this have pushed to Github, unless it does serious wrong with the db.
+BEGIN TRANSACTION;
+
+ALTER TABLE monitor
+    ADD sip_basic_auth_user TEXT default null;
+
+ALTER TABLE monitor
+    ADD sip_basic_auth_pass TEXT default null;
+
+
+COMMIT;
diff --git a/package-lock.json b/package-lock.json
index 100cdcd88..39d8e8988 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
 {
     "name": "uptime-kuma",
-    "version": "2.0.0-dev",
+    "version": "2.0.0-beta.0",
     "lockfileVersion": 3,
     "requires": true,
     "packages": {
         "": {
             "name": "uptime-kuma",
-            "version": "2.0.0-dev",
+            "version": "2.0.0-beta.0",
             "license": "MIT",
             "dependencies": {
                 "@grpc/grpc-js": "~1.8.22",
@@ -75,6 +75,7 @@
                 "redbean-node": "~0.3.0",
                 "redis": "~4.5.1",
                 "semver": "~7.5.4",
+                "sip": "^0.0.6",
                 "socket.io": "~4.8.0",
                 "socket.io-client": "~4.8.0",
                 "socks-proxy-agent": "6.1.1",
@@ -82,6 +83,7 @@
                 "tcp-ping": "~0.1.1",
                 "thirty-two": "~1.0.2",
                 "tough-cookie": "~4.1.3",
+                "uuid": "^11.0.5",
                 "ws": "^8.13.0"
             },
             "devDependencies": {
@@ -1238,6 +1240,14 @@
                 "node": ">=16"
             }
         },
+        "node_modules/@azure/msal-node/node_modules/uuid": {
+            "version": "8.3.2",
+            "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
+            "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
+            "bin": {
+                "uuid": "dist/bin/uuid"
+            }
+        },
         "node_modules/@babel/code-frame": {
             "version": "7.26.0",
             "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.0.tgz",
@@ -5200,6 +5210,15 @@
                 "node": ">=10"
             }
         },
+        "node_modules/aedes/node_modules/uuid": {
+            "version": "8.3.2",
+            "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
+            "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
+            "dev": true,
+            "bin": {
+                "uuid": "dist/bin/uuid"
+            }
+        },
         "node_modules/agent-base": {
             "version": "7.1.1",
             "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz",
@@ -5620,6 +5639,11 @@
             "dev": true,
             "license": "MIT"
         },
+        "node_modules/async-limiter": {
+            "version": "1.0.1",
+            "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz",
+            "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ=="
+        },
         "node_modules/async-lock": {
             "version": "1.4.1",
             "resolved": "https://registry.npmjs.org/async-lock/-/async-lock-1.4.1.tgz",
@@ -10039,6 +10063,15 @@
                 "uuid-parse": "^1.1.0"
             }
         },
+        "node_modules/hyperid/node_modules/uuid": {
+            "version": "8.3.2",
+            "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
+            "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
+            "dev": true,
+            "bin": {
+                "uuid": "dist/bin/uuid"
+            }
+        },
         "node_modules/iconv-lite": {
             "version": "0.6.3",
             "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
@@ -14588,6 +14621,25 @@
             "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
             "license": "ISC"
         },
+        "node_modules/sip": {
+            "version": "0.0.6",
+            "resolved": "https://registry.npmjs.org/sip/-/sip-0.0.6.tgz",
+            "integrity": "sha512-t+FYic4EQ25GTsIRWFVvsq+GmVkoZhrcoghANlnN6CsWMHGcfjPDYMD+nTBNrHR/WnRykF4nqx4i+gahAnW5NA==",
+            "dependencies": {
+                "ws": "^6.1.0"
+            },
+            "engines": {
+                "node": ">=0.2.2"
+            }
+        },
+        "node_modules/sip/node_modules/ws": {
+            "version": "6.2.3",
+            "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.3.tgz",
+            "integrity": "sha512-jmTjYU0j60B+vHey6TfR3Z7RD61z/hmxBS3VMSGIrroOWXQEneK1zNuotOUrGyBHQj0yrpsLHPWtigEFd13ndA==",
+            "dependencies": {
+                "async-limiter": "~1.0.0"
+            }
+        },
         "node_modules/sirv": {
             "version": "3.0.0",
             "resolved": "https://registry.npmjs.org/sirv/-/sirv-3.0.0.tgz",
@@ -16205,12 +16257,15 @@
             }
         },
         "node_modules/uuid": {
-            "version": "8.3.2",
-            "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
-            "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
-            "license": "MIT",
+            "version": "11.0.5",
+            "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.0.5.tgz",
+            "integrity": "sha512-508e6IcKLrhxKdBbcA2b4KQZlLVp2+J5UwQ6F7Drckkc5N9ZJwFa4TgWtsww9UG8fGHbm6gbV19TdM5pQ4GaIA==",
+            "funding": [
+                "https://github.com/sponsors/broofa",
+                "https://github.com/sponsors/ctavan"
+            ],
             "bin": {
-                "uuid": "dist/bin/uuid"
+                "uuid": "dist/esm/bin/uuid"
             }
         },
         "node_modules/uuid-parse": {
diff --git a/package.json b/package.json
index 4f8eef138..5d21195f3 100644
--- a/package.json
+++ b/package.json
@@ -133,6 +133,7 @@
         "redbean-node": "~0.3.0",
         "redis": "~4.5.1",
         "semver": "~7.5.4",
+        "sip": "^0.0.6",
         "socket.io": "~4.8.0",
         "socket.io-client": "~4.8.0",
         "socks-proxy-agent": "6.1.1",
@@ -140,6 +141,7 @@
         "tcp-ping": "~0.1.1",
         "thirty-two": "~1.0.2",
         "tough-cookie": "~4.1.3",
+        "uuid": "^11.0.5",
         "ws": "^8.13.0"
     },
     "devDependencies": {
diff --git a/server/database.js b/server/database.js
index 3b7646de8..2ef1a2778 100644
--- a/server/database.js
+++ b/server/database.js
@@ -112,6 +112,8 @@ class Database {
         "patch-fix-kafka-producer-booleans.sql": true,
         "patch-timeout.sql": true,
         "patch-monitor-tls-info-add-fk.sql": true, // The last file so far converted to a knex migration file
+        "patch-add-sip-fields.sql": true,
+        "patch-sip-auth.sql": true,
     };
 
     /**
diff --git a/server/model/monitor.js b/server/model/monitor.js
index 3ad8cfafc..fed3278de 100644
--- a/server/model/monitor.js
+++ b/server/model/monitor.js
@@ -5,7 +5,7 @@ const { log, UP, DOWN, PENDING, MAINTENANCE, flipStatus, MAX_INTERVAL_SECOND, MI
     SQL_DATETIME_FORMAT, evaluateJsonQuery
 } = require("../../src/util");
 const { tcping, ping, checkCertificate, checkStatusCode, getTotalClientInRoom, setting, mssqlQuery, postgresQuery, mysqlQuery, setSetting, httpNtlm, radius, grpcQuery,
-    redisPingAsync, kafkaProducerAsync, getOidcTokenClientCredentials, rootCertificatesFingerprints, axiosAbortSignal
+    redisPingAsync, kafkaProducerAsync, getOidcTokenClientCredentials, rootCertificatesFingerprints, axiosAbortSignal, sipRegisterRequest,sipOptionRequest
 } = require("../util-server");
 const { R } = require("redbean-node");
 const { BeanModel } = require("redbean-node/dist/bean-model");
@@ -34,6 +34,163 @@ const rootCertificates = rootCertificatesFingerprints();
  *      2 = PENDING
  *      3 = MAINTENANCE
  */
+
+const sipStatusCodes = [
+    { status: 100,
+        msg: "Trying" },
+    { status: 180,
+        msg: "Ringing" },
+    { status: 181,
+        msg: "Call Being Forwarded" },
+    { status: 182,
+        msg: "Queued" },
+    { status: 183,
+        msg: "Session Progress" },
+    { status: 199,
+        msg: "Early Dialog Terminated" },
+    { status: 200,
+        msg: "OK" },
+    { status: 202,
+        msg: "Accepted" },
+    { status: 204,
+        msg: "No Notification" },
+    { status: 300,
+        msg: "Multiple Choices" },
+    { status: 301,
+        msg: "Moved Permanently" },
+    { status: 302,
+        msg: "Moved Temporarily" },
+    { status: 305,
+        msg: "Use Proxy" },
+    { status: 380,
+        msg: "Alternate Service" },
+    { status: 400,
+        msg: "Bad Request" },
+    { status: 401,
+        msg: "Unauthorized" },
+    { status: 402,
+        msg: "Payment Required" },
+    { status: 403,
+        msg: "Forbidden" },
+    { status: 404,
+        msg: "Not Found" },
+    { status: 405,
+        msg: "Method Not Allowed" },
+    { status: 406,
+        msg: "Not Acceptable" },
+    { status: 407,
+        msg: "Proxy Authentication Required" },
+    { status: 408,
+        msg: "Request Timeout" },
+    { status: 409,
+        msg: "Conflict" },
+    { status: 410,
+        msg: "Gone" },
+    { status: 411,
+        msg: "Length Required" },
+    { status: 412,
+        msg: "Conditional Request Failed" },
+    { status: 413,
+        msg: "Request Entity Too Large" },
+    { status: 414,
+        msg: "Request-URI Too Long" },
+    { status: 415,
+        msg: "Unsupported Media Type" },
+    { status: 416,
+        msg: "Unsupported URI Scheme" },
+    { status: 417,
+        msg: "Unknown Resource-Priority" },
+    { status: 420,
+        msg: "Bad Extension" },
+    { status: 421,
+        msg: "Extension Required" },
+    { status: 422,
+        msg: "Session Interval Too Small" },
+    { status: 423,
+        msg: "Interval Too Brief" },
+    { status: 424,
+        msg: "Bad Location Information" },
+    { status: 425,
+        msg: "Bad Alert Message" },
+    { status: 428,
+        msg: "Use Identity Header" },
+    { status: 429,
+        msg: "Provide Referrer Identity" },
+    { status: 430,
+        msg: "Flow Failed" },
+    { status: 433,
+        msg: "Anonymity Disallowed" },
+    { status: 436,
+        msg: "Bad Identity-Info" },
+    { status: 437,
+        msg: "Unsupported Certificate" },
+    { status: 438,
+        msg: "Invalid Identity Header" },
+    { status: 439,
+        msg: "First Hop Lacks Outbound Support" },
+    { status: 440,
+        msg: "Max-Breadth Exceeded" },
+    { status: 469,
+        msg: "Bad Info Package" },
+    { status: 470,
+        msg: "Consent Needed" },
+    { status: 480,
+        msg: "Temporarily Unavailable" },
+    { status: 481,
+        msg: "Call/Transaction Does Not Exist" },
+    { status: 482,
+        msg: "Loop Detected" },
+    { status: 483,
+        msg: "Too Many Hops" },
+    { status: 484,
+        msg: "Address Incomplete" },
+    { status: 485,
+        msg: "Ambiguous" },
+    { status: 486,
+        msg: "Busy Here" },
+    { status: 487,
+        msg: "Request Terminated" },
+    { status: 488,
+        msg: "Not Acceptable Here" },
+    { status: 489,
+        msg: "Bad Event" },
+    { status: 491,
+        msg: "Request Pending" },
+    { status: 493,
+        msg: "Undecipherable" },
+    { status: 494,
+        msg: "Security Agreement Required" },
+    { status: 500,
+        msg: "Internal Server Error" },
+    { status: 501,
+        msg: "Not Implemented" },
+    { status: 502,
+        msg: "Bad Gateway" },
+    { status: 503,
+        msg: "Service Unavailable" },
+    { status: 504,
+        msg: "Server Time-out" },
+    { status: 505,
+        msg: "Version Not Supported" },
+    { status: 513,
+        msg: "Message Too Large" },
+    { status: 555,
+        msg: "Push Notification Service Not Supported" },
+    { status: 580,
+        msg: "Precondition Failure" },
+    { status: 600,
+        msg: "Busy Everywhere" },
+    { status: 603,
+        msg: "Decline" },
+    { status: 604,
+        msg: "Does Not Exist Anywhere" },
+    { status: 606,
+        msg: "Not Acceptable" },
+    { status: 607,
+        msg: "Unwanted" },
+    { status: 608,
+        msg: "Rejected" },
+];
 class Monitor extends BeanModel {
 
     /**
@@ -155,6 +312,12 @@ class Monitor extends BeanModel {
             snmpVersion: this.snmpVersion,
             rabbitmqNodes: JSON.parse(this.rabbitmqNodes),
             conditions: JSON.parse(this.conditions),
+            sipUrl: this.sipUrl,
+            sipPort: this.sipPort,
+            sipProtocol: this.sipProtocol,
+			sipMethod: this.sipMethod,
+			sipMaintainence: this.isSipMaintainence(),
+			sipAuthMethod: this.sipAuthMethod,
         };
 
         if (includeSensitiveData) {
@@ -186,6 +349,8 @@ class Monitor extends BeanModel {
                 kafkaProducerSaslOptions: JSON.parse(this.kafkaProducerSaslOptions),
                 rabbitmqUsername: this.rabbitmqUsername,
                 rabbitmqPassword: this.rabbitmqPassword,
+                sip_basic_auth_user: this.sip_basic_auth_user,
+                sip_basic_auth_pass: this.sip_basic_auth_pass,
             };
         }
 
@@ -318,7 +483,13 @@ class Monitor extends BeanModel {
     getKafkaProducerAllowAutoTopicCreation() {
         return Boolean(this.kafkaProducerAllowAutoTopicCreation);
     }
-
+    /**
+     * Parse to boolean
+     * @returns {boolean} Sip Allow Maintainenece Option
+     */
+    isSipMaintainence() {
+        return Boolean(this.sipMaintainence);
+    }
     /**
      * Start monitor
      * @param {Server} io Socket server instance
@@ -874,7 +1045,68 @@ class Monitor extends BeanModel {
                     bean.status = UP;
                     bean.ping = dayjs().valueOf() - startTime;
 
-                } else {
+                } else if (this.type === "sip") {
+                    try {
+                        console.log("Ping Result:", this.sipMethod);
+                        let sipResponse;
+                        let sipMessage;
+                        let startTime = dayjs().valueOf();
+                        let totalResponseTime;
+                        let requestCount;
+                        if (this.sipMethod !== "OPTIONS") {
+                            sipResponse = await sipRegisterRequest(this.sipUrl, this.sipPort, this.sipProtocol, this.sip_basic_auth_user, this.sip_basic_auth_pass, version);
+                            let sipResponseTime = dayjs().valueOf() - startTime;
+                            totalResponseTime += sipResponseTime;
+                            console.log("sipResponse", sipResponse);
+                            console.log("this.sipMaintainence", this.sipMaintainence);
+                            const matchingStatus = sipStatusCodes.find(code => code.status === sipResponse?.status);
+                            if (matchingStatus) {
+                                sipMessage = `${sipResponse?.status}-${matchingStatus.msg}`;
+                                // Assuming UP and DOWN are previously defined constants or variables
+                                bean.status = sipResponse?.status === 200 ? UP : DOWN;
+                                console.log("sipResponse?.status", sipResponse?.status);
+                                // Additional check for 503 status within matchingStatus
+                                if (sipResponse?.status === 503 && this.sipMaintainence == 1) {
+                                    sipMessage = "Monitor under maintenance";
+                                    bean.status = MAINTENANCE;
+                                }
+                            } else {
+                                sipMessage = ` ${sipResponse?.status}-Not Ok`;
+                                bean.status = DOWN;
+                            }
+                   
+                        } else if (this.sipMethod === "OPTIONS") {
+                            sipResponse = await sipOptionRequest(this.sipUrl, this.sipPort, this.sipProtocol, this.sip_basic_auth_user, this.sip_basic_auth_pass, version);
+                            let sipOptionsResponseTime = dayjs().valueOf() - startTime;
+                            totalResponseTime = sipOptionsResponseTime;
+                            requestCount++;
+                            console.log("=====resposne status", sipResponse?.status);
+                            console.log("this.sipMaintainence", this.sipMaintainence);
+                            const matchingStatus = sipStatusCodes.find(code => code.status === sipResponse?.status);
+                            if (matchingStatus) {
+                                sipMessage = `${sipResponse?.status}-${matchingStatus.msg}`;
+                                // Assuming UP and DOWN are previously defined constants or variables
+                                bean.status = sipResponse?.status === 200 ? UP : DOWN;
+
+                                // Additional check for 503 status within matchingStatus
+                                if (sipResponse?.status === 503 && this.sipMaintainence == 1) {
+                                    sipMessage = "Monitor under maintenance";
+                                    bean.status = MAINTENANCE;
+                                }
+                            } else {
+                                sipMessage = ` ${sipResponse?.status}-Not Ok`;
+                                bean.status = DOWN;
+                            }
+                        }
+                        bean.ping = dayjs().valueOf() - startTime;
+                        bean.msg = sipMessage;
+                        // bean.msg = `${sipResponse?.status} - ${sipResponse?.reason}`
+                    } catch (error) {
+                        bean.msg = `Error: ${error.message}`;
+                        bean.status = DOWN;
+                    }
+                }
+                else {
                     throw new Error("Unknown Monitor Type");
                 }
 
diff --git a/server/notification-providers/sip.js b/server/notification-providers/sip.js
new file mode 100644
index 000000000..19c8959ce
--- /dev/null
+++ b/server/notification-providers/sip.js
@@ -0,0 +1,64 @@
+const NotificationProvider = require("./notification-provider");
+
+class SIP extends NotificationProvider {
+    name = "sip";
+    async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
+        let monitorName = monitorJSON ? monitorJSON["name"] : "Unknown Monitor";
+        let subject = this.updateSubject(msg, monitorName);
+        let body;
+        if (heartbeatJSON) {
+            body += `\nTime (${heartbeatJSON["timezone"]}): ${heartbeatJSON["localDateTime"]}`;
+        }
+        try {
+            return "SIP Message Sent Successfully.";
+        } catch (error) {
+            console.error("Error sending SIP message:", error);
+            throw new Error("Failed to send SIP message.");
+        }
+    }
+
+    updateSubject(message, monitorName) {
+        if (!message) return "Default Subject"; // Handle null/undefined message
+
+        message = message.toLowerCase(); // Normalize input
+
+        if (/\bdown\b/i.test(message) || message.includes("offline")) {
+            return `🚨 ❌ Service Impacted...`;
+        }
+        if (/\bup\b/i.test(message) || message.includes("online")) {
+            return `🚨 ✅ Service Restored...`;
+        }
+        if (message.includes("maintenance")) {
+            if (message.includes("begin")) {
+                return `🚧 🔧 ❌  Maintenance Start...`;
+            }
+            if (/\bend\b/i.test(message)) {
+                return `🚧 🔧 ✅ Maintenance Complete...`;
+            }
+            if (message.includes("scheduled")) {
+                return `🚧 🪟 📆  Maintenance Window Scheduled...`;
+            }
+            if (message.includes("window begin")) {
+                return `🚧 🪟 🛑  Maintenance Window Start...`;
+            }
+            if (message.includes("window end")) {
+                return `🚧 🪟 ✅  Maintenance Window Complete...`;
+            }
+        }
+        if (message.includes("started on node")) {
+            return `📈 🔬 ✅  Monitoring Start...`;
+        }
+        if (message.includes("started")) {
+            return `📈 🔬 ✅  ${monitorName}`;
+        }
+
+        return "Default Subject";
+    }
+
+    async sendSIPMessage(notification, sipMessage) {
+        console.log("Sending SIP message with config:", notification);
+        console.log("Message:", sipMessage);
+    }
+}
+
+module.exports = SIP;
diff --git a/server/server.js b/server/server.js
index ec5ad49f6..3669e9632 100644
--- a/server/server.js
+++ b/server/server.js
@@ -874,6 +874,13 @@ let needSetup = false;
                 bean.rabbitmqUsername = monitor.rabbitmqUsername;
                 bean.rabbitmqPassword = monitor.rabbitmqPassword;
                 bean.conditions = JSON.stringify(monitor.conditions);
+                bean.sipUrl = monitor.sipUrl;
+                bean.sipPort = monitor.sipPort;
+                bean.sip_basic_auth_user = monitor.sip_basic_auth_user;
+                bean.sip_basic_auth_pass = monitor.sip_basic_auth_pass;
+                bean.sipMaintainence = monitor.sipMaintainence;
+                bean.sipMethod = monitor.sipMethod;
+                bean.sipAuthMethod = monitor.sipAuthMethod;
 
                 bean.validate();
 
diff --git a/server/util-server.js b/server/util-server.js
index 5ebc62ac5..9101d3e30 100644
--- a/server/util-server.js
+++ b/server/util-server.js
@@ -31,6 +31,10 @@ const dayjs = require("dayjs");
 // eslint-disable-next-line no-unused-vars
 const { Kafka, SASLOptions } = require("kafkajs");
 const crypto = require("crypto");
+let sip = require("sip");
+const uuid = require("uuid");
+let sharedSipServer;
+const SERVER_PORT = 25060;
 
 const isWindows = process.platform === /^win/.test(process.platform);
 /**
@@ -259,7 +263,213 @@ exports.kafkaProducerAsync = function (brokers, topic, message, options = {}, sa
         });
     });
 };
+/**
+ * Sends a SIP REGISTER request
+ * @param {string} sipServer The SIP server to register with
+ * @param {string} transport The transport protocol to use (e.g., 'udp' or 'tcp')
+ * @returns {Promise<void>}
+ */
+exports.sipRegisterRequest = function (sipServer, sipPort, transport, username, password, version) {
+  
+    // eslint-disable-next-line no-async-promise-executor
+    return new Promise(async (resolve, reject) => {
+        try {
+            const registerRequest = {
+                method: 'REGISTER',
+                uri: `sip:${sipServer}:${sipPort}`,
+                headers: {
+                    to: { uri: `sip:${sipServer}:${sipPort}` },
+                    from: { uri: `sip:${username}` },
+                    'call-id': uuid.v4(),
+                    cseq: { method: 'REGISTER', seq: 1 },
+                    'content-length': 0,
+                    contact: { uri: `sip:${username}` },
+                    "User-Agent": "SIP Health Monitor " + version,
+                    "Expires": 60,
+                },
+                transport: transport,
+            };
+            const registrationResponse = await exports.sipRegister(registerRequest);
+            console.log("registrationResponse", registrationResponse);
+            if (registrationResponse.status === 407 && registrationResponse.headers["proxy-authenticate"]) {
+                const proxyAuthenticateHeader = registrationResponse.headers["proxy-authenticate"][0];
+                const authorizedRegisterRequest = exports.constructAuthorizedRequest(
+                    registerRequest,
+                    username,
+                    password,
+                    proxyAuthenticateHeader
+                );
 
+                const secondResponse = await exports.sipRegister(authorizedRegisterRequest);
+                resolve(secondResponse);
+            } else {
+                resolve(registrationResponse);
+            }
+        } catch (error) {
+            console.error("Error:", error.message);
+            reject(error);
+        }
+    });
+};
+
+exports.sipRegister = function (registerRequest) {
+    const server = sip.create({
+        logger: "console",
+        port: 25060,
+    });
+    console.log("SIP server created:", server);
+    return new Promise((resolve, reject) => {
+        const timeout = 5000; // Timeout duration in milliseconds
+        let timeoutID;
+        // Cleanup function to clear the timeout and destroy the server
+        function cleanup() {
+            if (timeoutID) clearTimeout(timeoutID);
+            if (server && server.destroy) {
+                server.destroy();
+                console.log("SIP server destroyed.");
+            }
+        }
+        // Set a timeout to handle request expiry
+        timeoutID = setTimeout(() => {
+            console.error("SIP Register request timed out.");
+            reject(new Error("SIP Register request timed out."));
+            cleanup();
+        }, timeout);
+
+        try {
+            // Send the SIP register request
+            server.send(registerRequest, (response) => {
+                console.log("Received SIP register response:", response);
+                if (response) {
+                    resolve(response); // Resolve the promise with the response
+                    cleanup(); // Cleanup after resolving or rejecting
+                } else {
+                    reject(new Error("Empty SIP response received."));
+                    cleanup(); // Cleanup after resolving or rejecting
+
+                }
+            });
+        } catch (error) {
+            console.error("Error sending SIP register request:", error.message);
+            reject(new Error("Error sending SIP register request: " + error.message));
+            cleanup();
+        }
+    });
+};
+exports.constructAuthorizedRequest = function (request, username, password, proxyAuthenticateHeader) {
+    const digestChallenge = {
+        realm: proxyAuthenticateHeader.realm.replace(/"/g, ""),
+        nonce: proxyAuthenticateHeader.nonce.replace(/"/g, ""),
+    };
+    // Construct Digest authentication header manually
+    const ha1 = crypto.createHash("md5").update(`${username}:${digestChallenge.realm}:${password}`).digest("hex");
+    const ha2 = crypto.createHash("md5").update(`${request.method}:${request.uri}`).digest("hex");
+    const response = crypto.createHash("md5").update(`${ha1}:${digestChallenge.nonce}:${ha2}`).digest("hex");
+    const authorizationHeader = `Digest username="${username}", realm="${digestChallenge.realm}", nonce="${digestChallenge.nonce}", uri="${request.uri}", response="${response}"`;
+    const authorizedRequest = {
+        ...request,
+        headers: {
+            ...request.headers,
+            "Proxy-Authorization": authorizationHeader,
+        },
+    };
+    return authorizedRequest;
+};
+/**
+ * Sends a SIP OPTIONS request
+ * @param {string} sipServer The SIP server to send OPTIONS to
+ * @param {string} transport The transport protocol to use (e.g., 'udp' or 'tcp')
+ * @returns {Promise<void>}
+ */
+exports.sipOptionRequest = function (sipServer, sipPort, transport, username, password, version) {
+    const publicIP = process.env.PUBLIC_IP;
+    // eslint-disable-next-line no-async-promise-executor
+    return new Promise(async (resolve, reject) => {
+        try {
+            const optionsRequest = {
+                method: 'OPTIONS',
+                uri: `sip:${sipServer}:${sipPort}`,//hostname
+                headers: {
+                    to: { uri: `sip:${sipServer}:${sipPort}` },//hostname
+                    from: { uri:    `sip:${publicIP}` },//live ip || primary url
+                    'call-id': 1234,
+                    cseq: { method: 'OPTIONS', seq: 1 },
+                    'content-length': 0,
+                    contact: [ { uri:    `sip:${publicIP}` }],
+                    "User-Agent": "SIP Health Monitor" + version,
+
+                },
+                transport: transport,
+            };
+            let optionResponse
+            if(!username) {
+                console.log("will only send ok")
+                const optionResponse = await exports.sipOption(optionsRequest);
+                console.log("optionResponse", optionResponse);
+                resolve(optionResponse)
+            }
+            else {
+                optionResponse = await exports.sipRegister(optionsRequest);
+                console.log("optionResponse", optionResponse);
+                if (optionResponse.status === 407 && optionResponse.headers["proxy-authenticate"]) {
+                    const proxyAuthenticateHeader = optionResponse.headers["proxy-authenticate"][0];
+                    const authorizedOptionRequest = exports.constructAuthorizedRequest(
+                        optionsRequest,
+                        username,
+                        password,
+                        proxyAuthenticateHeader
+                    );
+    
+                    const secondResponse = await exports.sipOption(authorizedOptionRequest);
+                    resolve(secondResponse);
+                } 
+            }
+       
+        } catch (error) {
+            console.error("Error:", error.message);
+            reject(error);
+        }
+    });
+};
+exports.sipOption = function (optionsRequest) {
+    const server = sip.create({
+        logger: "console",
+        port: 5060,
+    });
+
+    console.log("SIP server created:", server);
+    return new Promise((resolve, reject) => {
+        const timeout = 5000; // Timeout duration in milliseconds
+        let timeoutID;
+        // Cleanup function to clear the timeout and destroy the server
+        function cleanup() {
+            if (timeoutID) clearTimeout(timeoutID);
+            if (server) {
+                server.destroy();
+                console.log("SIP server destroyed.");
+            }
+        }
+
+
+        try {
+            // Send the SIP options request
+            server.send(optionsRequest, (response) => {
+                console.log("Received SIP options response:", response);
+                if (response) {
+                    resolve(response); // Resolve the promise with the response
+                    cleanup(); // Perform cleanup
+                } else {
+                    reject(new Error("Empty SIP response received."));
+                    cleanup();
+                }
+            });
+        } catch (error) {
+            console.error("Error sending SIP options request:", error.message);
+            reject(new Error("Error sending SIP options request: " + error.message));
+            cleanup();
+        }
+    });
+};
 /**
  * Use NTLM Auth for a http request.
  * @param {object} options The http request options
diff --git a/src/lang/en.json b/src/lang/en.json
index e215f1031..7d8a98f9f 100644
--- a/src/lang/en.json
+++ b/src/lang/en.json
@@ -1051,5 +1051,10 @@
     "RabbitMQ Password": "RabbitMQ Password",
     "rabbitmqHelpText": "To use the monitor, you will need to enable the Management Plugin in your RabbitMQ setup. For more information, please consult the {rabitmq_documentation}.",
     "SendGrid API Key": "SendGrid API Key",
-    "Separate multiple email addresses with commas": "Separate multiple email addresses with commas"
+    "Separate multiple email addresses with commas": "Separate multiple email addresses with commas",
+    "SipProtocol": "Sip Protocol",
+    "SipPort": "SIP Port",
+    "process503AsMaintenanceLabel": "Process 503 As Maintenance Label",
+    "sipURL": "Hostname (IP/Domain)",
+    "ignoreTLSErrorForSIP":"Ignore TLS/SSL error for SIP websites"
 }
diff --git a/src/pages/EditMonitor.vue b/src/pages/EditMonitor.vue
index a83f91cab..745969a56 100644
--- a/src/pages/EditMonitor.vue
+++ b/src/pages/EditMonitor.vue
@@ -91,6 +91,7 @@
                                         <option v-if="!$root.info.isContainer" value="tailscale-ping">
                                             Tailscale Ping
                                         </option>
+                                          <option value="sip">SIP</option>
                                     </optgroup>
                                 </select>
                                 <i18n-t v-if="monitor.type === 'rabbitmq'" keypath="rabbitmqHelpText" tag="div" class="form-text">
@@ -117,7 +118,15 @@
                                 <label for="url" class="form-label">{{ $t("URL") }}</label>
                                 <input id="url" v-model="monitor.url" type="url" class="form-control" pattern="https?://.+" required data-testid="url-input">
                             </div>
-
+                            <!--SIP-->
+                            <div v-if="monitor.type === 'sip'" class="my-3">
+                                <label for="sipprotocol" class="form-label">{{ $t("SipProtocol") }}</label>
+                                <select id="sipprotocol" class="form-select" required v-model="monitor.sipProtocol">
+                                <option value="UDP">UDP</option>
+                                <option value="TCP">TCP</option>
+                                <option value="TLS">TLS</option>
+                                </select>
+                            </div>
                             <!-- gRPC URL -->
                             <div v-if="monitor.type === 'grpc-keyword' " class="my-3">
                                 <label for="grpc-url" class="form-label">{{ $t("URL") }}</label>
@@ -156,7 +165,19 @@
                                     {{ $t("invertKeywordDescription") }}
                                 </div>
                             </div>
-
+                            <!--SIP URL-->
+                            <div v-if="monitor.type === 'sip'" class="my-3">
+                                <label for="sip-url" class="form-label">{{ $t("sipURL") }}</label>
+                                <!-- <input id="sip-url" v-model="monitor.sipURL" type="url" class="form-control" pattern="((https?|ftp):\/\/)?([a-zA-Z0-9.-]
+                                +\.[a-zA-Z]{2,})(:\d{1,5})?\/?|
+                                (\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})" required> -->
+                                <input id="sip-url" v-model="monitor.sipUrl" type="text" class="form-control" pattern="((https?|ftp):\/\/)?([a-zA-Z0-9.-]+\.[a-zA-Z]{2,})(:\d{1,5})?\/?|(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})" required>
+                            </div>
+                            <div v-if="monitor.type === 'sip'" class="my-3">
+                                <label for="sipport" class="form-label mt-3">{{ $t("SipPort") }}</label>
+                                <input v-if="monitor.sipProtocol !== 'SRV'" id="sipport" type="number" class="form-control"
+                                v-model="monitor.sipPort" placeholder="Enter SIP Port">
+                            </div>
                             <!-- Remote Browser -->
                             <div v-if="monitor.type === 'real-browser'" class="my-3">
                                 <!-- Toggle -->
@@ -616,15 +637,15 @@
                                 <input id="expiry-notification" v-model="monitor.expiryNotification" class="form-check-input" type="checkbox" :disabled="monitor.ignoreTls">
                                 <label class="form-check-label" for="expiry-notification">
                                     {{ $t("Certificate Expiry Notification") }}
-                                </label>
+                                </label> 
                                 <div class="form-text">
                                 </div>
                             </div>
 
-                            <div v-if="monitor.type === 'http' || monitor.type === 'keyword' || monitor.type === 'json-query' || monitor.type === 'redis' " class="my-3 form-check">
+                            <div v-if="monitor.type === 'http' || monitor.type === 'sip' || monitor.type === 'keyword' || monitor.type === 'json-query' || monitor.type === 'redis' " class="my-3 form-check">
                                 <input id="ignore-tls" v-model="monitor.ignoreTls" class="form-check-input" type="checkbox" value="">
                                 <label class="form-check-label" for="ignore-tls">
-                                    {{ monitor.type === "redis" ? $t("ignoreTLSErrorGeneral") : $t("ignoreTLSError") }}
+                                    {{ monitor.type === "redis" || monitor.type === 'sip' ? $t("ignoreTLSErrorGeneral") : $t("ignoreTLSError") }}
                                 </label>
                             </div>
 
@@ -649,7 +670,19 @@
                                     {{ $t("upsideDownModeDescription") }}
                                 </div>
                             </div>
-
+                            <div v-if="monitor.type === 'sip'" class="my-3 form-check">
+                                <input
+                                    id="process-503-as-maintenance"
+                                    v-model="monitor.sipMaintainence"
+                                    class="form-check-input"
+                                    type="checkbox"
+                                    value=""
+                                />
+                                <label class="form-check-label" for="process-503-as-maintenance">
+                                    {{ $t("process503AsMaintenanceLabel") }}
+                                </label>
+                                </div>
+  
                             <div v-if="monitor.type === 'gamedig'" class="my-3 form-check">
                                 <input id="gamedig-guess-port" v-model="monitor.gamedigGivenPortOnly" :true-value="false" :false-value="true" class="form-check-input" type="checkbox">
                                 <label class="form-check-label" for="gamedig-guess-port">
@@ -667,7 +700,7 @@
                             </div>
 
                             <!-- HTTP / Keyword only -->
-                            <template v-if="monitor.type === 'http' || monitor.type === 'keyword' || monitor.type === 'json-query' || monitor.type === 'grpc-keyword' ">
+                            <template v-if="monitor.type === 'http' || monitor.type === 'keyword' || monitor.type === 'json-query' || monitor.type === 'grpc-keyword' || monitor.type === 'sip'">
                                 <div class="my-3">
                                     <label for="maxRedirects" class="form-label">{{ $t("Max. Redirects") }}</label>
                                     <input id="maxRedirects" v-model="monitor.maxredirects" type="number" class="form-control" required min="0" step="1">
@@ -825,7 +858,83 @@
                                     </div>
                                 </div>
                             </template>
+     <!-- SIP Options -->
+             
+  <template v-if="monitor.type === 'sip'
+                ">
+                <h2 class="mt-5 mb-2">{{ $t("SIP Options") }}</h2>
 
+                <!-- Method -->
+                <div class="my-3">
+                  <label for="method" class="form-label">{{
+                    $t("Method")
+                  }}</label>
+                  <select id="method" v-model="monitor.sipMethod" class="form-select">
+                 
+                    <option value="REGISTER">REGISTER</option>
+                    <option value="OPTIONS">OPTIONS</option>
+                  </select>
+                </div>
+
+                <!-- Encoding -->
+                <div class="my-3">
+                  <label for="httpBodyEncoding" class="form-label">{{
+                    $t("Body Encoding")
+                  }}</label>
+                  <select id="httpBodyEncoding" v-model="monitor.httpBodyEncoding" class="form-select">
+                    <option value="json">JSON</option>
+                    <option value="xml">XML</option>
+                  </select>
+                </div>
+
+                <!-- Body -->
+                <div class="my-3">
+                  <label for="body" class="form-label">{{ $t("Body") }}</label>
+                  <textarea id="body" v-model="monitor.body" class="form-control"
+                    :placeholder="bodyPlaceholder"></textarea>
+                </div>
+
+                <!-- Headers -->
+                <div class="my-3">
+                  <label for="headers" class="form-label">{{
+                    $t("Headers")
+                  }}</label>
+                  <textarea id="headers" v-model="monitor.headers" class="form-control"
+                    :placeholder="headersPlaceholder"></textarea>
+                </div>
+
+                <!-- HTTP Auth -->
+                <h4 class="mt-5 mb-2">{{ $t("Authentication") }}</h4>
+
+                <!-- Method -->
+                <div class="my-3">
+                  <label for="authmethod" class="form-label">{{
+                    $t("Method")
+                  }}</label>
+                  <select id="authsipmethod" v-model="monitor.sipAuthMethod" class="form-select">
+                    <option :value="null">
+                      {{ $t("None") }}
+                    </option>
+                    <option value="basic">
+                      {{ $t("SIP Basic Auth") }}
+                    </option>
+                   
+                  </select>
+                </div>
+                <template v-if="monitor.sipAuthMethod === 'basic'">
+  <div class="my-3">
+    <label for="basicauth-user" class="form-label">{{ $t("Username") }}</label>
+    <input id="basicauth-user" v-model="monitor.sip_basic_auth_user" type="text" class="form-control"
+      :placeholder="$t('Username')" />
+  </div>
+
+  <div class="my-3">
+    <label for="basicauth-pass" class="form-label">{{ $t("Password") }}</label>
+    <input id="basicauth-pass" v-model="monitor.sip_basic_auth_pass" type="password" autocomplete="new-password"
+      class="form-control" :placeholder="$t('Password')" />
+  </div>
+</template>
+              </template>
                             <!-- HTTP Options -->
                             <template v-if="monitor.type === 'http' || monitor.type === 'keyword' || monitor.type === 'json-query' ">
                                 <h2 class="mt-5 mb-2">{{ $t("HTTP Options") }}</h2>
@@ -1111,7 +1220,13 @@ const monitorDefaults = {
     rabbitmqNodes: [],
     rabbitmqUsername: "",
     rabbitmqPassword: "",
-    conditions: []
+    conditions: [],
+    sipProtocol: "UDP",
+    sipPort: 5060,
+    sipUrl:null,
+    sipMethod: "OPTIONS",
+	sipMaintainence:false,
+	sipAuthMethod: null,
 };
 
 export default {