@@ -726,7 +726,7 @@ public async Task<IActionResult> GetSyncProgress(string instanceId, Cancellation
726726
727727 [ HttpPost ]
728728 [ ValidateAntiForgeryToken ]
729- public async Task < IActionResult > CancelOperation ( Guid operationId , Guid assignmentId , CancellationToken cancellationToken = default )
729+ public async Task < IActionResult > CancelOperation ( Guid operationId , Guid assignmentId , string ? instanceId , CancellationToken cancellationToken = default )
730730 {
731731 return await ExecuteWithErrorHandlingAsync ( async ( ) =>
732732 {
@@ -759,6 +759,24 @@ public async Task<IActionResult> CancelOperation(Guid operationId, Guid assignme
759759
760760 if ( updateResult . IsSuccess )
761761 {
762+ // Terminate the durable function orchestration so a new one can be started
763+ if ( ! string . IsNullOrEmpty ( instanceId ) )
764+ {
765+ // Validate the instance ID belongs to this assignment
766+ var allowedPrefixes = new [ ]
767+ {
768+ $ "maprot-sync-{ assignmentId } ",
769+ $ "maprot-activate-{ assignmentId } ",
770+ $ "maprot-deactivate-{ assignmentId } ",
771+ $ "maprot-remove-{ assignmentId } "
772+ } ;
773+
774+ if ( allowedPrefixes . Any ( p => string . Equals ( instanceId , p , StringComparison . OrdinalIgnoreCase ) ) )
775+ {
776+ await syncApiClient . TerminateOrchestration ( instanceId , cancellationToken ) . ConfigureAwait ( false ) ;
777+ }
778+ }
779+
762780 // Also reset the assignment state so the user can retry
763781 var deploymentReset = assignment . DeploymentState is DeploymentState . Syncing or DeploymentState . Removing
764782 ? DeploymentState . Failed
@@ -791,4 +809,57 @@ public async Task<IActionResult> CancelOperation(Guid operationId, Guid assignme
791809 return RedirectToAction ( nameof ( AssignmentStatus ) , new { id = assignmentId } ) ;
792810 } , nameof ( CancelOperation ) ) . ConfigureAwait ( false ) ;
793811 }
812+
813+ [ HttpPost ]
814+ [ ValidateAntiForgeryToken ]
815+ public async Task < IActionResult > TerminateOrchestration ( string instanceId , Guid assignmentId , CancellationToken cancellationToken = default )
816+ {
817+ return await ExecuteWithErrorHandlingAsync ( async ( ) =>
818+ {
819+ // Validate the instance ID belongs to this assignment
820+ var allowedPrefixes = new [ ]
821+ {
822+ $ "maprot-sync-{ assignmentId } ",
823+ $ "maprot-activate-{ assignmentId } ",
824+ $ "maprot-deactivate-{ assignmentId } ",
825+ $ "maprot-remove-{ assignmentId } "
826+ } ;
827+
828+ if ( ! allowedPrefixes . Any ( p => string . Equals ( instanceId , p , StringComparison . OrdinalIgnoreCase ) ) )
829+ return BadRequest ( "The instance ID does not belong to the specified assignment." ) ;
830+
831+ var assignmentResponse = await repositoryApiClient . MapRotations . V1 . GetServerAssignment ( assignmentId , cancellationToken ) . ConfigureAwait ( false ) ;
832+
833+ if ( assignmentResponse . IsNotFound || assignmentResponse . Result ? . Data is null )
834+ return NotFound ( ) ;
835+
836+ var rotationResponse = await repositoryApiClient . MapRotations . V1 . GetMapRotation ( assignmentResponse . Result . Data . MapRotationId , cancellationToken ) . ConfigureAwait ( false ) ;
837+
838+ if ( rotationResponse . IsNotFound || rotationResponse . Result ? . Data is null )
839+ return NotFound ( ) ;
840+
841+ var authResult = await CheckAuthorizationAsync (
842+ authorizationService ,
843+ rotationResponse . Result . Data . GameType ,
844+ AuthPolicies . ManageMapRotations ,
845+ nameof ( TerminateOrchestration ) ,
846+ "MapRotation" ) . ConfigureAwait ( false ) ;
847+
848+ if ( authResult != null )
849+ return authResult ;
850+
851+ var terminated = await syncApiClient . TerminateOrchestration ( instanceId , cancellationToken ) . ConfigureAwait ( false ) ;
852+
853+ if ( terminated )
854+ {
855+ this . AddAlertSuccess ( $ "Orchestration '{ instanceId } ' terminated. You can now re-trigger the operation.") ;
856+ }
857+ else
858+ {
859+ this . AddAlertDanger ( $ "Failed to terminate orchestration '{ instanceId } '. It may have already completed or the sync service is unavailable.") ;
860+ }
861+
862+ return RedirectToAction ( nameof ( AssignmentStatus ) , new { id = assignmentId } ) ;
863+ } , nameof ( TerminateOrchestration ) ) . ConfigureAwait ( false ) ;
864+ }
794865}
0 commit comments