Co-Occurrence tutorial sheet + small bugfixes
This commit is contained in:
@@ -461,6 +461,15 @@
|
|||||||
<action selector="didClose:" destination="W5Q-oz-bFb" id="wyw-vo-6xL"/>
|
<action selector="didClose:" destination="W5Q-oz-bFb" id="wyw-vo-6xL"/>
|
||||||
</connections>
|
</connections>
|
||||||
</barButtonItem>
|
</barButtonItem>
|
||||||
|
<barButtonItem key="rightBarButtonItem" id="bTi-7F-CFS">
|
||||||
|
<button key="customView" opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="infoLight" showsTouchWhenHighlighted="YES" lineBreakMode="middleTruncation" id="kqK-SL-CxZ">
|
||||||
|
<rect key="frame" x="279" y="16" width="25" height="24"/>
|
||||||
|
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||||
|
<connections>
|
||||||
|
<action selector="showInfoScreen" destination="W5Q-oz-bFb" eventType="touchUpInside" id="TuI-R9-PNr"/>
|
||||||
|
</connections>
|
||||||
|
</button>
|
||||||
|
</barButtonItem>
|
||||||
</navigationItem>
|
</navigationItem>
|
||||||
</items>
|
</items>
|
||||||
</navigationBar>
|
</navigationBar>
|
||||||
@@ -502,14 +511,14 @@
|
|||||||
<nil key="textColor"/>
|
<nil key="textColor"/>
|
||||||
<nil key="highlightedColor"/>
|
<nil key="highlightedColor"/>
|
||||||
</label>
|
</label>
|
||||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" horizontalCompressionResistancePriority="300" insetsLayoutMarginsFromSafeArea="NO" text="Count" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="JWp-6l-HTJ">
|
<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="116.5" y="42.5" width="37" height="16"/>
|
<rect key="frame" x="109.5" y="42.5" width="37" height="16"/>
|
||||||
<fontDescription key="fontDescription" style="UICTFontTextStyleFootnote"/>
|
<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"/>
|
<color key="textColor" systemColor="secondaryLabelColor" red="0.23529411759999999" green="0.23529411759999999" blue="0.26274509800000001" alpha="0.59999999999999998" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
<nil key="highlightedColor"/>
|
<nil key="highlightedColor"/>
|
||||||
</label>
|
</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">
|
<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="157.5" y="39.5" width="32.5" height="21.5"/>
|
<rect key="frame" x="150.5" y="39.5" width="42.5" height="21.5"/>
|
||||||
<color key="backgroundColor" systemColor="secondarySystemBackgroundColor" red="0.94901960780000005" green="0.94901960780000005" blue="0.96862745100000003" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
<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"/>
|
<fontDescription key="fontDescription" style="UICTFontTextStyleFootnote"/>
|
||||||
<nil key="textColor"/>
|
<nil key="textColor"/>
|
||||||
@@ -521,7 +530,7 @@
|
|||||||
</userDefinedRuntimeAttributes>
|
</userDefinedRuntimeAttributes>
|
||||||
</label>
|
</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">
|
<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="265" y="39.5" width="40" height="21.5"/>
|
<rect key="frame" x="255.5" y="39.5" width="49.5" height="21.5"/>
|
||||||
<color key="backgroundColor" systemColor="secondarySystemBackgroundColor" red="0.94901960780000005" green="0.94901960780000005" blue="0.96862745100000003" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
<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"/>
|
<fontDescription key="fontDescription" style="UICTFontTextStyleFootnote"/>
|
||||||
<nil key="textColor"/>
|
<nil key="textColor"/>
|
||||||
@@ -532,14 +541,14 @@
|
|||||||
</userDefinedRuntimeAttribute>
|
</userDefinedRuntimeAttribute>
|
||||||
</userDefinedRuntimeAttributes>
|
</userDefinedRuntimeAttributes>
|
||||||
</label>
|
</label>
|
||||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" horizontalHuggingPriority="750" horizontalCompressionResistancePriority="250" insetsLayoutMarginsFromSafeArea="NO" text="Divergent" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="T4X-cn-msT">
|
<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="202" y="42.5" width="59" height="16"/>
|
<rect key="frame" x="205" y="42.5" width="46.5" height="16"/>
|
||||||
<fontDescription key="fontDescription" style="UICTFontTextStyleFootnote"/>
|
<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"/>
|
<color key="textColor" systemColor="secondaryLabelColor" red="0.23529411759999999" green="0.23529411759999999" blue="0.26274509800000001" alpha="0.59999999999999998" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
<nil key="highlightedColor"/>
|
<nil key="highlightedColor"/>
|
||||||
</label>
|
</label>
|
||||||
<view opaque="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="9Bb-e5-D3O" customClass="MeterBar" customModule="AppCheck" customModuleProvider="target">
|
<view opaque="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="9Bb-e5-D3O" customClass="MeterBar" customModule="AppCheck" customModuleProvider="target">
|
||||||
<rect key="frame" x="187" y="39.5" width="3" height="21.5"/>
|
<rect key="frame" x="190" y="39.5" width="3" height="21.5"/>
|
||||||
<constraints>
|
<constraints>
|
||||||
<constraint firstAttribute="width" constant="3" id="wWb-VG-Kqa"/>
|
<constraint firstAttribute="width" constant="3" id="wWb-VG-Kqa"/>
|
||||||
</constraints>
|
</constraints>
|
||||||
|
|||||||
@@ -36,6 +36,8 @@ struct QuickUI {
|
|||||||
static func text(attributed: NSAttributedString, frame: CGRect = CGRect.zero) -> UITextView {
|
static func text(attributed: NSAttributedString, frame: CGRect = CGRect.zero) -> UITextView {
|
||||||
let txt = self.text("", frame: frame)
|
let txt = self.text("", frame: frame)
|
||||||
txt.attributedText = attributed
|
txt.attributedText = attributed
|
||||||
|
txt.textContainerInset = .zero
|
||||||
|
//txt.textContainer.lineFragmentPadding = 0 // remove left right padding
|
||||||
return txt
|
return txt
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,18 @@
|
|||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
fileprivate let margin: CGFloat = 20
|
fileprivate var margin: CGFloat { 20 }
|
||||||
fileprivate let cornerRadius: CGFloat = 15
|
fileprivate var sheetInset: CGFloat { cornerRadius/2 }
|
||||||
fileprivate let uniRect = CGRect(x: 0, y: 0, width: 500, height: 500)
|
fileprivate var cornerRadius: CGFloat { 15 }
|
||||||
|
fileprivate var uniRect: CGRect { CGRect(x: 0, y: 0, width: 500, height: 500) }
|
||||||
|
|
||||||
class TutorialSheet: UIViewController, UIScrollViewDelegate {
|
class TutorialSheet: UIViewController, UIScrollViewDelegate {
|
||||||
|
|
||||||
|
/// Maximum displayable width of a Tutorial Sheet in portrait mode.
|
||||||
|
public static var verticalWidth: CGFloat {
|
||||||
|
let s = UIScreen.main.bounds.size
|
||||||
|
return min(s.width, s.height) - 2 * (margin + sheetInset)
|
||||||
|
}
|
||||||
|
|
||||||
public var buttonTitleNext: String = "Next"
|
public var buttonTitleNext: String = "Next"
|
||||||
public var buttonTitleDone: String = "Close"
|
public var buttonTitleDone: String = "Close"
|
||||||
|
|
||||||
@@ -105,7 +112,8 @@ class TutorialSheet: UIViewController, UIScrollViewDelegate {
|
|||||||
}
|
}
|
||||||
let prev = content.subviews.last
|
let prev = content.subviews.last
|
||||||
content.addSubview(x)
|
content.addSubview(x)
|
||||||
x.anchor([.top, .width, .height], to: pageScroll)
|
x.anchor([.top, .height], to: pageScroll)
|
||||||
|
x.widthAnchor =&= sheetBg.widthAnchor - 2 * sheetInset
|
||||||
x.leadingAnchor =&= (prev==nil ? content.leadingAnchor : prev!.trailingAnchor)
|
x.leadingAnchor =&= (prev==nil ? content.leadingAnchor : prev!.trailingAnchor)
|
||||||
lastAnchor?.isActive = false
|
lastAnchor?.isActive = false
|
||||||
lastAnchor = (x.trailingAnchor =&= pageScroll.trailingAnchor)
|
lastAnchor = (x.trailingAnchor =&= pageScroll.trailingAnchor)
|
||||||
@@ -125,7 +133,7 @@ class TutorialSheet: UIViewController, UIScrollViewDelegate {
|
|||||||
|
|
||||||
pager.anchor([.top, .left, .right], to: sheetBg)
|
pager.anchor([.top, .left, .right], to: sheetBg)
|
||||||
pageScroll.topAnchor =&= pager.bottomAnchor
|
pageScroll.topAnchor =&= pager.bottomAnchor
|
||||||
pageScroll.anchor([.left, .right, .top, .bottom], to: sheetBg, margin: cornerRadius/2) | .defaultHigh
|
pageScroll.anchor([.left, .right, .top, .bottom], to: sheetBg, margin: sheetInset) | .defaultHigh
|
||||||
button.topAnchor =&= pageScroll.bottomAnchor
|
button.topAnchor =&= pageScroll.bottomAnchor
|
||||||
button.anchor([.bottom, .centerX], to: sheetBg)
|
button.anchor([.bottom, .centerX], to: sheetBg)
|
||||||
// button.bottomAnchor =&= sheetBg.bottomAnchor - 30
|
// button.bottomAnchor =&= sheetBg.bottomAnchor - 30
|
||||||
|
|||||||
@@ -159,7 +159,6 @@ class SyncUpdate {
|
|||||||
notify(insert: r, .Latest)
|
notify(insert: r, .Latest)
|
||||||
}
|
}
|
||||||
} else if range != nil {
|
} else if range != nil {
|
||||||
// FIXME: removing latest entries will invalidate "last changed" label
|
|
||||||
if let r = rows(from(new!), to(old), scope: range!) {
|
if let r = rows(from(new!), to(old), scope: range!) {
|
||||||
notify(remove: r, .Latest)
|
notify(remove: r, .Latest)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,14 @@ extension UIFont {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension NSAttributedString {
|
||||||
|
static func image(_ img: UIImage) -> Self {
|
||||||
|
let att = NSTextAttachment()
|
||||||
|
att.image = img
|
||||||
|
return self.init(attachment: att)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
extension NSMutableAttributedString {
|
extension NSMutableAttributedString {
|
||||||
static private var def: UIFont = .preferredFont(forTextStyle: .body)
|
static private var def: UIFont = .preferredFont(forTextStyle: .body)
|
||||||
|
|
||||||
@@ -31,4 +39,13 @@ extension NSMutableAttributedString {
|
|||||||
]))
|
]))
|
||||||
return self
|
return self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func centered(_ content: NSAttributedString) -> Self {
|
||||||
|
let before = length
|
||||||
|
append(content)
|
||||||
|
let ps = NSMutableParagraphStyle()
|
||||||
|
ps.alignment = .center
|
||||||
|
addAttribute(.paragraphStyle, value: ps, range: .init(location: before, length: content.length))
|
||||||
|
return self
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,15 +33,24 @@ class VCCoOccurrence: UIViewController, UITableViewDataSource {
|
|||||||
let domain = fqdn!
|
let domain = fqdn!
|
||||||
let time = Timestamp(selectedTime)
|
let time = Timestamp(selectedTime)
|
||||||
DispatchQueue.global().async { [weak self] in
|
DispatchQueue.global().async { [weak self] in
|
||||||
guard let db = AppDB, let times = db.dnsLogsUniqTs(domain), times.count > 0 else {
|
let temp: [ContextAnalysisResult]
|
||||||
return // should never happen, or what did you tap then?
|
let total: Int32
|
||||||
|
if let db = AppDB,
|
||||||
|
let times = db.dnsLogsUniqTs(domain), times.count > 0,
|
||||||
|
let result = db.contextAnalysis(coOccurrence: times, plusMinus: time, exclude: domain),
|
||||||
|
result.count > 0
|
||||||
|
{
|
||||||
|
temp = result
|
||||||
|
var sum: Int32 = 0
|
||||||
|
for x in result { sum += x.count }
|
||||||
|
total = sum // if statement guarantees >= 1
|
||||||
|
} else {
|
||||||
|
temp = []
|
||||||
|
total = 1
|
||||||
}
|
}
|
||||||
guard let result = db.contextAnalysis(coOccurrence: times, plusMinus: time, exclude: domain) else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
self?.dataSource = result
|
|
||||||
self?.logMaxCount = log(CGFloat(result.reduce(0) { max($0, $1.count) }))
|
|
||||||
DispatchQueue.main.sync { [weak self] in
|
DispatchQueue.main.sync { [weak self] in
|
||||||
|
self?.dataSource = temp
|
||||||
|
self?.logMaxCount = log(CGFloat(total + 1))
|
||||||
self?.tableView.reloadData()
|
self?.tableView.reloadData()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -72,7 +81,9 @@ class VCCoOccurrence: UIViewController, UITableViewDataSource {
|
|||||||
cell.count.text = "\(src.count)"
|
cell.count.text = "\(src.count)"
|
||||||
cell.avgdiff.text = String(format: "%.2fs", src.avg)
|
cell.avgdiff.text = String(format: "%.2fs", src.avg)
|
||||||
|
|
||||||
cell.countMeter.percent = (log(CGFloat(src.count)) / logMaxCount)
|
// log percentage of total co-occurrence count + 1 (min: log(2))
|
||||||
|
cell.countMeter.percent = (log(CGFloat(src.count + 1)) / logMaxCount)
|
||||||
|
// log percentage of selected time window (0s/5s/15s/30s) + 1 (min: log(2))
|
||||||
cell.avgdiffMeter.percent = 1 - (log(CGFloat(src.avg + 1)) / logTimeDelta)
|
cell.avgdiffMeter.percent = 1 - (log(CGFloat(src.avg + 1)) / logTimeDelta)
|
||||||
return cell
|
return cell
|
||||||
}
|
}
|
||||||
@@ -86,3 +97,73 @@ class CoOccurrenceCell: UITableViewCell {
|
|||||||
@IBOutlet var countMeter: MeterBar!
|
@IBOutlet var countMeter: MeterBar!
|
||||||
@IBOutlet var avgdiffMeter: MeterBar!
|
@IBOutlet var avgdiffMeter: MeterBar!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// MARK: - Tutorial Screen
|
||||||
|
|
||||||
|
extension VCCoOccurrence {
|
||||||
|
|
||||||
|
@IBAction func showInfoScreen() {
|
||||||
|
let sampleCell: UIImage = {
|
||||||
|
let cell = tableView.dequeueReusableCell(withIdentifier: "CoOccurrenceCell") as! CoOccurrenceCell
|
||||||
|
cell.title.text = "example.org"
|
||||||
|
cell.rank.text = "9."
|
||||||
|
cell.count.text = "14"
|
||||||
|
cell.avgdiff.text = String(format: "%.2fs", 0.71)
|
||||||
|
cell.countMeter.percent = 0.35
|
||||||
|
cell.avgdiffMeter.percent = 0.95
|
||||||
|
|
||||||
|
// Bug: Sometimes dequeue will return a "broken" hidden cell.
|
||||||
|
// It can't be set visible and thus can't render an image.
|
||||||
|
// Funnily `cell.contentView` can rendered.
|
||||||
|
let theView = cell.isHidden ? cell.contentView : cell
|
||||||
|
|
||||||
|
// resize view to fit into tutorial sheet
|
||||||
|
let minWidth = TutorialSheet.verticalWidth - 10 //-> 2 * textContainer.lineFragmentPadding
|
||||||
|
theView.frame.size = theView.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize)
|
||||||
|
theView.frame.size.width = min(theView.frame.size.width, minWidth)
|
||||||
|
// set width in two steps because first call may change layoutMargins
|
||||||
|
theView.frame.size.width += theView.layoutMargins.left + theView.layoutMargins.right
|
||||||
|
// FIXME: In case `hidden == false`, backgroundColor will be black in Dark mode.
|
||||||
|
theView.backgroundColor = tableView.backgroundColor
|
||||||
|
return theView.asImage(insets: theView.layoutMargins)
|
||||||
|
}()
|
||||||
|
|
||||||
|
let x = TutorialSheet()
|
||||||
|
x.addSheet().addArrangedSubview(QuickUI.text(attributed: NSMutableAttributedString()
|
||||||
|
.h3("Co-Occurrence")
|
||||||
|
.normal(" allows you to find requests that happen often at the same time as the selected domain. " +
|
||||||
|
"Hence it will give you a hint what Apps might be involved in the activity." +
|
||||||
|
"\n\nHow do you interpret these results? Lets look at an example:\n\n")
|
||||||
|
.centered(.image(sampleCell))
|
||||||
|
.normal("\n\nThe domain ").bold("example.org").normal(" had ").bold("14").normal(" requests with an ").italic("average time divergence").normal(" of ").bold("0.71 seconds").normal(". " +
|
||||||
|
"That is, these 14 domain calls happend, on average, less then a second before or after the original request of the selected domain." +
|
||||||
|
"\n\nClose temporal proximity and high occurrence counts are both indicators for domain correlation. " +
|
||||||
|
"Results are sorted by a ranking index (").bold("9.").normal(") which strikes a balance between the two. " +
|
||||||
|
"Preferring entries with higher counts as well as low time divergence.")
|
||||||
|
.italic("\n\nTip: ").normal("As a visual guide you can look for the colored bar beside each value. " +
|
||||||
|
"The larger the bar, the greater the correlation.")
|
||||||
|
))
|
||||||
|
|
||||||
|
x.present(in: self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fileprivate extension UIView {
|
||||||
|
func asImage(insets: UIEdgeInsets = .zero) -> UIImage {
|
||||||
|
if #available(iOS 10.0, *) {
|
||||||
|
let renderer = UIGraphicsImageRenderer(bounds: bounds.inset(by: insets))
|
||||||
|
return renderer.image { rendererContext in
|
||||||
|
layer.render(in: rendererContext.cgContext)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
UIGraphicsBeginImageContext(bounds.inset(by: insets).size)
|
||||||
|
let ctx = UIGraphicsGetCurrentContext()!
|
||||||
|
ctx.translateBy(x: -insets.left, y: -insets.top)
|
||||||
|
layer.render(in:ctx)
|
||||||
|
let image = UIGraphicsGetImageFromCurrentImageContext()
|
||||||
|
UIGraphicsEndImageContext()
|
||||||
|
return UIImage(cgImage: image!.cgImage!)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user