diff --git a/addon/flamenco/manager/__init__.py b/addon/flamenco/manager/__init__.py index ac5c0287..6a975b3e 100644 --- a/addon/flamenco/manager/__init__.py +++ b/addon/flamenco/manager/__init__.py @@ -10,7 +10,7 @@ """ -__version__ = "781f1d93-dirty" +__version__ = "c875745b-dirty" # import ApiClient from flamenco.manager.api_client import ApiClient diff --git a/addon/flamenco/manager/api_client.py b/addon/flamenco/manager/api_client.py index 0ae975ca..010385fe 100644 --- a/addon/flamenco/manager/api_client.py +++ b/addon/flamenco/manager/api_client.py @@ -76,7 +76,7 @@ class ApiClient(object): self.default_headers[header_name] = header_value self.cookie = cookie # Set default User-Agent. - self.user_agent = 'Flamenco/781f1d93-dirty (Blender add-on)' + self.user_agent = 'Flamenco/c875745b-dirty (Blender add-on)' def __enter__(self): return self diff --git a/addon/flamenco/manager/configuration.py b/addon/flamenco/manager/configuration.py index 2521393c..a5552b6b 100644 --- a/addon/flamenco/manager/configuration.py +++ b/addon/flamenco/manager/configuration.py @@ -404,7 +404,7 @@ conf = flamenco.manager.Configuration( "OS: {env}\n"\ "Python Version: {pyversion}\n"\ "Version of the API: 1.0.0\n"\ - "SDK Package Version: 781f1d93-dirty".\ + "SDK Package Version: c875745b-dirty".\ format(env=sys.platform, pyversion=sys.version) def get_host_settings(self): diff --git a/addon/flamenco/manager/docs/JobUpdate.md b/addon/flamenco/manager/docs/JobUpdate.md new file mode 100644 index 00000000..4f7efe54 --- /dev/null +++ b/addon/flamenco/manager/docs/JobUpdate.md @@ -0,0 +1,15 @@ +# JobUpdate + + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**id** | **str** | UUID of the Job | +**updated** | **datetime** | Timestamp of last update | +**status** | [**JobStatus**](JobStatus.md) | | +**previous_status** | [**JobStatus**](JobStatus.md) | | [optional] +**any string name** | **bool, date, datetime, dict, float, int, list, str, none_type** | any string name can be used but the value must be the correct type | [optional] + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + + diff --git a/addon/flamenco/manager/model/job_update.py b/addon/flamenco/manager/model/job_update.py new file mode 100644 index 00000000..0c67df2a --- /dev/null +++ b/addon/flamenco/manager/model/job_update.py @@ -0,0 +1,283 @@ +""" + Flamenco manager + + Render Farm manager API # noqa: E501 + + The version of the OpenAPI document: 1.0.0 + Generated by: https://openapi-generator.tech +""" + + +import re # noqa: F401 +import sys # noqa: F401 + +from flamenco.manager.model_utils import ( # noqa: F401 + ApiTypeError, + ModelComposed, + ModelNormal, + ModelSimple, + cached_property, + change_keys_js_to_python, + convert_js_args_to_python_args, + date, + datetime, + file_type, + none_type, + validate_get_composed_info, + OpenApiModel +) +from flamenco.manager.exceptions import ApiAttributeError + + +def lazy_import(): + from flamenco.manager.model.job_status import JobStatus + globals()['JobStatus'] = JobStatus + + +class JobUpdate(ModelNormal): + """NOTE: This class is auto generated by OpenAPI Generator. + Ref: https://openapi-generator.tech + + Do not edit the class manually. + + Attributes: + allowed_values (dict): The key is the tuple path to the attribute + and the for var_name this is (var_name,). The value is a dict + with a capitalized key describing the allowed value and an allowed + value. These dicts store the allowed enum values. + attribute_map (dict): The key is attribute name + and the value is json key in definition. + discriminator_value_class_map (dict): A dict to go from the discriminator + variable value to the discriminator class name. + validations (dict): The key is the tuple path to the attribute + and the for var_name this is (var_name,). The value is a dict + that stores validations for max_length, min_length, max_items, + min_items, exclusive_maximum, inclusive_maximum, exclusive_minimum, + inclusive_minimum, and regex. + additional_properties_type (tuple): A tuple of classes accepted + as additional properties values. + """ + + allowed_values = { + } + + validations = { + } + + @cached_property + def additional_properties_type(): + """ + This must be a method because a model may have properties that are + of type self, this must run after the class is loaded + """ + lazy_import() + return (bool, date, datetime, dict, float, int, list, str, none_type,) # noqa: E501 + + _nullable = False + + @cached_property + def openapi_types(): + """ + This must be a method because a model may have properties that are + of type self, this must run after the class is loaded + + Returns + openapi_types (dict): The key is attribute name + and the value is attribute type. + """ + lazy_import() + return { + 'id': (str,), # noqa: E501 + 'updated': (datetime,), # noqa: E501 + 'status': (JobStatus,), # noqa: E501 + 'previous_status': (JobStatus,), # noqa: E501 + } + + @cached_property + def discriminator(): + return None + + + attribute_map = { + 'id': 'id', # noqa: E501 + 'updated': 'updated', # noqa: E501 + 'status': 'status', # noqa: E501 + 'previous_status': 'previous_status', # noqa: E501 + } + + read_only_vars = { + } + + _composed_schemas = {} + + @classmethod + @convert_js_args_to_python_args + def _from_openapi_data(cls, id, updated, status, *args, **kwargs): # noqa: E501 + """JobUpdate - a model defined in OpenAPI + + Args: + id (str): UUID of the Job + updated (datetime): Timestamp of last update + status (JobStatus): + + Keyword Args: + _check_type (bool): if True, values for parameters in openapi_types + will be type checked and a TypeError will be + raised if the wrong type is input. + Defaults to True + _path_to_item (tuple/list): This is a list of keys or values to + drill down to the model in received_data + when deserializing a response + _spec_property_naming (bool): True if the variable names in the input data + are serialized names, as specified in the OpenAPI document. + False if the variable names in the input data + are pythonic names, e.g. snake case (default) + _configuration (Configuration): the instance to use when + deserializing a file_type parameter. + If passed, type conversion is attempted + If omitted no type conversion is done. + _visited_composed_classes (tuple): This stores a tuple of + classes that we have traveled through so that + if we see that class again we will not use its + discriminator again. + When traveling through a discriminator, the + composed schema that is + is traveled through is added to this set. + For example if Animal has a discriminator + petType and we pass in "Dog", and the class Dog + allOf includes Animal, we move through Animal + once using the discriminator, and pick Dog. + Then in Dog, we will make an instance of the + Animal class but this time we won't travel + through its discriminator because we passed in + _visited_composed_classes = (Animal,) + previous_status (JobStatus): [optional] # noqa: E501 + """ + + _check_type = kwargs.pop('_check_type', True) + _spec_property_naming = kwargs.pop('_spec_property_naming', False) + _path_to_item = kwargs.pop('_path_to_item', ()) + _configuration = kwargs.pop('_configuration', None) + _visited_composed_classes = kwargs.pop('_visited_composed_classes', ()) + + self = super(OpenApiModel, cls).__new__(cls) + + if args: + raise ApiTypeError( + "Invalid positional arguments=%s passed to %s. Remove those invalid positional arguments." % ( + args, + self.__class__.__name__, + ), + path_to_item=_path_to_item, + valid_classes=(self.__class__,), + ) + + self._data_store = {} + self._check_type = _check_type + self._spec_property_naming = _spec_property_naming + self._path_to_item = _path_to_item + self._configuration = _configuration + self._visited_composed_classes = _visited_composed_classes + (self.__class__,) + + self.id = id + self.updated = updated + self.status = status + for var_name, var_value in kwargs.items(): + if var_name not in self.attribute_map and \ + self._configuration is not None and \ + self._configuration.discard_unknown_keys and \ + self.additional_properties_type is None: + # discard variable. + continue + setattr(self, var_name, var_value) + return self + + required_properties = set([ + '_data_store', + '_check_type', + '_spec_property_naming', + '_path_to_item', + '_configuration', + '_visited_composed_classes', + ]) + + @convert_js_args_to_python_args + def __init__(self, id, updated, status, *args, **kwargs): # noqa: E501 + """JobUpdate - a model defined in OpenAPI + + Args: + id (str): UUID of the Job + updated (datetime): Timestamp of last update + status (JobStatus): + + Keyword Args: + _check_type (bool): if True, values for parameters in openapi_types + will be type checked and a TypeError will be + raised if the wrong type is input. + Defaults to True + _path_to_item (tuple/list): This is a list of keys or values to + drill down to the model in received_data + when deserializing a response + _spec_property_naming (bool): True if the variable names in the input data + are serialized names, as specified in the OpenAPI document. + False if the variable names in the input data + are pythonic names, e.g. snake case (default) + _configuration (Configuration): the instance to use when + deserializing a file_type parameter. + If passed, type conversion is attempted + If omitted no type conversion is done. + _visited_composed_classes (tuple): This stores a tuple of + classes that we have traveled through so that + if we see that class again we will not use its + discriminator again. + When traveling through a discriminator, the + composed schema that is + is traveled through is added to this set. + For example if Animal has a discriminator + petType and we pass in "Dog", and the class Dog + allOf includes Animal, we move through Animal + once using the discriminator, and pick Dog. + Then in Dog, we will make an instance of the + Animal class but this time we won't travel + through its discriminator because we passed in + _visited_composed_classes = (Animal,) + previous_status (JobStatus): [optional] # noqa: E501 + """ + + _check_type = kwargs.pop('_check_type', True) + _spec_property_naming = kwargs.pop('_spec_property_naming', False) + _path_to_item = kwargs.pop('_path_to_item', ()) + _configuration = kwargs.pop('_configuration', None) + _visited_composed_classes = kwargs.pop('_visited_composed_classes', ()) + + if args: + raise ApiTypeError( + "Invalid positional arguments=%s passed to %s. Remove those invalid positional arguments." % ( + args, + self.__class__.__name__, + ), + path_to_item=_path_to_item, + valid_classes=(self.__class__,), + ) + + self._data_store = {} + self._check_type = _check_type + self._spec_property_naming = _spec_property_naming + self._path_to_item = _path_to_item + self._configuration = _configuration + self._visited_composed_classes = _visited_composed_classes + (self.__class__,) + + self.id = id + self.updated = updated + self.status = status + for var_name, var_value in kwargs.items(): + if var_name not in self.attribute_map and \ + self._configuration is not None and \ + self._configuration.discard_unknown_keys and \ + self.additional_properties_type is None: + # discard variable. + continue + setattr(self, var_name, var_value) + if var_name in self.read_only_vars: + raise ApiAttributeError(f"`{var_name}` is a read-only attribute. Use `from_openapi_data` to instantiate " + f"class with read only attributes.") diff --git a/addon/flamenco/manager/models/__init__.py b/addon/flamenco/manager/models/__init__.py index 6c5a0e30..a77f7a09 100644 --- a/addon/flamenco/manager/models/__init__.py +++ b/addon/flamenco/manager/models/__init__.py @@ -23,6 +23,7 @@ from flamenco.manager.model.job_all_of import JobAllOf from flamenco.manager.model.job_metadata import JobMetadata from flamenco.manager.model.job_settings import JobSettings from flamenco.manager.model.job_status import JobStatus +from flamenco.manager.model.job_update import JobUpdate from flamenco.manager.model.jobs_query import JobsQuery from flamenco.manager.model.jobs_query_result import JobsQueryResult from flamenco.manager.model.manager_configuration import ManagerConfiguration diff --git a/addon/flamenco/manager_README.md b/addon/flamenco/manager_README.md index 99f7424c..87930192 100644 --- a/addon/flamenco/manager_README.md +++ b/addon/flamenco/manager_README.md @@ -4,7 +4,7 @@ Render Farm manager API The `flamenco.manager` package is automatically generated by the [OpenAPI Generator](https://openapi-generator.tech) project: - API version: 1.0.0 -- Package version: 781f1d93-dirty +- Package version: c875745b-dirty - Build package: org.openapitools.codegen.languages.PythonClientCodegen For more information, please visit [https://flamenco.io/](https://flamenco.io/) @@ -101,6 +101,7 @@ Class | Method | HTTP request | Description - [JobMetadata](flamenco/manager/docs/JobMetadata.md) - [JobSettings](flamenco/manager/docs/JobSettings.md) - [JobStatus](flamenco/manager/docs/JobStatus.md) + - [JobUpdate](flamenco/manager/docs/JobUpdate.md) - [JobsQuery](flamenco/manager/docs/JobsQuery.md) - [JobsQueryResult](flamenco/manager/docs/JobsQueryResult.md) - [ManagerConfiguration](flamenco/manager/docs/ManagerConfiguration.md) diff --git a/cmd/flamenco-manager/main.go b/cmd/flamenco-manager/main.go index 2e55c159..5baa3aec 100644 --- a/cmd/flamenco-manager/main.go +++ b/cmd/flamenco-manager/main.go @@ -94,8 +94,8 @@ func main() { // // go persist.PeriodicMaintenanceLoop(mainCtx) - flamenco := buildFlamencoAPI(configService, persist) webUpdater := webupdates.New() + flamenco := buildFlamencoAPI(configService, persist, webUpdater) e := buildWebService(flamenco, persist, ssdp, webUpdater) installSignalHandler(mainCtxCancel) @@ -132,14 +132,14 @@ func main() { log.Info().Msg("shutdown complete") } -func buildFlamencoAPI(configService *config.Service, persist *persistence.DB) api.ServerInterface { +func buildFlamencoAPI(configService *config.Service, persist *persistence.DB, webUpdater *webupdates.BiDirComms) api.ServerInterface { timeService := clock.New() compiler, err := job_compilers.Load(timeService) if err != nil { log.Fatal().Err(err).Msg("error loading job compilers") } logStorage := task_logs.NewStorage(configService.Get().TaskLogsPath) - taskStateMachine := task_state_machine.NewStateMachine(persist) + taskStateMachine := task_state_machine.NewStateMachine(persist, webUpdater) shamanServer := shaman.NewServer(configService.Get().Shaman, nil) flamenco := api_impl.NewFlamenco(compiler, persist, logStorage, configService, taskStateMachine, shamanServer) return flamenco diff --git a/internal/manager/api_impl/api_impl.go b/internal/manager/api_impl/api_impl.go index 8c8fdf04..c36c7b1b 100644 --- a/internal/manager/api_impl/api_impl.go +++ b/internal/manager/api_impl/api_impl.go @@ -11,6 +11,7 @@ import ( "git.blender.org/flamenco/internal/manager/job_compilers" "git.blender.org/flamenco/internal/manager/persistence" "git.blender.org/flamenco/internal/manager/task_state_machine" + "git.blender.org/flamenco/internal/manager/webupdates" "git.blender.org/flamenco/pkg/api" "git.blender.org/flamenco/pkg/shaman" "github.com/labstack/echo/v4" @@ -66,6 +67,14 @@ type TaskStateMachine interface { // TaskStateMachine should be a subset of task_state_machine.StateMachine. var _ TaskStateMachine = (*task_state_machine.StateMachine)(nil) +type ChangeBroadcaster interface { + // BroadcastNewJob sends a 'new job' notification to all SocketIO clients. + BroadcastNewJob(jobUpdate api.JobUpdate) +} + +// ChangeBroadcaster should be a subset of webupdates.BiDirComms. +var _ ChangeBroadcaster = (*webupdates.BiDirComms)(nil) + type JobCompiler interface { ListJobTypes() api.AvailableJobTypes Compile(ctx context.Context, job api.SubmittedJob) (*job_compilers.AuthoredJob, error) diff --git a/internal/manager/task_state_machine/mocks/interfaces_mock.gen.go b/internal/manager/task_state_machine/mocks/interfaces_mock.gen.go index def73e23..7e930865 100644 --- a/internal/manager/task_state_machine/mocks/interfaces_mock.gen.go +++ b/internal/manager/task_state_machine/mocks/interfaces_mock.gen.go @@ -1,5 +1,5 @@ // Code generated by MockGen. DO NOT EDIT. -// Source: git.blender.org/flamenco/internal/manager/task_state_machine (interfaces: PersistenceService) +// Source: git.blender.org/flamenco/internal/manager/task_state_machine (interfaces: PersistenceService,ChangeBroadcaster) // Package mocks is a generated GoMock package. package mocks @@ -122,3 +122,38 @@ func (mr *MockPersistenceServiceMockRecorder) UpdateJobsTaskStatusesConditional( mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateJobsTaskStatusesConditional", reflect.TypeOf((*MockPersistenceService)(nil).UpdateJobsTaskStatusesConditional), arg0, arg1, arg2, arg3, arg4) } + +// MockChangeBroadcaster is a mock of ChangeBroadcaster interface. +type MockChangeBroadcaster struct { + ctrl *gomock.Controller + recorder *MockChangeBroadcasterMockRecorder +} + +// MockChangeBroadcasterMockRecorder is the mock recorder for MockChangeBroadcaster. +type MockChangeBroadcasterMockRecorder struct { + mock *MockChangeBroadcaster +} + +// NewMockChangeBroadcaster creates a new mock instance. +func NewMockChangeBroadcaster(ctrl *gomock.Controller) *MockChangeBroadcaster { + mock := &MockChangeBroadcaster{ctrl: ctrl} + mock.recorder = &MockChangeBroadcasterMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockChangeBroadcaster) EXPECT() *MockChangeBroadcasterMockRecorder { + return m.recorder +} + +// BroadcastJobUpdate mocks base method. +func (m *MockChangeBroadcaster) BroadcastJobUpdate(arg0 api.JobUpdate) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "BroadcastJobUpdate", arg0) +} + +// BroadcastJobUpdate indicates an expected call of BroadcastJobUpdate. +func (mr *MockChangeBroadcasterMockRecorder) BroadcastJobUpdate(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BroadcastJobUpdate", reflect.TypeOf((*MockChangeBroadcaster)(nil).BroadcastJobUpdate), arg0) +} diff --git a/internal/manager/task_state_machine/task_state_machine.go b/internal/manager/task_state_machine/task_state_machine.go index 6f655e14..700e1754 100644 --- a/internal/manager/task_state_machine/task_state_machine.go +++ b/internal/manager/task_state_machine/task_state_machine.go @@ -10,6 +10,7 @@ import ( "github.com/rs/zerolog/log" "git.blender.org/flamenco/internal/manager/persistence" + "git.blender.org/flamenco/internal/manager/webupdates" "git.blender.org/flamenco/pkg/api" ) @@ -19,11 +20,12 @@ const taskFailJobPercentage = 10 // Integer from 0 to 100. // StateMachine handles task and job status changes. type StateMachine struct { - persist PersistenceService + persist PersistenceService + broadcaster ChangeBroadcaster } // Generate mock implementations of these interfaces. -//go:generate go run github.com/golang/mock/mockgen -destination mocks/interfaces_mock.gen.go -package mocks git.blender.org/flamenco/internal/manager/task_state_machine PersistenceService +//go:generate go run github.com/golang/mock/mockgen -destination mocks/interfaces_mock.gen.go -package mocks git.blender.org/flamenco/internal/manager/task_state_machine PersistenceService,ChangeBroadcaster type PersistenceService interface { SaveTask(ctx context.Context, task *persistence.Task) error @@ -45,9 +47,18 @@ type PersistenceService interface { // PersistenceService should be a subset of persistence.DB var _ PersistenceService = (*persistence.DB)(nil) -func NewStateMachine(persist PersistenceService) *StateMachine { +type ChangeBroadcaster interface { + // BroadcastJobUpdate sends the job update to clients. + BroadcastJobUpdate(jobUpdate api.JobUpdate) +} + +// ChangeBroadcaster should be a subset of webupdates.BiDirComms +var _ ChangeBroadcaster = (*webupdates.BiDirComms)(nil) + +func NewStateMachine(persist PersistenceService, broadcaster ChangeBroadcaster) *StateMachine { return &StateMachine{ - persist: persist, + persist: persist, + broadcaster: broadcaster, } } @@ -244,6 +255,15 @@ func (sm *StateMachine) JobStatusChange(ctx context.Context, job *persistence.Jo if err != nil { return fmt.Errorf("updating job's tasks after job status change: %w", err) } + + // Broadcast this change to the SocketIO clients. + jobUpdate := api.JobUpdate{ + Id: job.UUID, + Updated: job.UpdatedAt, + PreviousStatus: &oldJobStatus, + Status: job.Status, + } + sm.broadcaster.BroadcastJobUpdate(jobUpdate) } return nil diff --git a/internal/manager/task_state_machine/task_state_machine_test.go b/internal/manager/task_state_machine/task_state_machine_test.go index c38aa620..0fa99788 100644 --- a/internal/manager/task_state_machine/task_state_machine_test.go +++ b/internal/manager/task_state_machine/task_state_machine_test.go @@ -18,7 +18,8 @@ import ( ) type StateMachineMocks struct { - persist *mocks.MockPersistenceService + persist *mocks.MockPersistenceService + broadcaster *mocks.MockChangeBroadcaster } // In the comments below, "T" indicates the performed task status change, and @@ -32,6 +33,8 @@ func TestTaskStatusChangeQueuedToActive(t *testing.T) { task := taskWithStatus(api.JobStatusQueued, api.TaskStatusQueued) mocks.expectSaveTaskWithStatus(t, task, api.TaskStatusActive) mocks.expectSaveJobWithStatus(t, task.Job, api.JobStatusActive) + mocks.expectBroadcastJobChange(task.Job, api.JobStatusQueued, api.JobStatusActive) + assert.NoError(t, sm.TaskStatusChange(ctx, task, api.TaskStatusActive)) } @@ -81,6 +84,8 @@ func TestTaskStatusChangeActiveToCompleted(t *testing.T) { mocks.expectSaveTaskWithStatus(t, task3, api.TaskStatusCompleted) mocks.persist.EXPECT().CountTasksOfJobInStatus(ctx, task.Job, api.TaskStatusCompleted).Return(3, 3, nil) // 3 of 3 complete. mocks.expectSaveJobWithStatus(t, task.Job, api.JobStatusCompleted) + mocks.expectBroadcastJobChange(task.Job, api.JobStatusActive, api.JobStatusCompleted) + assert.NoError(t, sm.TaskStatusChange(ctx, task3, api.TaskStatusCompleted)) } @@ -93,6 +98,8 @@ func TestTaskStatusChangeQueuedToFailed(t *testing.T) { mocks.expectSaveTaskWithStatus(t, task, api.TaskStatusFailed) mocks.expectSaveJobWithStatus(t, task.Job, api.JobStatusActive) mocks.persist.EXPECT().CountTasksOfJobInStatus(ctx, task.Job, api.TaskStatusFailed).Return(1, 100, nil) // 1 out of 100 failed. + mocks.expectBroadcastJobChange(task.Job, api.JobStatusQueued, api.JobStatusActive) + assert.NoError(t, sm.TaskStatusChange(ctx, task, api.TaskStatusFailed)) } @@ -104,6 +111,8 @@ func TestTaskStatusChangeActiveToFailedFailJob(t *testing.T) { task := taskWithStatus(api.JobStatusActive, api.TaskStatusActive) mocks.expectSaveTaskWithStatus(t, task, api.TaskStatusFailed) mocks.expectSaveJobWithStatus(t, task.Job, api.JobStatusFailed) + mocks.expectBroadcastJobChange(task.Job, api.JobStatusActive, api.JobStatusFailed) + mocks.persist.EXPECT().CountTasksOfJobInStatus(ctx, task.Job, api.TaskStatusFailed).Return(10, 100, nil) // 10 out of 100 failed. // Expect failure of the job to trigger cancellation of remaining tasks. @@ -127,6 +136,8 @@ func TestTaskStatusChangeRequeueOnCompletedJob(t *testing.T) { task := taskWithStatus(api.JobStatusCompleted, api.TaskStatusCompleted) mocks.expectSaveTaskWithStatus(t, task, api.TaskStatusQueued) mocks.expectSaveJobWithStatus(t, task.Job, api.JobStatusRequeued) + mocks.expectBroadcastJobChange(task.Job, api.JobStatusCompleted, api.JobStatusRequeued) + mocks.expectBroadcastJobChange(task.Job, api.JobStatusRequeued, api.JobStatusQueued) // Expect queueing of the job to trigger queueing of all its tasks, if those tasks were all completed before. // 2 out of 3 completed, because one was just queued. @@ -156,6 +167,8 @@ func TestTaskStatusChangeCancelSingleTask(t *testing.T) { mocks.expectSaveTaskWithStatus(t, task2, api.TaskStatusCanceled) mocks.persist.EXPECT().JobHasTasksInStatus(ctx, job, api.TaskStatusCancelRequested).Return(false, nil) mocks.expectSaveJobWithStatus(t, job, api.JobStatusCanceled) + mocks.expectBroadcastJobChange(task.Job, api.JobStatusCancelRequested, api.JobStatusCanceled) + assert.NoError(t, sm.TaskStatusChange(ctx, task2, api.TaskStatusCanceled)) } @@ -196,6 +209,10 @@ func TestJobRequeueWithSomeCompletedTasks(t *testing.T) { "Queued because job transitioned status from \"active\" to \"requeued\"", ) mocks.expectSaveJobWithStatus(t, job, api.JobStatusQueued) + + mocks.expectBroadcastJobChange(job, api.JobStatusActive, api.JobStatusRequeued) + mocks.expectBroadcastJobChange(job, api.JobStatusRequeued, api.JobStatusQueued) + assert.NoError(t, sm.JobStatusChange(ctx, job, api.JobStatusRequeued)) } @@ -224,14 +241,18 @@ func TestJobRequeueWithAllCompletedTasks(t *testing.T) { Return(0, 3, nil). // By now all tasks are queued. After(call3) + mocks.expectBroadcastJobChange(job, api.JobStatusCompleted, api.JobStatusRequeued) + mocks.expectBroadcastJobChange(job, api.JobStatusRequeued, api.JobStatusQueued) + assert.NoError(t, sm.JobStatusChange(ctx, job, api.JobStatusRequeued)) } func mockedTaskStateMachine(mockCtrl *gomock.Controller) (*StateMachine, *StateMachineMocks) { mocks := StateMachineMocks{ - persist: mocks.NewMockPersistenceService(mockCtrl), + persist: mocks.NewMockPersistenceService(mockCtrl), + broadcaster: mocks.NewMockChangeBroadcaster(mockCtrl), } - sm := NewStateMachine(mocks.persist) + sm := NewStateMachine(mocks.persist, mocks.broadcaster) return sm, &mocks } @@ -261,6 +282,19 @@ func (m *StateMachineMocks) expectSaveJobWithStatus( }) } +func (m *StateMachineMocks) expectBroadcastJobChange( + job *persistence.Job, + fromStatus, toStatus api.JobStatus, +) *gomock.Call { + expectUpdate := api.JobUpdate{ + Id: job.UUID, + Updated: job.UpdatedAt, + PreviousStatus: &fromStatus, + Status: toStatus, + } + return m.broadcaster.EXPECT().BroadcastJobUpdate(expectUpdate) +} + /* taskWithStatus() creates a task of a certain status, with a job of a certain status. */ func taskWithStatus(jobStatus api.JobStatus, taskStatus api.TaskStatus) *persistence.Task { job := persistence.Job{ diff --git a/internal/manager/webupdates/chatrooms.go b/internal/manager/webupdates/chatrooms.go new file mode 100644 index 00000000..96dec1a5 --- /dev/null +++ b/internal/manager/webupdates/chatrooms.go @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +package webupdates + +type SocketIORoomName string + +const ( + // Predefined SocketIO rooms. + SocketIORoomChat SocketIORoomName = "Chat" // For chat messages. + SocketIORoomJobs SocketIORoomName = "Jobs" // For job updates. +) + +type SocketIOEventType string + +const ( + // Predefined SocketIO event types. + SIOEventChatMessageRcv SocketIOEventType = "/chat" // clients send messages here + SIOEventChatMessageSend SocketIOEventType = "/message" // messages are broadcasted here + SIOEventJobUpdate SocketIOEventType = "/jobs" // sends api.JobUpdate +) diff --git a/internal/manager/webupdates/job_updates.go b/internal/manager/webupdates/job_updates.go new file mode 100644 index 00000000..297c6488 --- /dev/null +++ b/internal/manager/webupdates/job_updates.go @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +package webupdates + +import ( + "github.com/rs/zerolog/log" + + "git.blender.org/flamenco/pkg/api" +) + +// BroadcastJobUpdate sends the job update to clients. +func (b *BiDirComms) BroadcastJobUpdate(jobUpdate api.JobUpdate) { + log.Debug().Interface("jobUpdate", jobUpdate).Msg("socketIO: broadcasting job update") + b.sockserv.BroadcastTo(string(SocketIORoomJobs), "/jobs", jobUpdate) +} + +// BroadcastNewJob sends a "new job" notification to clients. +func (b *BiDirComms) BroadcastNewJob(jobUpdate api.JobUpdate) { + if jobUpdate.PreviousStatus != nil { + log.Warn().Interface("jobUpdate", jobUpdate).Msg("socketIO: new jobs should not have a previous state") + jobUpdate.PreviousStatus = nil + } + + log.Debug().Interface("jobUpdate", jobUpdate).Msg("socketIO: broadcasting new job") + b.sockserv.BroadcastTo(string(SocketIORoomJobs), "/jobs", jobUpdate) +} diff --git a/internal/manager/webupdates/webupdates.go b/internal/manager/webupdates/webupdates.go index f0b494e0..760f8962 100644 --- a/internal/manager/webupdates/webupdates.go +++ b/internal/manager/webupdates/webupdates.go @@ -37,7 +37,7 @@ func socketIOServer() *gosocketio.Server { // socket connection err = sio.On(gosocketio.OnConnection, func(c *gosocketio.Channel) { log.Debug().Str("clientID", c.Id()).Msg("socketIO: connected") - if err := c.Join("Room"); err != nil { + if err := c.Join(string(SocketIORoomChat)); err != nil { log.Warn().Err(err).Str("clientID", c.Id()).Msg("socketIO: unable to make client join broadcast message room") } }) @@ -48,7 +48,7 @@ func socketIOServer() *gosocketio.Server { // socket disconnection err = sio.On(gosocketio.OnDisconnection, func(c *gosocketio.Channel) { log.Debug().Str("clientID", c.Id()).Msg("socketIO: disconnected") - if err := c.Leave("Room"); err != nil { + if err := c.Leave(string(SocketIORoomChat)); err != nil { log.Warn().Err(err).Str("clientID", c.Id()).Msg("socketIO: unable to make client leave broadcast message room") } }) @@ -64,12 +64,12 @@ func socketIOServer() *gosocketio.Server { } // chat socket - err = sio.On("/chat", func(c *gosocketio.Channel, message Message) string { + err = sio.On(string(SIOEventChatMessageRcv), func(c *gosocketio.Channel, message Message) string { log.Info().Str("clientID", c.Id()). Str("text", message.Text). Str("name", message.Name). Msg("socketIO: message received") - c.BroadcastTo("Room", "/message", message.Text) + c.BroadcastTo(string(SocketIORoomChat), string(SIOEventChatMessageSend), message) return "message sent successfully." }) if err != nil { diff --git a/pkg/api/flamenco-manager.yaml b/pkg/api/flamenco-manager.yaml index 15048ea4..74b0dec6 100644 --- a/pkg/api/flamenco-manager.yaml +++ b/pkg/api/flamenco-manager.yaml @@ -892,6 +892,25 @@ components: "status": {$ref: "#/components/schemas/ShamanFileStatus"} required: [status] + # SocketIO API. These types are not used in any HTTP operation defined in + # the 'paths' section of this document, so some code generators may choose + # to skip these. + + JobUpdate: + type: object + properties: + "id": + type: string + format: uuid + description: UUID of the Job + "updated": + type: string + format: date-time + description: Timestamp of last update + "status": {$ref: "#/components/schemas/JobStatus"} + "previous_status": {$ref: "#/components/schemas/JobStatus"} + required: [id, updated, status] + securitySchemes: worker_auth: description: Username is the worker ID, password is the secret given at worker registration. diff --git a/pkg/api/generate.go b/pkg/api/generate.go index 6b7ad8dd..960708ef 100644 --- a/pkg/api/generate.go +++ b/pkg/api/generate.go @@ -1,8 +1,7 @@ -//go:generate oapi-codegen -generate types -o openapi_types.gen.go -package api flamenco-manager.yaml -//go:generate oapi-codegen -generate server -o openapi_server.gen.go -package api flamenco-manager.yaml -//go:generate oapi-codegen -generate spec -o openapi_spec.gen.go -package api flamenco-manager.yaml -//go:generate oapi-codegen -generate client -o openapi_client.gen.go -package api flamenco-manager.yaml - +// SPDX-License-Identifier: GPL-3.0-or-later package api -// SPDX-License-Identifier: GPL-3.0-or-later +//go:generate oapi-codegen -generate types,skip-prune -o openapi_types.gen.go -package api flamenco-manager.yaml +//go:generate oapi-codegen -generate server,skip-prune -o openapi_server.gen.go -package api flamenco-manager.yaml +//go:generate oapi-codegen -generate spec,skip-prune -o openapi_spec.gen.go -package api flamenco-manager.yaml +//go:generate oapi-codegen -generate client,skip-prune -o openapi_client.gen.go -package api flamenco-manager.yaml diff --git a/pkg/api/openapi_spec.gen.go b/pkg/api/openapi_spec.gen.go index 6f538abe..50a22a37 100644 --- a/pkg/api/openapi_spec.gen.go +++ b/pkg/api/openapi_spec.gen.go @@ -18,100 +18,101 @@ import ( // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "H4sIAAAAAAAC/+Q823LcOHa/guKmamYq7Itulq2naH2ZkTMzVix5J1VjlwSSh92QSIADgGr3uFS1H5E/", - "SbYqD9mn/ID3j1LAAUmwiZZaY8nj3fjB1eLl4ODcb+CHKBVlJThwraKDD5FK51BS+/NQKTbjkJ1SdWn+", - "zkClklWaCR4d9O4Spggl2vyiijBt/paQAruCjCRLoudAfhLyEuQ4iqNKigqkZmBXSUVZUp7Z30xDaX/8", - "k4Q8Ooj+MOmQmzjMJk/xheg6jvSyguggolLSpfn7QiTmbXdZacn4zF0/qyQTkuml9wDjGmYgmyfwauB1", - "TsvwjZthKk11fet2DP1O8EmzI6ou1yNS1ywzN3IhS6qjA7wQrz54HUcSfqmZhCw6+Ll5yBDH7aXFzdvC", - "CpU8kvhYxR2/3rXriuQCUm0QPLyirKBJAS9FcgJaG3QGknPC+KwAovA+ETmh5KVIiIGmAgIyFyzFn304", - "P82Bkxm7Ah6TgpVMWzm7ogXLzP81KKKFuaaAOCBj8ooXS1IrgyNZMD0nSDS7uFm7FcEB8VeFLYOc1oUe", - "4nU6B+JuIh5EzcWCO2RIrUCShcE9Aw2yZNyuP2eqIckYwXsww0u0VyZaiEKzyi3EeLeQkUeZ0xQsUMiY", - "NltHiA7/nBYK4iFx9RykQZoWhVgQ8+oqooTm2jwzB3IhEjKniiQAnKg6KZnWkI3JT6IuMsLKqliSDArA", - "14qCwHumECBVl4rkQiLoC5HEhPLMGBBRVqwwzzA9fss7QU+EKIByu6MrWgzpc7zUc8EJvK8kKMWEJX4C", - "xDxdUw2ZoZGQGW6w4QPYnfRZ1+LV8iYeisYlLIc4HGXANcsZSAekFfmYlLXSBp+as19qFETHtAunCMF1", - "jGJQOQvowiFfEnivJSVUzurSWJhG3pJqOTYvqvGJKOEYdWv59TckNWyoFWTmyVQC1YBbdfq39HDoVLyz", - "LHcQIVaWkDGqoVgSCQYUoXarGeSMM/NCbAyBXd4sGVuaiFo7jKjULK0LKls+rJEHVSeN+bzJ6gYM1Yl7", - "s1X1O0M4da9fMcVWlUzL+iYCGcXtq5aThzdHaCANsRq1kuTrgl0CoeSPBXAjxDTLRoJ/MyYnoA24c8uQ", - "czQz6I8pR1vAadGuoedUm6XrIuNfWYFsLRXwzBoQFSb0iosxCuAe2tAtnHR8WvEOdTIyd1AcUCEanpOn", - "tZTAdbEkwthx2sC1GuZZcjUm598dnnz3/NnZi6Pvn58dH55+d45RSsYkpFrIJamonpN/Judvo8kf7L+3", - "0TmhVWVImuG2gdel2V/OCjgzz0dxlDHZ/LSXnUedUzWH7Kx78l1AgdcJzdDAOwp4u/esBrovqsjRs0af", - "7baN0DiRGJMfBeGgjK1TWtapriUo8rV1XyomGUvNUlQyUN8QKoGouqqE1Ktbd8jHJrLZ2TabLgTVUWxl", - "4dZNhnfXePtuTYwSmSI/UE5nINEFMG1Vn5bGQAdCg4ImUNwtZHPE3DzcDIU0g2hgRR2cSCB63pq36Yah", - "VsC4f8+UboTBSvd6ug1p1IRxv23Hpz2LuGa73RKhDTbx+mBb7gaRYLy0dVmUKAwOXZRpLdF7SGsNt+UR", - "64P0VoC82w16YcZ5r4R29FxKIQ2w1Uwmg1503mjMMDUoQSk6C+G7gpCF2T0fwuZFQUvgqfgTSOWCxQ0p", - "c9W9cTMWzYNOr0JYvMTUixbFqzw6+PlmCTtp4kPz1nU8IKSNRUISY27YaI6VoDQtK2OPGnJnVMPI3AmF", - "TiwA7s2bo2eNm3lps6NbEqtNczpjKtqUrq6ye97NCncspg3NuvVaZN9dv0MG/QCaZlRTy6gss2EXLY57", - "tB/seCXOlAnTksolKR0w53bVmPwgpFXcqoD3vs9JKTdeqxQm/rcWqzZaTs7pOBmn54QLjXRowuRLsKEn", - "vKcGlhNoK2gH0UklmQbyQrLZ3HghE6OMoaSsMFgvEwn8XxLnAoWcNU+gDkQn9gFyov/3f66g8AxbT5BP", - "PB8RphNGc8F3WwFpHChNNbuymTPlqaEAJtFVAdr95kgsJvgopwyfaH9U1IToURz9UkNtf1CZztmV9xP9", - "M4IfGcmwbt8B6V2wvxFKbUg08heP4mhBbZI3yoUcmUhGBR38S5Gof6tB2qTH45JNwqODPWOvOklbx7vr", - "OLIZ2FmytFWKgdg2v84Y79Gx3YKj0bvrQVyAiHyITHJdGjZsha3wJ+vDC1aYeDzp9CFupPv7o3993gl3", - "MJcSea6gj+g0hGhHpw93KFCoDcV43Y682FPdZVce11ZN3mvQteQYvF+IRGEJxtgQ8wrmPCZKN1voVWM2", - "traDQCWkpCi9r0G5+s0gYto8WEIPdmt8FA4kXND2VPCczWpJddB/qzktKX/OTUiWBctgmEXOgZzYR4lR", - "XaIl5SoHSQ6Pj2zq04R143DirIWkM/hepDRcc3rWJk62ZmCMtZEQu5Z7eXyrr1pdJV7ZXYhKr2HGlAYJ", - "GcZ+QwrRLJOgwlpRUKXPrO3oF2q9XIGll+ujx4Jq45PDyYTI9YLKNZnGRqECbqmT3zayP2uLrupuav9J", - "ReKWFnFLVL9Y3BAjjlLMxC2W0SqVPcqs2VGIzyeQ1pLp5ZrweuOY+aZgGRXk6RzSS1EHarcnYDMt6/vQ", - "OOk5MElOvjvc3ntEUvOiqsuYKParTbeTpQaF2WoGyqBACifcTQkndat1pYeV6AY9rQmabeHgIOqqYuOZ", - "QB2JDqKdvWS6+2Qr3d5Ppjs7O9lWnuzu5el0//ETurWd0umjZCt7tDvNtvcePdl/PE0eT/cz2JvuZvvT", - "7ScwNYDYrxAdbO1u79qoG1crxGzG+Mxf6tFOsr+dPtpJnuxu7+bZ1k7yZGd/miePptNHT6aPp+kO3drb", - "39pP8x2a7e5uP9rZS7Ye76eP6OMne9P9J91S2/vXQ//cUOTYIjAo3lI9J4s5SKzHOiPp6lS9QmUDZ0yO", - "XM+poCZIaEqfzhy2DLAVH6pI6gwuZERwf5ExOeJEFBlI4nIe1SQIDpZdd0EVuagVNhzettshR8/eRjFJ", - "at16MgeFMN2EwRSxsAW8cxcbjVRRzyYqBQ4jo30TrAuPjp6d98pvndI7kdnQSSHuL1gBJxWkt/orBB73", - "2XS7NnX+dNiskPYeljJXuBLq+PwG8XAJ0KpgnNo/kfQZy3MwVovoOeVkMafasrKNkGMjHD7QBSsKAlzV", - "0jDOVes7NSZma5ad9yJ8IVav1gM2Y0nL6qGBqyBlOXMWyvLDenBnqxzSnj/vs6YKsqRx542u+BAbjIPZ", - "9JwGMOybWh9mEIa1Mx+GUSz0bXSgDrMam8xpY7fiqNqMwD8xPe9Svo1IHZPFnKVzklpzlqwhfUyENGF2", - "TDKogGe2U8ptRRTd8T84bzaNnzx2uBjqVq76GeZN7B1k8jW/5GLBbY2lEDTDvNswrBe5dvtHYK8RG9uU", - "e42m5jcHHjbQ6NFubSzxQEHDZwkQPoN7W8/8Pr9UJbiCsFdDbuVSlIQS6b3WuJTYZ6VLckVf3UFembjj", - "hQVlG3JUArGCZjyJe8xcg/dpUWcm9TILavSqFrvPKQOdYrb68DBi4S/Uqts9y4pnvj9VanCqpW84VlTc", - "8f+uPve+DOENRs+vxwe7dV1G0g13GPFsmg8rErhJ/e/Ta7fuxs7H/yB/+/PHv3z868f/+viXv/35439/", - "/OvH//Rnmw72pv1ymFvlLC2z6CD64P68tjFvzS/PUAh3zJ60pKk+o3XGRFMwM8xzudNE2jcnKp9ciERh", - "DL+1vTO2IP16/vGP35o/KxUdGCXKJS0Ne6Ot0ZZRMFbSGagzIc+uWAbCuEJ7JYojUeuq1thZhvcaODZt", - "onFl/Q9icOaeGuKFK7WYTcLkci3wATwphL4Rnqc4ihn+jxw1R/hKNFBYXzhuKa21HYtNp/DaeYu9YCHV", - "l4HbSorNo948yM2xuaubuDm5FquQwnlDf3foFLQ9gbYCrkSuu55BoAPgugeh4MTg8MYW3QPOrb1H7EgK", - "1yRZEuqar0bxsVyPU01o197W0+n2I1KImbNxdh6U6a+Ua+G66amV0p1Xmevj8IrDqGDcDRDxzATSYJO2", - "rxRJ20GQuZ3YMOFx42rtwmPy6grkwhgcRSoJV0zUqljiXppF295VKHgtxCwUTc+IQcobWDOrxZgpmvTe", - "zY8YpC0p7IJAZcGwaz2s3/VkYdNR0VBlG7mD5dJ1xeRPKHZCKrFZMbz1iUXLVU+FK/XqjcElvHrlu7X0", - "OGEz/uqulGjql2fre+T3vm2v9rpmtwOsbti1phqezimfQaCjgB2azlDcqUgdjCs8YBshla3D6h5wuQWD", - "vtFVmkqNmRxd0Etb+VYFQGUiGluJNrlwrTPM/DQo97TIc2MJArYVlcXWsk8M1ri9hUXgjNahLP2NAml4", - "b8ytMWH4MDl6FpOKKrUQMmtuoXbg4DOhunlUempv7Iyll+3xUMXSzvDMta6ia4Mj47nAuRWuaaq7UZF2", - "pIScAjXKV8vCvakOJpO8ifmYmAw7oq9xIvEFlSUpXRnt8PgoiqOCpeBSKbfOt8ffX+0M4C8Wi/GM1yYE", - "nLh31GRWFaOd8XQMfDzXJbbumS562LrlIm+yJdoaT8dT20OtgNOKmXjRXsJigOXMhFZskq424WZo7IyE", - "2mtHJlT8FnS/W2fkD5MwC2p7Om1ICty+T6uqcDWgyYVC0CjLt0l6sDtoOdenODchZtEmgyh/dVlSuUSM", - "sdbjg2lnbb2xMU1NXPSzDc9sT72D8ZxnlWBcW6c3c8OjA4AtH1qg1zHStumoVkIFaIrZBw7gOCvyR5Et", - "742O/WmjIf3sNKJweU3kGxQT7l8/IIdvQGhBFVF1moLK66JYEpyFt4PrLhy6YllNCxyfH68cSLgX7LAP", - "F8DP3iBNm60vbkhsQgmHBfaHhRxIhjfW50se9sl74F42Y884xQ9OEPuiNfmlGUYJC5jt9r80wB9GwLp5", - "mACxBlVgrP7a6QctjDaNP7fM9cYfAij/iAbFUrU1K3HTRYGy0ktSMKUJywkXem6sQUl1OrftF8AXvxyR", - "fAE6nSPCOESrbhG6V4mmjHsDKrmdibEnV3hGlJDtKZ1OBtvob53faOd7H5C5w2HiAK3ah7qB4oDPKAZD", - "x3Ye11Yy+zPZN1CyW6o1ARfdSa8e/T5ciOSMZddrSWjZiF7CH+n9+UPEzK5cad9FFghsoFixR8fb5iPe", - "/T6G30ZlIQE2NwhN8EyM5d0GthNf4pmLjUqDeUN2L7VZJ7N/agd/H4wUq+PLvznAaSWs6bmvxDg3hzhP", - "C2YL98bI1crNF2iBjRf8iylCU11T445pt5yr4LdkxXh8It3U0mjRDS0FvVMz3uSGmx7GRQVKAwFCd+Wd", - "BvvP6poGg16byMJndDI1h/cVpBoyAu4ZX4Qa9F3ws2j42Uidu/Au8FIXRHdvqlWJUmzGRyLPb4ik2Yy/", - "yvOhuu4OM84vj5AuZbYmvZcs//zOGOOOZj9QeelnyVSRJhm/hdpPaeFG0FHCrIoXzoA0weklt2fxYPmV", - "BDITeEbZgh+HWcJv4Qh/UKV2S6xX57be/jl1eViF+rtQ5o1l8LDWc+Aai9Ku9G2koWnqLdpjSvcskBJo", - "tjRPGXg4eNgrx7OO4UNx1a7aH/T3Hsui31syLKYktfe7+SyznzXGjKx/48sWqbuLB4Yki27sWwIe8F2u", - "IUJYDkapV4gNGq9A0fZBDZm/UCgtbV0j7nMDe/aP5fecPXd8QyI0E47NMBk1QaoxGAVkGO9jM87Zkq45", - "2JMVO23GeEuVxr6AHBUipYU1bbRQ923PrqC3m1oNRFW7j8+sca/pHLK6gFOcQn+4vNr/FE6AsfYjOH5R", - "a52h+lG47130j67b/KI52XodR7vTnfsrf/bG6gPIH4Ns6mvPgDM0mrvTJ4GjJiiATBEudOPpsGuN4hQT", - "JZrb9rMh0DvCi1u34x+EiwVudXvn87qWRosoN1gKrPWYsNtih+Pb9qT9TNivn3Bh7Sxq2x011lWSaAvf", - "o8ZtqmRlSjkBl4HSp6chkw+2T+jKJ2Fd8fr9m1RQHMBPL6Hcv7vwdrJOF108xDii2NQw7uwtTufQwFpY", - "05pC1XjUoIqcuvkD65Gd1fDFCJlm9UT3YVud8eH/vbilN90oCM5C6GXFUlsm8Sc3KilmEpSK3Rlh99EX", - "SXLKilrCrb6l8SgKeNarhhlyN9CNFTMREaoJHi6bNHPOEzwkcIM/6R8PeqB+VH+RUM/AHwZuIz53VuLz", - "5XDB4x0BdJsnrBg35zC85pWvLQ8ryS0mtMA8yX5lSjlHs/vwCJzaaHxh/kPuWc/KZ2PyRgE5VysU7SaG", - "zw2f8VwIsaS0XSLBQY2/pBrXUzx95X1GB1NQtSwLxi/dVDIKqKMANiw1HpZxRDHulRYFmdMrwE+G4Ygv", - "2ko3EJtAbr8oQIui/fBY5wU7Y4FEXTEWJw4hSpSvTBaZ3qE8KoGGjYU/0L2pyfBZ+qDmI3SoYFNL8jsY", - "keBMfQjfOnH8MkwyFIesN1kfNw4FRQKIG0LHLX5ZumLPbHQH3nwauJNA7vM3QmrlNB45RWW7sVsl/dDE", - "2WaZ1LYw/ApBH2CXcrgjCNi5QCw6e4Mfg9KsKDoUPPWw8CYfmgMp15MP9gr7Fda37vzZdCHhqRPClSB0", - "46NG9ssAw4i1efTGkHUwqjX8OuWvsHpWqj1oE1i12f0mq3Ynz949uMYNziOsb/R3x0i+NO3x54u7cxPB", - "EzR4eHKoKDdZ7VYi/38LYxxKYpw1acJ3dzbJnWPOIAdJ2mM56JstNayXfxttTx+/jbpykp2Otuk2L5Yk", - "MTGCrqVJjewXCrvtqTZyw7Gn9hzUgOGYqNNCCYShRAmCA4FCWTjdhHgITSstloBzoJlt0zkS/vsIlxk9", - "pXz0zOxz9MYCiAI09L6HGKKhkGzGOC3smgb+mBzlbgS9EP7IentejOl2lJxxd96L+ebaTpW3Z0gpJ5TZ", - "JzJIajzHv8HeXjnERi8cYtFNYrlxGi9SDXqktARa9i1EWylIGDf6PawVDGN5XEOtHDL9jUm8Fa9BCr89", - "fXzb404ce4Lotfx3t/aDEKR73SQAdjaKJKAX4ITdkdMbpGmma9yIgfusilV/ObA7bbDcyLJNb/YCnxhD", - "JXafTrhFaxsN7DTHCV4lRQrKMiIB82K7frLs6R2GEudrVeiAGJ6d43AjWhefHG4nX4oHsp7B1e7W+x3y", - "o7DFD6qHN61+5kKmLCmWJC2EwjLJd6enxyQVnIP98BYasKZC5AxvzjhTc1A9fgGB9zTVRNESXAiphT3e", - "Yl7JRG2iO3xBjd/yhqtf2a8OoDY5WUggxAGSiGy51pX6JR+zRJdWDMniakjmNzpUnPGeRF7Pa/DF5P6E", - "02BqlGkFRT7u7Jmd4xma3pciaVqytjb0Sw2SgYq9SdJ4ZShq3BsdUwGgh8dH/VlWvyMnyrLm7oCSMenD", - "UegWvCttBXw90u/w+Ci2C1mR65jvNmTLK+bvC5G0Sazy4Dt+Xb+7/r8AAAD//8lDTFXbXwAA", + "H4sIAAAAAAAC/+Q823LcOHa/guKmanYr7Itulq2naH2ZkTMzVix5J1VjlwSSh92QSIADgGr3uFS1H5E/", + "SbYqD9mn/ID3j1LAAUmwiZZatuTxbvzgavFycHDuN/BDlIqyEhy4VtHBh0ilcyip/XmoFJtxyE6pujR/", + "Z6BSySrNBI8OencJU4QSbX5RRZg2f0tIgV1BRpIl0XMgPwl5CXIcxVElRQVSM7CrpKIsKc/sb6ahtD/+", + "SUIeHUS/m3TITRxmk6f4QnQdR3pZQXQQUSnp0vx9IRLztrustGR85q6fVZIJyfTSe4BxDTOQzRN4NfA6", + "p2X4xs0wlaa6vnU7hn4n+KTZEVWX6xGpa5aZG7mQJdXRAV6IVx+8jiMJv9RMQhYd/Nw8ZIjj9tLi5m1h", + "hUoeSXys4o5f79p1RXIBqTYIHl5RVtCkgJciOQGtDToDyTlhfFYAUXifiJxQ8lIkxEBTAQGZC5bizz6c", + "n+bAyYxdAY9JwUqmrZxd0YJl5v8aFNHCXFNAHJAxecWLJamVwZEsmJ4TJJpd3KzdiuCA+KvClkFO60IP", + "8TqdA3E3EQ+i5mLBHTKkViDJwuCegQZZMm7XnzPVkGSM4D2Y4SXaKxMtRKFZ5RZivFvIyKPMaQoWKGRM", + "m60jRId/TgsF8ZC4eg7SIE2LQiyIeXUVUUJzbZ6ZA7kQCZlTRRIATlSdlExryMbkJ1EXGWFlVSxJBgXg", + "a0VB4D1TCJCqS0VyIRH0hUhiQnlmDIgoK1aYZ5gev+WdoCdCFEC53dEVLYb0OV7queAE3lcSlGLCEj8B", + "Yp6uqYbM0EjIDDfY8AHsTvqsa/FqeRMPReMSlkMcjjLgmuUMpAPSinxMylppg0/N2S81CqJj2oVThOA6", + "RjGonAV04ZAvCbzXkhIqZ3VpLEwjb0m1HJsX1fhElHCMurX8/R9IathQK8jMk6kEqgG36vRv6eHQqXhn", + "We4gQqwsIWNUQ7EkEgwoQu1WM8gZZ+aF2BgCu7xZMrY0EbV2GFGpWVoXVLZ8WCMPqk4a83mT1Q0YqhP3", + "Zqvqd4Zw6l6/YoqtKpmW9U0EMorbVy0nD2+O0EAaYjVqJcnvC3YJhJI/FsCNENMsGwn+hzE5AW3AnVuG", + "nKOZQX9MOdoCTot2DT2n2ixdFxn/xgpka6mAZ9aAqDChV1yMUQD30IZu4aTj04p3qJORuYPigArR8Jw8", + "raUEroslEcaO0wau1TDPkqsxOf/u8OS758/OXhx9//zs+PD0u3OMUjImIdVCLklF9Zz8Mzl/G01+Z/+9", + "jc4JrSpD0gy3Dbwuzf5yVsCZeT6Ko4zJ5qe97DzqnKo5ZGfdk+8CCrxOaIYG3lHA271nNdB9UUWOnjX6", + "bLdthMaJxJj8KAgHZWyd0rJOdS1Bkd9b96VikrHULEUlA/UHQiUQVVeVkHp16w752EQ2O9tm04WgOoqt", + "LNy6yfDuGm/frYlRIlPkB8rpDCS6AKat6tPSGOhAaFDQBIq7hWyOmJuHm6GQZhANrKiDEwlEz1vzNt0w", + "1AoY9++Z0o0wWOleT7chjZow7tN2fNqziGu22y0R2mATrw+25W4QCcZLW5dFicLg0EWZ1hK9h7TWcFse", + "sT5IbwXIu92gF2ac90poR8+lFNIAW81kMuhF543GDFODEpSisxC+KwhZmN3zIWxeFLQEnoo/gVQuWNyQ", + "MlfdGzdj0Tzo9CqExUtMvWhRvMqjg59vlrCTJj40b13HA0LaWCQkMeaGjeZYCUrTsjL2qCF3RjWMzJ1Q", + "6MQC4N68OXrWuJmXNju6JbHaNKczpqJN6eoqu+fdrHDHYtrQrFuvRfbd9Ttk0A+gaUY1tYzKMht20eK4", + "R/vBjlfiTJkwLalcktIBc25XjckPQlrFrQp47/uclHLjtUph4n9rsWqj5eScjpNxek640EiHJky+BBt6", + "wntqYDmBtoJ2EJ1UkmkgLySbzY0XMjHKGErKCoP1MpHA/yVxLlDIWfME6kB0Yh8gJ/p//+cKCs+w9QT5", + "xPMRYTphNBd8txWQxoHSVLMrmzlTnhoKYBJdFaDdb47EYoKPcsrwifZHRU2IHsXRLzXU9geV6ZxdeT/R", + "PyP4kZEM6/YdkN4F+xuh1IZEI3/xKI4W1CZ5o1zIkYlkVNDBvxTJGytkQ1tzT2pWSbhiolZnn6Bv96mi", + "p41mGvwLqjTBRz9DUYfqGRYk9W81SJtWenpgyxzRwZ7xCJ0ur9OO6ziyOe5ZsrR1oMDK+OuM8Z6ktkLi", + "pPDd9SDyQkQ+RCXjrDSCvhX2c59tcV6wwmQ8SWdx4sZ+fH/0r8878xHMVkWeK+gjOg0h2tHpwx1KQGpD", + "Q7FuR150r+6yK49rqxL7GnQtOaZHFyJRWOQyVtq8glmlyYPMFnr1ro2VZRAKrpfe16BchWwQk24ejmKM", + "cGsEGlYkFxY/FTxns1pSHYyQ1JyWlD/nJujNgoVGzNPnQE7so8QYR6Il5SoHSQ6Pj2xy2QTO43BpQgtJ", + "Z/C9SGm4qvesTU1tVca4QyMhdi338vhWI7O6SryyuxCVXsOMKQ0SMoyuhxSiWSZBhbXC2MUzazv6pXAv", + "G2Pp5fr4vKDaGNNwuiZyvaByTS63kaXHLXn+ocmdztqytrqb2n9WGb6lRdwS1S/HN8SIoxRrHRbLaJXK", + "HmXW7CjE5xNIa8n0ck0Cs3FWclM6ggrydA7ppagD1fETsLmsjS7QOOk5MElOvjvc3ntEUvOiqsuYKPar", + "LWgkSw0K6wEZKIMCKZxwN0Wy1K3WFXdW4keMZUxaYkszB1FXdxzPBOpIdBDt7CXT3Sdb6fZ+Mt3Z2cm2", + "8mR3L0+n+4+f0K3tlE4fJVvZo91ptr336Mn+42nyeLqfwd50N9ufbj+BqQHEfoXoYGt3e9fmNbhaIWYz", + "xmf+Uo92kv3t9NFO8mR3ezfPtnaSJzv70zx5NJ0+ejJ9PE136Nbe/tZ+mu/QbHd3+9HOXrL1eD99RB8/", + "2ZvuP+mW2t6/HvrnhiLHFoFBeZzqOVnMQWLF2xlJVwnslYIbOGNy5Lp6BTVBQlNcduawZYCtqVFFUmdw", + "ISOC+4uMyREnoshAEpdVqiY2dLDsuguqyEWtsKXztt0OOXr2NopJUuvWkzkohOkm0aCIhS2RnrvYaKSK", + "ejZRKXAYGe2bYOV9dPTsvFfg7JTeicyGTgpxf8EKOKkgvdVfIfC4z6bbtanzp8N2kLT3sFi8wpVQT+0T", + "xMOlmKuCcWr/RNJnLM/BWC2i55STxZxqy8o2B4mNcPhAF6woCHBVS8M41w/p1JiYrVl23ovwhVi9WnHZ", + "jCUtq4cGroKU5cxZKMsP68GdrXJIe/68z5oqyJLGnTe64kNsMA7WK+Y0gGHf1PowgzCsnfkwjGKhb6MD", + "la7V2GROG7sVR9VmBP6J6XmXVG9E6pgs5iydk9Sas2QN6WMipAmzY5JBBTyzvWhua87ojv/BebNp/OSx", + "w8VQt3L1xtx2AM+rldT8kosFt2lyIWiGlQ3DsF7k2u0fgb1GbGzb8zWamk8OPGyg0aPd2ljigYKGLxIg", + "fAH3tp75fX6pSnAFYa+G3MqlKAkl0nutcSmxz0qX5Iq+uoO8MnHHCwvKtjypBGIFzXgS95i5Bu/Tos5M", + "6mUW1OhVLXZfUgY6xWz14WHEwl+oVbd7lhXPfH+u1ODcUN9wrKi44/9dfe59GcIbjJ7f8Qj2Q7uMpBuf", + "MeLZtHdWJHCT+t/nV8fdjZ2P/0H+9uePf/n414//9fEvf/vzx//++NeP/+lPjx3sTfvlMLfKWVpm0UH0", + "wf15bWPeml+eoRDumD1pSVN9RuuMiaZgZpjncqeJtG9OVD65EInCGH5re2dsQfqF2OMfvzV/Vio6MEqU", + "S1oa9kZboy2jYKykM1BnQp5dsQyEcYX2ShRHotZVrbF3D+81cGyLRePK+h/E4Mw9NcQLV2oxm4TJ5YYM", + "BvCkEPpGeJ7iKGb4P3LUHOEr0UBhfeG4pbTW9oQ2nXNsJ1r2goVUXwZuKyk2j3oTNzfH5q5u4iYRW6xC", + "CueNVd6hF9N2XdoKuBK57roygR6L68+EghODQ9coWXFu7T1ih364JsmSUNfeNoqP5XqcG0O79raeTrcf", + "kULMnI2zE7dMf6Nck9zNp62U7rzKXB+HVxxGBeNuRItnJpAGm7R9o0jajtrM7UyMCY8bV2sXHpNXVyAX", + "xuAo0jRsiiXupVm07Q6GgtdCzELR9IwYpLyRQLNajJmiSe/dhI5B2pLCLghUFgznAob1u54sbDqMG6ps", + "I3ewXLqumPwZxU5IJTYrhrc+s2i56qlwpV69MbiEV698t5YeJ2zGX92VEk398mz9FMK9b9urva7Z7QCr", + "G3atqYanc8pngT6o69B0huJORepgXOEB2wipbB1W94DLLRj0ja7SVGrM5OiCXtrKtyoAKhPR2Eq0yYVr", + "nWHmp0G5p0WeG0sQsK2oLLaWfWKwxu0tLAJntA5l6W8USMN7Y26NCcOHydGzmFRUqYWQWXMLtQNHywnV", + "zaPSU3tjZyy9bI+HKpZ2hmeudRVdGxwZzwVOBnFNU90N47RDO+QUqFG+WhbuTXUwmeRNzMfEZNgRfY0z", + "ny+oLEnpymiHx0dRHBUsBZdKuXW+Pf7+amcAf7FYjGe8NiHgxL2jJrOqGO2Mp2Pg47kucTiC6aKHrVsu", + "8maHoq3xdDy1PdQKOK2YiRftJSwGWM5MaMUm6WoTbobGzkiovXZkQsVvQfe7dUb+MAmzoLan04akwO37", + "tKoKVwOaXCgEjbJ8m6QHu4OWc32KcxNiFm0yiPJXlyWVS8QYaz0+mHaa2RvM09TERT/b8Mz21DsYz3lW", + "Cca1dXozN547ANjyoQV6HSNtm45qJVSApph94OyFsyJ/FNny3ujYn+ca0s/OewqX10S+QTHh/vUDcvgG", + "hBZUEVWnKai8LoolwdMG9miAC4euWFbTAg8ojFeOfNwLdtiHC+Bnb5CmzdYXNyQ2oYTDAvvDQg4kwxuc", + "9CUP++Q9cC+bwXI8JwFOEPuiNfmlGUYJC5jt9r80wB9GwLp5mACxBlVgrP7a6QctjDaNv7TM9cYfAij/", + "iAbFUrU1K3HTRYGy0ktSMKUJywkXem6sQUl1OrftF8AXvx6RfAE6nSPCOKasbhG6V4mmjHsDKrmdibFn", + "g3hGlJDtOahOBtvob53faCeoH5C5w3HtAK3ah7qR7YDPKAZj3Xbi2VYy+1PvN1CyW6o1ARfdWboe/T5c", + "iOSMZddrSWjZiF7CH5r++UPEzK5cad9FFghsoFixR8fb5iPe/TaG30ZlIQE2NwhN8NSR5d0GthNf4pmL", + "jUqDeUN2L7VZJ7N/akerH4wUqwPinxzgtBLW9NxXYpybQ5ynBbOFe2PkauXmC7TAxgv+xRShqa6pcce0", + "W85V8FuyYjw+kW5qabTohpaC3qkZb3LDTQ/jogKlgQChu/JOg/0XdU2DQa9NZOELOpmaw/sKUg0ZAfeM", + "L0IN+i74WTT8bKTOXXgXeKkLors31apEKTbjI5HnN0TSbMZf5flQXXeHGefXR0iXMluT3kuWf35njHFH", + "sx+ovPSzZKpIk4zfQu2ntHBD/ihhVsULZ0Ca4PSS29OOsPxGApkJPAVuwY/DLOG3cIQ/qFK7Jdarc1tv", + "/5K6PKxC/V0o88YyeFjrOXCNRWlX+jbS0DT1Fu1BsHsWSAk0W5qnDDwcPOyV41nH8KG4alftD/p7j2XR", + "by0ZFlOS2vvdfJbZzxpjRta/8XWL1N3FA0OSRTf2LQGPUC/XECEsB6PUK8QGjVegaPughsxfKJSWtq4R", + "97mBPfvH8nvOnju+IRGaCcdmmIyaINUYjAIyjPexGedsSdcc7MmKnTZjvKVKY19AjgqR0sKaNlqo+7Zn", + "V9DbTa0Goqrd533WuNd0DlldwClOoT9cXu1/bCjAWPuZIb+otc5Q/SjcF0X6Hwew+UVzdvg6jnanO/dX", + "/uyN1QeQPwbZ1NeeAWdoNHenTwJHTVAAmSJc6MbTYdcaxSkmSjS37YdZoHdIGrduxz8IFwvc6vbOl3Ut", + "jRZRbrAUWOsxYbfFDse37bcMZsJ+X4YLa2dR2+6osa6SRFv4HjVuUyUrU8oJuAyUPj0NmXywfUJXPgnr", + "itfv36SC4gB+fgnl/t2Ft5N1uujiIcYRxaaGcWdvcTqHBtbCmtYUqsajBlXk1M0fWI/srIYvRsg0qye6", + "D9vqjA//78UtvelGQXAWQi8rltoyiT+5UUkxk6BU7E5hu8/qSJJTVtQSbvUtjUdRwLNeNcyQu4FurJiJ", + "iFBN8HDZpJlznuAhgRv8Sf940AP1o/qLhHoG/jBwG/G5sxJfLocLHu8IoNs8YcW4OYfhNa98bXlYSW4x", + "oQXmSfY7Xso5mt2HR+DURuML8x9yz3pWPhuTNwrIuVqhaDcxfG74jOdCiCWl7RIJDmr8NdW4nuLpK+9D", + "RZiCqmVZMH7pppJRQB0FsGGp8bCMI4pxr7QoyJxeAX6UDUd80Va6gdgEcvvNBloU7afdOi/YGQsk6oqx", + "OHEIUaJ8ZbLI9A7lUQk0bCz8ge5NTYbP0gc1H6FDBZtakt/AiARn6kP41onjl2GSoThkvcn6uHEoKBJA", + "3BA6bvHr0hV7ZqM78ObTwJ0Ech8YElIrp/HIKSrbjd0q6YcmzjbLpLaF4VcI+gC7lMMdQcDOBWLR2Rv8", + "3JZmRdGh4KmHhTf50BxIuZ58sFfYr7C+defPpgsJT50QrgShGx81sl8GGEaszaM3hqyDUa3h9z9/hdWz", + "Uu1Bm8Cqze43WbU7efbuwTVucB5hfaO/O0bytWmPP1/cnZsInqDBw5NDRbnJarcS+f9bGONQEuOsSRO+", + "u7NJ7hxzBjlI0h7LQd9sqWG9/Ntoe/r4bdSVk+x0tE23ebEkiYkRdC1NamS/AdltT7WRG449teegBgzH", + "RJ0WSiAMJUoQHAgUysLpJsRDaFppsQScA81sm86R8N9HuMzoKeWjZ2afozcWQBSgoffFyRANhWQzxmlh", + "1zTwx+QodyPohfBH1tvzYky3o+SMu/NezDfXdqq8PUNKOaHMPpFBUuM5/g329sohNnrhEItuEsuN03iR", + "atAjpSXQsm8h2kpBwrjR72GtYBjL4xpq5ZDpJybxVrwGKfz29PFtjztx7Ami1/Lf3doPQpDudZMA2Nko", + "koBegBN2R05vkKaZrnEjBu6zKlb95cDutMFyI8s2vdkLfMQNldh9OuEWrW00sNMcJ3iVFCkoy4gEzIvt", + "+smyp3cYSpyvVaEDYnh2jsONaF18cridfC0eyHoGV7tb73fIj8IWP6ge3rT6mQuZsqRYkrQQCssk352e", + "HpNUcA7202ZowJoKkTO8OeNMzUH1+AUE3tNUE0VLcCGkFvZ4i3klE7WJ7vAFNX7LG65+Y786gNrkZCGB", + "EAdIIrLlWlfql3zMEl1aMSSLqyGZ3+hQccZ7Enk9r8E3qfsTToOpUaYVFPm4s2d2jmdoel+KpGnJ2trQ", + "LzVIBir2JknjlaGocW90TAWAHh4f9WdZ/Y6cKMuauwNKxqQPR6Fb8K60FfD1SL/D46PYLmRFrmO+25At", + "r5i/L0TSJrHKg+/4df3u+v8CAAD///kI70U9YQAA", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/pkg/api/openapi_types.gen.go b/pkg/api/openapi_types.gen.go index c9d4390a..0f5227b7 100644 --- a/pkg/api/openapi_types.gen.go +++ b/pkg/api/openapi_types.gen.go @@ -225,6 +225,17 @@ type JobSettings struct { // JobStatus defines model for JobStatus. type JobStatus string +// JobUpdate defines model for JobUpdate. +type JobUpdate struct { + // UUID of the Job + Id string `json:"id"` + PreviousStatus *JobStatus `json:"previous_status,omitempty"` + Status JobStatus `json:"status"` + + // Timestamp of last update + Updated time.Time `json:"updated"` +} + // JobsQuery defines model for JobsQuery. type JobsQuery struct { Limit *int `json:"limit,omitempty"` diff --git a/web/app/src/App.vue b/web/app/src/App.vue index 78e3b468..88af3b9f 100644 --- a/web/app/src/App.vue +++ b/web/app/src/App.vue @@ -5,46 +5,45 @@ + /> + diff --git a/web/app/src/main.js b/web/app/src/main.js index 80574918..d9a96edd 100644 --- a/web/app/src/main.js +++ b/web/app/src/main.js @@ -10,34 +10,27 @@ import { BButton, } from "bootstrap-vue"; +import URLs from './urls' + import "bootstrap/dist/css/bootstrap.css"; import "bootstrap-vue/dist/bootstrap-vue.css"; -let url = new URL(window.location); -url.port = "8080"; -const flamencoAPIURL = url.href; -url.protocol = "ws:"; -const websocketURL = url.href; -console.log("Flamenco API:", flamencoAPIURL); -console.log("Websocket :", websocketURL); +// let flamencoManager = require('flamenco-manager'); +// let apiClient = new flamencoManager.ApiClient(URLs.api); -let flamencoManager = require('flamenco-manager'); -let apiClient = new flamencoManager.ApiClient(flamencoAPIURL); +// let query = new flamencoManager.JobsQuery(); +// // query.status_in = ["active"]; +// query.metadata = {project: "Heist"}; -let query = new flamencoManager.JobsQuery(); -// query.status_in = ["active"]; -query.metadata = {project: "Heist"}; - -let JobsApi = new flamencoManager.JobsApi(apiClient); -JobsApi.queryJobs(query).then(function(data) { - console.log('API called successfully.'); - console.log(data); -}, function(error) { - console.error(error); -}); +// let JobsApi = new flamencoManager.JobsApi(apiClient); +// JobsApi.queryJobs(query).then(function(data) { +// console.log('API called successfully.'); +// console.log(data); +// }, function(error) { +// console.error(error); +// }); Vue.config.productionTip = false -Vue.config.serverUrl = websocketURL; Vue.use(FormInputPlugin); Vue.use(NavbarPlugin); @@ -47,8 +40,7 @@ Vue.component("b-input-group", BInputGroup); Vue.component("b-button", BButton); Vue.use(IconsPlugin); -var vueApp = new Vue({ - render: h => h(App), -}); +var vueApp = new Vue(App); +vueApp.websocketURL = URLs.ws; vueApp.$mount("#app"); diff --git a/web/app/src/urls.js b/web/app/src/urls.js new file mode 100644 index 00000000..eb59cc14 --- /dev/null +++ b/web/app/src/urls.js @@ -0,0 +1,16 @@ +let url = new URL(window.location); +url.port = "8080"; +const flamencoAPIURL = url.href; + +url.protocol = "ws:"; +const websocketURL = url.href; + +const URLs = { + api: flamencoAPIURL, + ws: websocketURL, +}; + +console.log("Flamenco API:", URLs.api); +console.log("Websocket :", URLs.ws); + +export default URLs; diff --git a/web/manager-api/README.md b/web/manager-api/README.md index 8d6d9c5c..ac78ee6e 100644 --- a/web/manager-api/README.md +++ b/web/manager-api/README.md @@ -152,6 +152,7 @@ Class | Method | HTTP request | Description - [flamencoManager.Job](docs/Job.md) - [flamencoManager.JobAllOf](docs/JobAllOf.md) - [flamencoManager.JobStatus](docs/JobStatus.md) + - [flamencoManager.JobUpdate](docs/JobUpdate.md) - [flamencoManager.JobsQuery](docs/JobsQuery.md) - [flamencoManager.JobsQueryResult](docs/JobsQueryResult.md) - [flamencoManager.ManagerConfiguration](docs/ManagerConfiguration.md) diff --git a/web/manager-api/docs/JobUpdate.md b/web/manager-api/docs/JobUpdate.md new file mode 100644 index 00000000..e8b0ed29 --- /dev/null +++ b/web/manager-api/docs/JobUpdate.md @@ -0,0 +1,12 @@ +# flamencoManager.JobUpdate + +## Properties + +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**id** | **String** | UUID of the Job | +**updated** | **Date** | Timestamp of last update | +**status** | [**JobStatus**](JobStatus.md) | | +**previous_status** | [**JobStatus**](JobStatus.md) | | [optional] + + diff --git a/web/manager-api/src/ApiClient.js b/web/manager-api/src/ApiClient.js index 84a07e58..a825aca7 100644 --- a/web/manager-api/src/ApiClient.js +++ b/web/manager-api/src/ApiClient.js @@ -55,7 +55,7 @@ class ApiClient { * @default {} */ this.defaultHeaders = { - 'User-Agent': 'Flamenco/c00cf8b0-dirty / webbrowser' + 'User-Agent': 'Flamenco/c875745b-dirty / webbrowser' }; /** diff --git a/web/manager-api/src/index.js b/web/manager-api/src/index.js index f4026665..4ff6bb2b 100644 --- a/web/manager-api/src/index.js +++ b/web/manager-api/src/index.js @@ -25,6 +25,7 @@ import FlamencoVersion from './model/FlamencoVersion'; import Job from './model/Job'; import JobAllOf from './model/JobAllOf'; import JobStatus from './model/JobStatus'; +import JobUpdate from './model/JobUpdate'; import JobsQuery from './model/JobsQuery'; import JobsQueryResult from './model/JobsQueryResult'; import ManagerConfiguration from './model/ManagerConfiguration'; @@ -162,6 +163,12 @@ export { */ JobStatus, + /** + * The JobUpdate model constructor. + * @property {module:model/JobUpdate} + */ + JobUpdate, + /** * The JobsQuery model constructor. * @property {module:model/JobsQuery} diff --git a/web/manager-api/src/model/JobUpdate.js b/web/manager-api/src/model/JobUpdate.js new file mode 100644 index 00000000..e7eae895 --- /dev/null +++ b/web/manager-api/src/model/JobUpdate.js @@ -0,0 +1,104 @@ +/** + * Flamenco manager + * Render Farm manager API + * + * The version of the OpenAPI document: 1.0.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + * + */ + +import ApiClient from '../ApiClient'; +import JobStatus from './JobStatus'; + +/** + * The JobUpdate model module. + * @module model/JobUpdate + * @version 0.0.0 + */ +class JobUpdate { + /** + * Constructs a new JobUpdate. + * @alias module:model/JobUpdate + * @param id {String} UUID of the Job + * @param updated {Date} Timestamp of last update + * @param status {module:model/JobStatus} + */ + constructor(id, updated, status) { + + JobUpdate.initialize(this, id, updated, status); + } + + /** + * Initializes the fields of this object. + * This method is used by the constructors of any subclasses, in order to implement multiple inheritance (mix-ins). + * Only for internal use. + */ + static initialize(obj, id, updated, status) { + obj['id'] = id; + obj['updated'] = updated; + obj['status'] = status; + } + + /** + * Constructs a JobUpdate from a plain JavaScript object, optionally creating a new instance. + * Copies all relevant properties from data to obj if supplied or a new instance if not. + * @param {Object} data The plain JavaScript object bearing properties of interest. + * @param {module:model/JobUpdate} obj Optional instance to populate. + * @return {module:model/JobUpdate} The populated JobUpdate instance. + */ + static constructFromObject(data, obj) { + if (data) { + obj = obj || new JobUpdate(); + + if (data.hasOwnProperty('id')) { + obj['id'] = ApiClient.convertToType(data['id'], 'String'); + } + if (data.hasOwnProperty('updated')) { + obj['updated'] = ApiClient.convertToType(data['updated'], 'Date'); + } + if (data.hasOwnProperty('status')) { + obj['status'] = JobStatus.constructFromObject(data['status']); + } + if (data.hasOwnProperty('previous_status')) { + obj['previous_status'] = JobStatus.constructFromObject(data['previous_status']); + } + } + return obj; + } + + +} + +/** + * UUID of the Job + * @member {String} id + */ +JobUpdate.prototype['id'] = undefined; + +/** + * Timestamp of last update + * @member {Date} updated + */ +JobUpdate.prototype['updated'] = undefined; + +/** + * @member {module:model/JobStatus} status + */ +JobUpdate.prototype['status'] = undefined; + +/** + * @member {module:model/JobStatus} previous_status + */ +JobUpdate.prototype['previous_status'] = undefined; + + + + + + +export default JobUpdate; +