├── src └── main │ ├── java │ └── dev │ │ └── dadowl │ │ └── uptimer │ │ ├── events │ │ ├── UptimerEventType.kt │ │ ├── UptimerCheckCompletedEvent.kt │ │ ├── UptimerPingEvent.kt │ │ └── UptimerEventListener.kt │ │ ├── UptimerLogger.kt │ │ ├── UptimerItemType.kt │ │ ├── utils │ │ ├── JsonArrayBuilder.kt │ │ ├── JsonBuilder.kt │ │ ├── Utils.kt │ │ ├── Config.kt │ │ └── FileUtil.kt │ │ ├── webserver │ │ └── UptimerWebServer.kt │ │ ├── noticers │ │ ├── UptimerMailNoticer.kt │ │ ├── UptimerTgStatusMessage.kt │ │ └── UptimerTgNoticer.kt │ │ ├── DefaultConfig.kt │ │ ├── UptimerItem.kt │ │ └── Uptimer.kt │ └── resources │ └── log4j2.xml ├── LICENSE.md ├── .github └── workflows │ └── build.yml ├── .gitignore ├── readme.md └── pom.xml /src/main/java/dev/dadowl/uptimer/events/UptimerEventType.kt: -------------------------------------------------------------------------------- 1 | package dev.dadowl.uptimer.events 2 | 3 | enum class UptimerEventType { 4 | PING_ONLINE, 5 | PING_OFFLINE, 6 | PING_PENDING 7 | } -------------------------------------------------------------------------------- /src/main/java/dev/dadowl/uptimer/events/UptimerCheckCompletedEvent.kt: -------------------------------------------------------------------------------- 1 | package dev.dadowl.uptimer.events 2 | 3 | import dev.dadowl.uptimer.UptimerItem 4 | import java.util.EventObject 5 | 6 | class UptimerCheckCompletedEvent(source: List) : EventObject(source) -------------------------------------------------------------------------------- /src/main/java/dev/dadowl/uptimer/events/UptimerPingEvent.kt: -------------------------------------------------------------------------------- 1 | package dev.dadowl.uptimer.events 2 | 3 | import dev.dadowl.uptimer.UptimerItem 4 | import java.util.* 5 | 6 | class UptimerPingEvent(source: UptimerItem, var eventType: UptimerEventType = UptimerEventType.PING_ONLINE) : EventObject(source) -------------------------------------------------------------------------------- /src/main/java/dev/dadowl/uptimer/events/UptimerEventListener.kt: -------------------------------------------------------------------------------- 1 | package dev.dadowl.uptimer.events 2 | 3 | import java.util.* 4 | 5 | interface UptimerEventListener : EventListener { 6 | fun onPingEvent(event: UptimerPingEvent) {} 7 | 8 | fun onCheckCompletedEvent(event: UptimerCheckCompletedEvent) {} 9 | } -------------------------------------------------------------------------------- /src/main/java/dev/dadowl/uptimer/UptimerLogger.kt: -------------------------------------------------------------------------------- 1 | package dev.dadowl.uptimer 2 | 3 | import org.apache.logging.log4j.LogManager 4 | import org.apache.logging.log4j.Logger 5 | 6 | object UptimerLogger { 7 | private val logger = LogManager.getLogger(Logger::class.java) 8 | 9 | fun info(str: String?) { 10 | logger.info(str) 11 | } 12 | 13 | fun warn(str: String?) { 14 | logger.warn(str) 15 | } 16 | 17 | fun error(str: String?) { 18 | logger.error(str) 19 | } 20 | fun error(str: String?, throwable: Throwable) { 21 | logger.error(str, throwable) 22 | } 23 | 24 | fun debug(str: String?) { 25 | logger.debug(str) 26 | } 27 | } -------------------------------------------------------------------------------- /src/main/resources/log4j2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/main/java/dev/dadowl/uptimer/UptimerItemType.kt: -------------------------------------------------------------------------------- 1 | package dev.dadowl.uptimer 2 | 3 | import dev.dadowl.uptimer.utils.Utils 4 | 5 | enum class UptimerItemType(val isValid: (String) -> Boolean) { 6 | SITE(fun(value: String): Boolean { 7 | val reg = Regex( 8 | "((http|https)://)(www.)?[a-zA-Z0-9@:%._\\+~#?&//=-]{2,256}\\.[a-z]{2,6}\\b([-a-zA-Z0-9@:%._\\+~#?&//=]*)" 9 | ) 10 | return reg.containsMatchIn(value) 11 | }), 12 | HOST(fun(_: String): Boolean { 13 | return true 14 | }), 15 | IP(fun(value: String): Boolean { 16 | if (value.split(":").size > 1) { 17 | if (Utils.isValidIp(value.split(":")[0])) 18 | return true 19 | } else { 20 | if (Utils.isValidIp(value)) 21 | return true 22 | } 23 | return false 24 | }) 25 | } -------------------------------------------------------------------------------- /src/main/java/dev/dadowl/uptimer/utils/JsonArrayBuilder.kt: -------------------------------------------------------------------------------- 1 | package dev.dadowl.uptimer.utils 2 | 3 | import com.google.gson.JsonArray 4 | import com.google.gson.JsonObject 5 | 6 | class JsonArrayBuilder { 7 | private var json = JsonArray() 8 | 9 | fun add(value: String?): JsonArrayBuilder { 10 | json.add(value) 11 | return this 12 | } 13 | 14 | fun add(value: Int): JsonArrayBuilder { 15 | json.add(value) 16 | return this 17 | } 18 | 19 | fun add(value: Boolean): JsonArrayBuilder { 20 | json.add(value) 21 | return this 22 | } 23 | 24 | fun add(value: Long): JsonArrayBuilder { 25 | json.add(value) 26 | return this 27 | } 28 | 29 | fun add(value: JsonObject): JsonArrayBuilder { 30 | json.add(value) 31 | return this 32 | } 33 | 34 | fun add(value: JsonArray): JsonArrayBuilder { 35 | json.add(value) 36 | return this 37 | } 38 | 39 | override fun toString(): String { 40 | return json.toString() 41 | } 42 | 43 | fun build(): JsonArray { 44 | return json 45 | } 46 | } -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 DadOwl 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build a Java project with Maven, and cache/restore any dependencies to improve the workflow execution time 2 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-java-with-maven 3 | 4 | # This workflow uses actions that are not certified by GitHub. 5 | # They are provided by a third-party and are governed by 6 | # separate terms of service, privacy policy, and support 7 | # documentation. 8 | 9 | name: Java CI with Maven 10 | 11 | on: 12 | push: 13 | branches: [ "main", "develop" ] 14 | 15 | jobs: 16 | build: 17 | 18 | runs-on: ubuntu-latest 19 | 20 | steps: 21 | - uses: actions/checkout@v3 22 | - name: Set up JDK 17 23 | uses: actions/setup-java@v3 24 | with: 25 | java-version: '17' 26 | distribution: 'temurin' 27 | cache: maven 28 | - name: Build with Maven 29 | run: mvn -B package --file pom.xml 30 | 31 | # Optional: Uploads the full dependency graph to GitHub to improve the quality of Dependabot alerts this repository can receive 32 | - name: Update dependency graph 33 | uses: advanced-security/maven-dependency-submission-action@571e99aab1055c2e71a1e2309b9691de18d6b7d6 34 | -------------------------------------------------------------------------------- /src/main/java/dev/dadowl/uptimer/utils/JsonBuilder.kt: -------------------------------------------------------------------------------- 1 | package dev.dadowl.uptimer.utils 2 | 3 | import com.google.gson.JsonArray 4 | import com.google.gson.JsonObject 5 | 6 | class JsonBuilder { 7 | private var json = JsonObject() 8 | 9 | fun add(key: String?, value: String?): JsonBuilder { 10 | json.addProperty(key, value) 11 | return this 12 | } 13 | 14 | fun add(key: String?, value: Int): JsonBuilder { 15 | json.addProperty(key, value) 16 | return this 17 | } 18 | 19 | fun add(key: String?, value: Boolean): JsonBuilder { 20 | json.addProperty(key, value) 21 | return this 22 | } 23 | 24 | fun add(key: String?, value: Long): JsonBuilder { 25 | json.addProperty(key, value) 26 | return this 27 | } 28 | 29 | fun add(key: String?, value: JsonObject?): JsonBuilder { 30 | json.add(key, value) 31 | return this 32 | } 33 | 34 | fun add(key: Int, value: JsonObject?): JsonBuilder { 35 | json.add(key.toString(), value) 36 | return this 37 | } 38 | 39 | fun add(key: String?, array: JsonArray?): JsonBuilder { 40 | json.add(key, array) 41 | return this 42 | } 43 | 44 | override fun toString(): String { 45 | return json.toString() 46 | } 47 | 48 | fun build(): JsonObject { 49 | return json 50 | } 51 | } -------------------------------------------------------------------------------- /src/main/java/dev/dadowl/uptimer/utils/Utils.kt: -------------------------------------------------------------------------------- 1 | package dev.dadowl.uptimer.utils 2 | 3 | import org.apache.commons.validator.routines.InetAddressValidator 4 | import java.net.URL 5 | import java.time.LocalDateTime 6 | import java.time.format.DateTimeFormatter 7 | 8 | object Utils { 9 | 10 | var validator: InetAddressValidator = InetAddressValidator.getInstance() 11 | private val timeFormat = DateTimeFormatter.ofPattern("HH:mm:ss") 12 | 13 | fun isValidIp(ip: String): Boolean{ 14 | if (validator.isValidInet4Address(ip)) { 15 | return true 16 | } 17 | return false 18 | } 19 | 20 | fun isValidIpV6(ip: String): Boolean{ 21 | if (validator.isValidInet6Address(ip)) { 22 | return true 23 | } 24 | return false 25 | } 26 | 27 | fun isValidURL(url: String): Boolean { 28 | return try { 29 | URL(url).toURI() 30 | true 31 | } catch (e: Exception) { 32 | false 33 | } 34 | } 35 | 36 | fun getOnlyTime(date: LocalDateTime): String{ 37 | return date.format(timeFormat) 38 | } 39 | 40 | fun lastChar(str: String): String{ 41 | return str.toCharArray()[str.length - 1].toString() 42 | } 43 | 44 | fun replacePlaceholders(str: String, map: HashMap): String { 45 | var newStr = str 46 | 47 | map.forEach { (key, value) -> 48 | if (newStr.contains(key)){ 49 | newStr = newStr.replace(key, value) 50 | } 51 | } 52 | 53 | return newStr 54 | } 55 | 56 | } -------------------------------------------------------------------------------- /src/main/java/dev/dadowl/uptimer/webserver/UptimerWebServer.kt: -------------------------------------------------------------------------------- 1 | package dev.dadowl.uptimer.webserver 2 | 3 | import com.google.gson.JsonArray 4 | import dev.dadowl.uptimer.Uptimer 5 | import dev.dadowl.uptimer.UptimerLogger 6 | import dev.dadowl.uptimer.utils.JsonBuilder 7 | import spark.Route 8 | import spark.Service 9 | 10 | 11 | class UptimerWebServer(private val port: Int = 9000, private val hideIp: Boolean = true) { 12 | 13 | private val httpService = Service.ignite() 14 | 15 | fun start() { 16 | httpService.port(port) 17 | httpService.threadPool(350) 18 | httpService.internalServerError("Error: 500 internal error") 19 | httpService.notFound("Error: 404 Not found") 20 | 21 | httpService.get("/", Route { request, response -> 22 | response.type("application/json") 23 | mapOf( 24 | "Accept" to "application/json", 25 | "Access-Control-Allow-Origin" to "*", 26 | "Access-Control-Allow-Methods" to "GET", 27 | "Server" to "UptimerServer(https://github.com/dadowl/uptimer)", 28 | ).forEach{ 29 | response.header(it.key, it.value) 30 | } 31 | 32 | val jsonBuilder = JsonBuilder() 33 | val serversJson = JsonBuilder() 34 | 35 | val groupList = Uptimer.uptimerItems.map { it.group } 36 | for (group in groupList) { 37 | val items = Uptimer.uptimerItems.filter { it.group == group } 38 | val servers = JsonArray() 39 | items.forEach { servers.add(it.toJson(hideIp)) } 40 | serversJson.add(group, servers).build() 41 | } 42 | 43 | return@Route jsonBuilder 44 | .add("response", 45 | JsonBuilder() 46 | .add("status", Uptimer.getItemsStatus()) 47 | .add("items", serversJson.build()) 48 | .build()) 49 | .build() 50 | }) 51 | UptimerLogger.info("Starting web-server on port $port") 52 | } 53 | 54 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # User-specific stuff 2 | .idea/ 3 | 4 | *.iml 5 | *.ipr 6 | *.iws 7 | 8 | # IntelliJ 9 | out/ 10 | 11 | # Compiled class file 12 | *.class 13 | 14 | # Log file 15 | *.log 16 | 17 | # BlueJ files 18 | *.ctxt 19 | 20 | # Package Files # 21 | *.jar 22 | *.war 23 | *.nar 24 | *.ear 25 | *.zip 26 | *.tar.gz 27 | *.rar 28 | 29 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 30 | hs_err_pid* 31 | 32 | *~ 33 | 34 | # temporary files which can be created if a process still has a handle open of a deleted file 35 | .fuse_hidden* 36 | 37 | # KDE directory preferences 38 | .directory 39 | 40 | # Linux trash folder which might appear on any partition or disk 41 | .Trash-* 42 | 43 | # .nfs files are created when an open file is removed but is still being accessed 44 | .nfs* 45 | 46 | # General 47 | .DS_Store 48 | .AppleDouble 49 | .LSOverride 50 | 51 | # Icon must end with two \r 52 | Icon 53 | 54 | # Thumbnails 55 | ._* 56 | 57 | # Files that might appear in the root of a volume 58 | .DocumentRevisions-V100 59 | .fseventsd 60 | .Spotlight-V100 61 | .TemporaryItems 62 | .Trashes 63 | .VolumeIcon.icns 64 | .com.apple.timemachine.donotpresent 65 | 66 | # Directories potentially created on remote AFP share 67 | .AppleDB 68 | .AppleDesktop 69 | Network Trash Folder 70 | Temporary Items 71 | .apdisk 72 | 73 | # Windows thumbnail cache files 74 | Thumbs.db 75 | Thumbs.db:encryptable 76 | ehthumbs.db 77 | ehthumbs_vista.db 78 | 79 | # Dump file 80 | *.stackdump 81 | 82 | # Folder config file 83 | [Dd]esktop.ini 84 | 85 | # Recycle Bin used on file shares 86 | $RECYCLE.BIN/ 87 | 88 | # Windows Installer files 89 | *.cab 90 | *.msi 91 | *.msix 92 | *.msm 93 | *.msp 94 | 95 | # Windows shortcuts 96 | *.lnk 97 | 98 | target/ 99 | 100 | pom.xml.tag 101 | pom.xml.releaseBackup 102 | pom.xml.versionsBackup 103 | pom.xml.next 104 | 105 | release.properties 106 | dependency-reduced-pom.xml 107 | buildNumber.properties 108 | .mvn/timing.properties 109 | .mvn/wrapper/maven-wrapper.jar 110 | .flattened-pom.xml 111 | 112 | # Common working directory 113 | run/ 114 | 115 | config.json 116 | noticers.json 117 | servers.json -------------------------------------------------------------------------------- /src/main/java/dev/dadowl/uptimer/utils/Config.kt: -------------------------------------------------------------------------------- 1 | package dev.dadowl.uptimer.utils 2 | 3 | import com.google.gson.GsonBuilder 4 | import com.google.gson.JsonArray 5 | import com.google.gson.JsonElement 6 | import com.google.gson.JsonObject 7 | 8 | class Config() { 9 | 10 | var json = JsonObject() 11 | 12 | constructor(json: JsonObject): this(){ 13 | this.json = json 14 | } 15 | 16 | private fun getVariable(path: String): JsonElement? { 17 | var obj = GsonBuilder().create().fromJson(json, JsonObject::class.java) 18 | val seg = path.split("\\.".toRegex()).toTypedArray() 19 | for (element in seg) { 20 | obj = if (obj != null) { 21 | val ele = obj[element] ?: return null 22 | if (!ele.isJsonObject) return ele else ele.asJsonObject 23 | } else { 24 | return null 25 | } 26 | } 27 | return obj 28 | } 29 | 30 | fun getBoolean(str: String): Boolean{ 31 | return if (getVariable(str) != null) getVariable(str)!!.asBoolean else false 32 | } 33 | fun getBoolean(str: String, defaultValue: Boolean): Boolean{ 34 | return if (getVariable(str) != null) getVariable(str)!!.asBoolean else defaultValue 35 | } 36 | fun getNotBoolean(str: String): Boolean{ 37 | return !getBoolean(str) 38 | } 39 | 40 | fun getString(str: String): String{ 41 | return if (getVariable(str) != null) getVariable(str)!!.asString else "" 42 | } 43 | 44 | fun getString(str: String, defaultValue: String): String{ 45 | return if (getVariable(str) != null) getVariable(str)!!.asString else defaultValue 46 | } 47 | 48 | fun getLong(str: String): Long{ 49 | return if (getVariable(str) != null) getVariable(str)!!.asLong else -1L 50 | } 51 | 52 | fun getInt(str: String): Int{ 53 | return if (getVariable(str) != null) getVariable(str)!!.asInt else -1 54 | } 55 | 56 | fun getInt(str: String, value: Int): Int{ 57 | return if (getVariable(str) != null) getVariable(str)!!.asInt else value 58 | } 59 | 60 | fun getJsonObject(str: String): JsonObject{ 61 | return if (getVariable(str) != null) getVariable(str)!!.asJsonObject else JsonObject() 62 | } 63 | 64 | fun getJsonArray(str: String): JsonArray{ 65 | return if (getVariable(str) != null) getVariable(str)!!.asJsonArray else JsonArray() 66 | } 67 | 68 | } -------------------------------------------------------------------------------- /src/main/java/dev/dadowl/uptimer/utils/FileUtil.kt: -------------------------------------------------------------------------------- 1 | package dev.dadowl.uptimer.utils 2 | 3 | import com.google.gson.Gson 4 | import com.google.gson.GsonBuilder 5 | import com.google.gson.JsonElement 6 | import com.google.gson.JsonObject 7 | import dev.dadowl.uptimer.Uptimer 8 | import dev.dadowl.uptimer.UptimerLogger 9 | import java.io.File 10 | import java.io.FileInputStream 11 | import java.io.FileWriter 12 | import java.nio.charset.Charset 13 | 14 | object FileUtil { 15 | private val gson: Gson = GsonBuilder().setPrettyPrinting().create() 16 | 17 | fun openFile(fileName: String, json: JsonObject): JsonObject{ 18 | val filePath = getPath(fileName) 19 | val file = File(filePath) 20 | 21 | if (!file.exists()) { 22 | UptimerLogger.info("Create new file $filePath") 23 | file.parentFile.mkdirs() 24 | return try { 25 | file.createNewFile() 26 | saveFile(fileName, json) 27 | json 28 | } catch (ex: Exception){ 29 | UptimerLogger.error("Failed to load $filePath") 30 | ex.printStackTrace() 31 | json 32 | } 33 | } 34 | 35 | val fis = FileInputStream(file) 36 | 37 | if (file.length() < 1){ 38 | fis.close() 39 | return json 40 | } 41 | 42 | val data = ByteArray(file.length().toInt()) 43 | fis.read(data) 44 | fis.close() 45 | 46 | val js = String(data, Charset.forName("UTF-8")) 47 | 48 | var job: JsonObject = JsonBuilder().build() 49 | try { 50 | job = gson.fromJson(js, JsonElement::class.java).asJsonObject 51 | } catch (e: Exception){ 52 | Uptimer.stop("Json in $fileName is unreachable.") 53 | } 54 | 55 | if (job.size() == 0) { 56 | return json 57 | } 58 | 59 | UptimerLogger.info("Loaded file $filePath") 60 | return job 61 | } 62 | 63 | fun saveFile(fileName: String, settings: JsonObject){ 64 | val filePath = getPath(fileName) 65 | val file = File(filePath) 66 | 67 | try { 68 | val writer = FileWriter(file) 69 | writer.write(gson.toJson(settings)) 70 | writer.close() 71 | UptimerLogger.info("File saved $filePath") 72 | } catch (e: java.lang.Exception) { 73 | UptimerLogger.error("Failed to save file $filePath") 74 | e.printStackTrace() 75 | } 76 | 77 | } 78 | 79 | private fun getPath(fileName: String): String{ 80 | return "./$fileName" 81 | } 82 | 83 | 84 | } -------------------------------------------------------------------------------- /src/main/java/dev/dadowl/uptimer/noticers/UptimerMailNoticer.kt: -------------------------------------------------------------------------------- 1 | package dev.dadowl.uptimer.noticers 2 | 3 | import dev.dadowl.uptimer.UptimerItem 4 | import dev.dadowl.uptimer.UptimerLogger 5 | import dev.dadowl.uptimer.events.UptimerEventListener 6 | import dev.dadowl.uptimer.events.UptimerEventType 7 | import dev.dadowl.uptimer.events.UptimerPingEvent 8 | import dev.dadowl.uptimer.utils.Config 9 | import java.util.* 10 | import javax.mail.Message 11 | import javax.mail.Session 12 | import javax.mail.internet.InternetAddress 13 | import javax.mail.internet.MimeMessage 14 | 15 | 16 | class UptimerMailNoticer(config: Config) : UptimerEventListener { 17 | 18 | private var enabled = config.getBoolean("enabled", false) 19 | private val host = config.getString("smtp") 20 | private val port = config.getInt("port") 21 | private val username = config.getString("username") 22 | private val password = config.getString("password") 23 | private val senderName = config.getString("senderName") 24 | private val address = config.getString("address") 25 | private val sendTo = config.getString("sendTo") 26 | 27 | init { 28 | if (enabled) { 29 | if (host.isEmpty() || port == 0 || username.isEmpty() || password.isEmpty() || senderName.isEmpty() || address.isEmpty() || sendTo.isEmpty()) { 30 | enabled = false 31 | UptimerLogger.warn("Mail noticer is disabled. Settings error...") 32 | } 33 | } else { 34 | UptimerLogger.warn("Mail noticer is disabled.") 35 | } 36 | } 37 | 38 | private fun sendLetter(subject: String, text: String) { 39 | if (!enabled) return 40 | 41 | try { 42 | val props = System.getProperties() 43 | 44 | props["mail.smtp.host"] = host 45 | props["mail.smtp.ssl.enable"] = "true" 46 | props["mail.smtp.auth"] = "true" 47 | 48 | val session = Session.getDefaultInstance(props, null) 49 | 50 | 51 | val message = MimeMessage(session) 52 | 53 | message.subject = subject 54 | message.addHeader("Content-type", "text/HTML; charset=UTF-8") 55 | message.addHeader("format", "flowed") 56 | message.addHeader("Content-Transfer-Encoding", "8bit") 57 | message.setFrom(InternetAddress(address, senderName)) 58 | message.setText(text, "UTF-8") 59 | message.addRecipient(Message.RecipientType.TO, InternetAddress(sendTo)) 60 | message.sentDate = Date() 61 | 62 | val transport = session.transport 63 | transport.connect(host, port, username, password) 64 | 65 | transport.sendMessage(message, message.allRecipients) 66 | } catch (e: Exception) { 67 | UptimerLogger.error("Mail sending error!", e) 68 | } 69 | 70 | } 71 | 72 | override fun onPingEvent(event: UptimerPingEvent) { 73 | val uptimerItem = event.source as UptimerItem 74 | when (event.eventType) { 75 | UptimerEventType.PING_ONLINE -> { 76 | sendLetter("${uptimerItem.value} is UP", uptimerItem.formatMessage(uptimerItem.upMsg)) 77 | } 78 | 79 | UptimerEventType.PING_OFFLINE -> { 80 | sendLetter("${uptimerItem.value} is DOWN", uptimerItem.formatMessage(uptimerItem.downMsg)) 81 | } 82 | 83 | else -> {} 84 | } 85 | } 86 | } -------------------------------------------------------------------------------- /src/main/java/dev/dadowl/uptimer/noticers/UptimerTgStatusMessage.kt: -------------------------------------------------------------------------------- 1 | package dev.dadowl.uptimer.noticers 2 | 3 | import dev.dadowl.uptimer.Uptimer 4 | import dev.dadowl.uptimer.UptimerItem 5 | import dev.dadowl.uptimer.UptimerLogger 6 | import dev.dadowl.uptimer.utils.Config 7 | import org.telegram.telegrambots.meta.api.methods.updatingmessages.EditMessageText 8 | 9 | class UptimerTgStatusMessage( 10 | private val config: Config, 11 | private val tgChannel: Long, 12 | private val tg: UptimerTgNoticer 13 | ) { 14 | 15 | var id: Int = -1 16 | private var lines: ArrayList = arrayListOf( 17 | "{status}", 18 | "", 19 | "Servers:", 20 | "{group:servers}" 21 | ) 22 | private var statuses = hashMapOf( 23 | "allOnline" to "🟢 All servers are online!", 24 | "allOffline" to "🔴 All servers are offline!", 25 | "someOffline" to "🟡 Some servers are offline!" 26 | ) 27 | private var serverPattern = "{status} - {serverName} - {services}" 28 | private var statusText = "" 29 | 30 | init { 31 | if (id == -1){ 32 | UptimerLogger.warn("Status message id is -1! Ignoring this function.") 33 | } 34 | 35 | this.id = config.getInt("msgId", -1) 36 | 37 | val jarray = config.getJsonArray("lines") 38 | if (jarray.isEmpty) { 39 | UptimerLogger.warn("No status lines found. Use default lines.") 40 | } else { 41 | lines.clear() 42 | jarray.forEach { line -> 43 | lines.add(line.asString) 44 | } 45 | } 46 | 47 | statuses.forEach { (status, _) -> 48 | val cnf = Config(config.getJsonObject("statuses")) 49 | if (cnf.getString(status).isNotEmpty()) { 50 | statuses[status] = cnf.getString(status) 51 | } 52 | } 53 | if (config.getString("serverPattern").isNotEmpty()) 54 | serverPattern = config.getString("serverPattern") 55 | } 56 | 57 | fun updateStatusMessage(uptimerItems: List) { 58 | if (id == -1 || tgChannel == -1L) return 59 | 60 | val status = statuses[Uptimer.getItemsStatus()] ?: "allOffline" 61 | 62 | var finalString = "" 63 | for (line in lines) { 64 | var currentLine = line 65 | 66 | if (currentLine.contains("{status}")) { 67 | currentLine = currentLine.replace("{status}", status) 68 | } 69 | if (currentLine.contains("group")) { 70 | var currentGroup = currentLine.split(":")[1] 71 | currentGroup = currentGroup.substring(0, currentGroup.length - 1) 72 | 73 | val groupServers = uptimerItems.filter { it.group == currentGroup } 74 | if (groupServers.isNotEmpty()) { 75 | val serversString = StringBuilder() 76 | groupServers.forEachIndexed { index, server -> 77 | serversString.append( 78 | server.formatMessage( 79 | serverPattern 80 | ) + if (index + 1 != groupServers.size) "\n" else "" 81 | ) 82 | } 83 | currentLine = currentLine.replace(currentLine, serversString.toString()) 84 | } 85 | } 86 | 87 | finalString += "$currentLine\n" 88 | } 89 | 90 | if (statusText != finalString) { 91 | statusText = finalString 92 | val edit = EditMessageText() 93 | edit.chatId = tgChannel.toString() 94 | edit.messageId = id 95 | edit.text = finalString 96 | 97 | try { 98 | tg.execute(edit) 99 | } catch (_: Exception){} 100 | } 101 | } 102 | } -------------------------------------------------------------------------------- /src/main/java/dev/dadowl/uptimer/DefaultConfig.kt: -------------------------------------------------------------------------------- 1 | package dev.dadowl.uptimer 2 | 3 | import com.google.gson.JsonObject 4 | import dev.dadowl.uptimer.utils.JsonArrayBuilder 5 | import dev.dadowl.uptimer.utils.JsonBuilder 6 | 7 | enum class DefaultConfig(val json: JsonObject) { 8 | 9 | DEFAULT( 10 | JsonBuilder() 11 | .add("pingEvery", "5m") 12 | .add("downTryes", 3) 13 | .add("WebServer", 14 | JsonBuilder() 15 | .add("enable", true) 16 | .add("port", 9000) 17 | .add("hideIp", true) 18 | .build() 19 | ) 20 | .add("upMessage", "Server {serverName}({ip}) is UP!") 21 | .add("downMessage", "Server {serverName}({ip}) is DOWN!") 22 | .add("groupsDefaultMessages", 23 | JsonBuilder() 24 | .add("group-name", JsonBuilder() 25 | .add("upMessage", "example up message for group") 26 | .add("downMessage", "example down message for group") 27 | .build()) 28 | .build()) 29 | .build() 30 | ), 31 | NOTICERS( 32 | JsonBuilder() 33 | .add( 34 | "Telegram", 35 | JsonBuilder() 36 | .add("enabled", false) 37 | .add("token", "") 38 | .add("username", "") 39 | .add("channel", -1) 40 | .add("deleteAfter", "1h") 41 | .add("sendNotifications", true) 42 | .add("status", 43 | JsonBuilder() 44 | .add("msgId", -1) 45 | .add( 46 | "lines", 47 | JsonArrayBuilder() 48 | .add("{status}") 49 | .add("") 50 | .add("Servers:") 51 | .add("{group:servers}") 52 | .build() 53 | ) 54 | .add("serverPattern", "{status} - {serverName} - {services}") 55 | .add("statuses", 56 | JsonBuilder() 57 | .add("allOnline", "\uD83D\uDFE2 All servers are online!") 58 | .add("allOffline", "\uD83D\uDD34 All servers are offline!") 59 | .add("someOffline", "\uD83D\uDFE1 Some servers are offline!") 60 | .build() 61 | ) 62 | .build() 63 | ) 64 | .build() 65 | ) 66 | .add("mail", 67 | JsonBuilder() 68 | .add("enabled", false) 69 | .add("smtp", "") 70 | .add("port", 465) 71 | .add("username", "") 72 | .add("password", "") 73 | .add("address", "") 74 | .add("senderName", "UptimerMailer") 75 | .add("sendTo", "") 76 | .build() 77 | ) 78 | .build() 79 | ), 80 | SERVERS( 81 | JsonBuilder() 82 | .add( 83 | "servers", 84 | JsonArrayBuilder() 85 | .add( 86 | JsonBuilder() 87 | .add("ip", "8.8.8.8") 88 | .add("serverName", "Example server") 89 | .add("services", "Google DNS") 90 | .add("upMessage", "Server {serverName}({ip}) is UP! It was offline {downTime} seconds!") 91 | .build() 92 | ) 93 | .build() 94 | ) 95 | .build() 96 | ) 97 | 98 | } -------------------------------------------------------------------------------- /src/main/java/dev/dadowl/uptimer/UptimerItem.kt: -------------------------------------------------------------------------------- 1 | package dev.dadowl.uptimer 2 | 3 | import com.google.gson.JsonObject 4 | import dev.dadowl.uptimer.events.UptimerEventType 5 | import dev.dadowl.uptimer.events.UptimerPingEvent 6 | import dev.dadowl.uptimer.utils.Config 7 | import dev.dadowl.uptimer.utils.JsonBuilder 8 | import dev.dadowl.uptimer.utils.Utils 9 | import java.io.IOException 10 | import java.net.* 11 | import java.time.LocalDateTime 12 | import java.time.temporal.ChronoUnit 13 | 14 | 15 | class UptimerItem( 16 | var value: String, 17 | val serverName: String, 18 | val services: String, 19 | val type: UptimerItemType 20 | ) { 21 | 22 | var group = "servers" 23 | var status = PingStatus.ONLINE 24 | var downOn: LocalDateTime? = null 25 | private var downTryes = 0 26 | var httpStatusCode = 0 27 | 28 | var upMsg: String = "" 29 | var downMsg: String = "" 30 | 31 | enum class PingStatus(val icon: String) { 32 | ONLINE("\uD83D\uDFE2"), 33 | OFFLINE("\uD83D\uDD34"), 34 | PENDING("\uD83D\uDFE1") 35 | } 36 | 37 | constructor(json: JsonObject, value: String, type: UptimerItemType) : this( 38 | value, 39 | json.get("serverName").asString, 40 | json.get("services").asString, 41 | type 42 | ) { 43 | val itemConfig = Config(json) 44 | 45 | if (itemConfig.getString("group", "").isNotEmpty()) { 46 | group = itemConfig.getString("group", "servers") 47 | } 48 | 49 | upMsg = itemConfig.getString("upMessage", "").ifEmpty { 50 | var defaultUpMessage = Uptimer.getDefaultUpMessageForGroup(group) 51 | if (defaultUpMessage.isEmpty()) defaultUpMessage = Uptimer.defaultUpMessage 52 | 53 | defaultUpMessage 54 | } 55 | downMsg = itemConfig.getString("downMessage", "").ifEmpty { 56 | var defaultDownMessage = Uptimer.getDefaultDownMessageForGroup(group) 57 | if (defaultDownMessage.isEmpty()) defaultDownMessage = Uptimer.defaultDownMessage 58 | 59 | defaultDownMessage 60 | } 61 | } 62 | 63 | fun toStringMain(): String { 64 | return "UptimerItem(group = $group, value = $value, type: ${type}, services = $services)" 65 | } 66 | 67 | override fun toString(): String { 68 | return "UptimerItem(group = $group, value = $value, type: ${type}, services = $services, status = $status, upMsg = $upMsg, downMsg = $downMsg)" 69 | } 70 | 71 | fun toJson(hideIp: Boolean = true): JsonObject { 72 | val builder = JsonBuilder() 73 | .add("group", group) 74 | 75 | if (!hideIp) { 76 | builder.add("ip", this.value) 77 | } 78 | 79 | builder 80 | .add("services", this.services) 81 | .add("status", this.status.toString()) 82 | 83 | return builder.build() 84 | } 85 | 86 | fun ping() { 87 | UptimerLogger.info("PING $value") 88 | 89 | val online: Boolean = when (this.type) { 90 | UptimerItemType.SITE -> { 91 | val connection: HttpURLConnection = URL(this.value).openConnection() as HttpURLConnection 92 | connection.requestMethod = "HEAD" 93 | connection.connectTimeout = 5000 94 | connection.setRequestProperty("User-Agent", "Uptimer(https://github.com/dadowl/uptimer)") 95 | val responseCode: Int = connection.responseCode 96 | if (responseCode != 200) { 97 | httpStatusCode = responseCode 98 | 99 | false 100 | } else { 101 | true 102 | } 103 | } 104 | 105 | UptimerItemType.HOST, UptimerItemType.IP -> { 106 | if (this.value.split(":").size > 1) { 107 | val sockaddr: SocketAddress = 108 | InetSocketAddress(this.value.split(":")[0], this.value.split(":")[1].toInt()) 109 | val socket = Socket() 110 | 111 | try { 112 | socket.connect(sockaddr, 5000) 113 | true 114 | } catch (e: IOException) { 115 | false 116 | } finally { 117 | socket.close() 118 | } 119 | } else { 120 | val geek = InetAddress.getByName(value) 121 | 122 | geek.isReachable(5000) 123 | } 124 | } 125 | } 126 | 127 | if (!online) { 128 | if (this.status == PingStatus.ONLINE) { 129 | this.status = PingStatus.PENDING 130 | 131 | Uptimer.fireEventAsync(UptimerPingEvent(this, UptimerEventType.PING_PENDING)) 132 | } 133 | 134 | this.downTryes++ 135 | 136 | if (this.downTryes >= Uptimer.downTryes && downOn == null) { 137 | this.status = PingStatus.OFFLINE 138 | downOn = LocalDateTime.now() 139 | 140 | Uptimer.fireEventAsync(UptimerPingEvent(this, UptimerEventType.PING_OFFLINE)) 141 | } 142 | } else { 143 | if (this.status == PingStatus.OFFLINE) { 144 | clearItem() 145 | 146 | Uptimer.fireEventAsync(UptimerPingEvent(this, UptimerEventType.PING_ONLINE)) 147 | } 148 | if (this.status == PingStatus.PENDING) { 149 | clearItem() 150 | } 151 | } 152 | 153 | UptimerLogger.info( 154 | "$value is ${if (this.status == PingStatus.ONLINE) "UP" else "DOWN"}. " + 155 | "Current status: ${this.status}. " + 156 | when (this.status) { 157 | PingStatus.PENDING -> "Try: $downTryes" 158 | PingStatus.OFFLINE -> "Down on: $downOn" 159 | else -> "" 160 | } 161 | ) 162 | } 163 | 164 | private fun clearItem() { 165 | this.status = PingStatus.ONLINE 166 | this.downTryes = 0 167 | this.httpStatusCode = 0 168 | this.downOn = null 169 | } 170 | 171 | fun formatMessage(msg: String): String { 172 | val diff = if (this.downOn != null) ChronoUnit.SECONDS.between(this.downOn, LocalDateTime.now()) else "" 173 | 174 | return Utils.replacePlaceholders( 175 | msg, hashMapOf( 176 | "{ip}" to this.value, 177 | "{serverName}" to this.serverName, 178 | "{services}" to this.services, 179 | "{downTime}" to diff.toString(), 180 | "{errorCode}" to this.httpStatusCode.toString(), 181 | "{status}" to this.status.icon 182 | ) 183 | ) 184 | } 185 | } -------------------------------------------------------------------------------- /src/main/java/dev/dadowl/uptimer/noticers/UptimerTgNoticer.kt: -------------------------------------------------------------------------------- 1 | package dev.dadowl.uptimer.noticers 2 | 3 | import com.coreoz.wisp.schedule.Schedules 4 | import dev.dadowl.uptimer.Uptimer 5 | import dev.dadowl.uptimer.UptimerItem 6 | import dev.dadowl.uptimer.UptimerLogger 7 | import dev.dadowl.uptimer.events.UptimerCheckCompletedEvent 8 | import dev.dadowl.uptimer.events.UptimerEventListener 9 | import dev.dadowl.uptimer.events.UptimerEventType 10 | import dev.dadowl.uptimer.events.UptimerPingEvent 11 | import dev.dadowl.uptimer.utils.Config 12 | import dev.dadowl.uptimer.utils.Utils 13 | import org.telegram.telegrambots.bots.TelegramLongPollingBot 14 | import org.telegram.telegrambots.meta.TelegramBotsApi 15 | import org.telegram.telegrambots.meta.api.methods.pinnedmessages.PinChatMessage 16 | import org.telegram.telegrambots.meta.api.methods.send.SendMessage 17 | import org.telegram.telegrambots.meta.api.methods.updatingmessages.DeleteMessage 18 | import org.telegram.telegrambots.meta.api.objects.Update 19 | import org.telegram.telegrambots.meta.exceptions.TelegramApiException 20 | import org.telegram.telegrambots.meta.exceptions.TelegramApiRequestException 21 | import org.telegram.telegrambots.updatesreceivers.DefaultBotSession 22 | import java.time.LocalDateTime 23 | 24 | 25 | class UptimerTgNoticer(config: Config): TelegramLongPollingBot(), UptimerEventListener{ 26 | 27 | var enabled = config.getBoolean("enabled", false) 28 | private val tg_token = config.getString("token") 29 | private val tg_username = config.getString("username") 30 | val tg_channel = config.getLong("channel") 31 | val statusMessage = UptimerTgStatusMessage(Config(config.getJsonObject("status")), tg_channel, this) 32 | private val deleteAfter = config.getString("deleteAfter", "1h") 33 | private var delValue = 1L 34 | private var sendNotifications = true 35 | 36 | private val RECONNECT_PAUSE = 10000L 37 | 38 | init { 39 | if (enabled) { 40 | if (tg_token.isEmpty() || tg_username.isEmpty()){ 41 | UptimerLogger.warn("Telegram settings error.") 42 | enabled = false 43 | } 44 | 45 | if (tg_channel == -1L){ 46 | UptimerLogger.warn("Telegram channel id is invalid or not found. Messages will not be sent.") 47 | } 48 | 49 | if (deleteAfter.isEmpty()){ 50 | UptimerLogger.warn("Messages wil not be deleted.") 51 | } else { 52 | delValue = deleteAfter.substring(0, deleteAfter.length - 1).toLong() 53 | if (delValue <= 0) delValue = 1 54 | 55 | UptimerLogger.info("Messages will be deleted after $delValue${Utils.lastChar(deleteAfter)}!") 56 | } 57 | 58 | sendNotifications = config.getBoolean("sendNotifications", true) 59 | 60 | if (sendNotifications){ 61 | UptimerLogger.info("Messages will be sent to the Telegram channel.") 62 | } else { 63 | UptimerLogger.warn("Messages will not be sent to the Telegram channel.") 64 | } 65 | } else { 66 | UptimerLogger.warn("Telegram noticer is disabled.") 67 | } 68 | } 69 | 70 | override fun getBotToken(): String { 71 | return tg_token 72 | } 73 | 74 | override fun getBotUsername(): String { 75 | return tg_username 76 | } 77 | 78 | fun connect() { 79 | if (!enabled) return 80 | val telegramBotsApi = TelegramBotsApi(DefaultBotSession::class.java) 81 | try { 82 | telegramBotsApi.registerBot(this) 83 | UptimerLogger.info("TelegramAPI started. ") 84 | } catch (e: TelegramApiRequestException) { 85 | UptimerLogger.error("Cant Connect. Pause " + (RECONNECT_PAUSE / 1000).toString() + "sec and try again. Error: " + e.message) 86 | try { 87 | Thread.sleep(RECONNECT_PAUSE) 88 | } catch (e1: InterruptedException) { 89 | e1.printStackTrace() 90 | return 91 | } 92 | } 93 | } 94 | 95 | fun sendMessage(text: String, dev: Boolean){ 96 | if (!enabled || tg_channel == -1L) return 97 | val msg = SendMessage() 98 | msg.chatId = tg_channel.toString() 99 | msg.text = text 100 | try { 101 | val send = execute(msg) 102 | if (dev){ 103 | UptimerLogger.info("Status message id is ${send.messageId}") 104 | Uptimer.saveStatusId(send.messageId) 105 | UptimerLogger.info("Status message id saved in config file.") 106 | val pin = PinChatMessage(tg_channel.toString(), send.messageId, false) 107 | execute(pin) 108 | } else { 109 | deleteMessageDelayed(send.messageId) 110 | } 111 | } catch (e: TelegramApiException) { 112 | e.printStackTrace() 113 | } 114 | } 115 | 116 | fun sendMessage(text: String){ 117 | return sendMessage(text, false) 118 | } 119 | 120 | override fun onUpdateReceived(update: Update?) {} 121 | 122 | override fun onPingEvent(event: UptimerPingEvent) { 123 | if (!this.sendNotifications) return 124 | 125 | val uptimerItem = event.source as UptimerItem 126 | when(event.eventType){ 127 | UptimerEventType.PING_ONLINE -> { 128 | sendMessage(uptimerItem.formatMessage(uptimerItem.upMsg)) 129 | } 130 | UptimerEventType.PING_OFFLINE -> { 131 | sendMessage(uptimerItem.formatMessage(uptimerItem.downMsg)) 132 | } 133 | else -> {} 134 | } 135 | } 136 | 137 | override fun onCheckCompletedEvent(event: UptimerCheckCompletedEvent) { 138 | val items = event.source as List 139 | 140 | statusMessage.updateStatusMessage(items) 141 | } 142 | 143 | private fun deleteMessageDelayed(msgId: Int){ 144 | if (deleteAfter.isEmpty()) return 145 | 146 | val deleteAt = getDeleteTimeAt() 147 | 148 | UptimerLogger.info("Message will be deleted at ${Utils.getOnlyTime(deleteAt)}.") 149 | 150 | Uptimer.scheduler.schedule({deleteMessage(msgId)}, Schedules.executeAt(Utils.getOnlyTime(deleteAt))) 151 | } 152 | 153 | private fun deleteMessage(id: Int){ 154 | val delete = DeleteMessage() 155 | delete.messageId = id 156 | delete.chatId = tg_channel.toString() 157 | 158 | try { 159 | execute(delete) 160 | } catch (e: Exception){ 161 | e.printStackTrace() 162 | } 163 | } 164 | 165 | private fun getDeleteTimeAt(): LocalDateTime{ 166 | return if (deleteAfter.contains("h")){ 167 | LocalDateTime.now().plusHours(delValue) 168 | } else if (deleteAfter.contains("s")) { 169 | LocalDateTime.now().plusSeconds(delValue) 170 | } else { 171 | LocalDateTime.now().plusMinutes(delValue) 172 | } 173 | } 174 | } -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Uptimer 2 | 3 | Application for tracking the status of servers 4 | 5 | ### Features 6 | - Ping servers by ip, ip:port, websites 7 | - Notifications in the telegram channel and to the mail 8 | - Web server and API 9 | - Server groups 10 | - Configuring messages in the config 11 | - Placeholders for messages 12 | - Full customization 13 | 14 | ### Requirements 15 | - JDK 17 16 | 17 | ## Launch 18 | 19 | When you turn on the program for the first time, it will generate the necessary configs to work, launch the web server on port 9000 and immediately start working. 20 | 21 | Configuration help: 22 | 1. [General config](https://github.com/dadowl/uptimer#general-config) 23 | 2. [Web server](https://github.com/dadowl/uptimer#web-server) 24 | 3. [Adding Servers](https://github.com/dadowl/uptimer#adding-servers) 25 | 4. [Noticers setup](https://github.com/dadowl/uptimer#noticers-setup) 26 | 5. [Placeholders for messages](https://github.com/dadowl/uptimer#placeholders-for-messages) 27 | 28 | ## General config 29 | 30 | The general parameters for the operation of the application are specified in the config.json file. 31 | 32 | Let's go through the list: 33 | 1. pingEvery - indicates how often to ping the server. The value is specified in the format "5m", where 5 is the number, and m is the minutes, that is, the servers will ping every 5 minutes. You can also specify s, h, for seconds and hours respectively 34 | 2. downTryes - indicates after how many unsuccessful pings the server will be considered offline 35 | 3. upMessage and downMessage - messages that will be sent to the mail or telegram when the server appears online or goes offline. You can also use [placeholders](https://github.com/dadowl/uptimer#placeholders-for-messages) 36 | 4. groupsDefaultMessages - group messages for online and offline server events 37 | 38 | ## Web server 39 | 40 | The web server is configured in the config file.json in the Web Server section. 41 | 42 | Here: 43 | 1. enable - determines whether the web server is turned on or off 44 | 2. port - the port of the web server 45 | 3. hide Ip - is it necessary to hide the ip address of the server? It will be useful if you bring monitoring to the site and you do not need to show the ip addresses of your servers. 46 | 47 | When accessing the server, a response in json format will be output in the response object: 48 | The status item will indicate the current servers level 49 | In the items array, servers grouped by server group. 50 | 51 | Example of a response from the server: 52 | 53 | ![](https://dadowl.dev/files/uptimer/request_example.jpg) 54 | 55 | ## Servers setup 56 | 57 | The servers are configured in the servers.json file. 58 | 59 | Each server has required parameters: 60 | 1. host, site or ip - server address parameters, site - for websites, ip - for numeric addresses or addresses with a port, host - for domain names 61 | 2. serverName - the name of the server. However, you can specify anything here, this parameter is just used for the convenience of messages. 62 | 3. services - services that are running on this server. However, you can specify anything here, this parameter is just used for the convenience of messages. 63 | 64 | Also, you can register custom upMessage and downMessage messages on each server. You can also use [placeholders]((https://github.com/dadowl/uptimer#placeholders-for-messages)). 65 | 66 | Also, the server can be added to the group. This is done through the group parameter. For example, "group": "minecraft". By default, all servers have a group - servers. 67 | 68 | ## Noticers setup 69 | 70 | All noticers are configured in the noticers.json file. 71 | 72 | ### Telegram setup 73 | 74 | In the file above, there is a block for configuring Telegram. 75 | 76 | Let's go through the list of its parameters: 77 | 1. enabled - determines whether the Telegram noticer is enabled or not 78 | 2. token - the telegram bot token that is used for notifications. You can get it when creating a bot via [@BotFather](https://t.me/BotFather ) 79 | 3. username - the username of the bot, it is also obtained through [@BotFather](https://t.me/BotFather ) 80 | 4. channel - the channel where ping status messages will be sent. For example, the server is offline or has become online again. 81 | 5. deleteAfter - determines after how long to delete messages. Accepts in the same form as [pingEvery](https://github.com/dadowl/uptimer#general-config) 82 | 6. status - status message settings 83 | 7. sendNotifications - allows sending notifications or not 84 | 85 | Status message is a unique feature of this application. 86 | It will allow you to create a message in your telegram channel and pin it. In this case, after each ping, information about the servers will be updated in the status message, if the status has not changed after the previous ping, then nothing will happen. 87 | 88 | Parameters: 89 | 1. msgId - the id of the status message, it is this message that will be updated. You can either specify it manually, or by running the application with the --dev flag 90 | 2. lines - status message lines, placeholders here: 91 | {status} - the general status of the servers, depending on the current state, is set in the statuses section below 92 | {group:group_name} - server group. Instead of group_name, the name of the server group is specified. When generating a status message, all servers with the specified group will be displayed here. If there are no servers with such a group, then this line will not change. 93 | 3. serverPattern - server string that will be output when calling servers in {group:group_name}. You can also specify any message using placeholders here 94 | 4. statuses - here you specify what will be displayed in the status message lines in the placeholder {status} 95 | 96 | There are three parameters here: 97 | - allOnline - if all servers are online 98 | - allOffline - if all servers are offline 99 | - someOffline - if some servers are turned off 100 | 101 | Example of a status message: 102 | 103 | ![](https://dadowl.dev/files/uptimer/status_example.jpg) 104 | 105 | ### Email setup 106 | 107 | In the file listed above, there is a block for configuring Mail. 108 | 109 | Let's go through the list of its parameters: 110 | 1. enabled - determines whether mail noticer is enabled or not 111 | 2. smtp - smtp mail server 112 | 3. port - smtp server port 113 | 4. username - the name of the user from whom the email will be sent 114 | 5. password - the password of the user from whom the email will be sent 115 | 6. address - the email from which the email will be sent 116 | 7. senderName - the sender's name 117 | 8. sendTo - indicates which mail the status messages will be sent to 118 | 119 | ### Placeholders for messages 120 | 121 | Placeholders for messages: 122 | 1. ip - ip address of the server; 123 | 2. serverName - the name of the server; 124 | 3. services - services running on this host; 125 | 4. downTime - offline time; 126 | 5. errorCode - error code if the website is pinged, otherwise 0; 127 | 6. status - status icon according to the current status: 128 | - ONLINE(🟢) 129 | - OFFLINE(🔴) 130 | - PENDING(🟡) 131 | 132 | ## For developers 133 | 134 | You can create your own noticer and inherit it from the UptimerEventListener interface, overriding the onPingEvent and onCheckCompletedEvent methods as needed. -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | dev.dadowl 8 | uptimer 9 | 3.0.2 10 | 11 | 12 | 17 13 | 17 14 | 1.7.20 15 | 6.9.7.1 16 | 17 | 18 | 19 | 20 | org.jetbrains.kotlin 21 | kotlin-stdlib-jdk8 22 | ${kotlin.version} 23 | 24 | 25 | org.jetbrains.kotlin 26 | kotlin-test 27 | ${kotlin.version} 28 | test 29 | 30 | 31 | com.google.code.gson 32 | gson 33 | 2.9.0 34 | 35 | 36 | org.apache.logging.log4j 37 | log4j-core 38 | 2.19.0 39 | 40 | 41 | org.apache.logging.log4j 42 | log4j-api 43 | 2.19.0 44 | 45 | 46 | commons-validator 47 | commons-validator 48 | 1.7 49 | 50 | 51 | com.coreoz 52 | wisp 53 | 2.3.0 54 | 55 | 56 | org.telegram 57 | telegrambotsextensions 58 | ${telegram.version} 59 | 60 | 61 | com.sparkjava 62 | spark-kotlin 63 | 1.0.0-alpha 64 | 65 | 66 | com.sun.mail 67 | javax.mail 68 | 1.6.2 69 | 70 | 71 | org.jetbrains.kotlinx 72 | kotlinx-coroutines-core 73 | 1.6.4 74 | 75 | 76 | 77 | 78 | 79 | 80 | org.apache.maven.plugins 81 | maven-shade-plugin 82 | 3.2.4 83 | 84 | 85 | shade 86 | package 87 | 88 | shade 89 | 90 | 91 | 92 | 93 | 94 | kotlin-maven-plugin 95 | org.jetbrains.kotlin 96 | ${kotlin.version} 97 | 98 | 99 | compile 100 | compile 101 | 102 | 103 | ${project.basedir}/src/main/kotlin 104 | ${project.basedir}/src/main/java 105 | 106 | 107 | 108 | 109 | test-compile 110 | test-compile 111 | 112 | 113 | ${project.basedir}/src/test/kotlin 114 | ${project.basedir}/src/test/java 115 | 116 | 117 | 118 | 119 | 120 | 121 | org.apache.maven.plugins 122 | maven-compiler-plugin 123 | 3.8.1 124 | 125 | 126 | 127 | default-compile 128 | none 129 | 130 | 131 | 132 | default-testCompile 133 | none 134 | 135 | 136 | java-compile 137 | compile 138 | compile 139 | 140 | 141 | java-test-compile 142 | test-compile 143 | testCompile 144 | 145 | 146 | 147 | 11 148 | 149 | 150 | 151 | 152 | org.apache.maven.plugins 153 | maven-jar-plugin 154 | 3.1.0 155 | 156 | 157 | 158 | true 159 | lib/ 160 | dev.dadowl.uptimer.Uptimer 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | src/main/resources 169 | true 170 | 171 | 172 | 173 | 174 | -------------------------------------------------------------------------------- /src/main/java/dev/dadowl/uptimer/Uptimer.kt: -------------------------------------------------------------------------------- 1 | package dev.dadowl.uptimer 2 | 3 | import com.coreoz.wisp.Scheduler 4 | import com.coreoz.wisp.schedule.Schedules 5 | import com.google.gson.JsonArray 6 | import dev.dadowl.uptimer.events.UptimerCheckCompletedEvent 7 | import dev.dadowl.uptimer.events.UptimerEventListener 8 | import dev.dadowl.uptimer.events.UptimerPingEvent 9 | import dev.dadowl.uptimer.noticers.UptimerMailNoticer 10 | import dev.dadowl.uptimer.noticers.UptimerTgNoticer 11 | import dev.dadowl.uptimer.utils.Config 12 | import dev.dadowl.uptimer.utils.FileUtil 13 | import dev.dadowl.uptimer.utils.Utils 14 | import dev.dadowl.uptimer.webserver.UptimerWebServer 15 | import kotlinx.coroutines.DelicateCoroutinesApi 16 | import kotlinx.coroutines.Dispatchers 17 | import kotlinx.coroutines.GlobalScope 18 | import kotlinx.coroutines.launch 19 | import java.time.Duration 20 | import java.util.* 21 | import java.util.concurrent.Executors 22 | import kotlin.system.exitProcess 23 | 24 | object Uptimer { 25 | 26 | val scheduler = Scheduler() 27 | 28 | private val eventListeners = LinkedList() 29 | 30 | private var config = Config(FileUtil.openFile("config.json", DefaultConfig.DEFAULT.json)) 31 | private var noticersConfig = Config(FileUtil.openFile("noticers.json", DefaultConfig.NOTICERS.json)) 32 | private var serversConfig = Config(FileUtil.openFile("servers.json", DefaultConfig.SERVERS.json)) 33 | 34 | var devMode = false 35 | 36 | val uptimerWebServer = UptimerWebServer(config.getInt("WebServer.port", 9000), config.getBoolean("WebServer.hideIp", true)) 37 | 38 | val uptimerTgNoticer: UptimerTgNoticer = UptimerTgNoticer(Config(noticersConfig.getJsonObject("Telegram"))) 39 | val uptimerMailNoticer = UptimerMailNoticer(Config(noticersConfig.getJsonObject("mail"))) 40 | 41 | var defaultUpMessage = "Server {serverName}({ip}) is UP!" 42 | var defaultDownMessage = "Server {serverName}({ip}) is DOWN!" 43 | var pingEvery = "5m" 44 | var downTryes = 3 45 | 46 | val uptimerItems = ArrayList() 47 | 48 | @JvmStatic 49 | fun main(args: Array) { 50 | if (args.isNotEmpty() && args[0]=="--dev"){ 51 | devMode = true 52 | } 53 | 54 | uptimerTgNoticer.connect() 55 | 56 | if (devMode){ 57 | if (uptimerTgNoticer.enabled){ 58 | if (uptimerTgNoticer.statusMessage.id != -1) { 59 | UptimerLogger.warn("The status message id is currently installed. You sure to update status message id? [Y/N]") 60 | val scan = Scanner(System.`in`) 61 | when (scan.next().lowercase()) { 62 | "N", "n", "no" -> { 63 | stop() 64 | } 65 | } 66 | } 67 | uptimerTgNoticer.sendMessage("Status message", true) 68 | } 69 | stop() 70 | } 71 | 72 | addEventListener(uptimerTgNoticer) 73 | addEventListener(uptimerMailNoticer) 74 | 75 | if (config.getString("upMessage").isNotEmpty()){ 76 | defaultUpMessage = config.getString("upMessage") 77 | } else { 78 | UptimerLogger.warn("Up message is empty in config. Use default message.") 79 | } 80 | if (config.getString("downMessage").isNotEmpty()){ 81 | defaultDownMessage = config.getString("downMessage") 82 | } else { 83 | UptimerLogger.warn("Down message is empty in config. Use default message.") 84 | } 85 | 86 | downTryes = config.getInt("downTryes", downTryes) 87 | if (downTryes <= 0) downTryes = 1 88 | UptimerLogger.info("The server will be considered offline after $downTryes failed ping attempts.") 89 | 90 | pingEvery = config.getString("pingEvery", pingEvery) 91 | if(pingEvery.isEmpty()) pingEvery = "5m" 92 | 93 | var pingVal = pingEvery.substring(0, pingEvery.length - 1).toLong() 94 | if (pingVal <= 0) pingVal = 1 95 | 96 | val pingValue = if (pingEvery.contains("h")){ 97 | Duration.ofHours(pingVal) 98 | } else if (pingEvery.contains("s")) { 99 | Duration.ofSeconds(pingVal) 100 | } else { 101 | Duration.ofMinutes(pingVal) 102 | } 103 | 104 | UptimerLogger.info("Ping servers every $pingVal${Utils.lastChar(pingEvery)}!") 105 | 106 | if (config.getBoolean("WebServer.enable", true)) { 107 | uptimerWebServer.start() 108 | } else { 109 | UptimerLogger.info("The web server is disabled.") 110 | } 111 | 112 | loadUptimerItems() 113 | 114 | scheduler.schedule( 115 | { 116 | this.executePingAndNotify(uptimerItems) 117 | }, 118 | Schedules.afterInitialDelay(Schedules.fixedDelaySchedule(pingValue), Duration.ZERO) 119 | ) 120 | } 121 | 122 | private fun loadUptimerItems(){ 123 | UptimerLogger.info("Load UptimerItems") 124 | var jarray = JsonArray() 125 | 126 | try { 127 | jarray = serversConfig.getJsonArray("servers") 128 | } catch (ex: Exception){ 129 | stop("No items found.") 130 | } 131 | 132 | if (jarray.isEmpty) { 133 | stop("No items found.") 134 | } 135 | 136 | jarray.forEach{ item -> 137 | val jsonData = Config(item.asJsonObject) 138 | 139 | val (value, type) = when { 140 | jsonData.getString("host").isNotEmpty() -> jsonData.getString("host") to UptimerItemType.HOST 141 | jsonData.getString("site").isNotEmpty() -> jsonData.getString("site") to UptimerItemType.SITE 142 | else -> jsonData.getString("ip") to UptimerItemType.IP 143 | } 144 | 145 | if (type.isValid(value)) { 146 | val it = UptimerItem(item.asJsonObject, value, type) 147 | uptimerItems.add(it) 148 | UptimerLogger.info("Loaded ${it.toStringMain()}") 149 | } else { 150 | UptimerLogger.warn("Skipped - $type:$value - wrong IP!") 151 | } 152 | } 153 | 154 | if (uptimerItems.isEmpty()){ 155 | stop("No items found.") 156 | } 157 | } 158 | 159 | fun getItemsStatus(): String { 160 | return when { 161 | uptimerItems.all { it.status == UptimerItem.PingStatus.ONLINE } -> "allOnline" 162 | uptimerItems.all { it.status == UptimerItem.PingStatus.OFFLINE } -> "allOffline" 163 | else -> "someOffline" 164 | } 165 | } 166 | 167 | fun stop(){ 168 | UptimerLogger.info("Stopping...") 169 | exitProcess(0) 170 | } 171 | 172 | fun stop(err: String){ 173 | UptimerLogger.error(err) 174 | stop() 175 | } 176 | 177 | fun saveStatusId(id: Int){ 178 | noticersConfig.json.getAsJsonObject("Telegram").get("status").asJsonObject.addProperty("msgId", id) 179 | FileUtil.saveFile("noticers.json", noticersConfig.json) 180 | } 181 | 182 | fun addEventListener(listener: UptimerEventListener){ 183 | eventListeners.add(listener) 184 | } 185 | 186 | fun fireEvent(event: EventObject){ 187 | eventListeners.forEach { 188 | if (event is UptimerPingEvent){ 189 | it.onPingEvent(event) 190 | } 191 | if (event is UptimerCheckCompletedEvent){ 192 | it.onCheckCompletedEvent(event) 193 | } 194 | } 195 | } 196 | 197 | @OptIn(DelicateCoroutinesApi::class) 198 | fun fireEventAsync(event: EventObject) { 199 | GlobalScope.launch(Dispatchers.IO) { 200 | fireEvent(event) 201 | } 202 | } 203 | 204 | fun getDefaultUpMessageForGroup(group: String): String{ 205 | return config.getString("groupsDefaultMessages.$group.upMessage") 206 | } 207 | 208 | fun getDefaultDownMessageForGroup(group: String): String{ 209 | return config.getString("groupsDefaultMessages.$group.downMessage") 210 | } 211 | 212 | private fun executePingAndNotify(uptimerItems: List) { 213 | val executorService = Executors.newFixedThreadPool(uptimerItems.size) { runnable -> 214 | Thread(runnable).apply { 215 | isDaemon = true 216 | name = "PingerThread" 217 | } 218 | } 219 | 220 | try { 221 | val futures = uptimerItems.map { item -> 222 | executorService.submit { 223 | item.ping() 224 | } 225 | } 226 | 227 | futures.forEach { it.get() } 228 | 229 | fireEventAsync(UptimerCheckCompletedEvent(uptimerItems)) 230 | } finally { 231 | executorService.shutdown() 232 | } 233 | } 234 | } --------------------------------------------------------------------------------