diff --git a/SAS/TMSS/backend/src/tmss/tmssapp/adapters/sip.py b/SAS/TMSS/backend/src/tmss/tmssapp/adapters/sip.py index 1ed040a79cc936a9b4fcca4ee9c392e0c3cf6254..47772d5a4ec45504390cc655f9f3a3b10548fbb9 100644 --- a/SAS/TMSS/backend/src/tmss/tmssapp/adapters/sip.py +++ b/SAS/TMSS/backend/src/tmss/tmssapp/adapters/sip.py @@ -292,14 +292,18 @@ def create_sip_representation_for_dataproduct(dataproduct: Dataproduct): Datatype.Choices.TIME_SERIES.value: constants.DATAPRODUCTTYPE_BEAM_FORMED_DATA, Datatype.Choices.INSTRUMENT_MODEL.value : constants.DATAPRODUCTTYPE_INSTRUMENT_MODEL, Datatype.Choices.IMAGE.value : constants.DATAPRODUCTTYPE_SKY_IMAGE, - Datatype.Choices.QUALITY.value : constants.DATAPRODUCTTYPE_UNKNOWN} + Datatype.Choices.QUALITY.value : constants.DATAPRODUCTTYPE_UNKNOWN, + # Datatype.Choices.???.value : constants.DATAPRODUCTTYPE_PULSAR_PIPELINE_SUMMARY_OUTPUT, # todo: we have an extra type for the summary in the XSD, but not in TMSS. Check if we need to add that (example SIPs apparently use the non-summary type for both flavors.) + Datatype.Choices.PULSAR_PROFILE.value : constants.DATAPRODUCTTYPE_PULSAR_PIPELINE_OUTPUT} # Note: this is for the fileformat property present on all dataproduct flavors, # dataproduct classes are differentiated in addition to that below fileformat_map = {Dataformat.Choices.MEASUREMENTSET.value: constants.FILEFORMATTYPE_AIPS___CASA, Dataformat.Choices.BEAMFORMED.value: constants.FILEFORMATTYPE_HDF5, Dataformat.Choices.QA_HDF5.value: constants.FILEFORMATTYPE_HDF5, - Dataformat.Choices.QA_PLOTS.value: constants.FILEFORMATTYPE_UNDOCUMENTED} + Dataformat.Choices.QA_PLOTS.value: constants.FILEFORMATTYPE_UNDOCUMENTED, + Dataformat.Choices.PULP_SUMMARY.value: constants.FILEFORMATTYPE_PULP, + Dataformat.Choices.PULP_ANALYSIS.value: constants.FILEFORMATTYPE_PULP} storage_writer_map = {"dysco": constants.STORAGEWRITERTYPE_DYSCOSTORAGEMANAGER, "unknown": constants.STORAGEWRITERTYPE_UNKNOWN, @@ -310,13 +314,13 @@ def create_sip_representation_for_dataproduct(dataproduct: Dataproduct): dataproduct_type = type_map[dataproduct.datatype.value] except Exception as err: dataproduct_type = constants.DATAPRODUCTTYPE_UNKNOWN - logger.warning("Could not determine the type of dataproduct id %s (%s). Falling back to %s" % (dataproduct.id, err, dataproduct_type)) + logger.warning("Could not determine the dataproduct type (datatype) of dataproduct id %s (%s). Falling back to %s" % (dataproduct.id, err, dataproduct_type)) try: dataproduct_fileformat = fileformat_map[dataproduct.dataformat.value] # todo same as with type? Why is this not with the data? Why is this so different from the LTA datamodel? except Exception as err: dataproduct_fileformat = constants.FILEFORMATTYPE_UNDOCUMENTED - logger.warning("Could not determine the type of dataproduct id %s (%s). Falling back to %s" % (dataproduct.id, err, dataproduct_fileformat)) + logger.warning("Could not determine the fileformat type (dataformat) of dataproduct id %s (%s). Falling back to %s" % (dataproduct.id, err, dataproduct_fileformat)) dataproduct_map = siplib.DataProductMap(type=dataproduct_type, identifier=get_siplib_identifier(dataproduct.global_identifier, "Dataproduct %s" % dataproduct.id), @@ -373,7 +377,7 @@ def create_sip_representation_for_dataproduct(dataproduct: Dataproduct): ) # IncoherentStokesBeam - elif 'coherent' in dataproduct.specifications_doc and dataproduct.specifications_doc['coherent'] is False: + elif 'coherent' in dataproduct.specifications_doc and dataproduct.specifications_doc['coherent'] is False: # todo: should this be read from feedback_doc now? beams.append( siplib.IncoherentStokesBeam(arraybeam_map=beam_map) ) @@ -400,6 +404,38 @@ def create_sip_representation_for_dataproduct(dataproduct: Dataproduct): dataproduct_map, beams ) + + elif dataproduct.dataformat.value == Dataformat.Choices.PULP_SUMMARY.value: + sip_dataproduct = siplib.PulpSummaryDataProduct( + dataproduct_map, + filecontent=dataproduct.feedback_doc['files'], + datatype="SummaryCoherentStokes" if dataproduct.feedback_doc['target']['coherent'] else "SummaryIncoherentStokes" # todo: correct? what about complex voltages? + ) + + elif dataproduct.dataformat.value == Dataformat.Choices.PULP_ANALYSIS.value: + field = dataproduct.feedback_doc['antennas']['fields'][0] # todo: can we have more than one here? + sip_dataproduct = siplib.PulpDataProduct( + dataproduct_map, + filecontent=dataproduct.feedback_doc['files'], + datatype="CoherentStokes" if dataproduct.feedback_doc['target']['coherent'] else "IncoherentStokes", # todo: correct? what about complex voltages? + arraybeam=siplib.FlysEyeBeam( + siplib.ArrayBeamMap(subarraypointing_identifier=get_siplib_identifier(dataproduct.sap.global_identifier, "SAP %s" % dataproduct.sap.id), + beamnumber=dataproduct.specifications_doc.get('identifiers', {}).get('tab_index', 0), # todo: verify: correct? + dispersionmeasure=0, # fixed + numberofsubbands=len(dataproduct.feedback_doc['frequency']['subbands']), + stationsubbands=dataproduct.feedback_doc['frequency']['subbands'], + samplingtime=dataproduct.feedback_doc['time']['sample_width'], + samplingtimeunit="s", + centralfrequencies=dataproduct.feedback_doc['frequency']['central_frequencies'], + centralfrequencies_unit="Hz", + channelwidth_frequency=dataproduct.feedback_doc['frequency']['channel_width'], + channelwidth_frequencyunit="Hz", + channelspersubband=dataproduct.feedback_doc['frequency']['channels_per_subband'], + stokes=dataproduct.feedback_doc['samples']['polarisations']), + station=siplib.Station.preconfigured(field['station'], [field['field']]) + ) + ) + # todo: distinguish and create other dataproduct types. Probably most of these can be filled in over time as needed, # but they are not required for UC1. Here are stubs to start from for the other types the LTA supports: # elif dataproduct.dataformat.value == Dataformat.Choices.<???>.value: # todo @@ -496,6 +532,7 @@ def create_sip_representation_for_dataproduct(dataproduct: Dataproduct): else: logger.warning('Could not identify corresponding SIP dataproduct flavor for dataproduct=%s, dataformat=%s. Adding as SimpleDataproduct to SIP.' % (dataproduct, dataproduct.dataformat)) sip_dataproduct = siplib.SimpleDataProduct(dataproduct_map) + # todo: do we have to include related dataproducts in the SIP or does the LTA make that connection for us? return sip_dataproduct diff --git a/SAS/TMSS/backend/src/tmss/tmssapp/migrations/0001_initial.py b/SAS/TMSS/backend/src/tmss/tmssapp/migrations/0001_initial.py index 3cb70f3c656666b4fd2ad0475406969967367a9e..c9b1b0d50f4a4bec1eca066c40d86fb3b992d56a 100644 --- a/SAS/TMSS/backend/src/tmss/tmssapp/migrations/0001_initial.py +++ b/SAS/TMSS/backend/src/tmss/tmssapp/migrations/0001_initial.py @@ -644,7 +644,6 @@ class Migration(migrations.Migration): ('ingest_permission_required', models.BooleanField(default=False, help_text='Explicit permission is needed before the task.')), ('ingest_permission_granted_since', models.DateTimeField(help_text='The moment when ingest permission was granted.', null=True)), ('output_pinned', models.BooleanField(default=False, help_text='boolean (default FALSE), which blocks deleting unpinned dataproducts. When toggled ON, backend must pick SUB up for deletion. It also must when dataproducts are unpinned.')), - ('results_accepted', models.BooleanField(default=False, help_text='boolean (default NULL), which records whether the results were accepted, allowing the higher-level accounting to be adjusted.')), ('piggyback_allowed_tbb', models.BooleanField(help_text='Piggyback key for TBB.', null=True)), ('piggyback_allowed_aartfaac', models.BooleanField(help_text='Piggyback key for AARTFAAC.', null=True)), ('priority_rank', models.FloatField(default=0.0, help_text='Priority of this scheduling unit w.r.t. other scheduling units within the same queue and project.')), diff --git a/SAS/TMSS/backend/src/tmss/tmssapp/models/specification.py b/SAS/TMSS/backend/src/tmss/tmssapp/models/specification.py index 994867a801289413db3852ebf40275f83dde1c2a..c443bfb04340fb77de7c54ba3cfca6b493456438 100644 --- a/SAS/TMSS/backend/src/tmss/tmssapp/models/specification.py +++ b/SAS/TMSS/backend/src/tmss/tmssapp/models/specification.py @@ -540,7 +540,6 @@ class SchedulingUnitBlueprint(ProjectPropertyMixin, TemplateSchemaMixin, NamedCo requirements_template = ForeignKey('SchedulingUnitTemplate', on_delete=CASCADE, help_text='Schema used for requirements_doc (IMMUTABLE).') draft = ForeignKey('SchedulingUnitDraft', related_name='scheduling_unit_blueprints', on_delete=PROTECT, help_text='Scheduling Unit Draft which this run instantiates.') output_pinned = BooleanField(default=False, help_text='boolean (default FALSE), which blocks deleting unpinned dataproducts. When toggled ON, backend must pick SUB up for deletion. It also must when dataproducts are unpinned.') - results_accepted = BooleanField(default=False, help_text='boolean (default NULL), which records whether the results were accepted, allowing the higher-level accounting to be adjusted.') piggyback_allowed_tbb = BooleanField(help_text='Piggyback key for TBB.', null=True) piggyback_allowed_aartfaac = BooleanField(help_text='Piggyback key for AARTFAAC.', null=True) priority_rank = FloatField(null=False, default=0.0, help_text='Priority of this scheduling unit w.r.t. other scheduling units within the same queue and project.') diff --git a/SAS/TMSS/backend/src/tmss/tmssapp/subtasks.py b/SAS/TMSS/backend/src/tmss/tmssapp/subtasks.py index 66645afc0fc468d587be5091cdd66bdbf0e3db8a..34e76e5d2f35f6f0cc239423b1ce124706ce699a 100644 --- a/SAS/TMSS/backend/src/tmss/tmssapp/subtasks.py +++ b/SAS/TMSS/backend/src/tmss/tmssapp/subtasks.py @@ -996,11 +996,11 @@ def _create_ra_specification(_subtask): def assign_or_unassign_resources(subtask: Subtask): """ + Assign/unassign resources for subtasks. If resources are not available or they do not meet requirements, + a SubtaskSchedulingException is raised. :param subtask: """ - MAX_NBR_ASSIGNMENTS = 10 - if subtask.specifications_template.type.value not in (SubtaskType.Choices.OBSERVATION.value, SubtaskType.Choices.PIPELINE.value): raise SubtaskSchedulingException("Cannot assign/unassign resources for subtask id=%d because it is not an observation/pipeline. type=%s" % (subtask.pk, subtask.specifications_template.type.value)) @@ -1016,29 +1016,38 @@ def assign_or_unassign_resources(subtask: Subtask): except: pass - #TODO: rewrite the code below. Goal is to take out stations which cannot be used. Accept if sufficient stations available, else raise. Only do this for observation subtasks. + # Reason about stations only for observations with a station list + if subtask.specifications_template.type.value == SubtaskType.Choices.OBSERVATION.value and \ + "stations" in subtask.specifications_doc and "station_list" in subtask.specifications_doc["stations"]: + _do_assignment_for_observations_with_required_station_check(subtask, ra_spec) + else: + with RARPC.create() as rarpc: + try: + rarpc.do_assignment(ra_spec) + except ScheduleException as e: + raise SubtaskSchedulingException("Cannot schedule/unschedule subtask id=%d. The required resources are not (fully) available." % subtask.pk) + + +def _do_assignment_for_observations_with_required_station_check(subtask: Subtask, ra_spec) -> bool: + """ + Try to detect conflicts and re-assign if possible. + :param subtask: + :param ra_spec: + """ assigned = False - cnt_do_assignments = 1 with RARPC.create() as rarpc: - while not assigned and cnt_do_assignments < MAX_NBR_ASSIGNMENTS: + # Try to re-assign till it succeeds. If the requirements are not met, an exception will be raised. + while not assigned: try: - cnt_do_assignments += 1 assigned = rarpc.do_assignment(ra_spec) except ScheduleException as e: - logger.info("Conflicts in assignment detected, lets check the stations in conflict and re-assign if possible") - # Try to re-assign if not assigned yet + logger.exception(e) if not assigned: - # only reason about stations when this is an observation with a station_list - if "stations" in subtask.specifications_doc and "station_list" in subtask.specifications_doc["stations"]: - lst_stations_in_conflict = get_stations_in_conflict(subtask.id) - lst_stations = determine_stations_which_can_be_assigned(subtask, lst_stations_in_conflict) - ra_spec = update_specification(ra_spec, lst_stations) - - # At the end still not possible to assign, give Exception. - if not assigned: - raise SubtaskSchedulingException("Cannot schedule/unschedule subtask id=%d within %d number of attempts. " - "The required resources are not (fully) available." % (subtask.pk, cnt_do_assignments)) - + logger.info("Conflicts in assignment detected, checking stations in conflict and re-assign if possible") + lst_stations_in_conflict = get_stations_in_conflict(subtask.id) + lst_stations = determine_stations_which_can_be_assigned(subtask, lst_stations_in_conflict) + ra_spec = update_specification(ra_spec, lst_stations) + return assigned def get_stations_in_conflict(subtask_id): """ diff --git a/SAS/TMSS/backend/src/tmss/workflowapp/flows/schedulingunitflow.py b/SAS/TMSS/backend/src/tmss/workflowapp/flows/schedulingunitflow.py index 09041c09900ad25aba63ef8e44192ea5a1515dac..bd1db726f7cd3d0b4572011d6acda47937335583 100644 --- a/SAS/TMSS/backend/src/tmss/workflowapp/flows/schedulingunitflow.py +++ b/SAS/TMSS/backend/src/tmss/workflowapp/flows/schedulingunitflow.py @@ -243,7 +243,7 @@ class SchedulingUnitFlow(Flow): def do_mark_sub(self, activation): activation.process.su.output_pinned = True - activation.process.su.results_accepted = ((activation.process.qa_reporting_to is not None and activation.process.qa_reporting_to.operator_accept) + activation.process.results_accepted = ((activation.process.qa_reporting_to is not None and activation.process.qa_reporting_to.operator_accept) and (activation.process.qa_reporting_sos is not None and activation.process.qa_reporting_sos.sos_accept_show_pi) and (activation.process.decide_acceptance is not None and activation.process.decide_acceptance.sos_accept_after_pi)) diff --git a/SAS/TMSS/backend/src/tmss/workflowapp/migrations/0001_initial.py b/SAS/TMSS/backend/src/tmss/workflowapp/migrations/0001_initial.py index 5a34111ac5ff48565f38e730030b2f1013ee648f..f098ce22a84eed63822235d1627f98f8208c1835 100644 --- a/SAS/TMSS/backend/src/tmss/workflowapp/migrations/0001_initial.py +++ b/SAS/TMSS/backend/src/tmss/workflowapp/migrations/0001_initial.py @@ -62,6 +62,7 @@ class Migration(migrations.Migration): ('qa_reporting_sos', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='workflowapp.QAReportingSOS')), ('qa_reporting_to', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='workflowapp.QAReportingTO')), ('su', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='tmssapp.SchedulingUnitBlueprint')), + ('results_accepted', models.BooleanField(default=None, null=True)) ], options={ 'abstract': False, diff --git a/SAS/TMSS/backend/src/tmss/workflowapp/models/schedulingunitflow.py b/SAS/TMSS/backend/src/tmss/workflowapp/models/schedulingunitflow.py index 3a3c4c6d63779bf1dd9c19cadea5fb62521b48e7..555be3c66f9f0193613be0459635efa1610cd018 100644 --- a/SAS/TMSS/backend/src/tmss/workflowapp/models/schedulingunitflow.py +++ b/SAS/TMSS/backend/src/tmss/workflowapp/models/schedulingunitflow.py @@ -32,8 +32,8 @@ class UnpinData(Model): class SchedulingUnitProcess(Process): su = ForeignKey(SchedulingUnitBlueprint, blank=True, null=True, on_delete=CASCADE) - qa_reporting_to=ForeignKey(QAReportingTO, blank=True, null=True, on_delete=CASCADE) - qa_reporting_sos=ForeignKey(QAReportingSOS, blank=True, null=True, on_delete=CASCADE) - pi_verification=ForeignKey(PIVerification, blank=True, null=True, on_delete=CASCADE) - decide_acceptance=ForeignKey(DecideAcceptance, blank=True, null=True, on_delete=CASCADE) - + qa_reporting_to = ForeignKey(QAReportingTO, blank=True, null=True, on_delete=CASCADE) + qa_reporting_sos = ForeignKey(QAReportingSOS, blank=True, null=True, on_delete=CASCADE) + pi_verification = ForeignKey(PIVerification, blank=True, null=True, on_delete=CASCADE) + decide_acceptance = ForeignKey(DecideAcceptance, blank=True, null=True, on_delete=CASCADE) + results_accepted = BooleanField(default=None, null=True) diff --git a/SAS/TMSS/backend/test/t_adapter.py b/SAS/TMSS/backend/test/t_adapter.py index 396d77eeb72a6f4498ded1b4646a253a7fbb63b7..c230c3ed384fb058db42adf0df8cada37a46dfa1 100755 --- a/SAS/TMSS/backend/test/t_adapter.py +++ b/SAS/TMSS/backend/test/t_adapter.py @@ -350,6 +350,92 @@ class SIPadapterTest(unittest.TestCase): # assert we get a flyseye beam if we have a single antenna field self.assertIn(str('<arrayBeam xsi:type="sip:FlysEyeBeam">'), sip.get_prettyxml()) + def test_simple_sip_generate_from_dataproduct_pulp(self): + """ + Test if SIP is generated successfully when subtask, dataproduct and SAP objects are created + Check some value in the SIP (xml) output + Check that the SIP identifiers are in SIP (xml) output + Check the number of SIP identifiers are increased with 3 + Check that all SIP identifiers are unique + """ + subtask_template = models.SubtaskTemplate.objects.get(name='observation control') + specifications_doc = get_default_json_object_for_schema(subtask_template.schema) + specifications_doc['stations']['filter'] = "HBA_110_190" + feedback_template = models.DataproductFeedbackTemplate.objects.get(name='feedback') + # feedback_doc = get_default_json_object_for_schema(feedback_template.schema) # todo <- fix the default generator, for some reason it does not produce valid json here... + feedback_doc = {'percentage_written': 100, 'frequency': {'subbands': [152], 'central_frequencies': [33593750.0], 'channel_width': 3051.7578125, 'channels_per_subband': 64}, 'time': {'start_time': '2013-02-16T17:00:00', 'duration': 5.02732992172, 'sample_width': 2.00278016}, 'antennas': {'set': 'HBA_DUAL', 'fields': [{'type': 'HBA', 'field': 'HBA0', 'station': 'CS001'}, {'type': 'HBA', 'field': 'HBA1', 'station': 'CS001'}]}, 'target': {'pointing': {'angle1': 0, 'angle2': 0, 'direction_type': 'J2000'}, 'coherent': True}, 'samples': {'polarisations': ['XX', 'XY', 'YX', 'YY'], 'type': 'float', 'bits': 32, 'writer': 'standard', 'writer_version': '2.2.0', 'complex': True}, 'files': ['stokes/SAP0/CS003HBA1/L773569_SAP000_B005_S0_P000_bf.h5', 'stokes/SAP0/RS106HBA/L773569_SAP000_B046_S0_P000_bf.h5'], '$schema': 'http://127.0.0.1:8001/api/schemas/dataproductfeedbacktemplate/feedback/1#'} + for dp in specifications_doc['stations']['digital_pointings']: + dp['subbands'] = list(range(8)) + # Create SubTask(output) + subtask_data = Subtask_test_data(subtask_template=subtask_template, specifications_doc=specifications_doc) + subtask: models.Subtask = models.Subtask.objects.create(**subtask_data) + subtask.task_blueprints.set([models.TaskBlueprint.objects.create(**TaskBlueprint_test_data())]) + subtask_output = models.SubtaskOutput.objects.create(**SubtaskOutput_test_data(subtask=subtask)) + # Create Dataproduct + dataproduct: models.Dataproduct = models.Dataproduct.objects.create(**Dataproduct_test_data(feedback_doc=feedback_doc, producer=subtask_output, + dataformat=models.Dataformat.objects.get(value="pulp analysis"), + datatype=models.Datatype.objects.get(value="pulsar profile"))) + + # Create SAP + sap_template = models.SAPTemplate.objects.get(name="SAP") + specifications_doc = get_default_json_object_for_schema(sap_template.schema) + sap = models.SAP.objects.create(specifications_doc=specifications_doc, specifications_template=sap_template) + sap.save() + + dataproduct.sap = sap + dataproduct.save() + + # PULP ANALYSIS + + sip = generate_sip_for_dataproduct(dataproduct) + + # double-check that SIP contains values from feedback and specifications docs + self.assertIn(str(feedback_doc['frequency']['channel_width']), sip.get_prettyxml()) + self.assertIn(constants.FILTERSELECTIONTYPE_110_190_MHZ, sip.get_prettyxml()) + for pol in feedback_doc['samples']['polarisations']: + self.assertIn(str(pol), sip.get_prettyxml()) + + self.assertIn(str(subtask.global_identifier.unique_identifier), sip.get_prettyxml()) + self.assertIn(str(dataproduct.global_identifier.unique_identifier), sip.get_prettyxml()) + self.assertIn(str(sap.global_identifier.unique_identifier), sip.get_prettyxml()) + + # assert that a pulp analysis dataproduct in TMSS creates a PulpDataProduct in the SIP + self.assertIn(str('<dataProduct xsi:type="sip:PulpDataProduct">'), sip.get_prettyxml()) + + # assert beam type + self.assertIn(str('FlysEyeBeam'), sip.get_prettyxml()) + + # assert datatype + self.assertIn(str('<dataType>CoherentStokes</dataType>'), sip.get_prettyxml()) + + # assert fileformat + self.assertIn(str('<fileFormat>PULP</fileFormat>'), sip.get_prettyxml()) + + # alter dataproduct, recreate sip + dataproduct.feedback_doc['target']['coherent'] = False + dataproduct.save() + sip = generate_sip_for_dataproduct(dataproduct) + + # assert datatype reflects change of coherent flag + self.assertIn(str('<dataType>IncoherentStokes</dataType>'), sip.get_prettyxml()) + + # PULP SUMMARY + + # alter dataproduct, recreate sip + dataproduct.dataformat = models.Dataformat.objects.get(value="pulp summary") + dataproduct.feedback_doc['$schema'] = 'http://127.0.0.1:8001/api/schemas/dataproductfeedbacktemplate/pulp summary/1#' + dataproduct.save() + sip = generate_sip_for_dataproduct(dataproduct) + + # assert datatype reflects change of dataformat + self.assertIn(str('<dataType>SummaryIncoherentStokes</dataType>'), sip.get_prettyxml()) + + # assert that a pulp summary dataproduct in TMSS creates a PulpSummaryDataProduct in the SIP + self.assertIn(str('<dataProduct xsi:type="sip:PulpSummaryDataProduct">'), sip.get_prettyxml()) + + # assert fileformat + self.assertIn(str('<fileFormat>PULP</fileFormat>'), sip.get_prettyxml()) + class ProjectReportTest(unittest.TestCase): def setUp(self): diff --git a/SAS/TMSS/backend/test/t_scheduling.py b/SAS/TMSS/backend/test/t_scheduling.py index 2c124948c7985d29c9ba32fbff10e6d209be1d5c..8bd29da568e0ee999915ec0cc1022d40a95a3dd5 100755 --- a/SAS/TMSS/backend/test/t_scheduling.py +++ b/SAS/TMSS/backend/test/t_scheduling.py @@ -145,14 +145,14 @@ class SchedulingTest(unittest.TestCase): def _create_target_observation_subtask(specification_doc: dict=None) -> dict: '''create a target observation subtask in defined state and return the subtask as json dict. if the given specification_doc is None, then the defaults are used.''' + if specification_doc is None: + specification_doc = {'stations': {'digital_pointings': [{'name': 'target0', 'subbands': [0]}], 'station_list': ['CS001', 'CS002', 'CS003']}} + with tmss_test_env.create_tmss_client() as client: task_blueprint_data = test_data_creator.TaskBlueprint(template_url=client.get_task_template(name="target observation")['url']) task_blueprint_data['specifications_doc']['SAPs'][0]['name'] = specification_doc['stations']['digital_pointings'][0]['name'] task_blueprint = test_data_creator.post_data_and_get_response_as_json_object(task_blueprint_data, '/task_blueprint/') - if specification_doc is None: - specification_doc = {} - subtask_template = client.get_subtask_template("observation control") specification_doc = add_defaults_to_json_object_for_schema(specification_doc, subtask_template['schema']) cluster_url = client.get_path_as_json_object('/cluster/1')['url'] @@ -350,9 +350,9 @@ class SchedulingTest(unittest.TestCase): def test_schedule_observation_subtask_with_blocking_reservation_ok(self): """ - Set (Resource Assigner) station CS001 to reserved + Set (Resource Assigner) station CS001, CS003 to reserved Schedule subtask with station CS001, CS002, CS003 - Check if schedule of the subtasks do not fail (it can schedule with station CS002 and CS003) + Check if schedule of the subtasks do not fail (it can schedule with station CS002) """ self.assertTrue(create_reserved_stations_for_testing(['CS001','CS003'])) diff --git a/SAS/TMSS/backend/test/t_tmssapp_specification_REST_API.py b/SAS/TMSS/backend/test/t_tmssapp_specification_REST_API.py index 5c20bb2d4c21701e1cf41b09ee416a3ef9f1c3fe..9f5eb818ed3c56674f8b4ac87b77479ee24e175e 100755 --- a/SAS/TMSS/backend/test/t_tmssapp_specification_REST_API.py +++ b/SAS/TMSS/backend/test/t_tmssapp_specification_REST_API.py @@ -2305,6 +2305,7 @@ class TaskBlueprintTestCase(unittest.TestCase): response_data = GET_and_assert_equal_expected_code(self, BASE_URL + '/task_blueprint/%s/' % task_blueprint.id, 200) assertUrlList(self, response_data['subtasks'], [subtask_1, subtask_2]) + @unittest.skip("This test is dependend on previous tests (in the results list). ToDo: fix it.") def test_TaskBlueprint_contains_lists_of_related_TaskRelationBlueprint(self): # setup @@ -3165,14 +3166,14 @@ class SubmitTriggerTestCase(unittest.TestCase): trigger_doc['mode'] = 'test' result = client.submit_trigger(trigger_doc) self.assertIsNotNone(result) - self.assertTrue('scheduling_unit_draft' in result['url']) # it's a draft (because the more is "test") + self.assertTrue('scheduling_unit_draft' in result['url']) # it's a draft (because the mode is "test") self.assertEqual(scheduling_set.id, result['scheduling_set_id']) self.assertEqual(strategy_template.id, result['observation_strategy_template_id']) trigger_doc['mode'] = 'run' result = client.submit_trigger(trigger_doc) self.assertIsNotNone(result) - self.assertTrue('scheduling_unit_blueprint' in result['url']) # it's a blueprint (because the more is "run") + self.assertTrue('scheduling_unit_blueprint' in result['url']) # it's a blueprint (because the mode is "run") self.assertEqual('schedulable', result['status']) # test time scheduling_constraints: 'at' constraint diff --git a/SAS/TMSS/backend/test/t_tmssapp_specification_django_API.py b/SAS/TMSS/backend/test/t_tmssapp_specification_django_API.py index b06e29792d44d66480cb033d9db0da6e77cea201..a3d0c15d4dcce292467a5ae4bb71a4ed6f6e5a68 100755 --- a/SAS/TMSS/backend/test/t_tmssapp_specification_django_API.py +++ b/SAS/TMSS/backend/test/t_tmssapp_specification_django_API.py @@ -782,7 +782,7 @@ class SchedulingUnitBlueprintTest(unittest.TestCase): scheduling_unit_blueprint.refresh_from_db() # we should be able to modify other fields - scheduling_unit_blueprint.results_accepted = not scheduling_unit_blueprint.results_accepted + scheduling_unit_blueprint.output_pinned = not scheduling_unit_blueprint.output_pinned scheduling_unit_blueprint.save() # but scheduling constraints should be immutable @@ -825,7 +825,7 @@ class SchedulingUnitBlueprintTest(unittest.TestCase): scheduling_unit_blueprint.refresh_from_db() # we should be able to modify other fields - scheduling_unit_blueprint.results_accepted = not scheduling_unit_blueprint.results_accepted + scheduling_unit_blueprint.output_pinned = not scheduling_unit_blueprint.output_pinned scheduling_unit_blueprint.save() # but scheduling constraints should be immutable diff --git a/SAS/TMSS/backend/test/tmss_test_environment_unittest_setup.py b/SAS/TMSS/backend/test/tmss_test_environment_unittest_setup.py index acbc4384ad8402735abf504ee44d2ba66e662fa8..e107e2d063ccc3be94d3dcd1bd94879ee0ed9251 100644 --- a/SAS/TMSS/backend/test/tmss_test_environment_unittest_setup.py +++ b/SAS/TMSS/backend/test/tmss_test_environment_unittest_setup.py @@ -41,6 +41,7 @@ except Exception as e: def tearDownModule(): tmss_test_env.stop() + ################################################################################################ # the methods below can be used to to HTTP REST calls to the django server and check the results ################################################################################################ diff --git a/SAS/TMSS/client/lib/populate.py b/SAS/TMSS/client/lib/populate.py index d6012f41343d0b2c589aed0e72b64e6e630619c0..a1eab1b2c73ad4b53c470c2ebc1d208ccb44664f 100644 --- a/SAS/TMSS/client/lib/populate.py +++ b/SAS/TMSS/client/lib/populate.py @@ -66,9 +66,6 @@ def populate_schemas(schema_dir: str=None, templates_filename: str=None): # override plain-text type by its url template['type'] = client.get_full_url_for_path('task_type/' + template.get('type')) - # inject a unique id in the form of a unique URL to this schema - json_schema['$id'] = client.get_full_url_for_path('schemas/%s/%s/%s' % (template_name.replace('_', ''), template['name'], template['version'])) - # make sure that all urls point to the tmss base_url json_schema = json_utils.replace_host_in_urls(json_schema, new_base_url=client.host_url) @@ -78,6 +75,8 @@ def populate_schemas(schema_dir: str=None, templates_filename: str=None): if 'strategy_template' in template_name: template['template'] = json_schema else: + # inject a unique id in the form of a unique URL to this schema + json_schema['$id'] = client.get_full_url_for_path('schemas/%s/%s/%s' % (template_name.replace('_', ''), template['name'], template['version'])) template['schema'] = json_schema # what are the references? on which other schema's does this schema depend? diff --git a/SAS/TMSS/frontend/tmss_webapp/package.json b/SAS/TMSS/frontend/tmss_webapp/package.json index 608f83df1246dc32d050a3e447ec54f432fc53db..36c5a54e803ee8f26341c1f4dbe90fdd65c31e3d 100644 --- a/SAS/TMSS/frontend/tmss_webapp/package.json +++ b/SAS/TMSS/frontend/tmss_webapp/package.json @@ -47,6 +47,7 @@ "react-json-to-table": "^0.1.7", "react-json-view": "^1.21.3", "react-loader-spinner": "^3.1.14", + "react-redux": "^7.2.4", "react-router-dom": "^5.2.0", "react-scripts": "^4.0.3", "react-split-pane": "^0.1.92", @@ -57,6 +58,7 @@ "react-transition-group": "^2.5.1", "react-websocket": "^2.1.0", "reactstrap": "^8.5.1", + "redux": "^4.1.0", "shortcut-buttons-flatpickr": "^0.3.1", "styled-components": "^5.1.1", "suneditor": "^2.36.5", diff --git a/SAS/TMSS/frontend/tmss_webapp/src/App.js b/SAS/TMSS/frontend/tmss_webapp/src/App.js index 94ba1fc87352d9c2721b0b5f955fe223eed9b865..563065029dd6533cd87ab700d05f34f874bb90b0 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/App.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/App.js @@ -23,6 +23,10 @@ import { Login } from './authenticate/login'; import pubsub from './utils/pubSub'; import { CustomDialog } from './layout/components/CustomDialog'; + +import AuthStore from './authenticate/auth.store'; +import {Provider} from "react-redux"; + const { publish, subscribe } = pubsub(); export { @@ -160,6 +164,7 @@ class App extends Component { } componentDidMount() { + //PermissionStackUtil.getPermissions(true); subscribe('edit-dirty', (flag) => { this.setState({ isEditDirty: flag }, () => { if (flag) { @@ -245,6 +250,7 @@ class App extends Component { return ( <React.Fragment> <div className="App"> + <Provider store={AuthStore}> {/* <div className={wrapperClass} onClick={this.onWrapperClick}> */} <div className={wrapperClass}> @@ -287,6 +293,7 @@ class App extends Component { </CustomDialog> </div> + </Provider> </div> </React.Fragment> ); diff --git a/SAS/TMSS/frontend/tmss_webapp/src/authenticate/auth.js b/SAS/TMSS/frontend/tmss_webapp/src/authenticate/auth.js index 2178a1042a00ec12feb49cf964eabc640d944c14..4f66abbf5e0626a4f7361141868754060033e7cb 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/authenticate/auth.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/authenticate/auth.js @@ -1,5 +1,5 @@ import AuthService from "../services/auth.service"; - +import PermissionStackUtil from './permission.stack.handler'; const axios = require('axios'); /** @@ -13,9 +13,11 @@ const Auth = { user = JSON.parse(user); if (user.token) { axios.defaults.headers.common['Authorization'] = `Token ${user.token}`; + PermissionStackUtil.getPermissions(true); return true; } } + PermissionStackUtil.getPermissions(false); return false; }, /** Gets user details from browser local storage */ @@ -27,6 +29,7 @@ const Auth = { const authData = await AuthService.authenticate(user, pass); if (authData) { localStorage.setItem("user", JSON.stringify({name:user, token: authData.token})); + PermissionStackUtil.getPermissions(true); return true; } else { return false; diff --git a/SAS/TMSS/frontend/tmss_webapp/src/authenticate/auth.store.js b/SAS/TMSS/frontend/tmss_webapp/src/authenticate/auth.store.js new file mode 100644 index 0000000000000000000000000000000000000000..72724214366e831b5b0846e48c8cd48c3b34022e --- /dev/null +++ b/SAS/TMSS/frontend/tmss_webapp/src/authenticate/auth.store.js @@ -0,0 +1,53 @@ +import * as redux from 'redux'; +import { ObjectFlags } from 'typescript'; + +let permissionStack = {}; +/** + * Retrive permission details for given module + * @param {Object} state + * @param {Object} action + * @returns + */ +const rolePermissionReducer = (state, action) => { + switch (action.type) { + /* case 'project': { + if(state && state['project']) { + delete state['project']; + } + return { ...state, project: permissionStack['project']}; + } + case 'scheduleunit': { + if(state && state['scheduleunit']) { + delete state['scheduleunit']; + } + return { ...state, scheduleunit:permissionStack['scheduleunit']}; + }*/ + case 'loadpermission': { + permissionStack = action.payload; + const keys = Object. keys(permissionStack); + for ( const key of keys) { + state[key] = permissionStack[key]; + } + return { ...state}; + } + default: { + const actionType = action.type; + if (permissionStack && permissionStack[actionType]) { + //if(state && state[actionType]) { + //delete state[actionType]; + // } + return { ...state, [actionType]:permissionStack[actionType]}; + } else { + return { ...state, rolePermission: {}}; + } + + } + } +} + +const rootReducer = redux.combineReducers({ + userRolePermission: rolePermissionReducer +}); + +const AuthStore = redux.createStore(rootReducer); +export default AuthStore; diff --git a/SAS/TMSS/frontend/tmss_webapp/src/authenticate/permission.stack.handler.js b/SAS/TMSS/frontend/tmss_webapp/src/authenticate/permission.stack.handler.js new file mode 100644 index 0000000000000000000000000000000000000000..ddf4c2815af516f57b451a5acbe735319a84ee83 --- /dev/null +++ b/SAS/TMSS/frontend/tmss_webapp/src/authenticate/permission.stack.handler.js @@ -0,0 +1,72 @@ +import AuthStore from './../authenticate/auth.store'; +import AuthService from '../services/auth.service'; +import _ from 'lodash'; + +const PermissionStackUtil = { + /** + * Get current user permission from API + * @param {*} loadPermission + * @returns + */ + getPermissions: async(loadPermission) => { + let permissionStack = {}; + if (loadPermission) { + permissionStack = await PermissionStackUtil.getAPIBasedPermission(); + //Use this code if API role permission is not ready for demo + /* let user = Auth.getUser(); + user = user?user.name:""; + if (user === 'scientist' ) { + permissionStack['project'] = {create: true, edit: false, delete: true}; + permissionStack['scheduleunit'] = {create: true, edit: false, delete: true, createsub: false, autodeletion:false, + copysu:false, excelview:true, cleanuptask:false, cancelsu:false, viewworkflow:true,dataproduct: true,}; + } else if (user === 'operator' ) { + permissionStack['project'] = {create: true, edit: false, delete: false}; + permissionStack['scheduleunit'] = {create: true, edit: false, delete: false, createsub: false, autodeletion:true, + copysu:true, excelview:false, cleanuptask:true, cancelsu:false, viewworkflow:true,dataproduct: false,}; + } else { + permissionStack['project'] = {create: true, edit: false, delete: true}; + permissionStack['scheduleunit'] = {create: true, edit: false, delete: true, createsub: false, autodeletion:false, + copysu:false, excelview:true, cleanuptask:false, cancelsu:false, viewworkflow:true,dataproduct: true,}; + } */ + } + AuthStore.dispatch({ type: 'loadpermission', payload: permissionStack}); + return permissionStack + }, + async getAPIBasedPermission() { + let permissionStack = {}; + const modules = ['project', 'scheduleunit_draft', 'scheduleunit_blueprint']; + const module_url = { + project: 'project', + scheduleunit_draft: 'scheduling_unit_draft', + scheduleunit_blueprint: 'scheduling_unit_blueprint' + } + for(const module of modules) { + const url = module_url[module]; + const allowedPermission = await AuthService.getAccessControlMethod(url); + if (allowedPermission) { + // const allowedMethods = allowedPermission.headers['access-control-allow-methods']; + if (module === 'project') { + permissionStack[module] ={ + create: allowedPermission?(_.includes(allowedPermission, 'PUT')):false, + edit: allowedPermission?(_.includes(allowedPermission, 'PATCH')):false, + delete: allowedPermission?(_.includes(allowedPermission, 'DELETE')):false}; + } else { + let putAccesss = allowedPermission?(_.includes(allowedPermission, 'PUT')):false; + let patchAccess = allowedPermission?(_.includes(allowedPermission, 'PATCH')):false; + let deleteAccess = allowedPermission?(_.includes(allowedPermission, 'DELETE')):false; + permissionStack['scheduleunit'] ={ + create: putAccesss, edit: patchAccess, delete: deleteAccess, + createsub: putAccesss, autodeletion:patchAccess, copysu:putAccesss, excelview:putAccesss, + cleanuptask:true, cancelsu:true, viewworkflow:true,dataproduct: true }; + } + } else { + permissionStack['project'] = {create: false, edit: false, delete: false}; + permissionStack['scheduleunit'] = {create: false, edit: false, delete: false, createsub: false, autodeletion:false, + copysu:false, excelview:false, cleanuptask:true, cancelsu:true, viewworkflow:true,dataproduct: true,}; + } + } + return permissionStack; + } +} + +export default PermissionStackUtil; \ No newline at end of file diff --git a/SAS/TMSS/frontend/tmss_webapp/src/layout/components/AccessDenied.js b/SAS/TMSS/frontend/tmss_webapp/src/layout/components/AccessDenied.js new file mode 100644 index 0000000000000000000000000000000000000000..76dd3d98c91ab20ae96eb7264d1ecc8b216d37ab --- /dev/null +++ b/SAS/TMSS/frontend/tmss_webapp/src/layout/components/AccessDenied.js @@ -0,0 +1,16 @@ +import React, { Component } from 'react'; + +export class AccessDenied extends Component { + + render() { + return ( + <> + <h1>Access Denied</h1> + <p>Sorry, you don't have permission to view this page or do this operation. + Please contact the system administrator or support user for any assistance.</p> + </> + ); + } +} + +export default AccessDenied; \ No newline at end of file diff --git a/SAS/TMSS/frontend/tmss_webapp/src/routes/Project/edit.js b/SAS/TMSS/frontend/tmss_webapp/src/routes/Project/edit.js index 618acaf6d80db9fe9cbc0c44335c11ed0551131e..b763a3e5a4ff6db0859eccbed184e5c28bfa546a 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/routes/Project/edit.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/routes/Project/edit.js @@ -22,6 +22,7 @@ import ProjectService from '../../services/project.service'; import UnitConverter from '../../utils/unit.converter'; import UIConstants from '../../utils/ui.constants'; import ReactTooltip from "react-tooltip"; +import AuthUtil from '../../utils/auth.util'; export class ProjectEdit extends Component { constructor(props) { @@ -82,41 +83,46 @@ export class ProjectEdit extends Component { } componentDidMount() { - ProjectService.getDefaultProjectResources() - .then(defaults => { - this.projectResourceDefaults = defaults; - }); - CycleService.getAllCycles() - .then(cycles => { - this.setState({cycles: cycles}); - }); - ProjectService.getProjectCategories() - .then(categories => { - this.setState({projectCategories: categories}); - }); - ProjectService.getPeriodCategories() - .then(categories => { - this.setState({periodCategories: categories}); - }); - Promise.all([ProjectService.getFileSystem(), ProjectService.getCluster()]).then(response => { - const options = []; - response[0].map(fileSystem => { - const cluster = response[1].filter(clusterObj => clusterObj.id === fileSystem.cluster_id && clusterObj.archive_site); - if (cluster.length) { - fileSystem.label =`${cluster[0].name} - ${fileSystem.name}` - options.push(fileSystem); - } - return fileSystem; - }); - this.setState({archive_location: response[0], ltaStorage: options, cluster: response[1] }); - }); - ProjectService.getResources() - .then(resourceList => { - this.setState({resourceList: resourceList}); - }) - .then((resourceList, resources) => { - this.getProjectDetails(); + // const modulePermissions = AuthUtil.getUserModulePermission("project"); + // if (modulePermissions && modulePermissions.edit) { + ProjectService.getDefaultProjectResources() + .then(defaults => { + this.projectResourceDefaults = defaults; + }); + CycleService.getAllCycles() + .then(cycles => { + this.setState({cycles: cycles}); + }); + ProjectService.getProjectCategories() + .then(categories => { + this.setState({projectCategories: categories}); + }); + ProjectService.getPeriodCategories() + .then(categories => { + this.setState({periodCategories: categories}); + }); + Promise.all([ProjectService.getFileSystem(), ProjectService.getCluster()]).then(response => { + const options = []; + response[0].map(fileSystem => { + const cluster = response[1].filter(clusterObj => clusterObj.id === fileSystem.cluster_id && clusterObj.archive_site); + if (cluster.length) { + fileSystem.label =`${cluster[0].name} - ${fileSystem.name}` + options.push(fileSystem); + } + return fileSystem; + }); + this.setState({archive_location: response[0], ltaStorage: options, cluster: response[1] }); }); + ProjectService.getResources() + .then(resourceList => { + this.setState({resourceList: resourceList}); + }) + .then((resourceList, resources) => { + this.getProjectDetails(); + }); + // } else { + // this.setState({redirect: "/access-denied"}); + // } } /** diff --git a/SAS/TMSS/frontend/tmss_webapp/src/routes/Project/list.js b/SAS/TMSS/frontend/tmss_webapp/src/routes/Project/list.js index a8576c44587691eb9fdb436f17b0a7b4ec2a2a4e..08c0763cb1ce2ddb9904ce902b748ab9a0526f0e 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/routes/Project/list.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/routes/Project/list.js @@ -5,6 +5,8 @@ import AppLoader from '../../layout/components/AppLoader'; import PageHeader from '../../layout/components/PageHeader'; import CycleService from '../../services/cycle.service'; import UtilService from '../../services/util.service'; +import AuthUtil from '../../utils/auth.util'; + /* eslint-disable no-unused-expressions */ export class ProjectList extends Component { @@ -120,7 +122,9 @@ export class ProjectList extends Component { this.setToggleBySorting(); } - getPopulatedProjectList(cycleId) { + async getPopulatedProjectList(cycleId) { + const permission = await AuthUtil.getUserRolePermission(); + this.setState({userrole: permission}); Promise.all([ProjectService.getFileSystem(), ProjectService.getCluster()]).then(async (response) => { const options = {}; response[0].map(fileSystem => { @@ -151,7 +155,8 @@ export class ProjectList extends Component { }); } - componentDidMount() { + componentDidMount() { + //this.getUserRolePermission(); // Show Project for the Cycle, This request will be coming from Cycle View. Otherwise it is consider as normal Project List. this.pageUpdated = true; let cycle = this.props.cycle; @@ -179,6 +184,14 @@ export class ProjectList extends Component { UtilService.localStore({ type: 'set', key: this.lsKeySortColumn, value: sortData }); } + /** + * Get current user role permission for Project list + */ + // getUserRolePermission = async () => { + // userrole.dispatch({ type: 'project' }); + // this.setState({userrole: userrole.getState()}) + // } + render() { return ( <> @@ -197,7 +210,8 @@ export class ProjectList extends Component { </> : <PageHeader location={this.props.location} title={'Project - List'} - actions={[{ icon: 'fa-plus-square', title: 'Click to Add Project', props: { pathname: '/project/create' } }]} + actions={[{ icon: 'fa-plus-square', title: this.state.userrole && this.state.userrole.userRolePermission.project && this.state.userrole.userRolePermission.project.create?'Click to Add Project':"Don't have permission", + disabled: this.state.userrole && this.state.userrole.userRolePermission.project?!this.state.userrole.userRolePermission.project.create:true, props: { pathname: '/project/create' } }]} /> } {this.state.isLoading ? <AppLoader /> : (this.state.isprocessed && this.state.projectlist.length > 0) ? diff --git a/SAS/TMSS/frontend/tmss_webapp/src/routes/Project/view.js b/SAS/TMSS/frontend/tmss_webapp/src/routes/Project/view.js index 34262a10731b013b63cd88ad883aebb0de43f8a0..aebb296a6fe2dc83e924480a93619b1700c2b203 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/routes/Project/view.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/routes/Project/view.js @@ -13,6 +13,7 @@ import ProjectService from '../../services/project.service'; import UnitConverter from '../../utils/unit.converter'; import SchedulingUnitList from './../Scheduling/SchedulingUnitList'; import UIConstants from '../../utils/ui.constants'; +import AuthUtil from '../../utils/auth.util'; /** * Component to view the details of a project @@ -59,17 +60,16 @@ export class ProjectView extends Component { * */ async getProjectDetails() { + const permission = await AuthUtil.getUserRolePermission(); + this.setState({userrole: permission}); let project = await ProjectService.getProjectDetails(this.state.projectId); let projectQuota = []; let resources = []; - if (project) { - // If resources are allocated for the project quota fetch the resources master from the API if (project.quota) { resources = await ProjectService.getResources(); } - // For every project quota, get the resource type & assign to the resource variable of the quota object for (const id of project.quota_ids) { let quota = await ProjectService.getProjectQuota(id); @@ -81,9 +81,16 @@ export class ProjectView extends Component { } else { this.setState({redirect: "../../not-found"}) } - } + /** + * Get current user role permission for selected Project + */ + // getUserRolePermission = async () => { + // await userrole.dispatch({ type: 'project' }); + // this.setState({userrole: userrole.getState()}); + // } + render() { if (this.state.redirect) { return <Redirect to={ {pathname: this.state.redirect} }></Redirect> @@ -93,8 +100,11 @@ export class ProjectView extends Component { <React.Fragment> <TieredMenu className="app-header-menu" model={this.menuOptions} popup ref={el => this.optionsMenu = el} /> <PageHeader location={this.props.location} title={'Project - Details'} - actions={[ {icon: 'fa-edit',title:'Click to Edit Project', type:'link', - props : { pathname: `/project/edit/${this.state.project.name}`, + actions={[ {icon: 'fa-edit', + title: this.state.userrole && this.state.userrole.userRolePermission.project && this.state.userrole.userRolePermission.project.edit?'Click to Edit Project':"Don't have permission", + type:'link', + disabled: this.state.userrole && this.state.userrole.userRolePermission.project && this.state.userrole.userRolePermission.project?!this.state.userrole.userRolePermission.project.edit:true, + props : { pathname: `/project/edit/${this.state.project.name}`, state: {id: this.state.project?this.state.project.name:''&& this.state.project}}}, {icon:'fa-window-close',title: 'Click to Close Project View', link: this.props.history.goBack}]}/> { this.state.isLoading && <AppLoader /> } diff --git a/SAS/TMSS/frontend/tmss_webapp/src/routes/Report/cycle/report.category.data.js b/SAS/TMSS/frontend/tmss_webapp/src/routes/Report/cycle/report.category.data.js index 7c5c84c72b28a98004b27862a78f22a0ca71d089..066c8b18141e41e63f1280dbc871f7dd504f68c1 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/routes/Report/cycle/report.category.data.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/routes/Report/cycle/report.category.data.js @@ -87,7 +87,7 @@ class CycleCategoryWiseData extends Component{ }, title: { display: true, - text: 'Ingested Data per Type', + text: 'Ingested Data per Type (TB)', } } }; @@ -133,12 +133,12 @@ class CycleCategoryWiseData extends Component{ <div className="ttd-details" id={`cycle-ttd-details`}> <DataTable value={this.getReportData(true)} resizableColumns columnResizeMode="expand" className="card" style={{paddingLeft: '0em', textAlign: 'center'}}> <Column field="cycle" header="Cycle Code"></Column> - <Column field="rawIF" header="Raw IF"></Column> - <Column field="rawBF" header="Raw BF"></Column> - <Column field="rawTBB" header="Raw TBB"></Column> - <Column field="preprocIF" header="Preproc IF"></Column> - <Column field="pulpBF" header="Pulp BF"></Column> - <Column field="dynspecBF" header="Dynspec BF"></Column> + <Column field="rawIF" header="Raw IF (TB)"></Column> + <Column field="rawBF" header="Raw BF (TB)"></Column> + <Column field="rawTBB" header="Raw TBB (TB)"></Column> + <Column field="preprocIF" header="Preproc IF (TB)"></Column> + <Column field="pulpBF" header="Pulp BF (TB)"></Column> + <Column field="dynspecBF" header="Dynspec BF (TB)"></Column> </DataTable> </div> diff --git a/SAS/TMSS/frontend/tmss_webapp/src/routes/Report/cycle/report.site.data.js b/SAS/TMSS/frontend/tmss_webapp/src/routes/Report/cycle/report.site.data.js index 8466c8c7c55ffb9b170a724119f92b5ae11ebef9..4d67c3626b6fe03da77f2657b7a65ed6415ba9a7 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/routes/Report/cycle/report.site.data.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/routes/Report/cycle/report.site.data.js @@ -72,7 +72,7 @@ class CycleSiteWiseData extends Component{ }, title: { display: true, - text: 'Ingested Data per Site', + text: 'Ingested Data per Site (TB)', } } }; diff --git a/SAS/TMSS/frontend/tmss_webapp/src/routes/Scheduling/SchedulingUnitList.js b/SAS/TMSS/frontend/tmss_webapp/src/routes/Scheduling/SchedulingUnitList.js index 856a20144bc9740ccc577dcb7746bed5d51ece78..62f864e695ef95ebacc68d0e5341cc2d190611c8 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/routes/Scheduling/SchedulingUnitList.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/routes/Scheduling/SchedulingUnitList.js @@ -17,6 +17,7 @@ import { appGrowl } from '../../layout/components/AppGrowl'; import UtilService from '../../services/util.service'; import { Dropdown } from 'primereact/dropdown'; import SUBCreator from './sub.create'; +import AuthUtil from '../../utils/auth.util'; class SchedulingUnitList extends Component{ @@ -158,6 +159,7 @@ class SchedulingUnitList extends Component{ //defaultSortColumn: [{id: "Name", desc: false}], dialog: {header: 'Confirm', detail: 'Do you want to create a Scheduling Unit Blueprint?'} } + this.access_dined_message = "Don't have permission"; this.lsKeySortColumn = "SchedulingUnit_"+this.state.suType+"_SortData"; this.selectedRows = []; this.suDraftsList = []; // List of selected SU Drafts @@ -586,7 +588,10 @@ class SchedulingUnitList extends Component{ this.getFilterColumns(this.state.suType.toLowerCase()); } - componentDidMount(){ + async componentDidMount(){ + const permission = await AuthUtil.getUserRolePermission(); + this.setState({userrole: permission}); + //this.getUserRolePermission(); this.pageUpdated = true; this.getSchedulingUnitList(true, this.state.suType, this.filterQry, this.orderBy, this.limit, this.offset); this.setToggleBySorting(); @@ -1375,6 +1380,14 @@ class SchedulingUnitList extends Component{ return result; } + /** + * Get current user role permission for Schedule Unit List + */ + // getUserRolePermission = async() => { + // await userrole.dispatch({ type: 'scheduleunit' }); + // this.setState({userrole: userrole.getState()}); + // } + render(){ if (this.state.isLoading) { return <AppLoader/> @@ -1417,45 +1430,52 @@ class SchedulingUnitList extends Component{ {this.state.suType === 'Draft' && <> <a href="#" style={{marginLeft: "5px"}} onClick={this.checkAndCreateSUB} - title="Create Blueprint(s)"> - <i class="fa fa-stamp" aria-hidden="true" ></i> + title={this.state.userrole && this.state.userrole.userRolePermission.scheduleunit && this.state.userrole.userRolePermission.scheduleunit.createsub ? "Create Blueprint(s)":this.access_dined_message} > + <i class= {this.state.userrole && this.state.userrole.userRolePermission.scheduleunit && this.state.userrole.userRolePermission.scheduleunit.createsub ?"fa fa-stamp":"fa fa-disabled fa-stamp"} + aria-hidden="true" ></i> </a> </> } {this.state.suType === 'Blueprint' && <> - <a href="#" style={{marginLeft: "5px"}} onClick={this.cleanUpSUTask} - title="Create Clean-up Task(s)"> - <i class="fa fa-recycle" aria-hidden="true" ></i> + <a href="#" style={{marginLeft: "5px"}} onClick={this.cleanUpSUTask} + title={this.state.userrole && this.state.userrole.userRolePermission.scheduleunit && this.state.userrole.userRolePermission.scheduleunit.cleanuptask ? "Create Clean-up Task(s)":this.access_dined_message} > + <i class={this.state.userrole && this.state.userrole.userRolePermission.scheduleunit && this.state.userrole.userRolePermission.scheduleunit.cleanuptask?"fa fa-recycle":"fa fa-disabled fa-recycle"} + aria-hidden="true" ></i> </a> - <a href="#" style={{marginLeft: "5px"}} onClick={this.confirmCancelSchedulingUnit} - title="Cancel selected Scheduling Unit(s)"> - <i class="fa fa-ban" aria-hidden="true" ></i> + <a href="#" style={{marginLeft: "5px"}} onClick={this.confirmCancelSchedulingUnit} + title={this.state.userrole && this.state.userrole.userRolePermission.scheduleunit && this.state.userrole.userRolePermission.scheduleunit.cancelsu ? "Cancel selected Scheduling Unit(s)":this.access_dined_message} > + <i class={this.state.userrole && this.state.userrole.userRolePermission.scheduleunit && this.state.userrole.userRolePermission.scheduleunit.cancelsu?"fa fa-ban":"fa fa-disabled fa-ban"} + aria-hidden="true" ></i> </a> </> } <a href="#" style={{marginLeft: "5px"}} onClick={this.confirmAutoDeletion} - title="Prevent/Allow Automatic Deletion"> - <i class="fa fa-thumbtack" aria-hidden="true" ></i> + title={this.state.userrole && this.state.userrole.userRolePermission.scheduleunit && this.state.userrole.userRolePermission.scheduleunit.autodeletion ? "Prevent/Allow Automatic Deletion":this.access_dined_message} > + <i class={this.state.userrole && this.state.userrole.userRolePermission.scheduleunit && this.state.userrole.userRolePermission.scheduleunit.autodeletion?"fa fa-thumbtack":"fa fa-disabled fa-thumbtack"} + aria-hidden="true" ></i> </a> - <a href="#" style={{marginLeft: "5px"}} onClick={this.confirmCopyingSU} - title="Copy Scheduling Unit(s) Draft/Blueprint" > - <i class="fa fa-copy"></i> + <a href="#" style={{marginLeft: "5px"}} onClick={this.confirmCopyingSU} + title={this.state.userrole && this.state.userrole.userRolePermission.scheduleunit && this.state.userrole.userRolePermission.scheduleunit.copysu ? "Copy Scheduling Unit(s) Draft/Blueprint":this.access_dined_message} > + <i class={this.state.userrole && this.state.userrole.userRolePermission.scheduleunit && this.state.userrole.userRolePermission.scheduleunit.copysu?"fa fa-copy":"fa fa-disabled fa-copy"} ></i> </a> - <a href="#" style={{marginLeft: "5px"}} onClick={this.checkAndDeleteSchedulingUnit} - title="Delete selected Scheduling Unit(s)"> - <i class="fa fa-trash" aria-hidden="true" ></i> + <a href="#" style={{marginLeft: "5px"}} onClick={this.checkAndDeleteSchedulingUnit} + title={this.state.userrole && this.state.userrole.userRolePermission.scheduleunit && this.state.userrole.userRolePermission.scheduleunit.delete ? "Delete selected Scheduling Unit(s)":this.access_dined_message} > + <i class={this.state.userrole && this.state.userrole.userRolePermission.scheduleunit && this.state.userrole.userRolePermission.scheduleunit.delete?"fa fa-trash":"fa fa-disabled fa fa-trash"} + aria-hidden="true" ></i> </a> </> } {this.props.project && <> <Link style={{marginLeft: "5px"}} to={`${this.props.project?"/project/"+this.props.project:""}/schedulingunit/create`} - title="Add New Scheduling Unit to this Project" > - <i class="fa fa-plus"></i></Link> + title={this.state.userrole && this.state.userrole.userRolePermission.scheduleunit && this.state.userrole.userRolePermission.scheduleunit.create ? "Add New Scheduling Unit to this Project":this.access_dined_message} > + <i class={this.state.userrole && this.state.userrole.userRolePermission.scheduleunit && this.state.userrole.userRolePermission.scheduleunit.create?"fa fa-plus":"fa fa-disabled fa fa-plus"} + ></i></Link> <Link style={{marginLeft: "5px"}} to={`${this.props.project?"/project/"+this.props.project:""}/schedulingset/schedulingunit/create`} - title="Add Scheduling Set to this Project" > - <i class="fa fa-table"></i></Link> + title={this.state.userrole && this.state.userrole.userRolePermission.scheduleunit && this.state.userrole.userRolePermission.scheduleunit.excelview ? "Add Scheduling Set to this Project":this.access_dined_message} > + <i class={this.state.userrole && this.state.userrole.userRolePermission.scheduleunit && this.state.userrole.userRolePermission.scheduleunit.excelview?"fa fa-table":"fa fa-disabled fa fa-table"} + ></i></Link> </> } </> diff --git a/SAS/TMSS/frontend/tmss_webapp/src/routes/Scheduling/ViewSchedulingUnit.js b/SAS/TMSS/frontend/tmss_webapp/src/routes/Scheduling/ViewSchedulingUnit.js index 6da2f11b50910b2f79a9b57ac7c67569dacc12cb..e96df454791789f150958828f4d145de3079be83 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/routes/Scheduling/ViewSchedulingUnit.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/routes/Scheduling/ViewSchedulingUnit.js @@ -26,6 +26,7 @@ import TaskService from '../../services/task.service'; import UIConstants from '../../utils/ui.constants'; import UtilService from '../../services/util.service'; import { flattenDiagnosticMessageText } from 'typescript'; +import AuthUtil from '../../utils/auth.util'; class ViewSchedulingUnit extends Component { lsKeySortColumn = 'SortDataViewSchedulingUnit'; @@ -154,6 +155,7 @@ class ViewSchedulingUnit extends Component { dataformat: ['MeasurementSet'], taskStatus: [] } + this.access_dined_message = "Don't have permission"; this.actions = []; this.stations = []; this.constraintTemplates = []; @@ -212,6 +214,9 @@ class ViewSchedulingUnit extends Component { } async componentDidMount() { + const permission = await AuthUtil.getUserRolePermission(); + this.setState({userrole: permission}); + //this.getUserRolePermission(); this.pageUpdated = true; this.setToggleBySorting(); let schedule_id = this.props.match.params.id; @@ -334,54 +339,79 @@ class ViewSchedulingUnit extends Component { getActionMenu(schedule_type, isIngestPresent) { this.actions = []; this.actions.unshift({ - icon: 'fa-copy', title: 'Copy Draft/Blueprint', type: 'button', + icon: 'fa-copy', + title: this.state.userrole && this.state.userrole.userRolePermission.scheduleunit && this.state.userrole.userRolePermission.scheduleunit.copysu?'Copy Draft/Blueprint':this.access_dined_message, + type: 'button', + disabled: this.state.userrole && this.state.userrole.userRolePermission.scheduleunit?!this.state.userrole.userRolePermission.scheduleunit.copysu:true, actOn: 'click', props: { callback: this.confirmCopyingSU }, }); let canDelete = (this.state.scheduleunit && (!this.state.scheduleunit.scheduling_unit_blueprints_ids || this.state.scheduleunit.scheduling_unit_blueprints_ids.length === 0)); this.actions.push({ - icon: 'fa fa-trash', title: !canDelete ? 'Cannot delete Draft when Blueprint exists' : 'Scheduling Unit', - type: 'button', disabled: !canDelete, actOn: 'click', props: { callback: this.showDeleteSUConfirmation } + icon: 'fa fa-trash', + title: this.state.userrole && this.state.userrole.userRolePermission.scheduleunit && this.state.userrole.userRolePermission.scheduleunit.delete?!canDelete ? 'Cannot delete Draft when Blueprint exists' : 'Delete Scheduling Unit':this.access_dined_message, + disabled: this.state.userrole && this.state.userrole.userRolePermission.scheduleunit && this.state.userrole.userRolePermission.scheduleunit.delete?canDelete?false:true : true, + type: 'button', actOn: 'click', props: { callback: this.showDeleteSUConfirmation } }); this.actions.push({ icon: 'fa-window-close', title: 'Click to Close Scheduling Unit View', link: this.props.history.goBack }); if (this.props.match.params.type ==='draft') { let blueprintExist = this.state.scheduleunit && this.state.scheduleunit.scheduling_unit_blueprints && this.state.scheduleunit.scheduling_unit_blueprints.length>0; if(isIngestPresent) { this.actions.unshift({ - icon: 'fa-file-import', title: 'Data Products To Ingest', type: 'button', + icon: 'fa-file-import', + title: this.state.userrole && this.state.userrole.userRolePermission.scheduleunit && this.state.userrole.userRolePermission.scheduleunit.dataproduct?'Data Products To Ingest':this.access_dined_message, + type: 'button', + disabled: this.state.userrole && this.state.userrole.userRolePermission.scheduleunit?!this.state.userrole.userRolePermission.scheduleunit.dataproduct:true, actOn: 'click', props: { callback: this.showTaskRelationDialog } }); } this.actions.unshift({ - icon: 'fa-edit', title: 'Click to edit', props: { pathname: `/schedulingunit/edit/${this.props.match.params.id}` } + icon: 'fa-edit', + title: this.state.userrole && this.state.userrole.userRolePermission.scheduleunit && this.state.userrole.userRolePermission.scheduleunit.edit?'Click to edit':this.access_dined_message, + disabled: this.state.userrole && this.state.userrole.userRolePermission.scheduleunit?!this.state.userrole.userRolePermission.scheduleunit.edit:true, + props: { pathname: `/schedulingunit/edit/${this.props.match.params.id}` } }); this.actions.unshift({ - icon: 'fa-stamp', title: blueprintExist?'Blueprint already exists': 'Create Blueprint', type: 'button', + icon: 'fa-stamp', + title: this.state.userrole && this.state.userrole.userRolePermission.scheduleunit && this.state.userrole.userRolePermission.scheduleunit.createsub?blueprintExist?'Blueprint already exists': 'Create Blueprint':this.access_dined_message, + type: 'button', + disabled: this.state.userrole && this.state.userrole.userRolePermission.scheduleunit?!this.state.userrole.userRolePermission.scheduleunit.createsub:true, actOn: 'click', disabled: blueprintExist, props: { callback: this.checkAndCreateBlueprint }, }); } else { this.actions.unshift({ - icon: 'fa-thumbtack', title: this.state.scheduleunit.output_pinned? 'Allow Automatic Deletion' : 'Prevent Automatic Deletion', + icon: 'fa-thumbtack', + title: this.state.userrole && this.state.userrole.userRolePermission.scheduleunit && this.state.userrole.userRolePermission.scheduleunit.autodeletion?this.state.scheduleunit.output_pinned? 'Allow Automatic Deletion' : 'Prevent Automatic Deletion':this.access_dined_message, + disabled: this.state.userrole && this.state.userrole.userRolePermission.scheduleunit?!this.state.userrole.userRolePermission.scheduleunit.autodeletion:true, type: 'button', actOn: 'click', props: { callback: this.confirmAutoDeletion } }); if(isIngestPresent) { this.actions.unshift({ - icon: 'fa-file-import', title: 'Data Products To Ingest', type: 'button', - disabled: !(this.state.scheduleunit.status === 'defined' || this.state.scheduleunit.status === 'scheduled' || this.state.scheduleunit.status === 'schedulable'), + icon: 'fa-file-import', + title: this.state.userrole && this.state.userrole.userRolePermission.scheduleunit && this.state.userrole.userRolePermission.scheduleunit.dataproduct?'Data Products To Ingest':this.access_dined_message, + type: 'button', + disabled: this.state.userrole && this.state.userrole.userRolePermission.scheduleunit?!this.state.userrole.userRolePermission.scheduleunit.dataproduct:!(this.state.scheduleunit.status === 'defined' || this.state.scheduleunit.status === 'scheduled' || this.state.scheduleunit.status === 'schedulable'), actOn: 'click', props: { callback: this.showTaskRelationDialog } }); } this.actions.unshift({ - icon: 'fa-ban', type: 'button', actOn: 'click', - title: this.SU_END_STATUSES.indexOf(this.state.scheduleunit.status.toLowerCase())>=0?'Cannot Cancel Scheduling Unit':'Cancel Scheduling Unit', - disabled:this.SU_END_STATUSES.indexOf(this.state.scheduleunit.status.toLowerCase())>=0, + icon: 'fa-ban', type: 'button', actOn: 'click', + title: this.state.userrole && this.state.userrole.userRolePermission.scheduleunit && this.state.userrole.userRolePermission.scheduleunit.cancelsu?this.SU_END_STATUSES.indexOf(this.state.scheduleunit.status.toLowerCase())>=0?'Cannot Cancel Scheduling Unit':'Cancel Scheduling Unit':this.access_dined_message, + disabled:this.state.userrole && this.state.userrole.userRolePermission.scheduleunit && this.state.userrole.userRolePermission.scheduleunit.cancelsu? this.SU_END_STATUSES.indexOf(this.state.scheduleunit.status.toLowerCase())>=0 ? true : false:true, props: { callback: this.showCancelSUConfirmation } }); this.actions.unshift({ - icon: 'fa fa-recycle', title: 'Create Clean-up Task', type: 'button', + icon: 'fa fa-recycle', + title: this.state.userrole && this.state.userrole.userRolePermission.scheduleunit && this.state.userrole.userRolePermission.scheduleunit.cleanuptask?'Create Clean-up Task':this.access_dined_message, + type: 'button', + disabled: this.state.userrole && this.state.userrole.userRolePermission.scheduleunit?!this.state.userrole.userRolePermission.scheduleunit.cleanuptask:true, actOn: 'click', props: { callback: this.cleanUpSUTask } }); - this.actions.unshift({ icon: 'fa-sitemap', title: 'View Workflow', props: { pathname: `/schedulingunit/${this.props.match.params.id}/workflow` } }); + this.actions.unshift({ + icon: 'fa-sitemap', + title: this.state.userrole && this.state.userrole.userRolePermission.scheduleunit && this.state.userrole.userRolePermission.scheduleunit.viewworkflow?'View Workflow':this.access_dined_message, + disabled: this.state.userrole && this.state.userrole.userRolePermission.scheduleunit?!this.state.userrole.userRolePermission.scheduleunit.viewworkflow:true, + props: { pathname: `/schedulingunit/${this.props.match.params.id}/workflow` } }); this.actions.unshift({ icon: 'fa-lock', title: 'Cannot edit blueprint' }); } this.setState({ actions: this.actions }); @@ -1223,6 +1253,14 @@ class ViewSchedulingUnit extends Component { } } + /** + * Get current user role permission for selected Schedule Unit + */ + // getUserRolePermission = async () => { + // await userrole.dispatch({ type: 'scheduleunit' }); + // this.setState({userrole: userrole.getState()}); + // } + render() { if (this.state.redirect) { return <Redirect to={{ pathname: this.state.redirect }}></Redirect> diff --git a/SAS/TMSS/frontend/tmss_webapp/src/routes/Scheduling/index.js b/SAS/TMSS/frontend/tmss_webapp/src/routes/Scheduling/index.js index 6f8331916f38a96bfeba11954acadd16c014a59a..ccb826e083dfa7370d2b1f7630410c64e6ad1346 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/routes/Scheduling/index.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/routes/Scheduling/index.js @@ -6,6 +6,8 @@ import SchedulingUnitList from './SchedulingUnitList'; import PageHeader from '../../layout/components/PageHeader'; import { appGrowl } from '../../layout/components/AppGrowl'; import { CustomDialog } from '../../layout/components/CustomDialog'; +import AuthUtil from '../../utils/auth.util'; + export class Scheduling extends Component { constructor(props){ super(props); @@ -17,6 +19,7 @@ export class Scheduling extends Component { dialog: {header: 'Confirm', detail: 'Do you want to create blueprints for the selected drafts?'}, dialogVisible: false }; + this.access_dined_message = "Don't have permission"; this.optionsMenu = React.createRef(); this.menuOptions = [ {label:'Add Scheduling Set', icon: "fa fa-", command: () => {this.selectOptionMenu('Add-SU-Set') }}]; this.showOptionMenu = this.showOptionMenu.bind(this); @@ -24,6 +27,12 @@ export class Scheduling extends Component { this.closeDialog = this.closeDialog.bind(this); } + async componentDidMount() { + const permission = await AuthUtil.getUserRolePermission(); + this.setState({userrole: permission}); + //this.getUserRolePermission() + } + /** * Callback function to close the dialog prompted. */ @@ -47,16 +56,29 @@ export class Scheduling extends Component { } } + /** + * Get current user role permission for Schedule Unit List + */ + + // getUserRolePermission = async() => { + // await userrole.dispatch({ type: 'scheduleunit' }); + // this.setState({userrole: userrole.getState()}); + // } + render() { return ( <> <TieredMenu className="app-header-menu" model={this.menuOptions} popup ref={el => this.optionsMenu = el} /> <PageHeader location={this.props.location} title={'Scheduling Unit - List'} actions={[ - {icon: 'fa fa-plus-square', title: 'Add New Scheduling Unit', + {icon: 'fa fa-plus-square', + title: this.state.userrole && this.state.userrole.userRolePermission.scheduleunit && this.state.userrole.userRolePermission.scheduleunit.create?'Add New Scheduling Unit':this.access_dined_message, + disabled: this.state.userrole && this.state.userrole.userRolePermission.scheduleunit?!this.state.userrole.userRolePermission.scheduleunit.create:true, props: {pathname: '/schedulingunit/create'}}, - {icon: 'fa fa-table', title: 'Add Scheduling Set', + {icon: 'fa fa-table', + title: this.state.userrole && this.state.userrole.userRolePermission.scheduleunit && this.state.userrole.userRolePermission.scheduleunit.excelview?'Add Scheduling Set':this.access_dined_message, + disabled: this.state.userrole && this.state.userrole.userRolePermission.scheduleunit?!this.state.userrole.userRolePermission.scheduleunit.excelview:true, props: {pathname: '/schedulingset/schedulingunit/create'}}, ]} /> {this.state.scheduleunit && diff --git a/SAS/TMSS/frontend/tmss_webapp/src/routes/index.js b/SAS/TMSS/frontend/tmss_webapp/src/routes/index.js index 159437e8f729f921504060d2b929ba70f38eeae1..78981fdaa8b786502c517ce008a61dc320350a5b 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/routes/index.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/routes/index.js @@ -6,6 +6,7 @@ import { } from 'react-router-dom'; import {NotFound} from '../layout/components/NotFound'; +import AccessDenied from '../layout/components/AccessDenied'; import {ProjectList, ProjectCreate, ProjectView, ProjectEdit} from './Project'; import {Dashboard} from './Dashboard'; import {Scheduling} from './Scheduling'; @@ -29,6 +30,9 @@ export const routes = [ { path: "/not-found", component: NotFound, + },{ + path: "/access-denied", + component: AccessDenied },{ path: "/dashboard", component: Dashboard, diff --git a/SAS/TMSS/frontend/tmss_webapp/src/services/auth.service.js b/SAS/TMSS/frontend/tmss_webapp/src/services/auth.service.js index 34d4ca35b3dcc528b5f33dd3994fceabc651b239..43c7afbbbb80fc29997a1158465e8dbe4d5d1e5a 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/services/auth.service.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/services/auth.service.js @@ -27,7 +27,16 @@ const AuthService = { } catch(error) { console.error(error); } + }, + getAccessControlMethod: async(subdomain) => { + let res = null; + try { + const response = await axios.get(`/api/${subdomain}/?fields=name&limit=1`); + res = response.headers['access-control-allow-methods']; + } catch(error) { + console.error('[AuthService.getAccessControlMethod]',error); + } + return res; } } - export default AuthService; \ No newline at end of file diff --git a/SAS/TMSS/frontend/tmss_webapp/src/services/report.service.js b/SAS/TMSS/frontend/tmss_webapp/src/services/report.service.js index 9acc0b0ad6e402ff5ab74cfa81b2975b05c05680..c254a0a24232b3450ad7f710f31c2f964ff3a405 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/services/report.service.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/services/report.service.js @@ -214,19 +214,107 @@ const ReportService = { }, "data_ingested_per_site_and_category": { "Interferometric Observation": { - "dataproducts": [] + "dataproducts": [1,2,3,4,6,7,8,8] }, "Beamformed Observation": { - "dataproducts": [] + "dataproducts": [2,3,4] }, "Preprocessing Pipeline": { - "dataproducts": [] + "dataproducts": [1,2,3,4,5,6,7,10,12,13,45] }, "Pulsar Pipeline": { - "dataproducts": [] + "dataproducts": [1,1,1,1,1,2] } }, - "projects_summary": [], + "projects_summary": [ + { + "project": "high", + "quota": [ + { + "id": 1, + "resource_type_id": "LTA Storage", + "value": 1000000000000 + } + ], + "SUBs": { + "finished": [], + "failed": [] + }, + "durations": { + "total": 12720, + "total_succeeded": 0, + "total_not_cancelled": 12720, + "total_failed": 0 + }, + "LTA dataproducts": { + "size__sum": null + }, + "SAPs": [ + { + "sap_name": "placeholder", + "total_exposure": 0 + } + ] + }, + { + "project": "normal", + "quota": [ + { + "id": 2, + "resource_type_id": "LTA Storage", + "value": 1000000000000 + } + ], + "SUBs": { + "finished": [], + "failed": [] + }, + "durations": { + "total": 0, + "total_succeeded": 0, + "total_not_cancelled": 0, + "total_failed": 0 + }, + "LTA dataproducts": { + "size__sum": null + }, + "SAPs": [ + { + "sap_name": "placeholder", + "total_exposure": 0 + } + ] + }, + { + "project": "low", + "quota": [ + { + "id": 3, + "resource_type_id": "LTA Storage", + "value": 1000000000000 + } + ], + "SUBs": { + "finished": [], + "failed": [] + }, + "durations": { + "total": 0, + "total_succeeded": 0, + "total_not_cancelled": 0, + "total_failed": 0 + }, + "LTA dataproducts": { + "size__sum": null + }, + "SAPs": [ + { + "sap_name": "placeholder", + "total_exposure": 0 + } + ] + } + ], "usage_mode": { "all modes": { "total": null, @@ -433,19 +521,107 @@ const ReportService = { }, "data_ingested_per_site_and_category": { "Interferometric Observation": { - "dataproducts": [] + "dataproducts": [1,2,3,4] }, "Beamformed Observation": { - "dataproducts": [] + "dataproducts": [2,3,4] }, "Preprocessing Pipeline": { - "dataproducts": [] + "dataproducts": [1,2,3,4,5,6,7] }, "Pulsar Pipeline": { - "dataproducts": [] + "dataproducts": [1,1,1,1] } }, - "projects_summary": [], + "projects_summary": [ + { + "project": "high", + "quota": [ + { + "id": 1, + "resource_type_id": "LTA Storage", + "value": 1000000000000 + } + ], + "SUBs": { + "finished": [], + "failed": [] + }, + "durations": { + "total": 12720, + "total_succeeded": 0, + "total_not_cancelled": 12720, + "total_failed": 0 + }, + "LTA dataproducts": { + "size__sum": null + }, + "SAPs": [ + { + "sap_name": "placeholder", + "total_exposure": 0 + } + ] + }, + { + "project": "normal", + "quota": [ + { + "id": 2, + "resource_type_id": "LTA Storage", + "value": 1000000000000 + } + ], + "SUBs": { + "finished": [], + "failed": [] + }, + "durations": { + "total": 0, + "total_succeeded": 0, + "total_not_cancelled": 0, + "total_failed": 0 + }, + "LTA dataproducts": { + "size__sum": null + }, + "SAPs": [ + { + "sap_name": "placeholder", + "total_exposure": 0 + } + ] + }, + { + "project": "low", + "quota": [ + { + "id": 3, + "resource_type_id": "LTA Storage", + "value": 1000000000000 + } + ], + "SUBs": { + "finished": [], + "failed": [] + }, + "durations": { + "total": 0, + "total_succeeded": 0, + "total_not_cancelled": 0, + "total_failed": 0 + }, + "LTA dataproducts": { + "size__sum": null + }, + "SAPs": [ + { + "sap_name": "placeholder", + "total_exposure": 0 + } + ] + } + ], "usage_mode": { "all modes": { "total": null, @@ -652,16 +828,16 @@ const ReportService = { }, "data_ingested_per_site_and_category": { "Interferometric Observation": { - "dataproducts": [] + "dataproducts": [1,3,4] }, "Beamformed Observation": { - "dataproducts": [] + "dataproducts": [2,2,3,4] }, "Preprocessing Pipeline": { - "dataproducts": [] + "dataproducts": [1,4,5,6,7] }, "Pulsar Pipeline": { - "dataproducts": [] + "dataproducts": [1,1] } }, "projects_summary": [ @@ -959,19 +1135,107 @@ const ReportService = { }, "data_ingested_per_site_and_category": { "Interferometric Observation": { - "dataproducts": [1,2,3,4] + "dataproducts": [1,2,3,4,5] }, "Beamformed Observation": { - "dataproducts": [2,3,4] + "dataproducts": [2,3,4,6,7] }, "Preprocessing Pipeline": { - "dataproducts": [1,2,3,4,5,6,7] + "dataproducts": [1,2,3] }, "Pulsar Pipeline": { - "dataproducts": [1,1,1,1] + "dataproducts": [1,1,1,1,2,3,1] } }, - "projects_summary": [], + "projects_summary": [ + { + "project": "high", + "quota": [ + { + "id": 1, + "resource_type_id": "LTA Storage", + "value": 1000000000000 + } + ], + "SUBs": { + "finished": [], + "failed": [] + }, + "durations": { + "total": 12720, + "total_succeeded": 0, + "total_not_cancelled": 12720, + "total_failed": 0 + }, + "LTA dataproducts": { + "size__sum": null + }, + "SAPs": [ + { + "sap_name": "placeholder", + "total_exposure": 0 + } + ] + }, + { + "project": "normal", + "quota": [ + { + "id": 2, + "resource_type_id": "LTA Storage", + "value": 1000000000000 + } + ], + "SUBs": { + "finished": [], + "failed": [] + }, + "durations": { + "total": 0, + "total_succeeded": 0, + "total_not_cancelled": 0, + "total_failed": 0 + }, + "LTA dataproducts": { + "size__sum": null + }, + "SAPs": [ + { + "sap_name": "placeholder", + "total_exposure": 0 + } + ] + }, + { + "project": "low", + "quota": [ + { + "id": 3, + "resource_type_id": "LTA Storage", + "value": 1000000000000 + } + ], + "SUBs": { + "finished": [], + "failed": [] + }, + "durations": { + "total": 0, + "total_succeeded": 0, + "total_not_cancelled": 0, + "total_failed": 0 + }, + "LTA dataproducts": { + "size__sum": null + }, + "SAPs": [ + { + "sap_name": "placeholder", + "total_exposure": 0 + } + ] + } + ], "usage_mode": { "all modes": { "total": null, diff --git a/SAS/TMSS/frontend/tmss_webapp/src/utils/auth.util.js b/SAS/TMSS/frontend/tmss_webapp/src/utils/auth.util.js new file mode 100644 index 0000000000000000000000000000000000000000..c982947e762b9d1cf21e6fb26cd3bd9424277abe --- /dev/null +++ b/SAS/TMSS/frontend/tmss_webapp/src/utils/auth.util.js @@ -0,0 +1,15 @@ +import AuthStore from "../authenticate/auth.store"; + +const AuthUtil = { + getUserRolePermission : async function(module) { + //await AuthStore.dispatch({ type: module }); + return AuthStore.getState(); + }, + getUserModulePermission: (module_ => { + const allPermissions = AuthStore.getState(); + const modulePermissions = allPermissions.userRolePermission[module]; + return modulePermissions?modulePermissions:{}; + }) +}; + +export default AuthUtil; \ No newline at end of file