diff --git a/build.gradle.kts b/build.gradle.kts index cd86bc879..e31908cac 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -87,9 +87,18 @@ tasks { ) baseOutputDirectory = javadocDirectory } + val download83 by registering(DownloadJavadocs::class) { + version = "8.3" + urls = listOf( + "https://files.inductiveautomation.com/sdk/javadoc/ignition83/8.3.0/allclasses-index.html", + "https://docs.oracle.com/en/java/javase/17/docs/api/allclasses-index.html", + "https://www.javadoc.io/static/org.python/jython-standalone/2.7.3/allclasses-noframe.html", + ) + baseOutputDirectory = javadocDirectory + } processResources { duplicatesStrategy = DuplicatesStrategy.WARN - dependsOn(download79, download80, download81) + dependsOn(download79, download80, download81, download83) } } diff --git a/src/main/kotlin/io/github/inductiveautomation/kindling/statistics/GatewayBackup.kt b/src/main/kotlin/io/github/inductiveautomation/kindling/statistics/GatewayBackup.kt index dae5995ae..6f62b42a4 100644 --- a/src/main/kotlin/io/github/inductiveautomation/kindling/statistics/GatewayBackup.kt +++ b/src/main/kotlin/io/github/inductiveautomation/kindling/statistics/GatewayBackup.kt @@ -1,5 +1,8 @@ package io.github.inductiveautomation.kindling.statistics +import io.github.inductiveautomation.kindling.statistics.config83.PlatformResourceCategory +import io.github.inductiveautomation.kindling.statistics.config83.ResourceCategory +import io.github.inductiveautomation.kindling.utils.MajorVersion import io.github.inductiveautomation.kindling.utils.Properties import io.github.inductiveautomation.kindling.utils.SQLiteConnection import io.github.inductiveautomation.kindling.utils.XML_FACTORY @@ -15,7 +18,11 @@ import java.nio.file.Path import java.sql.Connection import java.util.Properties import kotlin.io.path.createTempFile +import kotlin.io.path.div +import kotlin.io.path.exists import kotlin.io.path.inputStream +import kotlin.io.path.listDirectoryEntries +import kotlin.io.path.name import kotlin.io.path.outputStream class GatewayBackup(path: Path) { @@ -24,6 +31,10 @@ class GatewayBackup(path: Path) { val info: Document = root.resolve(BACKUP_INFO).inputStream().use(XML_FACTORY::parse) + val version by lazy { + MajorVersion.lookup(info.getElementsByTagName("version").item(0).textContent) + } + val projectsDirectory: Path = root.resolve(PROJECTS) val configDirectory: Path = root.resolve(CONFIG) @@ -51,6 +62,8 @@ class GatewayBackup(path: Path) { Properties(root.resolve(REDUNDANCY).inputStream(), Properties::loadFromXML) } + val resources: Resources by lazy { Resources() } + companion object { private const val IDB = "db_backup_sqlite.idb" private const val BACKUP_INFO = "backupinfo.xml" @@ -59,4 +72,31 @@ class GatewayBackup(path: Path) { private const val PROJECTS = "projects" private const val CONFIG = "config" } + + inner class Resources { + val directory = configDirectory / "resources" + + // Can be changed later for 8.3-specific stats with deployment modes + private var deploymentId: String = "core" + + val modules: List = directory.listDirectoryEntries().map { it.name } + + fun getPlatformCategory(category: PlatformResourceCategory): ResourceCategory { + val path = directory / deploymentId / "ignition" / category.folderName + return ResourceCategory(path) + } + + fun getModuleCategory(moduleId: String, type: String): ResourceCategory { + val path = directory / deploymentId / moduleId / type + check(path.exists()) { "No resource category $moduleId/$type" } + return ResourceCategory(path) + } + + fun withDeploymentId(deploymentId: String, block: Resources.() -> T): T { + this.deploymentId = deploymentId + val result = this.block() + this.deploymentId = "core" + return result + } + } } diff --git a/src/main/kotlin/io/github/inductiveautomation/kindling/statistics/StatisticCalculator.kt b/src/main/kotlin/io/github/inductiveautomation/kindling/statistics/StatisticCalculator.kt index 0d1eb1999..0ff51ddde 100644 --- a/src/main/kotlin/io/github/inductiveautomation/kindling/statistics/StatisticCalculator.kt +++ b/src/main/kotlin/io/github/inductiveautomation/kindling/statistics/StatisticCalculator.kt @@ -1,5 +1,11 @@ package io.github.inductiveautomation.kindling.statistics +import io.github.inductiveautomation.kindling.utils.MajorVersion + fun interface StatisticCalculator { suspend fun calculate(backup: GatewayBackup): T? } + +abstract class CalculatorSupport( + private val calculatorMap: Map>, +) : Map> by calculatorMap diff --git a/src/main/kotlin/io/github/inductiveautomation/kindling/statistics/categories/DatabaseStatistics.kt b/src/main/kotlin/io/github/inductiveautomation/kindling/statistics/categories/DatabaseStatistics.kt index b5caca261..d7c4a6177 100644 --- a/src/main/kotlin/io/github/inductiveautomation/kindling/statistics/categories/DatabaseStatistics.kt +++ b/src/main/kotlin/io/github/inductiveautomation/kindling/statistics/categories/DatabaseStatistics.kt @@ -1,12 +1,21 @@ package io.github.inductiveautomation.kindling.statistics.categories import com.inductiveautomation.ignition.common.datasource.DatabaseVendor +import io.github.inductiveautomation.kindling.statistics.CalculatorSupport import io.github.inductiveautomation.kindling.statistics.GatewayBackup import io.github.inductiveautomation.kindling.statistics.Statistic import io.github.inductiveautomation.kindling.statistics.StatisticCalculator +import io.github.inductiveautomation.kindling.statistics.config83.PlatformResourceCategory +import io.github.inductiveautomation.kindling.utils.MajorVersion import io.github.inductiveautomation.kindling.utils.executeQuery import io.github.inductiveautomation.kindling.utils.get import io.github.inductiveautomation.kindling.utils.toList +import kotlinx.coroutines.async +import kotlinx.coroutines.awaitAll +import kotlinx.coroutines.coroutineScope +import kotlinx.serialization.ExperimentalSerializationApi +import kotlinx.serialization.json.jsonObject +import kotlinx.serialization.json.jsonPrimitive data class DatabaseStatistics( val connections: List, @@ -23,8 +32,17 @@ data class DatabaseStatistics( val cacheSize: Long, ) + companion object Calculators : CalculatorSupport( + mapOf( + MajorVersion.SevenNine to Calculator, + MajorVersion.EightZero to Calculator, + MajorVersion.EightOne to Calculator, + MajorVersion.EightThree to Calculator83, + ), + ) + @Suppress("SqlResolve") - companion object Calculator : StatisticCalculator { + object Calculator : StatisticCalculator { private val DATABASE_STATS = """ SELECT @@ -62,4 +80,30 @@ data class DatabaseStatistics( return DatabaseStatistics(connections) } } + + object Calculator83 : StatisticCalculator { + @OptIn(ExperimentalSerializationApi::class) + override suspend fun calculate(backup: GatewayBackup): DatabaseStatistics? = coroutineScope { + val dbConnections = backup.resources.getPlatformCategory(PlatformResourceCategory.DATABASE_CONNECTION) + val sfEngine = backup.resources.getPlatformCategory(PlatformResourceCategory.STORE_AND_FORWARD_ENGINE) + + val connections = dbConnections.resources.map { connectionResource -> + async { + val sfResource = sfEngine[connectionResource.name] ?: return@async null + Connection( + name = connectionResource.name, + description = connectionResource.data.description, + vendor = DatabaseVendor.valueOf(connectionResource.config["translator"]!!.jsonPrimitive.content), + enabled = connectionResource.data.attributes.enabled, + sfEnabled = sfResource.data.attributes.enabled, + bufferSize = sfResource.config["dataThreshold"]!!.jsonPrimitive.content.toLong(), + cacheSize = sfResource.config["secondaryMaintenancePolicy"]!!.jsonObject["limit"]!!.jsonObject["value"]!!.jsonPrimitive.content.toLong(), + ) + } + }.toList().awaitAll().filterNotNull() + + if (connections.isEmpty()) return@coroutineScope null + DatabaseStatistics(connections) + } + } } diff --git a/src/main/kotlin/io/github/inductiveautomation/kindling/statistics/categories/DeviceStatistics.kt b/src/main/kotlin/io/github/inductiveautomation/kindling/statistics/categories/DeviceStatistics.kt index 531990a95..885c7c517 100644 --- a/src/main/kotlin/io/github/inductiveautomation/kindling/statistics/categories/DeviceStatistics.kt +++ b/src/main/kotlin/io/github/inductiveautomation/kindling/statistics/categories/DeviceStatistics.kt @@ -1,11 +1,18 @@ package io.github.inductiveautomation.kindling.statistics.categories +import io.github.inductiveautomation.kindling.statistics.CalculatorSupport import io.github.inductiveautomation.kindling.statistics.GatewayBackup import io.github.inductiveautomation.kindling.statistics.Statistic import io.github.inductiveautomation.kindling.statistics.StatisticCalculator +import io.github.inductiveautomation.kindling.utils.MajorVersion import io.github.inductiveautomation.kindling.utils.executeQuery import io.github.inductiveautomation.kindling.utils.get import io.github.inductiveautomation.kindling.utils.toList +import kotlinx.coroutines.async +import kotlinx.coroutines.awaitAll +import kotlinx.coroutines.coroutineScope +import kotlinx.serialization.json.jsonObject +import kotlinx.serialization.json.jsonPrimitive data class DeviceStatistics( val devices: List, @@ -20,8 +27,17 @@ data class DeviceStatistics( val total = devices.size val enabled = devices.count { it.enabled } + companion object Calculators : CalculatorSupport( + mapOf( + MajorVersion.SevenNine to Calculator, + MajorVersion.EightZero to Calculator, + MajorVersion.EightOne to Calculator, + MajorVersion.EightThree to Calculator83, + ), + ) + @Suppress("SqlResolve") - companion object Calculator : StatisticCalculator { + object Calculator : StatisticCalculator { private val DEVICES = """ SELECT @@ -52,4 +68,24 @@ data class DeviceStatistics( return DeviceStatistics(devices) } } + + object Calculator83 : StatisticCalculator { + override suspend fun calculate(backup: GatewayBackup): DeviceStatistics? = coroutineScope { + val devicesCategory = backup.resources.getModuleCategory("com.inductiveautomation.opcua", "device") + + val devices: List = devicesCategory.resources.map { + async { + Device( + name = it.name, + type = it.config["profile"]!!.jsonObject["type"]!!.jsonPrimitive.content, + description = it.data.description, + enabled = it.data.attributes.enabled, + ) + } + }.toList().awaitAll() + + if (devices.isEmpty()) return@coroutineScope null + DeviceStatistics(devices) + } + } } diff --git a/src/main/kotlin/io/github/inductiveautomation/kindling/statistics/categories/GatewayNetworkStatistics.kt b/src/main/kotlin/io/github/inductiveautomation/kindling/statistics/categories/GatewayNetworkStatistics.kt index 21d960fee..54487c2f1 100644 --- a/src/main/kotlin/io/github/inductiveautomation/kindling/statistics/categories/GatewayNetworkStatistics.kt +++ b/src/main/kotlin/io/github/inductiveautomation/kindling/statistics/categories/GatewayNetworkStatistics.kt @@ -1,11 +1,18 @@ package io.github.inductiveautomation.kindling.statistics.categories +import io.github.inductiveautomation.kindling.statistics.CalculatorSupport import io.github.inductiveautomation.kindling.statistics.GatewayBackup import io.github.inductiveautomation.kindling.statistics.Statistic import io.github.inductiveautomation.kindling.statistics.StatisticCalculator +import io.github.inductiveautomation.kindling.statistics.config83.PlatformResourceCategory +import io.github.inductiveautomation.kindling.utils.MajorVersion import io.github.inductiveautomation.kindling.utils.executeQuery import io.github.inductiveautomation.kindling.utils.get import io.github.inductiveautomation.kindling.utils.toList +import kotlinx.coroutines.async +import kotlinx.coroutines.awaitAll +import kotlinx.coroutines.coroutineScope +import kotlinx.serialization.json.jsonPrimitive data class GatewayNetworkStatistics( val outgoing: List, @@ -21,8 +28,17 @@ data class GatewayNetworkStatistics( val uuid: String, ) + companion object Calculators : CalculatorSupport( + mapOf( + MajorVersion.SevenNine to Calculator, + MajorVersion.EightZero to Calculator, + MajorVersion.EightOne to Calculator, + MajorVersion.EightThree to Calculator83, + ), + ) + @Suppress("SqlResolve") - companion object Calculator : StatisticCalculator { + object Calculator : StatisticCalculator { private val OUTGOING_CONNECTIONS = """ SELECT @@ -65,4 +81,34 @@ data class GatewayNetworkStatistics( return GatewayNetworkStatistics(outgoing, incoming) } } + + object Calculator83 : StatisticCalculator { + override suspend fun calculate(backup: GatewayBackup): GatewayNetworkStatistics? = coroutineScope { + val outgoingCategory = backup.resources.getPlatformCategory( + PlatformResourceCategory.GATEWAY_NETWORK_OUTGOING, + ) + val incomingCategory = backup.resources.getPlatformCategory( + PlatformResourceCategory.GATEWAY_NETWORK_INCOMING, + ) + + val outgoing = outgoingCategory.resources.map { + async { + OutgoingConnection( + host = it.config["host"]!!.jsonPrimitive.content, + port = it.config["port"]!!.jsonPrimitive.content.toInt(), + enabled = it.data.attributes.enabled, + ) + } + }.toList().awaitAll() + + val incoming = incomingCategory.resources.map { + async { + IncomingConnection(it.config["connectionId"]!!.jsonPrimitive.content) + } + }.toList().awaitAll() + + if (outgoing.isEmpty() && incoming.isEmpty()) return@coroutineScope null + GatewayNetworkStatistics(outgoing, incoming) + } + } } diff --git a/src/main/kotlin/io/github/inductiveautomation/kindling/statistics/categories/MetaStatistics.kt b/src/main/kotlin/io/github/inductiveautomation/kindling/statistics/categories/MetaStatistics.kt index 691731e62..9b4d56835 100644 --- a/src/main/kotlin/io/github/inductiveautomation/kindling/statistics/categories/MetaStatistics.kt +++ b/src/main/kotlin/io/github/inductiveautomation/kindling/statistics/categories/MetaStatistics.kt @@ -1,10 +1,14 @@ package io.github.inductiveautomation.kindling.statistics.categories +import io.github.inductiveautomation.kindling.statistics.CalculatorSupport import io.github.inductiveautomation.kindling.statistics.GatewayBackup import io.github.inductiveautomation.kindling.statistics.Statistic import io.github.inductiveautomation.kindling.statistics.StatisticCalculator +import io.github.inductiveautomation.kindling.statistics.config83.PlatformResourceCategory +import io.github.inductiveautomation.kindling.utils.MajorVersion import io.github.inductiveautomation.kindling.utils.asScalarMap import io.github.inductiveautomation.kindling.utils.executeQuery +import kotlinx.serialization.json.jsonPrimitive data class MetaStatistics( val uuid: String?, @@ -15,8 +19,17 @@ data class MetaStatistics( val initMemory: Int, val maxMemory: Int, ) : Statistic { + companion object Calculators : CalculatorSupport( + mapOf( + MajorVersion.SevenNine to Calculator, + MajorVersion.EightZero to Calculator, + MajorVersion.EightOne to Calculator, + MajorVersion.EightThree to Calculator83, + ), + ) + @Suppress("SqlResolve") - companion object Calculator : StatisticCalculator { + object Calculator : StatisticCalculator { private val SYS_PROPS = """ SELECT * @@ -41,4 +54,29 @@ data class MetaStatistics( ) } } + + object Calculator83 : StatisticCalculator { + override suspend fun calculate(backup: GatewayBackup): MetaStatistics { + val sysProps = backup.resources.getPlatformCategory( + PlatformResourceCategory.SYSTEM_PROPERTIES, + ).resources.single().config + + val localSysProps = backup.resources.withDeploymentId("local") { + getPlatformCategory(PlatformResourceCategory.LOCAL_SYSTEM_PROPERTIES) + }.resources.single().config + + val edition = backup.info.getElementsByTagName("edition").item(0)?.textContent + val version = backup.info.getElementsByTagName("version").item(0).textContent + + return MetaStatistics( + uuid = localSysProps["systemUID"]?.jsonPrimitive?.content, + gatewayName = sysProps["systemName"]!!.jsonPrimitive.content, + edition = edition.takeUnless { it.isNullOrEmpty() } ?: "Standard", + role = backup.redundancyInfo.getProperty("redundancy.noderole"), + version = version, + initMemory = backup.ignitionConf.getProperty("wrapper.java.initmemory").takeWhile { it.isDigit() }.toInt(), + maxMemory = backup.ignitionConf.getProperty("wrapper.java.maxmemory").takeWhile { it.isDigit() }.toInt(), + ) + } + } } diff --git a/src/main/kotlin/io/github/inductiveautomation/kindling/statistics/categories/OpcServerStatistics.kt b/src/main/kotlin/io/github/inductiveautomation/kindling/statistics/categories/OpcServerStatistics.kt index f82002695..a3e7cdba1 100644 --- a/src/main/kotlin/io/github/inductiveautomation/kindling/statistics/categories/OpcServerStatistics.kt +++ b/src/main/kotlin/io/github/inductiveautomation/kindling/statistics/categories/OpcServerStatistics.kt @@ -1,11 +1,19 @@ package io.github.inductiveautomation.kindling.statistics.categories +import io.github.inductiveautomation.kindling.statistics.CalculatorSupport import io.github.inductiveautomation.kindling.statistics.GatewayBackup import io.github.inductiveautomation.kindling.statistics.Statistic import io.github.inductiveautomation.kindling.statistics.StatisticCalculator +import io.github.inductiveautomation.kindling.statistics.config83.PlatformResourceCategory +import io.github.inductiveautomation.kindling.utils.MajorVersion import io.github.inductiveautomation.kindling.utils.executeQuery import io.github.inductiveautomation.kindling.utils.get import io.github.inductiveautomation.kindling.utils.toList +import kotlinx.coroutines.async +import kotlinx.coroutines.awaitAll +import kotlinx.coroutines.coroutineScope +import kotlinx.serialization.json.jsonObject +import kotlinx.serialization.json.jsonPrimitive import org.intellij.lang.annotations.Language import java.sql.ResultSet import java.sql.SQLException @@ -24,11 +32,20 @@ data class OpcServerStatistics( val uaServers = servers.count { it.type == UA_SERVER_TYPE } val comServers = servers.count { it.type == COM_SERVER_TYPE } - @Suppress("SqlResolve") - companion object Calculator : StatisticCalculator { + companion object Calculators : CalculatorSupport( + mapOf( + MajorVersion.SevenNine to Calculator, + MajorVersion.EightZero to Calculator, + MajorVersion.EightOne to Calculator, + MajorVersion.EightThree to Calculator83, + ), + ) { const val UA_SERVER_TYPE = "com.inductiveautomation.OpcUaServerType" const val COM_SERVER_TYPE = "OPC_COM_ServerType" + } + @Suppress("SqlResolve") + object Calculator : StatisticCalculator { private val UA_SERVER_QUERY = """ SELECT @@ -103,8 +120,28 @@ data class OpcServerStatistics( enabled = enabled(rs), ) } - } catch (e: SQLException) { + } catch (_: SQLException) { emptyList() } } + + object Calculator83 : StatisticCalculator { + override suspend fun calculate(backup: GatewayBackup): OpcServerStatistics = coroutineScope { + val opcServersCategory = backup.resources.getPlatformCategory(PlatformResourceCategory.OPC_CONNECTION) + + val servers = opcServersCategory.resources.map { + async { + OpcServer( + name = it.name, + type = it.config["profile"]!!.jsonObject["type"]!!.jsonPrimitive.content, + description = it.data.description, + readOnly = it.config["profile"]!!.jsonObject["type"]!!.jsonPrimitive.content.toBoolean(), + enabled = it.data.attributes.enabled, + ) + } + }.toList().awaitAll() + + OpcServerStatistics(servers) + } + } } diff --git a/src/main/kotlin/io/github/inductiveautomation/kindling/statistics/categories/ProjectStatistics.kt b/src/main/kotlin/io/github/inductiveautomation/kindling/statistics/categories/ProjectStatistics.kt index 93f85b7b3..95ed15dd6 100644 --- a/src/main/kotlin/io/github/inductiveautomation/kindling/statistics/categories/ProjectStatistics.kt +++ b/src/main/kotlin/io/github/inductiveautomation/kindling/statistics/categories/ProjectStatistics.kt @@ -1,8 +1,15 @@ package io.github.inductiveautomation.kindling.statistics.categories +import io.github.inductiveautomation.kindling.statistics.CalculatorSupport import io.github.inductiveautomation.kindling.statistics.GatewayBackup import io.github.inductiveautomation.kindling.statistics.Statistic import io.github.inductiveautomation.kindling.statistics.StatisticCalculator +import io.github.inductiveautomation.kindling.statistics.categories.ProjectStatistics.Calculator.hasPerspectiveResources +import io.github.inductiveautomation.kindling.statistics.categories.ProjectStatistics.Calculator.hasVisionResources +import io.github.inductiveautomation.kindling.utils.MajorVersion +import kotlinx.coroutines.async +import kotlinx.coroutines.awaitAll +import kotlinx.coroutines.coroutineScope import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.Serializable import kotlinx.serialization.json.Json @@ -53,7 +60,15 @@ data class ProjectStatistics( val typeId: String, ) - companion object Calculator : StatisticCalculator { + companion object Calculators : CalculatorSupport( + mapOf( // 7.9 not supported + MajorVersion.EightZero to Calculator, + MajorVersion.EightOne to Calculator, + MajorVersion.EightThree to Calculator, + ), + ) + + object Calculator : StatisticCalculator { const val PERSPECTIVE_MODULE_ID = "com.inductiveautomation.perspective" const val VISION_MODULE_ID = "com.inductiveautomation.vision" @@ -87,52 +102,56 @@ data class ProjectStatistics( return null } - return ProjectStatistics( - projects.listDirectoryEntries().mapNotNull { projectPath -> - if (!projectPath.isDirectory()) { - return@mapNotNull null - } - val manifest = projectPath.resolve("project.json").inputStream().use { JSON.decodeFromStream(it) } - val resources = - buildList { - projectPath.forEachDirectoryEntry { moduleId -> - if (moduleId.isDirectory()) { - moduleId.forEachDirectoryEntry { typeId -> - if (typeId.isDirectory()) { - if (typeId.resolve("resource.json").exists()) { - // singleton - add(Resource(moduleId.name, typeId.name)) - } else { - typeId.walk(PathWalkOption.INCLUDE_DIRECTORIES) - .filter { it.name == "resource.json" } - .forEach { path -> - add( - Resource( - moduleId.name, - typeId.name, - path.parent.name, - path.parent.parent.pathString, - ), - ) + return coroutineScope { + ProjectStatistics( + projects.listDirectoryEntries().map { projectPath -> + async { + if (!projectPath.isDirectory()) { + return@async null + } + val manifest = projectPath.resolve("project.json").inputStream().use { JSON.decodeFromStream(it) } + val resources = + buildList { + projectPath.forEachDirectoryEntry { moduleId -> + if (moduleId.isDirectory()) { + moduleId.forEachDirectoryEntry { typeId -> + if (typeId.isDirectory()) { + if (typeId.resolve("resource.json").exists()) { + // singleton + add(Resource(moduleId.name, typeId.name)) + } else { + typeId.walk(PathWalkOption.INCLUDE_DIRECTORIES) + .filter { it.name == "resource.json" } + .forEach { path -> + add( + Resource( + moduleId.name, + typeId.name, + path.parent.name, + path.parent.parent.pathString, + ), + ) + } } + } } } } } - } - } - Project( - projectPath.name, - manifest.title.takeUnless(String?::isNullOrEmpty), - manifest.description.takeUnless(String?::isNullOrEmpty), - manifest.enabled, - manifest.parent, - manifest.inheritable, - resources, - ) - }, - ) + Project( + projectPath.name, + manifest.title.takeUnless(String?::isNullOrEmpty), + manifest.description.takeUnless(String?::isNullOrEmpty), + manifest.enabled, + manifest.parent, + manifest.inheritable, + resources, + ) + } + }.awaitAll().filterNotNull(), + ) + } } } } diff --git a/src/main/kotlin/io/github/inductiveautomation/kindling/statistics/config83/PlatformResourceCategory.kt b/src/main/kotlin/io/github/inductiveautomation/kindling/statistics/config83/PlatformResourceCategory.kt new file mode 100644 index 000000000..2fa4b2417 --- /dev/null +++ b/src/main/kotlin/io/github/inductiveautomation/kindling/statistics/config83/PlatformResourceCategory.kt @@ -0,0 +1,43 @@ +package io.github.inductiveautomation.kindling.statistics.config83 + +@Suppress("unused") +enum class PlatformResourceCategory { + ALARM_JOURNAL, + AUDIT_PROFILE, + COBRANDING, + DATABASE_CONNECTION, + DATABASE_DRIVER, + DATABASE_TRANSLATOR, + EDGE_SYNC_SETTINGS, + EDGE_SYSTEM_PROPERTIES, + EMAIL_PROFILE, + GATEWAY_NETWORK_INCOMING, + GATEWAY_NETWORK_OUTGOING, + GATEWAY_NETWORK_PROXY_RULES, + GATEWAY_NETWORK_QUEUE_SETTINGS, + GENERAL_ALARM_SETTINGS, + HOLIDAY, + IDENTITY_PROVIDER, + IMAGES, + KEYBOARD_LAYOUT { + override val folderName: String = "keyboard_layout" // why?? + }, + LOCAL_SYSTEM_PROPERTIES, + METRICS_DASHBOARD, + OPC_CONNECTION, + QUICKSTART, + ROSTER_CONFIG, + SCHEDULE, + SECURITY_LEVELS, + SECURITY_PROPERTIES, + SECURITY_ZONE, + STORE_AND_FORWARD_ENGINE, + SYSTEM_PROPERTIES, + TAG_GROUP, + TAG_PROVIDER, + TRANSLATIONS, + USER_SOURCE, + ; + + open val folderName: String = name.replace("_", "-").lowercase() +} diff --git a/src/main/kotlin/io/github/inductiveautomation/kindling/statistics/config83/Resource.kt b/src/main/kotlin/io/github/inductiveautomation/kindling/statistics/config83/Resource.kt new file mode 100644 index 000000000..618bc9c75 --- /dev/null +++ b/src/main/kotlin/io/github/inductiveautomation/kindling/statistics/config83/Resource.kt @@ -0,0 +1,85 @@ +package io.github.inductiveautomation.kindling.statistics.config83 + +import kotlinx.serialization.ExperimentalSerializationApi +import kotlinx.serialization.Serializable +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.JsonObject +import kotlinx.serialization.json.decodeFromStream +import java.nio.file.Path +import kotlin.io.path.div +import kotlin.io.path.inputStream +import kotlin.io.path.name +import kotlin.io.path.walk +import kotlin.time.ExperimentalTime +import kotlin.time.Instant +import kotlin.uuid.ExperimentalUuidApi +import kotlin.uuid.Uuid + +@OptIn(ExperimentalSerializationApi::class) +class Resource(val filePath: Path) { + val deploymentId: String + val moduleId: String + val categoryName: String + val name: String + + init { + val pathParts = filePath.map { it.name } + val resourceIndex = pathParts.indexOf("resources") + + deploymentId = pathParts[resourceIndex + 1] + moduleId = pathParts[resourceIndex + 2] + categoryName = pathParts[resourceIndex + 3] + name = filePath.parent.name + } + + val config: JsonObject by lazy { + (filePath.parent!! / "config.json").inputStream().use(Json::decodeFromStream) + } + + val data: ResourceJson by lazy { + filePath.inputStream().use(Json::decodeFromStream) + } +} + +@Serializable +data class ResourceJson( + val scope: String, + val description: String? = null, + val version: Int, + val restricted: Boolean, + val overridable: Boolean, + val files: List, + val attributes: Attributes, +) { + @Serializable + data class Attributes + @OptIn(ExperimentalUuidApi::class) + constructor( + val lastModification: LastModification, + val uuid: Uuid? = null, + val lastModificationSignature: String, + val enabled: Boolean = true, + ) + + @Serializable + data class LastModification + @OptIn(ExperimentalTime::class) + constructor( + val actor: String, + val timestamp: Instant, + ) +} + +class ResourceCategory(val directory: Path) { + val name: String by directory::name + + val resources: Sequence = directory.walk().mapNotNull { + if (it.name == "resource.json") { + Resource(it) + } else { + null + } + } + + operator fun get(resourceName: String): Resource? = resources.find { it.name == resourceName } +} diff --git a/src/main/kotlin/io/github/inductiveautomation/kindling/utils/StackTrace.kt b/src/main/kotlin/io/github/inductiveautomation/kindling/utils/StackTrace.kt index 8533fc8c0..b129d7fca 100644 --- a/src/main/kotlin/io/github/inductiveautomation/kindling/utils/StackTrace.kt +++ b/src/main/kotlin/io/github/inductiveautomation/kindling/utils/StackTrace.kt @@ -37,7 +37,8 @@ fun StackElement.toBodyLine(version: String): BodyLine = MajorVersion.lookup(ver enum class MajorVersion(val version: String) { SevenNine("7.9"), EightZero("8.0"), - EightOne("8.1"); + EightOne("8.1"), + EightThree("8.3"); val classMap: Properties? by lazy { Properties().also { properties -> @@ -47,16 +48,19 @@ enum class MajorVersion(val version: String) { companion object { private val versionCache = LinkedHashMap().apply { - put("dev", EightOne) + put("dev", EightThree) repeat(22) { patch -> put("7.9.$patch", SevenNine) } repeat(18) { patch -> put("8.0.$patch", EightZero) } - repeat(33) { patch -> + repeat(50) { patch -> put("8.1.$patch", EightOne) } + repeat(2) { patch -> + put("8.3.$patch", EightThree) + } } fun lookup(version: String): MajorVersion? = versionCache.getOrPut(version) { diff --git a/src/main/kotlin/io/github/inductiveautomation/kindling/zip/views/gwbk/GwbkStatsView.kt b/src/main/kotlin/io/github/inductiveautomation/kindling/zip/views/gwbk/GwbkStatsView.kt index 86ab268ec..eabdf8888 100644 --- a/src/main/kotlin/io/github/inductiveautomation/kindling/zip/views/gwbk/GwbkStatsView.kt +++ b/src/main/kotlin/io/github/inductiveautomation/kindling/zip/views/gwbk/GwbkStatsView.kt @@ -28,7 +28,6 @@ import javax.swing.JPopupMenu import javax.swing.SwingConstants import javax.swing.UIManager import javax.swing.border.LineBorder -import kotlin.io.path.exists import kotlin.io.path.extension class GwbkStatsView( @@ -44,18 +43,17 @@ class GwbkStatsView( private val gatewayBackup = GatewayBackup(path) init { - if (gatewayBackup.configDirectory.exists()) { - add(eightThreeWarning(), "wrap") - } - add(MetaStatistics.Calculator renderedWith MetaStatisticsRenderer(), "growx, wrap") - add(ProjectStatistics.Calculator renderedWith ProjectStatisticsRenderer(), "growx, sg") - add(DatabaseStatistics.Calculator renderedWith DatabaseStatisticsRenderer(), "growx, sg") - add(DeviceStatistics.Calculator renderedWith DeviceStatisticsRenderer(), "growx, sg") - add(OpcServerStatistics.Calculator renderedWith OpcConnectionsStatisticsRenderer(), "growx, sg") - add(GatewayNetworkStatistics.Calculator renderedWith GatewayNetworkStatisticsRenderer(), "growx, sg") + val version = gatewayBackup.version + + add(MetaStatistics[version] renderedWith MetaStatisticsRenderer(), "growx, wrap") + add(ProjectStatistics[version] renderedWith ProjectStatisticsRenderer(), "growx, sg") + add(DatabaseStatistics[version] renderedWith DatabaseStatisticsRenderer(), "growx, sg") + add(DeviceStatistics[version] renderedWith DeviceStatisticsRenderer(), "growx, sg") + add(OpcServerStatistics[version] renderedWith OpcConnectionsStatisticsRenderer(), "growx, sg") + add(GatewayNetworkStatistics[version] renderedWith GatewayNetworkStatisticsRenderer(), "growx, sg") } - private infix fun StatisticCalculator.renderedWith(renderer: StatisticRenderer): JPanel { + private infix fun StatisticCalculator?.renderedWith(renderer: StatisticRenderer): JPanel { val headerLabel = JLabel(renderer.title, renderer.icon, SwingConstants.LEFT).apply { putClientProperty("FlatLaf.styleClass", "h3") } @@ -70,7 +68,7 @@ class GwbkStatsView( BACKGROUND.launch { val statistic: T? = try { - calculate(gatewayBackup) + this@renderedWith?.calculate(gatewayBackup) } catch (e: Exception) { MainPanel.LOGGER.error("Error calculating statistic", e) null @@ -94,14 +92,6 @@ class GwbkStatsView( } } - private fun eightThreeWarning(): JLabel = JLabel( - "8.3 Backups are not yet processed correctly", - FlatSVGIcon("icons/bx-error.svg"), - SwingConstants.CENTER, - ).apply { - putClientProperty("FlatLaf.styleClass", "h2") - } - companion object { private val BACKGROUND = CoroutineScope(SupervisorJob() + Dispatchers.IO)