Context analysis: +/-5min raw logs
This commit is contained in:
BIN
main/Assets.xcassets/.DS_Store
vendored
BIN
main/Assets.xcassets/.DS_Store
vendored
Binary file not shown.
23
main/Assets.xcassets/jump-to-target.imageset/Contents.json
vendored
Normal file
23
main/Assets.xcassets/jump-to-target.imageset/Contents.json
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "img.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "img@2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "img@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
main/Assets.xcassets/jump-to-target.imageset/img.png
vendored
Normal file
BIN
main/Assets.xcassets/jump-to-target.imageset/img.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 230 B |
BIN
main/Assets.xcassets/jump-to-target.imageset/img@2x.png
vendored
Normal file
BIN
main/Assets.xcassets/jump-to-target.imageset/img@2x.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 409 B |
BIN
main/Assets.xcassets/jump-to-target.imageset/img@3x.png
vendored
Normal file
BIN
main/Assets.xcassets/jump-to-target.imageset/img@3x.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 544 B |
@@ -388,7 +388,7 @@
|
||||
<scene sceneID="ws3-sK-l8m">
|
||||
<objects>
|
||||
<tableViewController id="h7Z-Qr-pJ5" customClass="TVCHostDetails" customModule="AppCheck" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" allowsSelection="NO" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="28" sectionFooterHeight="28" id="4ms-FO-Fge">
|
||||
<tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="28" sectionFooterHeight="28" id="4ms-FO-Fge">
|
||||
<rect key="frame" x="0.0" y="0.0" width="320" height="568"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
|
||||
@@ -404,11 +404,11 @@
|
||||
</connections>
|
||||
</tabBar>
|
||||
<prototypes>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" reuseIdentifier="HostDetailCell" textLabel="J2P-mU-Vad" detailTextLabel="eWb-mX-udN" style="IBUITableViewCellStyleValue1" id="ZCA-Dz-i92">
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" accessoryType="disclosureIndicator" indentationWidth="10" reuseIdentifier="HostDetailCell" textLabel="J2P-mU-Vad" detailTextLabel="eWb-mX-udN" style="IBUITableViewCellStyleValue1" id="ZCA-Dz-i92">
|
||||
<rect key="frame" x="0.0" y="77" width="320" height="43.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="ZCA-Dz-i92" id="nxe-48-jAQ">
|
||||
<rect key="frame" x="0.0" y="0.0" width="320" height="43.5"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="293" height="43.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="Title" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="J2P-mU-Vad">
|
||||
@@ -419,7 +419,7 @@
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="Detail" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="eWb-mX-udN">
|
||||
<rect key="frame" x="260" y="12" width="44" height="20.5"/>
|
||||
<rect key="frame" x="241" y="12" width="44" height="20.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleBody"/>
|
||||
<color key="textColor" systemColor="secondaryLabelColor" red="0.23529411759999999" green="0.23529411759999999" blue="0.26274509800000001" alpha="0.59999999999999998" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
@@ -427,6 +427,9 @@
|
||||
</label>
|
||||
</subviews>
|
||||
</tableViewCellContentView>
|
||||
<connections>
|
||||
<segue destination="rjy-Di-Cru" kind="push" id="SfC-iY-Ce0"/>
|
||||
</connections>
|
||||
</tableViewCell>
|
||||
</prototypes>
|
||||
<connections>
|
||||
@@ -512,13 +515,13 @@
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" horizontalCompressionResistancePriority="500" insetsLayoutMarginsFromSafeArea="NO" text="Count" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="JWp-6l-HTJ">
|
||||
<rect key="frame" x="109.5" y="42.5" width="37" height="16"/>
|
||||
<rect key="frame" x="129" y="42.5" width="37" height="16"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleFootnote"/>
|
||||
<color key="textColor" systemColor="secondaryLabelColor" red="0.23529411759999999" green="0.23529411759999999" blue="0.26274509800000001" alpha="0.59999999999999998" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="5900" textAlignment="natural" lineBreakMode="clip" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="q5v-FM-iGo" customClass="TagLabel" customModule="AppCheck" customModuleProvider="target">
|
||||
<rect key="frame" x="150.5" y="39.5" width="42.5" height="21.5"/>
|
||||
<rect key="frame" x="170" y="39.5" width="32.5" height="21.5"/>
|
||||
<color key="backgroundColor" systemColor="secondarySystemBackgroundColor" red="0.94901960780000005" green="0.94901960780000005" blue="0.96862745100000003" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleFootnote"/>
|
||||
<nil key="textColor"/>
|
||||
@@ -530,7 +533,7 @@
|
||||
</userDefinedRuntimeAttributes>
|
||||
</label>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="10.35s" textAlignment="natural" lineBreakMode="clip" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="zCg-I0-4Tz" customClass="TagLabel" customModule="AppCheck" customModuleProvider="target">
|
||||
<rect key="frame" x="255.5" y="39.5" width="49.5" height="21.5"/>
|
||||
<rect key="frame" x="265" y="39.5" width="40" height="21.5"/>
|
||||
<color key="backgroundColor" systemColor="secondarySystemBackgroundColor" red="0.94901960780000005" green="0.94901960780000005" blue="0.96862745100000003" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleFootnote"/>
|
||||
<nil key="textColor"/>
|
||||
@@ -542,13 +545,13 @@
|
||||
</userDefinedRuntimeAttributes>
|
||||
</label>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" horizontalHuggingPriority="750" horizontalCompressionResistancePriority="400" insetsLayoutMarginsFromSafeArea="NO" text="Diverge" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="T4X-cn-msT">
|
||||
<rect key="frame" x="205" y="42.5" width="46.5" height="16"/>
|
||||
<rect key="frame" x="214.5" y="42.5" width="46.5" height="16"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleFootnote"/>
|
||||
<color key="textColor" systemColor="secondaryLabelColor" red="0.23529411759999999" green="0.23529411759999999" blue="0.26274509800000001" alpha="0.59999999999999998" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<view opaque="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="9Bb-e5-D3O" customClass="MeterBar" customModule="AppCheck" customModuleProvider="target">
|
||||
<rect key="frame" x="190" y="39.5" width="3" height="21.5"/>
|
||||
<rect key="frame" x="199.5" y="39.5" width="3" height="21.5"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="3" id="wWb-VG-Kqa"/>
|
||||
</constraints>
|
||||
@@ -638,6 +641,57 @@
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="yYY-5U-gct" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="2100" y="-1950"/>
|
||||
</scene>
|
||||
<!--Occurrence Context-->
|
||||
<scene sceneID="A1T-7G-agr">
|
||||
<objects>
|
||||
<tableViewController id="rjy-Di-Cru" customClass="TVCOccurrenceContext" customModule="AppCheck" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="28" sectionFooterHeight="28" id="EfM-yv-85f">
|
||||
<rect key="frame" x="0.0" y="0.0" width="320" height="568"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
|
||||
<prototypes>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="none" indentationWidth="10" reuseIdentifier="OccurrenceContextCell" textLabel="xgq-hW-e3R" detailTextLabel="No8-Bf-ptL" style="IBUITableViewCellStyleValue2" id="KQh-Ei-If8">
|
||||
<rect key="frame" x="0.0" y="28" width="320" height="43.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="KQh-Ei-If8" id="i32-u4-1Q8">
|
||||
<rect key="frame" x="0.0" y="0.0" width="320" height="43.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="Title" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="xgq-hW-e3R">
|
||||
<rect key="frame" x="16" y="12" width="91" height="20.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleBody"/>
|
||||
<color key="textColor" systemColor="secondaryLabelColor" red="0.23529411759999999" green="0.23529411759999999" blue="0.26274509800000001" alpha="0.59999999999999998" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="Subtitle" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumScaleFactor="0.80000000000000004" id="No8-Bf-ptL">
|
||||
<rect key="frame" x="113" y="12" width="59" height="20.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleBody"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
</tableViewCellContentView>
|
||||
</tableViewCell>
|
||||
</prototypes>
|
||||
<connections>
|
||||
<outlet property="dataSource" destination="rjy-Di-Cru" id="6CT-Vd-Ixn"/>
|
||||
<outlet property="delegate" destination="rjy-Di-Cru" id="JtY-um-ZTF"/>
|
||||
</connections>
|
||||
</tableView>
|
||||
<navigationItem key="navigationItem" title="Occurrence Context" id="2mj-It-uND">
|
||||
<barButtonItem key="rightBarButtonItem" image="jump-to-target" id="TqX-qO-B3s">
|
||||
<connections>
|
||||
<action selector="jumpToTsZero" destination="rjy-Di-Cru" id="RS6-IO-hi4"/>
|
||||
</connections>
|
||||
</barButtonItem>
|
||||
</navigationItem>
|
||||
</tableViewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="cYd-oX-akc" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="2800" y="-1250"/>
|
||||
</scene>
|
||||
<!--Recordings-->
|
||||
@@ -1244,6 +1298,7 @@ Duration: 60:00</string>
|
||||
<image name="filter-clear" width="20" height="20"/>
|
||||
<image name="intersection" width="25" height="25"/>
|
||||
<image name="journal" width="25" height="25"/>
|
||||
<image name="jump-to-target" width="20" height="20"/>
|
||||
<image name="settings" width="25" height="25"/>
|
||||
<image name="tag" width="25" height="25"/>
|
||||
</resources>
|
||||
|
||||
@@ -54,6 +54,10 @@ extension SQLiteDatabase {
|
||||
return sqlite3_column_int64($0, 0)
|
||||
}) ?? 0
|
||||
}
|
||||
|
||||
fileprivate func col_ts(_ stmt: OpaquePointer, _ col: Int32) -> Timestamp {
|
||||
sqlite3_column_int64(stmt, col)
|
||||
}
|
||||
}
|
||||
|
||||
class WhereClauseBuilder: CustomStringConvertible {
|
||||
@@ -105,6 +109,7 @@ struct GroupedDomain {
|
||||
var options: FilterOptions? = nil
|
||||
}
|
||||
typealias GroupedTsOccurrence = (ts: Timestamp, total: Int32, blocked: Int32)
|
||||
typealias DomainTsPair = (domain: String, ts: Timestamp)
|
||||
|
||||
extension SQLiteDatabase {
|
||||
|
||||
@@ -150,7 +155,7 @@ extension SQLiteDatabase {
|
||||
func dnsLogsMinDate() -> Timestamp? {
|
||||
try? run(sql:"SELECT min(ts) FROM heap") {
|
||||
try ifStep($0, SQLITE_ROW)
|
||||
return sqlite3_column_int64($0, 0)
|
||||
return col_ts($0, 0)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -164,8 +169,19 @@ extension SQLiteDatabase {
|
||||
let Where = WhereClauseBuilder().and(in: range).and(min: ts1, max: ts2)
|
||||
return try? run(sql:"SELECT min(rowid), max(rowid) FROM heap \(Where);", bind: Where.bindings) {
|
||||
try ifStep($0, SQLITE_ROW)
|
||||
let max = sqlite3_column_int64($0, 1)
|
||||
return (max == 0) ? nil : (sqlite3_column_int64($0, 0), max)
|
||||
let max = col_ts($0, 1)
|
||||
return (max == 0) ? nil : (col_ts($0, 0), max)
|
||||
}
|
||||
}
|
||||
|
||||
/// Get raw logs between two timestamps. `ts >= ? AND ts <= ?`
|
||||
/// - Returns: List sorted by `ts` in descending order (newest entries first).
|
||||
func dnsLogs(between ts1: Timestamp, and ts2: Timestamp) -> [DomainTsPair]? {
|
||||
try? run(sql: "SELECT fqdn, ts FROM heap WHERE ts >= ? AND ts <= ? ORDER BY ts DESC, rowid ASC;",
|
||||
bind: [BindInt64(ts1), BindInt64(ts2)]) {
|
||||
allRows($0) {
|
||||
(readText($0, 0) ?? "", col_ts($0, 1))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -192,7 +208,7 @@ extension SQLiteDatabase {
|
||||
GroupedDomain(domain: readText($0, 0) ?? "",
|
||||
total: sqlite3_column_int($0, 1),
|
||||
blocked: sqlite3_column_int($0, 2),
|
||||
lastModified: sqlite3_column_int64($0, 3))
|
||||
lastModified: col_ts($0, 3))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -206,7 +222,7 @@ extension SQLiteDatabase {
|
||||
let Where = WhereClauseBuilder().and(in: range).and("fqdn = ?", BindText(fqdn))
|
||||
return try? run(sql: "SELECT ts, COUNT(ts), COUNT(opt) FROM heap \(Where) GROUP BY ts ORDER BY ts DESC;", bind: Where.bindings) {
|
||||
allRows($0) {
|
||||
(sqlite3_column_int64($0, 0), sqlite3_column_int($0, 1), sqlite3_column_int($0, 2))
|
||||
(col_ts($0, 0), sqlite3_column_int($0, 1), sqlite3_column_int($0, 2))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -230,7 +246,7 @@ extension SQLiteDatabase {
|
||||
/// Get sorted, unique list of `ts` with given `fqdn`.
|
||||
func dnsLogsUniqTs(_ fqdn: String) -> [Timestamp]? {
|
||||
try? run(sql: "SELECT DISTINCT ts FROM heap WHERE fqdn = ? ORDER BY ts;", bind: [BindText(fqdn)]) {
|
||||
allRows($0) { sqlite3_column_int64($0, 0) }
|
||||
allRows($0) { col_ts($0, 0) }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -348,9 +364,9 @@ extension SQLiteDatabase {
|
||||
// MARK: read
|
||||
|
||||
private func readRecording(_ stmt: OpaquePointer) -> Recording {
|
||||
let end = sqlite3_column_int64(stmt, 2)
|
||||
let end = col_ts(stmt, 2)
|
||||
return Recording(id: sqlite3_column_int64(stmt, 0),
|
||||
start: sqlite3_column_int64(stmt, 1),
|
||||
start: col_ts(stmt, 1),
|
||||
stop: end == 0 ? nil : end,
|
||||
appId: readText(stmt, 3),
|
||||
title: readText(stmt, 4),
|
||||
|
||||
@@ -16,9 +16,21 @@ struct QLog {
|
||||
}
|
||||
}
|
||||
|
||||
// See: https://noahgilmore.com/blog/dark-mode-uicolor-compatibility/
|
||||
extension UIColor {
|
||||
static var sysBg: UIColor { get { if #available(iOS 13.0, *) { return .systemBackground } else { return .white } }}
|
||||
static var sysFg: UIColor { get { if #available(iOS 13.0, *) { return .label } else { return .black } }}
|
||||
/// `.systemBackground ?? .white`
|
||||
static var sysBg: UIColor { if #available(iOS 13.0, *) { return .systemBackground } else { return .white } }
|
||||
/// `.label ?? .black`
|
||||
static var sysFg: UIColor { if #available(iOS 13.0, *) { return .label } else { return .black } }
|
||||
/// `.link ?? .systemBlue`
|
||||
static var sysLink: UIColor { if #available(iOS 13.0, *) { return .link } else { return .systemBlue } }
|
||||
|
||||
/// `.label ?? .black`
|
||||
static var sysLabel: UIColor { if #available(iOS 13.0, *) { return .label } else { return .black } }
|
||||
/// `.secondaryLabel ?? rgba(60, 60, 67, 0.6)`
|
||||
static var sysLabel2: UIColor { if #available(iOS 13.0, *) { return .secondaryLabel } else { return .init(red: 60/255.0, green: 60/255.0, blue: 67/255.0, alpha: 0.6) } }
|
||||
/// `.tertiaryLabel ?? rgba(60, 60, 67, 0.3)`
|
||||
static var sysLabel3: UIColor { if #available(iOS 13.0, *) { return .tertiaryLabel } else { return .init(red: 60/255.0, green: 60/255.0, blue: 67/255.0, alpha: 0.3) } }
|
||||
}
|
||||
|
||||
extension UIEdgeInsets {
|
||||
|
||||
100
main/Requests/Analytics/TVCOccurrenceContext.swift
Normal file
100
main/Requests/Analytics/TVCOccurrenceContext.swift
Normal file
@@ -0,0 +1,100 @@
|
||||
import UIKit
|
||||
|
||||
class TVCOccurrenceContext: UITableViewController {
|
||||
|
||||
var ts: Timestamp!
|
||||
var domain: String!
|
||||
|
||||
private let dT: Timestamp = 300 // +/- 5 minutes
|
||||
private lazy var dataSource: [DomainTsPair] = {
|
||||
var list: [DomainTsPair] = []
|
||||
list.append(("[…]", ts + dT))
|
||||
list.append(contentsOf: AppDB?.dnsLogs(between: ts - dT, and: ts + dT) ?? [])
|
||||
list.append(("[…]", ts - dT))
|
||||
return list
|
||||
}()
|
||||
|
||||
override func viewDidLoad() {
|
||||
navigationItem.title = "± 5 Min Context"
|
||||
super.viewDidLoad()
|
||||
}
|
||||
|
||||
override func viewDidAppear(_ animated: Bool) {
|
||||
jumpToTsZero()
|
||||
}
|
||||
|
||||
@IBAction private func jumpToTsZero() {
|
||||
if let i = dataSource.firstIndex(where: { isChoosenOne($0) }) {
|
||||
tableView.scrollToRow(at: IndexPath(row: i), at: .middle, animated: true)
|
||||
}
|
||||
}
|
||||
|
||||
private func isChoosenOne(_ obj: DomainTsPair) -> Bool {
|
||||
obj.domain == domain && obj.ts == ts
|
||||
}
|
||||
|
||||
private func firstOrLast(_ row: Int) -> Bool {
|
||||
row == 0 || row == dataSource.count - 1
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Table View Data Source
|
||||
|
||||
override func tableView(_: UITableView, numberOfRowsInSection _: Int) -> Int { dataSource.count }
|
||||
|
||||
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: "OccurrenceContextCell")!
|
||||
let src = dataSource[indexPath.row]
|
||||
cell.detailTextLabel?.text = src.domain
|
||||
|
||||
if firstOrLast(indexPath.row) {
|
||||
cell.detailTextLabel?.textColor = .sysLabel2 // same as textLabel
|
||||
} else if isChoosenOne(src) {
|
||||
cell.detailTextLabel?.textColor = .sysLink
|
||||
} else {
|
||||
cell.detailTextLabel?.textColor = .sysLabel
|
||||
}
|
||||
|
||||
if src.ts > ts {
|
||||
cell.textLabel?.text = "+ " + TimeFormat.from(src.ts - ts)
|
||||
} else if src.ts < ts {
|
||||
cell.textLabel?.text = "− " + TimeFormat.from(ts - src.ts)
|
||||
} else {
|
||||
cell.textLabel?.text = "0"
|
||||
}
|
||||
//cell.textLabel?.text = String(format: "%+d s", src.ts - ts)
|
||||
return cell
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Tap to Copy
|
||||
|
||||
private var rowToCopy: Int = Int.max
|
||||
|
||||
override func tableView(_ tableView: UITableView, willSelectRowAt indexPath: IndexPath) -> IndexPath? {
|
||||
if firstOrLast(indexPath.row) { return nil }
|
||||
if rowToCopy == indexPath.row {
|
||||
UIMenuController.shared.setMenuVisible(false, animated: true)
|
||||
rowToCopy = Int.max
|
||||
return nil
|
||||
}
|
||||
rowToCopy = indexPath.row
|
||||
self.becomeFirstResponder()
|
||||
let cell = tableView.cellForRow(at: indexPath)!
|
||||
UIMenuController.shared.setTargetRect(cell.bounds, in: cell)
|
||||
UIMenuController.shared.setMenuVisible(true, animated: true)
|
||||
return nil
|
||||
}
|
||||
|
||||
override var canBecomeFirstResponder: Bool { true }
|
||||
|
||||
override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
|
||||
action == #selector(UIResponderStandardEditActions.copy)
|
||||
}
|
||||
|
||||
override func copy(_ sender: Any?) {
|
||||
guard rowToCopy < dataSource.count else { return }
|
||||
UIPasteboard.general.string = dataSource[rowToCopy].domain
|
||||
rowToCopy = Int.max
|
||||
}
|
||||
}
|
||||
@@ -14,14 +14,14 @@ class TVCHostDetails: UITableViewController, SyncUpdateDelegate, UITabBarDelegat
|
||||
sync.addObserver(self) // calls `syncUpdate(reset:)`
|
||||
if #available(iOS 10.0, *) {
|
||||
sync.allowPullToRefresh(onTVC: self, forObserver: self)
|
||||
actionsBar.unselectedItemTintColor = .systemBlue
|
||||
actionsBar.unselectedItemTintColor = .sysLink
|
||||
}
|
||||
UIDevice.orientationDidChangeNotification.observe(call: #selector(didChangeOrientation), on: self)
|
||||
}
|
||||
|
||||
// MARK: - Table View Data Source
|
||||
|
||||
override func tableView(_ _: UITableView, numberOfRowsInSection _: Int) -> Int { dataSource.count }
|
||||
override func tableView(_: UITableView, numberOfRowsInSection _: Int) -> Int { dataSource.count }
|
||||
|
||||
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: "HostDetailCell")!
|
||||
@@ -53,6 +53,10 @@ extension TVCHostDetails {
|
||||
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
|
||||
if segue.identifier == "segueAnalysisCoOccurrence" {
|
||||
(segue.destination as? VCCoOccurrence)?.fqdn = fullDomain
|
||||
} else if let index = tableView.indexPathForSelectedRow?.row {
|
||||
let tvc = segue.destination as? TVCOccurrenceContext
|
||||
tvc?.domain = fullDomain
|
||||
tvc?.ts = dataSource[index].ts
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user