2
0
mirror of https://github.com/VinylDNS/vinyldns synced 2025-09-04 16:25:32 +00:00

add history

This commit is contained in:
Aravindh-Raju
2023-04-05 18:23:32 +05:30
parent 19ba3c09fe
commit f103d2c6b8
8 changed files with 125 additions and 25 deletions

View File

@@ -59,7 +59,6 @@ class ZoneViewLoaderIntegrationSpec extends AnyWordSpec with Matchers {
Some(ZoneConnection("invalid-connection.", "bad-key", Encrypted("invalid-key"), "10.1.1.1")) Some(ZoneConnection("invalid-connection.", "bad-key", Encrypted("invalid-key"), "10.1.1.1"))
) )
val backend = backendResolver.resolve(zone).asInstanceOf[DnsBackend] val backend = backendResolver.resolve(zone).asInstanceOf[DnsBackend]
println(s"${backend.id}, ${backend.xfrInfo}, ${backend.resolver.getAddress}")
DnsZoneViewLoader(zone, backendResolver.resolve(zone), 10000) DnsZoneViewLoader(zone, backendResolver.resolve(zone), 10000)
.load() .load()
.unsafeRunSync() .unsafeRunSync()

View File

@@ -92,10 +92,12 @@ class RecordSetService(
for { for {
zone <- getZone(recordSet.zoneId) zone <- getZone(recordSet.zoneId)
authZones = dottedHostsConfig.zoneAuthConfigs.map(x => x.zone) authZones = dottedHostsConfig.zoneAuthConfigs.map(x => x.zone)
change <- RecordSetChangeGenerator.forAdd(recordSet, zone, Some(auth)).toResult isShared = zone.shared
newRecordSet = if(isShared) recordSet else recordSet.copy(ownerGroupId = Some(zone.adminGroupId))
change <- RecordSetChangeGenerator.forAdd(newRecordSet, zone, Some(auth)).toResult
// because changes happen to the RS in forAdd itself, converting 1st and validating on that // because changes happen to the RS in forAdd itself, converting 1st and validating on that
rsForValidations = change.recordSet rsForValidations = change.recordSet
_ <- isNotHighValueDomain(recordSet, zone, highValueDomainConfig).toResult _ <- isNotHighValueDomain(newRecordSet, zone, highValueDomainConfig).toResult
_ <- recordSetDoesNotExist( _ <- recordSetDoesNotExist(
backendResolver.resolve, backendResolver.resolve,
zone, zone,
@@ -142,11 +144,13 @@ class RecordSetService(
_ <- unchangedRecordName(existing, recordSet, zone).toResult _ <- unchangedRecordName(existing, recordSet, zone).toResult
_ <- unchangedRecordType(existing, recordSet).toResult _ <- unchangedRecordType(existing, recordSet).toResult
_ <- unchangedZoneId(existing, recordSet).toResult _ <- unchangedZoneId(existing, recordSet).toResult
change <- RecordSetChangeGenerator.forUpdate(existing, recordSet, zone, Some(auth)).toResult isShared = zone.shared
newRecordSet = if(isShared) recordSet else recordSet.copy(ownerGroupId = Some(zone.adminGroupId))
change <- RecordSetChangeGenerator.forUpdate(existing, newRecordSet, zone, Some(auth)).toResult
// because changes happen to the RS in forUpdate itself, converting 1st and validating on that // because changes happen to the RS in forUpdate itself, converting 1st and validating on that
rsForValidations = change.recordSet rsForValidations = change.recordSet
superUserCanUpdateOwnerGroup = canSuperUserUpdateOwnerGroup(existing, recordSet, zone, auth) superUserCanUpdateOwnerGroup = canSuperUserUpdateOwnerGroup(existing, newRecordSet, zone, auth)
_ <- isNotHighValueDomain(recordSet, zone, highValueDomainConfig).toResult _ <- isNotHighValueDomain(newRecordSet, zone, highValueDomainConfig).toResult
_ <- canUpdateRecordSet(auth, existing.name, existing.typ, zone, existing.ownerGroupId, superUserCanUpdateOwnerGroup).toResult _ <- canUpdateRecordSet(auth, existing.name, existing.typ, zone, existing.ownerGroupId, superUserCanUpdateOwnerGroup).toResult
ownerGroup <- getGroupIfProvided(rsForValidations.ownerGroupId) ownerGroup <- getGroupIfProvided(rsForValidations.ownerGroupId)
_ <- canUseOwnerGroup(rsForValidations.ownerGroupId, ownerGroup, auth).toResult _ <- canUseOwnerGroup(rsForValidations.ownerGroupId, ownerGroup, auth).toResult
@@ -575,7 +579,6 @@ class RecordSetService(
recordType: Option[RecordType] = None, recordType: Option[RecordType] = None,
authPrincipal: AuthPrincipal authPrincipal: AuthPrincipal
): Result[ListRecordSetChangesResponse] = { ): Result[ListRecordSetChangesResponse] = {
println("In listRecordSetChanges")
if(zoneId.isDefined) { if(zoneId.isDefined) {
for { for {
zone <- getZone(zoneId.get) zone <- getZone(zoneId.get)

View File

@@ -108,7 +108,11 @@ object ZoneSyncHandler extends DnsConversions with Monitored with TransactionPro
case (dnsZoneView, vinylDnsZoneView) => vinylDnsZoneView.diff(dnsZoneView) case (dnsZoneView, vinylDnsZoneView) => vinylDnsZoneView.diff(dnsZoneView)
} }
recordSetChanges.flatMap { allChanges => recordSetChanges.flatMap { allChanges =>
val changesWithUserIds = allChanges.map(_.withUserId(zoneChange.userId)) val changesWithUserIds = if(zone.shared) {
allChanges.map(_.withUserId(zoneChange.userId))
} else {
allChanges.map(_.withUserId(zoneChange.userId)).map(_.withGroupId(Some(zone.adminGroupId)))
}
if (changesWithUserIds.isEmpty) { if (changesWithUserIds.isEmpty) {
logger.info( logger.info(

View File

@@ -76,6 +76,12 @@ case class RecordSetChange(
def withUserId(newUserId: String): RecordSetChange = this.copy(userId = newUserId) def withUserId(newUserId: String): RecordSetChange = this.copy(userId = newUserId)
def withGroupId(adminGroupId: Option[String]): RecordSetChange =
copy(
recordSet = recordSet
.copy(ownerGroupId = adminGroupId)
)
override def toString: String = { override def toString: String = {
val sb = new StringBuilder val sb = new StringBuilder
sb.append("RecordSetChange: [") sb.append("RecordSetChange: [")

View File

@@ -89,9 +89,6 @@ class MySqlRecordChangeRepository
*/ */
def save(db: DB, changeSet: ChangeSet): IO[ChangeSet] = def save(db: DB, changeSet: ChangeSet): IO[ChangeSet] =
monitor("repo.RecordChange.save") { monitor("repo.RecordChange.save") {
println("fqdn: ", changeSet.changes.map(x => x.recordSet.fqdn))
println("name: ", changeSet.changes.map(x => x.recordSet.name))
println("zone: ", changeSet.changes.map(x => x.zone.name))
IO { IO {
db.withinTx { implicit session => db.withinTx { implicit session =>
changeSet.changes changeSet.changes

View File

@@ -76,7 +76,8 @@ class FrontendController @Inject() (
} }
def viewRecordSets(): Action[AnyContent] = userAction.async { implicit request => def viewRecordSets(): Action[AnyContent] = userAction.async { implicit request =>
Future(Ok(views.html.recordsets.recordSets(request.user.userName))) val canReview = request.user.isSuper || request.user.isSupport
Future(Ok(views.html.recordsets.recordSets(request.user.userName, canReview)))
} }
def viewAllBatchChanges(): Action[AnyContent] = userAction.async { implicit request => def viewAllBatchChanges(): Action[AnyContent] = userAction.async { implicit request =>

View File

@@ -1,4 +1,4 @@
@(rootAccountName: String)(implicit request: play.api.mvc.Request[Any], customLinks: models.CustomLinks, meta: models.Meta) @(rootAccountName: String, rootAccountCanReview: Boolean)(implicit request: play.api.mvc.Request[Any], customLinks: models.CustomLinks, meta: models.Meta)
@content = { @content = {
<!-- PAGE CONTENT --> <!-- PAGE CONTENT -->
@@ -80,8 +80,8 @@
<div class="vinyldns-panel-top"> <div class="vinyldns-panel-top">
<div class="btn-group"> <div class="btn-group">
<button id="refresh-records-button" class="btn btn-default" ng-click="refreshRecords()"><span class="fa fa-refresh"></span> Refresh</button> <button id="refresh-records-button" class="btn btn-default" ng-click="refreshRecords()"><span class="fa fa-refresh"></span> Refresh</button>
<button id="create-record-button" class="btn btn-default" ng-click="createRecord(defaultTtl)"><span class="fa fa-plus"></span> Create Record Set</button> <button id="create-record-button" class="btn btn-default" ng-if="canCreateRecords" ng-click="createRecord(defaultTtl)"><span class="fa fa-plus"></span> Create Record Set</button>
<button id="zone-sync-button" class="btn btn-default mb-control" data-toggle="modal" data-target="#mb-sync"><span class="fa fa-exchange"></span> Sync Zone</button> <button id="zone-sync-button" class="btn btn-default mb-control" ng-if="zoneInfo.accessLevel=='Delete'" data-toggle="modal" data-target="#mb-sync"><span class="fa fa-exchange"></span> Sync Zone</button>
</div> </div>
<div> <div>
@@ -164,7 +164,9 @@
@if(meta.sharedDisplayEnabled) { @if(meta.sharedDisplayEnabled) {
<th>Owner Group Name</th> <th>Owner Group Name</th>
} }
@if(rootAccountCanReview) {
<th>Record History</th> <th>Record History</th>
}
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@@ -372,9 +374,11 @@
title="Group with ID {{record.ownerGroupId}} no longer exists."><span class="fa fa-warning"></span> Group deleted</span> title="Group with ID {{record.ownerGroupId}} no longer exists."><span class="fa fa-warning"></span> Group deleted</span>
</td> </td>
} }
@if(rootAccountCanReview) {
<td> <td>
<span><button class="btn btn-info btn-sm" ng-click="viewRecordHistory(record.fqdn, record.type)">View History</button></span> <span><button class="btn btn-info btn-sm" ng-click="viewRecordHistory(record.fqdn, record.type)">View History</button></span>
</td> </td>
}
</tr> </tr>
</tbody> </tbody>
</table> </table>
@@ -420,7 +424,7 @@
</div> </div>
<div class="panel-body"> <div class="panel-body">
<div class="btn-group"> <div class="btn-group">
<button class="btn btn-default" ng-click="refreshRecordChangeHistory()"><span class="fa fa-refresh"></span>Refresh</button> <button class="btn btn-default" ng-click="refreshRecordChangeHistory(recordFqdn, recordType)"><span class="fa fa-refresh"></span> Refresh</button>
</div> </div>
<!-- PAGINATION --> <!-- PAGINATION -->
@@ -460,13 +464,13 @@
</td> </td>
<td class="col-md-3 wrap-long-text"> <td class="col-md-3 wrap-long-text">
{{change.systemMessage}} {{change.systemMessage}}
<!-- <div ng-if="change.status !='Failed'">--> <div ng-if="change.status !='Failed'">
<!-- <a ng-if="change.changeType =='Create'" ng-click="viewRecordInfo(change.recordSet)" class="force-cursor">View created recordset</a>--> <a ng-if="change.changeType =='Create'" ng-click="viewRecordInfo(change.recordSet)" class="force-cursor">View created recordset</a>
<!-- <a ng-if="change.changeType =='Delete'" ng-click="viewRecordInfo(change.recordSet)" class="force-cursor">View deleted recordset</a>--> <a ng-if="change.changeType =='Delete'" ng-click="viewRecordInfo(change.recordSet)" class="force-cursor">View deleted recordset</a>
<!-- <div><a ng-if="change.changeType =='Update'" ng-click="viewRecordInfo(change.recordSet)" class="force-cursor">View new recordset</a></div>--> <div><a ng-if="change.changeType =='Update'" ng-click="viewRecordInfo(change.recordSet)" class="force-cursor">View new recordset</a></div>
<!-- <div><a ng-if="change.changeType =='Update'" ng-click="viewRecordInfo(change.updates)" class="force-cursor">View old recordset</a></div>--> <div><a ng-if="change.changeType =='Update'" ng-click="viewRecordInfo(change.updates)" class="force-cursor">View old recordset</a></div>
<!-- </div>--> </div>
</td> </td>
</tr> </tr>
</tbody> </tbody>
@@ -497,6 +501,8 @@
</div> </div>
</div> </div>
</div> </div>
<recordmodal></recordmodal>
} }
@plugins = { @plugins = {

View File

@@ -30,6 +30,45 @@
$scope.groups = []; $scope.groups = [];
$scope.recordFqdn = undefined; $scope.recordFqdn = undefined;
$scope.recordType = undefined; $scope.recordType = undefined;
$scope.recordsetChanges = {};
$scope.currentRecord = {};
$scope.zoneInfo = {};
$scope.profile = {};
$scope.recordTypes = ['A', 'AAAA', 'CNAME', 'DS', 'MX', 'NS', 'PTR', 'SRV', 'NAPTR', 'SSHFP', 'TXT', 'SOA'];
$scope.sshfpAlgorithms = [{name: '(1) RSA', number: 1}, {name: '(2) DSA', number: 2}, {name: '(3) ECDSA', number: 3},
{name: '(4) Ed25519', number: 4}];
$scope.sshfpTypes = [{name: '(1) SHA-1', number: 1}, {name: '(2) SHA-256', number: 2}];
$scope.dsAlgorithms = [{name: '(3) DSA', number: 3}, {name: '(5) RSASHA1', number: 5},
{name: '(6) DSA_NSEC3_SHA1', number: 6}, {name: '(7) RSASHA1_NSEC3_SHA1' , number: 7},
{name: '(8) RSASHA256', number: 8}, {name: '(10) RSASHA512' , number: 10},
{name: '(12) ECC_GOST', number: 12}, {name: '(13) ECDSAP256SHA256' , number: 13},
{name: '(14) ECDSAP384SHA384', number: 14}, {name: '(15) ED25519', number: 15},
{name: '(16) ED448', number: 16},{name: '(253) PRIVATEDNS', number: 253},
{name: '(254) PRIVATEOID', number: 254}]
$scope.dsDigestTypes = [{name: '(1) SHA1', number: 1}, {name: '(2) SHA256', number: 2}, {name: '(3) GOSTR341194', number: 3}, {name: '(4) SHA384', number: 4}]
$scope.isZoneAdmin = false;
$scope.canReadZone = false;
$scope.canCreateRecords = false;
$scope.zoneId = undefined;
$scope.recordModalState = {
CREATE: 0,
UPDATE: 1,
DELETE: 2,
CONFIRM_UPDATE: 3,
CONFIRM_DELETE: 4,
VIEW_DETAILS: 5
};
// read-only data for setting various classes/attributes in record modal
$scope.recordModalParams = {
readOnly: {
class: "",
readOnly: true
},
editable: {
class: "record-edit",
readOnly: false
}
};
// paging status for record changes // paging status for record changes
var changePaging = pagingService.getNewPagingParams(100); var changePaging = pagingService.getNewPagingParams(100);
@@ -73,8 +112,6 @@
.appendTo(ul); }; .appendTo(ul); };
$scope.viewRecordHistory = function(recordFqdn, recordType) { $scope.viewRecordHistory = function(recordFqdn, recordType) {
$log.log("recordFqdn: ", recordFqdn);
$log.log("recordType: ", recordType);
$scope.recordFqdn = recordFqdn; $scope.recordFqdn = recordFqdn;
$scope.recordType = recordType; $scope.recordType = recordType;
$scope.refreshRecordChangeHistory($scope.recordFqdn, $scope.recordType); $scope.refreshRecordChangeHistory($scope.recordFqdn, $scope.recordType);
@@ -93,6 +130,7 @@
function success(response) { function success(response) {
recordsPaging.next = response.data.nextId; recordsPaging.next = response.data.nextId;
updateRecordDisplay(response.data['recordSets']); updateRecordDisplay(response.data['recordSets']);
getMembership();
} }
return recordsService return recordsService
.listRecordSetData(recordsPaging.maxItems, undefined, recordName, recordType, $scope.nameSort, $scope.ownerGroupFilter) .listRecordSetData(recordsPaging.maxItems, undefined, recordName, recordType, $scope.nameSort, $scope.ownerGroupFilter)
@@ -201,7 +239,8 @@
$scope.refreshRecordChangeHistory = function(recordFqdn, recordType) { $scope.refreshRecordChangeHistory = function(recordFqdn, recordType) {
changePaging = pagingService.resetPaging(changePaging); changePaging = pagingService.resetPaging(changePaging);
function success(response) { function success(response) {
$log.log('recordsService::getRecordSetChangeHistory-success'); $scope.zoneId = response.data.zoneId;
$scope.refreshZone();
changePaging.next = response.data.nextId; changePaging.next = response.data.nextId;
updateChangeDisplay(response.data.recordSetChanges) updateChangeDisplay(response.data.recordSetChanges)
} }
@@ -272,5 +311,50 @@
return 'info'; return 'info';
} }
}; };
$scope.viewRecordInfo = function(record) {
$scope.currentRecord = recordsService.toDisplayRecord(record);
$scope.recordModal = {
action: $scope.recordModalState.VIEW_DETAILS,
title: "Record Info",
basics: $scope.recordModalParams.readOnly,
details: $scope.recordModalParams.readOnly,
sharedZone: $scope.zoneInfo.shared,
sharedDisplayEnabled: $scope.sharedDisplayEnabled
};
$("#record_modal").modal("show");
};
$scope.refreshZone = function() {
function success(response) {
$log.debug('recordsService::getZone-success');
$scope.zoneInfo = response.data.zone;
// Get current user's groups and determine if they're an admin of this zone
getMembership()
}
return recordsService
.getZone($scope.zoneId)
.then(success)
.catch(function (error){
handleError(error, 'recordsService::getZone-catch');
});
};
function getMembership(){
groupsService
.getGroupsStored()
.then(
function (results) {
$scope.myGroups = results.groups;
$scope.myGroupIds = results.groups.map(function(grp) {return grp['id']});
})
.catch(function (error){
handleError(error, 'groupsService::getGroupsStored-failure');
});
}
$scope.canAccessGroup = function(groupId) {
return $scope.myGroupIds !== undefined && $scope.myGroupIds.indexOf(groupId) > -1;
};
}); });
})(); })();