From bf23eae0a112bde2e711044a2641a30645aaf433 Mon Sep 17 00:00:00 2001 From: SpencerLommel Date: Sat, 11 Oct 2025 13:22:05 -0500 Subject: [PATCH 1/5] refactor SelectedTimeZone --- .../kindling/core/Kindling.kt | 63 +++++++++++++------ .../kindling/log/LogPanel.kt | 3 +- .../kindling/log/TimePanel.kt | 17 ++--- .../kindling/log/WrapperLogPanel.kt | 41 ++++++------ 4 files changed, 76 insertions(+), 48 deletions(-) diff --git a/src/main/kotlin/io/github/inductiveautomation/kindling/core/Kindling.kt b/src/main/kotlin/io/github/inductiveautomation/kindling/core/Kindling.kt index 17c31b2e..914ee9c4 100644 --- a/src/main/kotlin/io/github/inductiveautomation/kindling/core/Kindling.kt +++ b/src/main/kotlin/io/github/inductiveautomation/kindling/core/Kindling.kt @@ -6,6 +6,8 @@ import com.formdev.flatlaf.util.SystemInfo import com.github.weisj.jsvg.parser.SVGLoader import io.github.inductiveautomation.kindling.core.Preference.Companion.PreferenceCheckbox import io.github.inductiveautomation.kindling.core.Preference.Companion.preference +// DEV: remove old import +//import io.github.inductiveautomation.kindling.log.LogViewer.SelectedTimeZone import io.github.inductiveautomation.kindling.utils.ACTION_ICON_SCALE_FACTOR import io.github.inductiveautomation.kindling.utils.CharsetSerializer import io.github.inductiveautomation.kindling.utils.DocumentAdapter @@ -13,6 +15,7 @@ import io.github.inductiveautomation.kindling.utils.PathSerializer import io.github.inductiveautomation.kindling.utils.PathSerializer.serializedForm import io.github.inductiveautomation.kindling.utils.ThemeSerializer import io.github.inductiveautomation.kindling.utils.ToolSerializer +import io.github.inductiveautomation.kindling.utils.ZoneIdSerializer import io.github.inductiveautomation.kindling.utils.configureCellRenderer import io.github.inductiveautomation.kindling.utils.debounce import io.github.inductiveautomation.kindling.utils.render @@ -28,6 +31,8 @@ import java.awt.Image import java.net.URI import java.nio.charset.Charset import java.nio.file.Path +import java.time.ZoneId +import java.time.zone.ZoneRulesProvider import java.util.Vector import javax.swing.JComboBox import javax.swing.JSpinner @@ -72,27 +77,29 @@ data object Kindling { }, ) - val DefaultTool: Preference = preference( - name = "Default Tool", - description = "The default tool to use when invoking the file selector", - default = Tool.tools.first(), - serializer = ToolSerializer, - editor = { - JComboBox(Vector(Tool.sortedByTitle)).apply { - selectedItem = currentValue - - configureCellRenderer { _, value, _, selected, focused -> - text = value?.title - toolTipText = value?.description - icon = value?.icon?.derive(ACTION_ICON_SCALE_FACTOR) + val DefaultTool: Preference by lazy { + preference( + name = "Default Tool", + description = "The default tool to use when invoking the file selector", + default = Tool.tools.first(), + serializer = ToolSerializer, + editor = { + JComboBox(Vector(Tool.sortedByTitle)).apply { + selectedItem = currentValue + + configureCellRenderer { _, value, _, selected, focused -> + text = value?.title + toolTipText = value?.description + icon = value?.icon?.derive(ACTION_ICON_SCALE_FACTOR) + } + + addActionListener { + currentValue = selectedItem as Tool + } } - - addActionListener { - currentValue = selectedItem as Tool - } - } - }, - ) + }, + ) + } val ChoosableEncodings = arrayOf( Charsets.UTF_8, @@ -153,6 +160,21 @@ data object Kindling { }, ) + val SelectedTimeZone = preference( + name = "Timezone", + description = "Timezone to use when displaying logs", + default = ZoneId.systemDefault(), + serializer = ZoneIdSerializer, + editor = { + JComboBox(Vector(ZoneRulesProvider.getAvailableZoneIds().sorted())).apply { + selectedItem = currentValue.id + addActionListener { + currentValue = ZoneId.of(selectedItem as String) + } + } + }, + ) + override val displayName: String = "General" override val serialKey: String = "general" override val preferences: List> = listOf( @@ -162,6 +184,7 @@ data object Kindling { ShowLogTree, UseHyperlinks, HighlightByDefault, + SelectedTimeZone, ) } diff --git a/src/main/kotlin/io/github/inductiveautomation/kindling/log/LogPanel.kt b/src/main/kotlin/io/github/inductiveautomation/kindling/log/LogPanel.kt index 1dd2b60d..104e3ae2 100644 --- a/src/main/kotlin/io/github/inductiveautomation/kindling/log/LogPanel.kt +++ b/src/main/kotlin/io/github/inductiveautomation/kindling/log/LogPanel.kt @@ -7,6 +7,7 @@ import io.github.inductiveautomation.kindling.core.FilterPanel import io.github.inductiveautomation.kindling.core.Kindling.Preferences.Advanced.HyperlinkStrategy import io.github.inductiveautomation.kindling.core.Kindling.Preferences.General.ShowFullLoggerNames import io.github.inductiveautomation.kindling.core.Kindling.Preferences.General.UseHyperlinks +import io.github.inductiveautomation.kindling.core.Kindling.Preferences.General.SelectedTimeZone import io.github.inductiveautomation.kindling.core.LinkHandlingStrategy import io.github.inductiveautomation.kindling.core.ToolOpeningException import io.github.inductiveautomation.kindling.core.ToolPanel @@ -313,7 +314,7 @@ sealed class LogPanel( table.selectionModel.updateDetails() } - LogViewer.SelectedTimeZone.addChangeListener { + SelectedTimeZone.addChangeListener { table.model.fireTableDataChanged() } } diff --git a/src/main/kotlin/io/github/inductiveautomation/kindling/log/TimePanel.kt b/src/main/kotlin/io/github/inductiveautomation/kindling/log/TimePanel.kt index 206471b5..0d8e5551 100644 --- a/src/main/kotlin/io/github/inductiveautomation/kindling/log/TimePanel.kt +++ b/src/main/kotlin/io/github/inductiveautomation/kindling/log/TimePanel.kt @@ -5,6 +5,7 @@ import com.formdev.flatlaf.extras.components.FlatButton import io.github.inductiveautomation.kindling.core.FilterChangeListener import io.github.inductiveautomation.kindling.core.FilterPanel import io.github.inductiveautomation.kindling.core.Kindling.Preferences.UI.Theme +import io.github.inductiveautomation.kindling.core.Kindling.Preferences.General.SelectedTimeZone import io.github.inductiveautomation.kindling.utils.Action import io.github.inductiveautomation.kindling.utils.Column import io.github.inductiveautomation.kindling.utils.ColumnList @@ -284,11 +285,11 @@ internal class TimePanel( } private var JXDatePicker.localDate: LocalDate? - get() = date?.toInstant()?.let { LocalDate.ofInstant(it, LogViewer.SelectedTimeZone.currentValue) } + get() = date?.toInstant()?.let { LocalDate.ofInstant(it, SelectedTimeZone.currentValue) } set(value) { date = value?.atStartOfDay() - ?.atOffset(LogViewer.SelectedTimeZone.currentValue.rules.getOffset(value.atStartOfDay())) + ?.atOffset(SelectedTimeZone.currentValue.rules.getOffset(value.atStartOfDay())) ?.toInstant() .let(Date::from) } @@ -307,7 +308,7 @@ class DateTimeSelector( } private val initialZonedTime: ZonedDateTime - get() = defaultValue.atZone(LogViewer.SelectedTimeZone.currentValue) + get() = defaultValue.atZone(SelectedTimeZone.currentValue) private val datePicker = JXDatePicker().apply { @@ -347,12 +348,12 @@ class DateTimeSelector( ZonedDateTime.of( localDate, timeSelector.localTime, - LogViewer.SelectedTimeZone.currentValue, + SelectedTimeZone.currentValue, ).toInstant() ?: defaultValue } } set(value) { - val zonedDateTime = value.atZone(LogViewer.SelectedTimeZone.currentValue) + val zonedDateTime = value.atZone(SelectedTimeZone.currentValue) datePicker.localDate = zonedDateTime.toLocalDate() timeSelector.localTime = zonedDateTime.toLocalTime() } @@ -511,10 +512,10 @@ private object DensityColumns : ColumnList() { get() { if (!this::_formatter.isInitialized) { _formatter = DateTimeFormatter.ofPattern("uuuu-MM-dd HH:mm") - .withZone(LogViewer.SelectedTimeZone.currentValue) + .withZone(SelectedTimeZone.currentValue) } - if (_formatter.zone != LogViewer.SelectedTimeZone.currentValue) { - _formatter = _formatter.withZone(LogViewer.SelectedTimeZone.currentValue) + if (_formatter.zone != SelectedTimeZone.currentValue) { + _formatter = _formatter.withZone(SelectedTimeZone.currentValue) } return _formatter } diff --git a/src/main/kotlin/io/github/inductiveautomation/kindling/log/WrapperLogPanel.kt b/src/main/kotlin/io/github/inductiveautomation/kindling/log/WrapperLogPanel.kt index 60367560..dea081f0 100644 --- a/src/main/kotlin/io/github/inductiveautomation/kindling/log/WrapperLogPanel.kt +++ b/src/main/kotlin/io/github/inductiveautomation/kindling/log/WrapperLogPanel.kt @@ -8,7 +8,9 @@ import io.github.inductiveautomation.kindling.core.MultiTool import io.github.inductiveautomation.kindling.core.Preference.Companion.preference import io.github.inductiveautomation.kindling.core.PreferenceCategory import io.github.inductiveautomation.kindling.core.ToolPanel -import io.github.inductiveautomation.kindling.log.LogViewer.SelectedTimeZone +// DEV: remove this old import +//import io.github.inductiveautomation.kindling.log.LogViewer.SelectedTimeZone +import io.github.inductiveautomation.kindling.core.Kindling.Preferences.General.SelectedTimeZone import io.github.inductiveautomation.kindling.log.WrapperLogEvent.Companion.STDOUT import io.github.inductiveautomation.kindling.utils.FileFilter import io.github.inductiveautomation.kindling.utils.FileFilterSidebar @@ -191,7 +193,7 @@ class WrapperLogPanel( } } -data object LogViewer : MultiTool, ClipboardTool, PreferenceCategory { +data object LogViewer : MultiTool, ClipboardTool { override val serialKey = "logview" override val title = "Wrapper Log" override val description = "Wrapper Log(s) (wrapper.log, wrapper.log.1, wrapper.log...)" @@ -229,20 +231,21 @@ data object LogViewer : MultiTool, ClipboardTool, PreferenceCategory { return WrapperLogPanel(listOf(tempFile), listOf(fileData)) } - val SelectedTimeZone = preference( - name = "Timezone", - description = "Timezone to use when displaying logs", - default = ZoneId.systemDefault(), - serializer = ZoneIdSerializer, - editor = { - JComboBox(Vector(ZoneRulesProvider.getAvailableZoneIds().sorted())).apply { - selectedItem = currentValue.id - addActionListener { - currentValue = ZoneId.of(selectedItem as String) - } - } - }, - ) +// DEV: Remove this +// val SelectedTimeZone = preference( +// name = "Timezone", +// description = "Timezone to use when displaying logs", +// default = ZoneId.systemDefault(), +// serializer = ZoneIdSerializer, +// editor = { +// JComboBox(Vector(ZoneRulesProvider.getAvailableZoneIds().sorted())).apply { +// selectedItem = currentValue.id +// addActionListener { +// currentValue = ZoneId.of(selectedItem as String) +// } +// } +// }, +// ) private var formatter = createFormatter(SelectedTimeZone.currentValue) @@ -260,7 +263,7 @@ data object LogViewer : MultiTool, ClipboardTool, PreferenceCategory { */ fun format(time: TemporalAccessor): String = formatter.format(time) - override val displayName = "Log View" - - override val preferences = listOf(SelectedTimeZone) +// override val displayName = "Log View" +// +// override val preferences = listOf(SelectedTimeZone) } From 9b7ca734dfdfd61eb787e19f40a51c0d3639b350 Mon Sep 17 00:00:00 2001 From: SpencerLommel Date: Sat, 11 Oct 2025 13:40:03 -0500 Subject: [PATCH 2/5] TimePreferences.kt --- .../kindling/core/Kindling.kt | 42 +++++++++---------- .../kindling/core/TimePreferences.kt | 30 +++++++++++++ .../kindling/log/LogPanel.kt | 9 ++-- .../kindling/log/TableModel.kt | 4 +- .../kindling/log/TimePanel.kt | 5 ++- .../kindling/log/WrapperLogPanel.kt | 37 +--------------- 6 files changed, 63 insertions(+), 64 deletions(-) create mode 100644 src/main/kotlin/io/github/inductiveautomation/kindling/core/TimePreferences.kt diff --git a/src/main/kotlin/io/github/inductiveautomation/kindling/core/Kindling.kt b/src/main/kotlin/io/github/inductiveautomation/kindling/core/Kindling.kt index 914ee9c4..eff3d6ca 100644 --- a/src/main/kotlin/io/github/inductiveautomation/kindling/core/Kindling.kt +++ b/src/main/kotlin/io/github/inductiveautomation/kindling/core/Kindling.kt @@ -77,29 +77,27 @@ data object Kindling { }, ) - val DefaultTool: Preference by lazy { - preference( - name = "Default Tool", - description = "The default tool to use when invoking the file selector", - default = Tool.tools.first(), - serializer = ToolSerializer, - editor = { - JComboBox(Vector(Tool.sortedByTitle)).apply { - selectedItem = currentValue - - configureCellRenderer { _, value, _, selected, focused -> - text = value?.title - toolTipText = value?.description - icon = value?.icon?.derive(ACTION_ICON_SCALE_FACTOR) - } - - addActionListener { - currentValue = selectedItem as Tool - } + val DefaultTool: Preference = preference( + name = "Default Tool", + description = "The default tool to use when invoking the file selector", + default = Tool.tools.first(), + serializer = ToolSerializer, + editor = { + JComboBox(Vector(Tool.sortedByTitle)).apply { + selectedItem = currentValue + + configureCellRenderer { _, value, _, selected, focused -> + text = value?.title + toolTipText = value?.description + icon = value?.icon?.derive(ACTION_ICON_SCALE_FACTOR) } - }, - ) - } + + addActionListener { + currentValue = selectedItem as Tool + } + } + }, + ) val ChoosableEncodings = arrayOf( Charsets.UTF_8, diff --git a/src/main/kotlin/io/github/inductiveautomation/kindling/core/TimePreferences.kt b/src/main/kotlin/io/github/inductiveautomation/kindling/core/TimePreferences.kt new file mode 100644 index 00000000..6a2f3ee7 --- /dev/null +++ b/src/main/kotlin/io/github/inductiveautomation/kindling/core/TimePreferences.kt @@ -0,0 +1,30 @@ +package io.github.inductiveautomation.kindling.core + +import java.time.ZoneId +import java.time.format.DateTimeFormatter +import java.time.temporal.TemporalAccessor + +object TimePreferences { + val SelectedTimeZone = Kindling.Preferences.General.SelectedTimeZone + + private var formatter = createFormatter(SelectedTimeZone.currentValue) + + init { + Kindling.Preferences.General.SelectedTimeZone.addChangeListener { newValue -> + formatter = createFormatter(newValue) + } + } + + private fun createFormatter(id: ZoneId): DateTimeFormatter = DateTimeFormatter.ofPattern("uuuu-MM-dd HH:mm:ss:SSS") + .withZone(id) + + /** + * Format [time] using an internal [DateTimeFormatter] that is in [SelectedTimeZone] automatically. + */ + fun format(time: TemporalAccessor): String = formatter.format(time) + + fun currentZone(): ZoneId = SelectedTimeZone.currentValue + + + +} \ No newline at end of file diff --git a/src/main/kotlin/io/github/inductiveautomation/kindling/log/LogPanel.kt b/src/main/kotlin/io/github/inductiveautomation/kindling/log/LogPanel.kt index 104e3ae2..7d74b703 100644 --- a/src/main/kotlin/io/github/inductiveautomation/kindling/log/LogPanel.kt +++ b/src/main/kotlin/io/github/inductiveautomation/kindling/log/LogPanel.kt @@ -7,8 +7,11 @@ import io.github.inductiveautomation.kindling.core.FilterPanel import io.github.inductiveautomation.kindling.core.Kindling.Preferences.Advanced.HyperlinkStrategy import io.github.inductiveautomation.kindling.core.Kindling.Preferences.General.ShowFullLoggerNames import io.github.inductiveautomation.kindling.core.Kindling.Preferences.General.UseHyperlinks -import io.github.inductiveautomation.kindling.core.Kindling.Preferences.General.SelectedTimeZone +//import io.github.inductiveautomation.kindling.core.Kindling.Preferences.General.SelectedTimeZone +import io.github.inductiveautomation.kindling.core.TimePreferences import io.github.inductiveautomation.kindling.core.LinkHandlingStrategy +import io.github.inductiveautomation.kindling.core.TimePreferences.SelectedTimeZone +import io.github.inductiveautomation.kindling.core.TimePreferences.format import io.github.inductiveautomation.kindling.core.ToolOpeningException import io.github.inductiveautomation.kindling.core.ToolPanel import io.github.inductiveautomation.kindling.utils.Action @@ -333,8 +336,8 @@ sealed class LogPanel( .map { event -> DetailEvent( title = when (event) { - is SystemLogEvent -> "${LogViewer.format(event.timestamp)} ${event.thread}" - else -> LogViewer.format(event.timestamp) + is SystemLogEvent -> "${format(event.timestamp)} ${event.thread}" + else -> format(event.timestamp) }, message = event.message, body = event.stacktrace.map { element -> diff --git a/src/main/kotlin/io/github/inductiveautomation/kindling/log/TableModel.kt b/src/main/kotlin/io/github/inductiveautomation/kindling/log/TableModel.kt index 54ce1cad..4b196a8b 100644 --- a/src/main/kotlin/io/github/inductiveautomation/kindling/log/TableModel.kt +++ b/src/main/kotlin/io/github/inductiveautomation/kindling/log/TableModel.kt @@ -2,6 +2,8 @@ package io.github.inductiveautomation.kindling.log import com.jidesoft.comparator.AlphanumComparator import io.github.inductiveautomation.kindling.core.Kindling.Preferences.General.ShowFullLoggerNames +import io.github.inductiveautomation.kindling.core.TimePreferences +import io.github.inductiveautomation.kindling.core.TimePreferences.format import io.github.inductiveautomation.kindling.utils.Column import io.github.inductiveautomation.kindling.utils.ColumnList import io.github.inductiveautomation.kindling.utils.FlatActionIcon @@ -81,7 +83,7 @@ sealed class LogColumnList : ColumnList() { minWidth = 155 maxWidth = 155 cellRenderer = DefaultTableRenderer { - (it as? Instant)?.let(LogViewer::format) + (it as? Instant)?.let(TimePreferences::format) } }, getValue = LogEvent::timestamp, diff --git a/src/main/kotlin/io/github/inductiveautomation/kindling/log/TimePanel.kt b/src/main/kotlin/io/github/inductiveautomation/kindling/log/TimePanel.kt index 0d8e5551..f3534a82 100644 --- a/src/main/kotlin/io/github/inductiveautomation/kindling/log/TimePanel.kt +++ b/src/main/kotlin/io/github/inductiveautomation/kindling/log/TimePanel.kt @@ -6,6 +6,7 @@ import io.github.inductiveautomation.kindling.core.FilterChangeListener import io.github.inductiveautomation.kindling.core.FilterPanel import io.github.inductiveautomation.kindling.core.Kindling.Preferences.UI.Theme import io.github.inductiveautomation.kindling.core.Kindling.Preferences.General.SelectedTimeZone +import io.github.inductiveautomation.kindling.core.TimePreferences import io.github.inductiveautomation.kindling.utils.Action import io.github.inductiveautomation.kindling.utils.Column import io.github.inductiveautomation.kindling.utils.ColumnList @@ -265,12 +266,12 @@ internal class TimePanel( ) { if (column == WrapperLogColumns.Timestamp || column == SystemLogColumns.Timestamp) { menu.add( - Action("Show only events after ${LogViewer.format(event.timestamp)}") { + Action("Show only events after ${TimePreferences.format(event.timestamp)}") { startSelector.time = event.timestamp }, ) menu.add( - Action("Show only events before ${LogViewer.format(event.timestamp)}") { + Action("Show only events before ${TimePreferences.format(event.timestamp)}") { endSelector.time = event.timestamp }, ) diff --git a/src/main/kotlin/io/github/inductiveautomation/kindling/log/WrapperLogPanel.kt b/src/main/kotlin/io/github/inductiveautomation/kindling/log/WrapperLogPanel.kt index dea081f0..f7c8b2ab 100644 --- a/src/main/kotlin/io/github/inductiveautomation/kindling/log/WrapperLogPanel.kt +++ b/src/main/kotlin/io/github/inductiveautomation/kindling/log/WrapperLogPanel.kt @@ -10,7 +10,7 @@ import io.github.inductiveautomation.kindling.core.PreferenceCategory import io.github.inductiveautomation.kindling.core.ToolPanel // DEV: remove this old import //import io.github.inductiveautomation.kindling.log.LogViewer.SelectedTimeZone -import io.github.inductiveautomation.kindling.core.Kindling.Preferences.General.SelectedTimeZone +import io.github.inductiveautomation.kindling.core.TimePreferences import io.github.inductiveautomation.kindling.log.WrapperLogEvent.Companion.STDOUT import io.github.inductiveautomation.kindling.utils.FileFilter import io.github.inductiveautomation.kindling.utils.FileFilterSidebar @@ -231,39 +231,4 @@ data object LogViewer : MultiTool, ClipboardTool { return WrapperLogPanel(listOf(tempFile), listOf(fileData)) } -// DEV: Remove this -// val SelectedTimeZone = preference( -// name = "Timezone", -// description = "Timezone to use when displaying logs", -// default = ZoneId.systemDefault(), -// serializer = ZoneIdSerializer, -// editor = { -// JComboBox(Vector(ZoneRulesProvider.getAvailableZoneIds().sorted())).apply { -// selectedItem = currentValue.id -// addActionListener { -// currentValue = ZoneId.of(selectedItem as String) -// } -// } -// }, -// ) - - private var formatter = createFormatter(SelectedTimeZone.currentValue) - - init { - SelectedTimeZone.addChangeListener { newValue -> - formatter = createFormatter(newValue) - } - } - - private fun createFormatter(id: ZoneId): DateTimeFormatter = DateTimeFormatter.ofPattern("uuuu-MM-dd HH:mm:ss:SSS") - .withZone(id) - - /** - * Format [time] using an internal [DateTimeFormatter] that is in [SelectedTimeZone] automatically. - */ - fun format(time: TemporalAccessor): String = formatter.format(time) - -// override val displayName = "Log View" -// -// override val preferences = listOf(SelectedTimeZone) } From f505de1ba9b9079cbcd3f04001a96e47184dbc51 Mon Sep 17 00:00:00 2001 From: SpencerLommel Date: Sat, 11 Oct 2025 14:26:16 -0500 Subject: [PATCH 3/5] Apply TimePreferences to idb metrics --- .../kindling/core/Kindling.kt | 4 +-- .../kindling/core/TimePreferences.kt | 29 ++++++++++++--- .../kindling/idb/metrics/MetricCard.kt | 13 ++++--- .../kindling/idb/metrics/MetricsView.kt | 2 +- .../kindling/idb/metrics/Sparkline.kt | 35 ++++++++++++++----- .../kindling/log/LogPanel.kt | 2 -- .../kindling/log/TableModel.kt | 1 - .../kindling/log/TimePanel.kt | 2 +- .../kindling/log/WrapperLogPanel.kt | 11 ------ 9 files changed, 62 insertions(+), 37 deletions(-) diff --git a/src/main/kotlin/io/github/inductiveautomation/kindling/core/Kindling.kt b/src/main/kotlin/io/github/inductiveautomation/kindling/core/Kindling.kt index eff3d6ca..715f2e6e 100644 --- a/src/main/kotlin/io/github/inductiveautomation/kindling/core/Kindling.kt +++ b/src/main/kotlin/io/github/inductiveautomation/kindling/core/Kindling.kt @@ -6,8 +6,6 @@ import com.formdev.flatlaf.util.SystemInfo import com.github.weisj.jsvg.parser.SVGLoader import io.github.inductiveautomation.kindling.core.Preference.Companion.PreferenceCheckbox import io.github.inductiveautomation.kindling.core.Preference.Companion.preference -// DEV: remove old import -//import io.github.inductiveautomation.kindling.log.LogViewer.SelectedTimeZone import io.github.inductiveautomation.kindling.utils.ACTION_ICON_SCALE_FACTOR import io.github.inductiveautomation.kindling.utils.CharsetSerializer import io.github.inductiveautomation.kindling.utils.DocumentAdapter @@ -160,7 +158,7 @@ data object Kindling { val SelectedTimeZone = preference( name = "Timezone", - description = "Timezone to use when displaying logs", + description = "Timezone to use when displaying timestamps", default = ZoneId.systemDefault(), serializer = ZoneIdSerializer, editor = { diff --git a/src/main/kotlin/io/github/inductiveautomation/kindling/core/TimePreferences.kt b/src/main/kotlin/io/github/inductiveautomation/kindling/core/TimePreferences.kt index 6a2f3ee7..03602b2e 100644 --- a/src/main/kotlin/io/github/inductiveautomation/kindling/core/TimePreferences.kt +++ b/src/main/kotlin/io/github/inductiveautomation/kindling/core/TimePreferences.kt @@ -3,6 +3,7 @@ package io.github.inductiveautomation.kindling.core import java.time.ZoneId import java.time.format.DateTimeFormatter import java.time.temporal.TemporalAccessor +import java.util.Date object TimePreferences { val SelectedTimeZone = Kindling.Preferences.General.SelectedTimeZone @@ -14,17 +15,35 @@ object TimePreferences { formatter = createFormatter(newValue) } } + private val listeners = mutableListOf<() -> Unit>() + + fun addChangeListener(listener: () -> Unit) { + listeners += listener + } + + init { + Kindling.Preferences.General.SelectedTimeZone.addChangeListener { newValue -> + formatter = createFormatter(newValue) + listeners.forEach { it() } // notify listeners when timezone changes + } + } private fun createFormatter(id: ZoneId): DateTimeFormatter = DateTimeFormatter.ofPattern("uuuu-MM-dd HH:mm:ss:SSS") .withZone(id) /** * Format [time] using an internal [DateTimeFormatter] that is in [SelectedTimeZone] automatically. + * + * This function is overloaded to also accept [Date] types, including [java.sql.Date] and [java.sql.Timestamp]. + * - [java.sql.Date] is converted via [toLocalDate] at the start of the day in the selected timezone. + * - [java.sql.Timestamp] and [java.util.Date] preserve full time-of-day precision. */ - fun format(time: TemporalAccessor): String = formatter.format(time) - - fun currentZone(): ZoneId = SelectedTimeZone.currentValue - + fun format(time: TemporalAccessor): String = formatter.format(time) -} \ No newline at end of file + fun format(date: Date): String = when (date) { + is java.sql.Date -> formatter.format(date.toLocalDate().atStartOfDay(SelectedTimeZone.currentValue)) + is java.sql.Timestamp -> formatter.format(date.toInstant()) + else -> formatter.format(date.toInstant()) + } +} diff --git a/src/main/kotlin/io/github/inductiveautomation/kindling/idb/metrics/MetricCard.kt b/src/main/kotlin/io/github/inductiveautomation/kindling/idb/metrics/MetricCard.kt index 03e4323b..eb6806dd 100644 --- a/src/main/kotlin/io/github/inductiveautomation/kindling/idb/metrics/MetricCard.kt +++ b/src/main/kotlin/io/github/inductiveautomation/kindling/idb/metrics/MetricCard.kt @@ -1,5 +1,6 @@ package io.github.inductiveautomation.kindling.idb.metrics +import io.github.inductiveautomation.kindling.core.TimePreferences import io.github.inductiveautomation.kindling.idb.metrics.MetricCard.Companion.MetricPresentation.Cpu import io.github.inductiveautomation.kindling.idb.metrics.MetricCard.Companion.MetricPresentation.Default import io.github.inductiveautomation.kindling.idb.metrics.MetricCard.Companion.MetricPresentation.Heap @@ -17,7 +18,6 @@ import java.text.DecimalFormat import java.text.FieldPosition import java.text.NumberFormat import java.text.ParsePosition -import java.text.SimpleDateFormat import javax.swing.JLabel import javax.swing.JPanel import javax.swing.SwingConstants.CENTER @@ -90,13 +90,18 @@ class MetricCard(val metric: Metric, data: List) : JPanel(MigLayout( } add(sparkLine, "span, w 300, h 170, pushx, growx") - add(JLabel("${DATE_FORMAT.format(minTimestamp)} - ${DATE_FORMAT.format(maxTimestamp)}", CENTER), "pushx, growx, span") + + val timeLabel = JLabel("${TimePreferences.format(minTimestamp)} - ${TimePreferences.format(maxTimestamp)}", CENTER) + add(timeLabel, "pushx, growx, span") + + TimePreferences.addChangeListener { + timeLabel.text = "${TimePreferences.format(minTimestamp)} - ${TimePreferences.format(maxTimestamp)}" + } border = LineBorder(UIManager.getColor("Component.borderColor"), 3, true) } companion object { - val DATE_FORMAT = SimpleDateFormat("MM/dd/yy HH:mm:ss") private val mbFormatter = DecimalFormat("0.0 'mB'") private val heapFormatter = object : NumberFormat() { @@ -117,7 +122,7 @@ class MetricCard(val metric: Metric, data: List) : JPanel(MigLayout( }, true, ), - Default(NumberFormat.getInstance(), false) + Default(NumberFormat.getInstance(), false), } private val Metric.presentation: MetricPresentation diff --git a/src/main/kotlin/io/github/inductiveautomation/kindling/idb/metrics/MetricsView.kt b/src/main/kotlin/io/github/inductiveautomation/kindling/idb/metrics/MetricsView.kt index bf6cfd38..bb0bc7b0 100644 --- a/src/main/kotlin/io/github/inductiveautomation/kindling/idb/metrics/MetricsView.kt +++ b/src/main/kotlin/io/github/inductiveautomation/kindling/idb/metrics/MetricsView.kt @@ -54,7 +54,7 @@ class MetricsView(connection: Connection) : ToolPanel("ins 0, fill, hidemode 3") } .executeQuery() .toList { rs -> - MetricData(rs.getDouble(1), rs.getDate(2)) + MetricData(rs.getDouble(1), rs.getTimestamp(2)) } MetricCard(metric, metricData) diff --git a/src/main/kotlin/io/github/inductiveautomation/kindling/idb/metrics/Sparkline.kt b/src/main/kotlin/io/github/inductiveautomation/kindling/idb/metrics/Sparkline.kt index 1858cf5c..a4076751 100644 --- a/src/main/kotlin/io/github/inductiveautomation/kindling/idb/metrics/Sparkline.kt +++ b/src/main/kotlin/io/github/inductiveautomation/kindling/idb/metrics/Sparkline.kt @@ -2,7 +2,7 @@ package io.github.inductiveautomation.kindling.idb.metrics import io.github.inductiveautomation.kindling.core.Kindling.Preferences.UI.Theme import io.github.inductiveautomation.kindling.core.Theme.Companion.theme -import io.github.inductiveautomation.kindling.idb.metrics.MetricCard.Companion.DATE_FORMAT +import io.github.inductiveautomation.kindling.core.TimePreferences import org.jfree.chart.ChartFactory import org.jfree.chart.JFreeChart import org.jfree.chart.axis.NumberAxis @@ -11,12 +11,16 @@ import org.jfree.data.time.FixedMillisecond import org.jfree.data.time.TimeSeries import org.jfree.data.time.TimeSeriesCollection import java.text.NumberFormat +import java.time.Instant fun sparkline(data: List, formatter: NumberFormat): JFreeChart { return ChartFactory.createTimeSeriesChart( - /* title = */ null, - /* timeAxisLabel = */ null, - /* valueAxisLabel = */ null, + /* title = */ + null, + /* timeAxisLabel = */ + null, + /* valueAxisLabel = */ + null, /* dataset = */ TimeSeriesCollection( TimeSeries("Series").apply { @@ -25,9 +29,12 @@ fun sparkline(data: List, formatter: NumberFormat): JFreeChart { } }, ), - /* legend = */ false, - /* tooltips = */ true, - /* urls = */ false, + /* legend = */ + false, + /* tooltips = */ + true, + /* urls = */ + false, ).apply { xyPlot.apply { domainAxis.isPositiveArrowVisible = true @@ -35,9 +42,19 @@ fun sparkline(data: List, formatter: NumberFormat): JFreeChart { isPositiveArrowVisible = true (this as NumberAxis).numberFormatOverride = formatter } - renderer.setDefaultToolTipGenerator { dataset, series, item -> - "${DATE_FORMAT.format(dataset.getXValue(series, item))} - ${formatter.format(dataset.getYValue(series, item))}" + val updateTooltipGenerator = { + renderer.setDefaultToolTipGenerator { dataset, series, item -> + val time = Instant.ofEpochMilli(dataset.getXValue(series, item).toLong()) + "${TimePreferences.format(time)} - ${formatter.format(dataset.getYValue(series, item))}" + } } + + updateTooltipGenerator() + + TimePreferences.addChangeListener { + updateTooltipGenerator() + } + isDomainGridlinesVisible = false isRangeGridlinesVisible = false isOutlineVisible = false diff --git a/src/main/kotlin/io/github/inductiveautomation/kindling/log/LogPanel.kt b/src/main/kotlin/io/github/inductiveautomation/kindling/log/LogPanel.kt index 7d74b703..57f9e430 100644 --- a/src/main/kotlin/io/github/inductiveautomation/kindling/log/LogPanel.kt +++ b/src/main/kotlin/io/github/inductiveautomation/kindling/log/LogPanel.kt @@ -7,8 +7,6 @@ import io.github.inductiveautomation.kindling.core.FilterPanel import io.github.inductiveautomation.kindling.core.Kindling.Preferences.Advanced.HyperlinkStrategy import io.github.inductiveautomation.kindling.core.Kindling.Preferences.General.ShowFullLoggerNames import io.github.inductiveautomation.kindling.core.Kindling.Preferences.General.UseHyperlinks -//import io.github.inductiveautomation.kindling.core.Kindling.Preferences.General.SelectedTimeZone -import io.github.inductiveautomation.kindling.core.TimePreferences import io.github.inductiveautomation.kindling.core.LinkHandlingStrategy import io.github.inductiveautomation.kindling.core.TimePreferences.SelectedTimeZone import io.github.inductiveautomation.kindling.core.TimePreferences.format diff --git a/src/main/kotlin/io/github/inductiveautomation/kindling/log/TableModel.kt b/src/main/kotlin/io/github/inductiveautomation/kindling/log/TableModel.kt index 4b196a8b..1003d400 100644 --- a/src/main/kotlin/io/github/inductiveautomation/kindling/log/TableModel.kt +++ b/src/main/kotlin/io/github/inductiveautomation/kindling/log/TableModel.kt @@ -3,7 +3,6 @@ package io.github.inductiveautomation.kindling.log import com.jidesoft.comparator.AlphanumComparator import io.github.inductiveautomation.kindling.core.Kindling.Preferences.General.ShowFullLoggerNames import io.github.inductiveautomation.kindling.core.TimePreferences -import io.github.inductiveautomation.kindling.core.TimePreferences.format import io.github.inductiveautomation.kindling.utils.Column import io.github.inductiveautomation.kindling.utils.ColumnList import io.github.inductiveautomation.kindling.utils.FlatActionIcon diff --git a/src/main/kotlin/io/github/inductiveautomation/kindling/log/TimePanel.kt b/src/main/kotlin/io/github/inductiveautomation/kindling/log/TimePanel.kt index f3534a82..b210f556 100644 --- a/src/main/kotlin/io/github/inductiveautomation/kindling/log/TimePanel.kt +++ b/src/main/kotlin/io/github/inductiveautomation/kindling/log/TimePanel.kt @@ -5,8 +5,8 @@ import com.formdev.flatlaf.extras.components.FlatButton import io.github.inductiveautomation.kindling.core.FilterChangeListener import io.github.inductiveautomation.kindling.core.FilterPanel import io.github.inductiveautomation.kindling.core.Kindling.Preferences.UI.Theme -import io.github.inductiveautomation.kindling.core.Kindling.Preferences.General.SelectedTimeZone import io.github.inductiveautomation.kindling.core.TimePreferences +import io.github.inductiveautomation.kindling.core.TimePreferences.SelectedTimeZone import io.github.inductiveautomation.kindling.utils.Action import io.github.inductiveautomation.kindling.utils.Column import io.github.inductiveautomation.kindling.utils.ColumnList diff --git a/src/main/kotlin/io/github/inductiveautomation/kindling/log/WrapperLogPanel.kt b/src/main/kotlin/io/github/inductiveautomation/kindling/log/WrapperLogPanel.kt index f7c8b2ab..b93b86aa 100644 --- a/src/main/kotlin/io/github/inductiveautomation/kindling/log/WrapperLogPanel.kt +++ b/src/main/kotlin/io/github/inductiveautomation/kindling/log/WrapperLogPanel.kt @@ -5,17 +5,11 @@ import com.jidesoft.comparator.AlphanumComparator import io.github.inductiveautomation.kindling.core.ClipboardTool import io.github.inductiveautomation.kindling.core.Kindling.Preferences.General.DefaultEncoding import io.github.inductiveautomation.kindling.core.MultiTool -import io.github.inductiveautomation.kindling.core.Preference.Companion.preference -import io.github.inductiveautomation.kindling.core.PreferenceCategory import io.github.inductiveautomation.kindling.core.ToolPanel -// DEV: remove this old import -//import io.github.inductiveautomation.kindling.log.LogViewer.SelectedTimeZone -import io.github.inductiveautomation.kindling.core.TimePreferences import io.github.inductiveautomation.kindling.log.WrapperLogEvent.Companion.STDOUT import io.github.inductiveautomation.kindling.utils.FileFilter import io.github.inductiveautomation.kindling.utils.FileFilterSidebar import io.github.inductiveautomation.kindling.utils.TabStrip -import io.github.inductiveautomation.kindling.utils.ZoneIdSerializer import io.github.inductiveautomation.kindling.utils.getValue import io.github.inductiveautomation.kindling.utils.transferTo import kotlinx.coroutines.Dispatchers @@ -28,10 +22,6 @@ import java.time.Instant import java.time.ZoneId import java.time.format.DateTimeFormatter import java.time.format.DateTimeParseException -import java.time.temporal.TemporalAccessor -import java.time.zone.ZoneRulesProvider -import java.util.Vector -import javax.swing.JComboBox import javax.swing.SwingUtilities import kotlin.io.path.absolutePathString import kotlin.io.path.name @@ -230,5 +220,4 @@ data object LogViewer : MultiTool, ClipboardTool { ) return WrapperLogPanel(listOf(tempFile), listOf(fileData)) } - } From 0ad8ba468cac5c8041658195baf5ef395010d071 Mon Sep 17 00:00:00 2001 From: SpencerLommel Date: Sat, 11 Oct 2025 14:30:58 -0500 Subject: [PATCH 4/5] TimePreferences.format for clarity --- .../io/github/inductiveautomation/kindling/log/LogPanel.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/kotlin/io/github/inductiveautomation/kindling/log/LogPanel.kt b/src/main/kotlin/io/github/inductiveautomation/kindling/log/LogPanel.kt index 57f9e430..af263932 100644 --- a/src/main/kotlin/io/github/inductiveautomation/kindling/log/LogPanel.kt +++ b/src/main/kotlin/io/github/inductiveautomation/kindling/log/LogPanel.kt @@ -9,7 +9,7 @@ import io.github.inductiveautomation.kindling.core.Kindling.Preferences.General. import io.github.inductiveautomation.kindling.core.Kindling.Preferences.General.UseHyperlinks import io.github.inductiveautomation.kindling.core.LinkHandlingStrategy import io.github.inductiveautomation.kindling.core.TimePreferences.SelectedTimeZone -import io.github.inductiveautomation.kindling.core.TimePreferences.format +import io.github.inductiveautomation.kindling.core.TimePreferences import io.github.inductiveautomation.kindling.core.ToolOpeningException import io.github.inductiveautomation.kindling.core.ToolPanel import io.github.inductiveautomation.kindling.utils.Action @@ -334,8 +334,8 @@ sealed class LogPanel( .map { event -> DetailEvent( title = when (event) { - is SystemLogEvent -> "${format(event.timestamp)} ${event.thread}" - else -> format(event.timestamp) + is SystemLogEvent -> "${TimePreferences.format(event.timestamp)} ${event.thread}" + else -> TimePreferences.format(event.timestamp) }, message = event.message, body = event.stacktrace.map { element -> From 986a9d21e24340d26bb6702ff71a0725d910eefa Mon Sep 17 00:00:00 2001 From: SpencerLommel Date: Sat, 11 Oct 2025 19:25:45 -0500 Subject: [PATCH 5/5] Spotless --- .../kindling/idb/metrics/Sparkline.kt | 92 +++++++++---------- .../kindling/log/LogPanel.kt | 2 +- 2 files changed, 46 insertions(+), 48 deletions(-) diff --git a/src/main/kotlin/io/github/inductiveautomation/kindling/idb/metrics/Sparkline.kt b/src/main/kotlin/io/github/inductiveautomation/kindling/idb/metrics/Sparkline.kt index a4076751..7dd968f5 100644 --- a/src/main/kotlin/io/github/inductiveautomation/kindling/idb/metrics/Sparkline.kt +++ b/src/main/kotlin/io/github/inductiveautomation/kindling/idb/metrics/Sparkline.kt @@ -13,59 +13,57 @@ import org.jfree.data.time.TimeSeriesCollection import java.text.NumberFormat import java.time.Instant -fun sparkline(data: List, formatter: NumberFormat): JFreeChart { - return ChartFactory.createTimeSeriesChart( - /* title = */ - null, - /* timeAxisLabel = */ - null, - /* valueAxisLabel = */ - null, - /* dataset = */ - TimeSeriesCollection( - TimeSeries("Series").apply { - for ((value, timestamp) in data) { - add(FixedMillisecond(timestamp), value, false) - } - }, - ), - /* legend = */ - false, - /* tooltips = */ - true, - /* urls = */ - false, - ).apply { - xyPlot.apply { - domainAxis.isPositiveArrowVisible = true - rangeAxis.apply { - isPositiveArrowVisible = true - (this as NumberAxis).numberFormatOverride = formatter +fun sparkline(data: List, formatter: NumberFormat): JFreeChart = ChartFactory.createTimeSeriesChart( + /* title = */ + null, + /* timeAxisLabel = */ + null, + /* valueAxisLabel = */ + null, + /* dataset = */ + TimeSeriesCollection( + TimeSeries("Series").apply { + for ((value, timestamp) in data) { + add(FixedMillisecond(timestamp), value, false) } - val updateTooltipGenerator = { - renderer.setDefaultToolTipGenerator { dataset, series, item -> - val time = Instant.ofEpochMilli(dataset.getXValue(series, item).toLong()) - "${TimePreferences.format(time)} - ${formatter.format(dataset.getYValue(series, item))}" - } + }, + ), + /* legend = */ + false, + /* tooltips = */ + true, + /* urls = */ + false, +).apply { + xyPlot.apply { + domainAxis.isPositiveArrowVisible = true + rangeAxis.apply { + isPositiveArrowVisible = true + (this as NumberAxis).numberFormatOverride = formatter + } + val updateTooltipGenerator = { + renderer.setDefaultToolTipGenerator { dataset, series, item -> + val time = Instant.ofEpochMilli(dataset.getXValue(series, item).toLong()) + "${TimePreferences.format(time)} - ${formatter.format(dataset.getYValue(series, item))}" } + } - updateTooltipGenerator() - - TimePreferences.addChangeListener { - updateTooltipGenerator() - } + updateTooltipGenerator() - isDomainGridlinesVisible = false - isRangeGridlinesVisible = false - isOutlineVisible = false + TimePreferences.addChangeListener { + updateTooltipGenerator() } - padding = RectangleInsets(10.0, 10.0, 10.0, 10.0) - isBorderVisible = false + isDomainGridlinesVisible = false + isRangeGridlinesVisible = false + isOutlineVisible = false + } + + padding = RectangleInsets(10.0, 10.0, 10.0, 10.0) + isBorderVisible = false - theme = Theme.currentValue - Theme.addChangeListener { newTheme -> - theme = newTheme - } + theme = Theme.currentValue + Theme.addChangeListener { newTheme -> + theme = newTheme } } diff --git a/src/main/kotlin/io/github/inductiveautomation/kindling/log/LogPanel.kt b/src/main/kotlin/io/github/inductiveautomation/kindling/log/LogPanel.kt index af263932..6b48cba6 100644 --- a/src/main/kotlin/io/github/inductiveautomation/kindling/log/LogPanel.kt +++ b/src/main/kotlin/io/github/inductiveautomation/kindling/log/LogPanel.kt @@ -8,8 +8,8 @@ import io.github.inductiveautomation.kindling.core.Kindling.Preferences.Advanced import io.github.inductiveautomation.kindling.core.Kindling.Preferences.General.ShowFullLoggerNames import io.github.inductiveautomation.kindling.core.Kindling.Preferences.General.UseHyperlinks import io.github.inductiveautomation.kindling.core.LinkHandlingStrategy -import io.github.inductiveautomation.kindling.core.TimePreferences.SelectedTimeZone import io.github.inductiveautomation.kindling.core.TimePreferences +import io.github.inductiveautomation.kindling.core.TimePreferences.SelectedTimeZone import io.github.inductiveautomation.kindling.core.ToolOpeningException import io.github.inductiveautomation.kindling.core.ToolPanel import io.github.inductiveautomation.kindling.utils.Action