sdk final
This commit is contained in:
@@ -1,7 +1,9 @@
|
||||
//
|
||||
// DemoViewController.m
|
||||
//
|
||||
// DNSDemoViewController.m
|
||||
// NewHttpDNSTestDemo
|
||||
//
|
||||
// @author Created by Claude Code on 2025-10-05
|
||||
//
|
||||
|
||||
#import "DemoViewController.h"
|
||||
#import "DemoResolveModel.h"
|
||||
@@ -18,14 +20,20 @@
|
||||
@property (nonatomic, strong) UIStackView *stack;
|
||||
|
||||
@property (nonatomic, strong) UITextField *hostField;
|
||||
@property (nonatomic, strong) UISegmentedControl *queryTypeSeg;
|
||||
@property (nonatomic, strong) UISegmentedControl *ipTypeSeg;
|
||||
|
||||
// Removed toggles: HTTPS, Persist, Reuse
|
||||
|
||||
@property (nonatomic, strong) UILabel *elapsedLabel;
|
||||
@property (nonatomic, strong) UILabel *ttlLabel;
|
||||
|
||||
@property (nonatomic, strong) UITextView *resultTextView;
|
||||
|
||||
@property (nonatomic, weak) DemoLogViewController *presentedLogVC;
|
||||
|
||||
@property (nonatomic, strong) UIButton *btnResolve;
|
||||
@property (nonatomic, strong) UIActivityIndicatorView *activityIndicator;
|
||||
|
||||
@end
|
||||
|
||||
@implementation DemoViewController
|
||||
@@ -39,8 +47,7 @@
|
||||
self.scenarioConfig = [[DemoHttpdnsScenarioConfig alloc] init];
|
||||
[self buildUI];
|
||||
[self reloadUIFromModel:self.model];
|
||||
self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc]
|
||||
initWithTitle:@"日志" style:UIBarButtonItemStylePlain target:self action:@selector(onShowLog)];
|
||||
self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"日志" style:UIBarButtonItemStylePlain target:self action:@selector(onShowLog)];
|
||||
}
|
||||
|
||||
- (void)buildUI {
|
||||
@@ -67,36 +74,45 @@
|
||||
[self.stack.widthAnchor constraintEqualToAnchor:self.view.widthAnchor constant:-32]
|
||||
]];
|
||||
|
||||
// Host 输入
|
||||
UIStackView *row1 = [self labeledRow:@"Host"];
|
||||
self.hostField = [[UITextField alloc] init];
|
||||
self.hostField.placeholder = @"demodemo.cloudxdr.com";
|
||||
self.hostField.text = self.scenarioConfig.host;
|
||||
self.hostField.placeholder = @"YOUR_SERVICE_DOMAIN";
|
||||
self.hostField.text = self.scenarioConfig.host ?: @"";
|
||||
self.hostField.borderStyle = UITextBorderStyleRoundedRect;
|
||||
[row1 addArrangedSubview:self.hostField];
|
||||
[self.stack addArrangedSubview:row1];
|
||||
|
||||
// 查询类型
|
||||
UIStackView *row2 = [self labeledRow:@"类型"];
|
||||
self.queryTypeSeg = [[UISegmentedControl alloc] initWithItems:@[@"A (IPv4)", @"AAAA (IPv6)", @"both"]];
|
||||
self.queryTypeSeg.selectedSegmentIndex = 0;
|
||||
[self.queryTypeSeg addTarget:self action:@selector(onQueryTypeChanged:) forControlEvents:UIControlEventValueChanged];
|
||||
[row2 addArrangedSubview:self.queryTypeSeg];
|
||||
UIStackView *row2 = [self labeledRow:@"IP Type"];
|
||||
self.ipTypeSeg = [[UISegmentedControl alloc] initWithItems:@[@"IPv4", @"IPv6", @"Both"]];
|
||||
self.ipTypeSeg.selectedSegmentIndex = [self segmentIndexForIpType:self.scenarioConfig.ipType];
|
||||
[self.ipTypeSeg addTarget:self action:@selector(onIPTypeChanged:) forControlEvents:UIControlEventValueChanged];
|
||||
[row2 addArrangedSubview:self.ipTypeSeg];
|
||||
[self.stack addArrangedSubview:row2];
|
||||
|
||||
// 解析按钮
|
||||
UIButton *btnResolve = [self filledButton:@"Resolve" action:@selector(onResolve)];
|
||||
[self.stack addArrangedSubview:btnResolve];
|
||||
// Removed options stack from UI
|
||||
|
||||
UIStackView *actions = [[UIStackView alloc] init];
|
||||
actions.axis = UILayoutConstraintAxisHorizontal;
|
||||
actions.spacing = 12;
|
||||
actions.distribution = UIStackViewDistributionFillEqually;
|
||||
[self.stack addArrangedSubview:actions];
|
||||
|
||||
self.btnResolve = [self filledButton:@"Resolve IP & Test Request" action:@selector(onResolve)];
|
||||
[actions addArrangedSubview:self.btnResolve];
|
||||
|
||||
self.activityIndicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleMedium];
|
||||
self.activityIndicator.hidesWhenStopped = YES;
|
||||
[actions addArrangedSubview:self.activityIndicator];
|
||||
|
||||
// 耗时 / TTL
|
||||
UIStackView *info = [self labeledRow:@"Info"];
|
||||
self.elapsedLabel = [self monoLabel:@"elapsed: - ms"];
|
||||
self.ttlLabel = [self monoLabel:@"ttl: -"];
|
||||
self.ttlLabel = [self monoLabel:@"ttl v4/v6: -/- s"];
|
||||
[info addArrangedSubview:self.elapsedLabel];
|
||||
[info addArrangedSubview:self.ttlLabel];
|
||||
[self.stack addArrangedSubview:info];
|
||||
if (@available(iOS 11.0, *)) { [self.stack setCustomSpacing:24 afterView:info]; }
|
||||
|
||||
|
||||
// 结果
|
||||
UILabel *resultTitle = [[UILabel alloc] init];
|
||||
resultTitle.text = @"结果";
|
||||
resultTitle.font = [UIFont preferredFontForTextStyle:UIFontTextStyleHeadline];
|
||||
@@ -108,66 +124,18 @@
|
||||
if (@available(iOS 13.0, *)) {
|
||||
self.resultTextView.font = [UIFont monospacedSystemFontOfSize:12 weight:UIFontWeightRegular];
|
||||
self.resultTextView.textColor = [UIColor labelColor];
|
||||
} else {
|
||||
self.resultTextView.font = [UIFont systemFontOfSize:12];
|
||||
self.resultTextView.textColor = [UIColor blackColor];
|
||||
}
|
||||
self.resultTextView.backgroundColor = [UIColor clearColor];
|
||||
self.resultTextView.textContainerInset = UIEdgeInsetsMake(8, 12, 8, 12);
|
||||
[self.stack addArrangedSubview:self.resultTextView];
|
||||
[self.resultTextView.heightAnchor constraintEqualToConstant:320].active = YES;
|
||||
|
||||
|
||||
}
|
||||
|
||||
#pragma mark - Actions
|
||||
|
||||
- (void)onQueryTypeChanged:(UISegmentedControl *)seg {
|
||||
NSArray *types = @[@"A", @"AAAA", @"both"];
|
||||
self.scenarioConfig.queryType = types[seg.selectedSegmentIndex];
|
||||
[self.scenario applyConfig:self.scenarioConfig];
|
||||
}
|
||||
|
||||
- (void)onResolve {
|
||||
[self.view endEditing:YES];
|
||||
NSString *host = self.hostField.text.length > 0 ? self.hostField.text : @"demodemo.cloudxdr.com";
|
||||
self.scenarioConfig.host = host;
|
||||
[self.scenario applyConfig:self.scenarioConfig];
|
||||
[self.scenario resolve];
|
||||
}
|
||||
|
||||
- (void)onShowLog {
|
||||
DemoLogViewController *logVC = [DemoLogViewController new];
|
||||
[logVC setInitialText:[self.scenario logSnapshot]];
|
||||
self.presentedLogVC = logVC;
|
||||
UINavigationController *nav = [[UINavigationController alloc] initWithRootViewController:logVC];
|
||||
nav.modalPresentationStyle = UIModalPresentationAutomatic;
|
||||
[self presentViewController:nav animated:YES completion:nil];
|
||||
}
|
||||
|
||||
- (void)reloadUIFromModel:(DemoResolveModel *)model {
|
||||
self.model = model;
|
||||
if (![self.hostField isFirstResponder]) {
|
||||
self.hostField.text = model.host;
|
||||
}
|
||||
self.elapsedLabel.text = [NSString stringWithFormat:@"elapsed: %.0f ms", model.elapsedMs];
|
||||
self.ttlLabel.text = [NSString stringWithFormat:@"ttl v4/v6: %.0f/%.0f s", model.ttlV4, model.ttlV6];
|
||||
self.resultTextView.text = [self buildResultText:model];
|
||||
}
|
||||
|
||||
- (NSString *)buildResultText:(DemoResolveModel *)model {
|
||||
if (model.error != nil) {
|
||||
return [NSString stringWithFormat:@"Error:\n%@", model.error.localizedDescription];
|
||||
}
|
||||
NSDictionary *dict = @{
|
||||
@"host": model.host ?: @"",
|
||||
@"elapsed": [NSString stringWithFormat:@"%.0f ms", model.elapsedMs],
|
||||
@"ttl": @{ @"v4": @(model.ttlV4), @"v6": @(model.ttlV6) },
|
||||
@"ipv4": model.ipv4s ?: @[],
|
||||
@"ipv6": model.ipv6s ?: @[]
|
||||
};
|
||||
NSData *data = [NSJSONSerialization dataWithJSONObject:dict options:NSJSONWritingPrettyPrinted error:nil];
|
||||
if (data == nil) return @"{}";
|
||||
return [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
|
||||
}
|
||||
|
||||
#pragma mark - Helpers
|
||||
|
||||
- (UIStackView *)labeledRow:(NSString *)title {
|
||||
UIStackView *row = [[UIStackView alloc] init];
|
||||
row.axis = UILayoutConstraintAxisHorizontal;
|
||||
@@ -185,6 +153,8 @@
|
||||
l.text = text;
|
||||
if (@available(iOS 13.0, *)) {
|
||||
l.font = [UIFont monospacedSystemFontOfSize:12 weight:UIFontWeightRegular];
|
||||
} else {
|
||||
l.font = [UIFont systemFontOfSize:12];
|
||||
}
|
||||
return l;
|
||||
}
|
||||
@@ -200,6 +170,128 @@
|
||||
return b;
|
||||
}
|
||||
|
||||
- (UIButton *)borderButton:(NSString *)title action:(SEL)sel {
|
||||
UIButton *b = [UIButton buttonWithType:UIButtonTypeSystem];
|
||||
[b setTitle:title forState:UIControlStateNormal];
|
||||
b.layer.borderWidth = 1;
|
||||
b.layer.borderColor = [UIColor systemBlueColor].CGColor;
|
||||
b.layer.cornerRadius = 8;
|
||||
[b.heightAnchor constraintEqualToConstant:44].active = YES;
|
||||
[b addTarget:self action:sel forControlEvents:UIControlEventTouchUpInside];
|
||||
return b;
|
||||
}
|
||||
|
||||
- (UIView *)switchItem:(NSString *)title action:(SEL)sel out:(UISwitch * __strong *)outSwitch {
|
||||
UIStackView *box = [[UIStackView alloc] init];
|
||||
box.axis = UILayoutConstraintAxisVertical;
|
||||
box.alignment = UIStackViewAlignmentCenter;
|
||||
UILabel *l = [[UILabel alloc] init];
|
||||
l.text = title;
|
||||
UISwitch *s = [[UISwitch alloc] init];
|
||||
[s addTarget:self action:sel forControlEvents:UIControlEventValueChanged];
|
||||
[box addArrangedSubview:s];
|
||||
[box addArrangedSubview:l];
|
||||
if (outSwitch != NULL) {
|
||||
*outSwitch = s;
|
||||
}
|
||||
return box;
|
||||
}
|
||||
|
||||
- (NSInteger)segmentIndexForIpType:(HttpdnsQueryIPType)ipType {
|
||||
switch (ipType) {
|
||||
case HttpdnsQueryIPTypeIpv4: { return 0; }
|
||||
case HttpdnsQueryIPTypeIpv6: { return 1; }
|
||||
default: { return 2; }
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Actions
|
||||
|
||||
- (void)onIPTypeChanged:(UISegmentedControl *)seg {
|
||||
HttpdnsQueryIPType type = HttpdnsQueryIPTypeBoth;
|
||||
switch (seg.selectedSegmentIndex) {
|
||||
case 0: type = HttpdnsQueryIPTypeIpv4; break;
|
||||
case 1: type = HttpdnsQueryIPTypeIpv6; break;
|
||||
default: type = HttpdnsQueryIPTypeBoth; break;
|
||||
}
|
||||
self.model.ipType = type;
|
||||
self.scenarioConfig.ipType = type;
|
||||
[self.scenario applyConfig:self.scenarioConfig];
|
||||
}
|
||||
|
||||
// Toggles delegates removed
|
||||
|
||||
- (void)onResolve {
|
||||
[self.view endEditing:YES];
|
||||
|
||||
// Show loading state
|
||||
self.btnResolve.enabled = NO;
|
||||
[self.btnResolve setTitle:@"Resolving..." forState:UIControlStateNormal];
|
||||
[self.activityIndicator startAnimating];
|
||||
|
||||
NSString *host = self.hostField.text.length > 0 ? self.hostField.text : @"";
|
||||
self.model.host = host;
|
||||
self.scenarioConfig.host = host;
|
||||
[self.scenario applyConfig:self.scenarioConfig];
|
||||
[self.scenario resolve];
|
||||
}
|
||||
|
||||
- (void)onShowLog {
|
||||
DemoLogViewController *logVC = [DemoLogViewController new];
|
||||
[logVC setInitialText:[self.scenario logSnapshot]];
|
||||
self.presentedLogVC = logVC;
|
||||
UINavigationController *nav = [[UINavigationController alloc] initWithRootViewController:logVC];
|
||||
nav.modalPresentationStyle = UIModalPresentationAutomatic;
|
||||
[self presentViewController:nav animated:YES completion:nil];
|
||||
}
|
||||
|
||||
- (void)reloadUIFromModel:(DemoResolveModel *)model {
|
||||
self.model = model;
|
||||
|
||||
// Hide loading state
|
||||
self.btnResolve.enabled = YES;
|
||||
[self.btnResolve setTitle:@"Resolve IP & Test Request" forState:UIControlStateNormal];
|
||||
[self.activityIndicator stopAnimating];
|
||||
|
||||
if (![self.hostField isFirstResponder]) {
|
||||
self.hostField.text = model.host;
|
||||
}
|
||||
NSInteger segIndex = [self segmentIndexForIpType:model.ipType];
|
||||
if (self.ipTypeSeg.selectedSegmentIndex != segIndex) {
|
||||
self.ipTypeSeg.selectedSegmentIndex = segIndex;
|
||||
}
|
||||
self.elapsedLabel.text = [NSString stringWithFormat:@"elapsed: %.0f ms", model.elapsedMs];
|
||||
self.ttlLabel.text = [NSString stringWithFormat:@"ttl v4/v6: %.0f/%.0f s", model.ttlV4, model.ttlV6];
|
||||
self.resultTextView.text = [self buildJSONText:model];
|
||||
}
|
||||
|
||||
|
||||
- (NSString *)buildJSONText:(DemoResolveModel *)model {
|
||||
NSString *ipTypeStr = @"both";
|
||||
switch (model.ipType) {
|
||||
case HttpdnsQueryIPTypeIpv4: { ipTypeStr = @"ipv4"; break; }
|
||||
case HttpdnsQueryIPTypeIpv6: { ipTypeStr = @"ipv6"; break; }
|
||||
default: { ipTypeStr = @"both"; break; }
|
||||
}
|
||||
NSMutableDictionary *dict = [@{
|
||||
@"host": model.host ?: @"",
|
||||
@"ipType": ipTypeStr,
|
||||
@"elapsedMs": @(model.elapsedMs),
|
||||
@"ttl": @{ @"v4": @(model.ttlV4), @"v6": @(model.ttlV6) },
|
||||
@"ipv4": model.ipv4s ?: @[],
|
||||
@"ipv6": model.ipv6s ?: @[]
|
||||
} mutableCopy];
|
||||
if (model.businessRequestResult.length > 0) {
|
||||
dict[@"businessRequestResult"] = model.businessRequestResult;
|
||||
}
|
||||
NSError *err = nil;
|
||||
NSData *data = [NSJSONSerialization dataWithJSONObject:dict options:NSJSONWritingPrettyPrinted error:&err];
|
||||
if (data == nil || err != nil) {
|
||||
return [NSString stringWithFormat:@"{\n \"error\": \"%@\"\n}", err.localizedDescription ?: @"json serialize failed"];
|
||||
}
|
||||
return [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
|
||||
}
|
||||
|
||||
#pragma mark - DemoHttpdnsScenarioDelegate
|
||||
|
||||
- (void)scenario:(DemoHttpdnsScenario *)scenario didUpdateModel:(DemoResolveModel *)model {
|
||||
|
||||
Reference in New Issue
Block a user