diff --git a/apps/assets/const/host.py b/apps/assets/const/host.py index 6236379a5..371ab3688 100644 --- a/apps/assets/const/host.py +++ b/apps/assets/const/host.py @@ -67,6 +67,7 @@ class HostTypes(BaseType): return { cls.LINUX: [ {'name': 'Linux'}, + {'name': 'Gateway'} ], cls.UNIX: [ {'name': 'Unix'}, @@ -75,16 +76,31 @@ class HostTypes(BaseType): {'name': 'AIX', 'automation': { 'push_account_method': 'push_account_aix', 'change_secret_method': 'push_secret_aix' - }}, + }} ], cls.WINDOWS: [ {'name': 'Windows'}, - {'name': 'Windows-TLS', 'protocols_setting': { - 'rdp': {'security': 'tls'}, - }}, - {'name': 'Windows-RDP', 'protocols_setting': { - 'rdp': {'security': 'rdp'}, - }} + { + 'name': 'Windows-TLS', + 'protocols_setting': { + 'rdp': {'security': 'tls'}, + } + }, + { + 'name': 'Windows-RDP', + 'protocols_setting': { + 'rdp': {'security': 'rdp'}, + } + }, + { + 'name': 'RemoteAppHost', + '_protocols': ['rdp', 'ssh'], + 'protocols_setting': { + 'ssh': { + 'required': True + } + } + } ], cls.OTHER_HOST: [] } diff --git a/apps/assets/const/types.py b/apps/assets/const/types.py index 620527bfd..b77872ad0 100644 --- a/apps/assets/const/types.py +++ b/apps/assets/const/types.py @@ -224,7 +224,10 @@ class AllTypes(ChoicesMixin): if _protocols: protocols_data = [p for p in protocols_data if p['name'] in _protocols] for p in protocols_data: - p['setting'] = {**_protocols_setting.get(p['name'], {}), **p.get('setting', {})} + setting = _protocols_setting.get(p['name'], {}) + p['required'] = setting.pop('required', False) + p['default'] = setting.pop('default', False) + p['setting'] = {**setting, **p.get('setting', {})} platform_data = { **default_platform_data, **d, diff --git a/apps/assets/migrations/0108_auto_20221019_1706.py b/apps/assets/migrations/0108_auto_20221019_1706.py index f59a5b7a9..57279fffc 100644 --- a/apps/assets/migrations/0108_auto_20221019_1706.py +++ b/apps/assets/migrations/0108_auto_20221019_1706.py @@ -55,7 +55,7 @@ class Migration(migrations.Migration): migrations.AddField( model_name='changesecretautomation', name='secret_strategy', - field=models.CharField(choices=[('specific', 'Specific'), ('random_one', 'All assets use the same random password'), ('random_all', 'All assets use different random password')], default='random_one', max_length=16, verbose_name='Secret strategy'), + field=models.CharField(choices=[('specific', 'Specific'), ('random_one', 'All assets use the same random password'), ('random_all', 'All assets use different random password')], default='specific', max_length=16, verbose_name='Secret strategy'), ), migrations.AddField( model_name='changesecretautomation', diff --git a/apps/terminal/api/__init__.py b/apps/terminal/api/__init__.py index 16021a5ed..24c30fd5e 100644 --- a/apps/terminal/api/__init__.py +++ b/apps/terminal/api/__init__.py @@ -1,10 +1,5 @@ # -*- coding: utf-8 -*- # -from .terminal import * from .session import * -from .command import * -from .task import * -from .storage import * -from .status import * -from .sharing import * -from .endpoint import * +from .component import * +from .applet import * diff --git a/apps/terminal/api/applet/__init__.py b/apps/terminal/api/applet/__init__.py new file mode 100644 index 000000000..b2a4cac34 --- /dev/null +++ b/apps/terminal/api/applet/__init__.py @@ -0,0 +1,2 @@ +from .applet import * +from .host import * diff --git a/apps/terminal/api/applet/applet.py b/apps/terminal/api/applet/applet.py new file mode 100644 index 000000000..5d683fa7b --- /dev/null +++ b/apps/terminal/api/applet/applet.py @@ -0,0 +1,71 @@ +import os.path +import shutil +import zipfile + +import yaml +from django.core.files.storage import default_storage +from rest_framework import viewsets +from rest_framework.decorators import action +from rest_framework.response import Response + +from terminal import serializers, models +from terminal.serializers import AppletUploadSerializer + + +class AppletViewSet(viewsets.ModelViewSet): + queryset = models.Applet.objects.all() + serializer_class = serializers.AppletSerializer + rbac_perms = { + 'upload': 'terminal.add_applet', + } + + @action(detail=False, methods=['post'], serializer_class=AppletUploadSerializer) + def upload(self, request, *args, **kwargs): + serializer = self.get_serializer(data=request.data) + serializer.is_valid(raise_exception=True) + + file = serializer.validated_data['file'] + save_to = 'applets/{}'.format(file.name + '.tmp.zip') + if default_storage.exists(save_to): + default_storage.delete(save_to) + rel_path = default_storage.save(save_to, file) + + path = default_storage.path(rel_path) + extract_to = default_storage.path('applets/{}.tmp'.format(file.name)) + if os.path.exists(extract_to): + shutil.rmtree(extract_to) + + update = request.query_params.get('update') + with zipfile.ZipFile(path) as zp: + if zp.testzip() is not None: + return Response({'msg': 'Invalid Zip file'}, status=400) + zp.extractall(extract_to) + + tmp_dir = os.path.join(extract_to, file.name.replace('.zip', '')) + files = ['manifest.yml', 'icon.png', 'i18n.yml'] + for name in files: + path = os.path.join(tmp_dir, name) + if not os.path.exists(path): + return Response({'error': 'Missing file: {}'.format(path)}, status=400) + + with open(os.path.join(tmp_dir, 'manifest.yml')) as f: + manifest = yaml.safe_load(f) + + name = manifest.get('name', '') + instance = models.Applet.objects.filter(name=name).first() + if instance and not update: + return Response({'error': 'Applet already exists: {}'.format(name)}, status=400) + + serializer = serializers.AppletSerializer(data=manifest, instance=instance) + serializer.is_valid(raise_exception=True) + save_to = default_storage.path('applets/{}'.format(name)) + if os.path.exists(save_to): + shutil.rmtree(save_to) + shutil.move(tmp_dir, save_to) + serializer.save() + return Response(serializer.data, status=201) + + +class AppletPublicationViewSet(viewsets.ModelViewSet): + queryset = models.AppletPublication.objects.all() + serializer_class = serializers.AppletPublicationSerializer diff --git a/apps/terminal/api/applet/host.py b/apps/terminal/api/applet/host.py new file mode 100644 index 000000000..c3c301e30 --- /dev/null +++ b/apps/terminal/api/applet/host.py @@ -0,0 +1,16 @@ +from rest_framework import viewsets + +from terminal import serializers, models + +__all__ = ['AppletHostViewSet', 'AppletHostDeploymentViewSet'] + + +class AppletHostViewSet(viewsets.ModelViewSet): + queryset = models.AppletHost.objects.all() + serializer_class = serializers.AppletHostSerializer + + +class AppletHostDeploymentViewSet(viewsets.ModelViewSet): + queryset = models.AppletHostDeployment.objects.all() + serializer_class = serializers.AppletHostDeploymentSerializer + diff --git a/apps/terminal/api/component/__init__.py b/apps/terminal/api/component/__init__.py new file mode 100644 index 000000000..afefe0c18 --- /dev/null +++ b/apps/terminal/api/component/__init__.py @@ -0,0 +1,4 @@ +from .terminal import * +from .storage import * +from .status import * +from .endpoint import * diff --git a/apps/terminal/api/endpoint.py b/apps/terminal/api/component/endpoint.py similarity index 97% rename from apps/terminal/api/endpoint.py rename to apps/terminal/api/component/endpoint.py index f864c6d13..364cd803e 100644 --- a/apps/terminal/api/endpoint.py +++ b/apps/terminal/api/component/endpoint.py @@ -2,15 +2,15 @@ from rest_framework.decorators import action from rest_framework.response import Response from rest_framework import status from rest_framework.request import Request + from common.drf.api import JMSBulkModelViewSet +from common.permissions import IsValidUserOrConnectionToken from django.utils.translation import ugettext_lazy as _ from django.shortcuts import get_object_or_404 from assets.models import Asset from orgs.utils import tmp_to_root_org -from terminal.models import Session -from ..models import Endpoint, EndpointRule -from .. import serializers -from common.permissions import IsValidUserOrConnectionToken +from terminal.models import Session, Endpoint, EndpointRule +from terminal import serializers __all__ = ['EndpointViewSet', 'EndpointRuleViewSet'] diff --git a/apps/terminal/api/status.py b/apps/terminal/api/component/status.py similarity index 93% rename from apps/terminal/api/status.py rename to apps/terminal/api/component/status.py index 3ec00436b..5e27926ea 100644 --- a/apps/terminal/api/status.py +++ b/apps/terminal/api/component/status.py @@ -9,9 +9,9 @@ from rest_framework import viewsets, generics from rest_framework.views import Response from rest_framework import status -from ..models import Terminal, Status, Session -from .. import serializers -from ..utils import TypedComponentsStatusMetricsUtil +from terminal.models import Terminal, Status, Session +from terminal import serializers +from terminal.utils import TypedComponentsStatusMetricsUtil logger = logging.getLogger(__file__) diff --git a/apps/terminal/api/storage.py b/apps/terminal/api/component/storage.py similarity index 97% rename from apps/terminal/api/storage.py rename to apps/terminal/api/component/storage.py index d9694c337..d46b6f91f 100644 --- a/apps/terminal/api/storage.py +++ b/apps/terminal/api/component/storage.py @@ -11,8 +11,8 @@ from django_filters import utils from terminal import const from common.const.http import GET from terminal.filters import CommandStorageFilter, CommandFilter, CommandFilterForStorageTree -from ..models import CommandStorage, ReplayStorage -from ..serializers import CommandStorageSerializer, ReplayStorageSerializer +from terminal.models import CommandStorage, ReplayStorage +from terminal.serializers import CommandStorageSerializer, ReplayStorageSerializer __all__ = [ 'CommandStorageViewSet', 'CommandStorageTestConnectiveApi', diff --git a/apps/terminal/api/terminal.py b/apps/terminal/api/component/terminal.py similarity index 97% rename from apps/terminal/api/terminal.py rename to apps/terminal/api/component/terminal.py index 209492baa..a58181bf4 100644 --- a/apps/terminal/api/terminal.py +++ b/apps/terminal/api/component/terminal.py @@ -14,9 +14,9 @@ from common.exceptions import JMSException from common.drf.api import JMSBulkModelViewSet from common.utils import get_object_or_none, get_request_ip from common.permissions import WithBootstrapToken -from ..models import Terminal -from .. import serializers -from .. import exceptions +from terminal.models import Terminal +from terminal import serializers +from terminal import exceptions __all__ = [ 'TerminalViewSet', 'TerminalConfig', diff --git a/apps/terminal/api/session/__init__.py b/apps/terminal/api/session/__init__.py new file mode 100644 index 000000000..a046d4b3d --- /dev/null +++ b/apps/terminal/api/session/__init__.py @@ -0,0 +1,4 @@ +from .session import * +from .sharing import * +from .command import * +from .task import * diff --git a/apps/terminal/api/command.py b/apps/terminal/api/session/command.py similarity index 98% rename from apps/terminal/api/command.py rename to apps/terminal/api/session/command.py index 5b60a114a..fec0848a5 100644 --- a/apps/terminal/api/command.py +++ b/apps/terminal/api/session/command.py @@ -13,11 +13,11 @@ from common.drf.api import JMSBulkModelViewSet from common.utils import get_logger from terminal.backends.command.serializers import InsecureCommandAlertSerializer from terminal.exceptions import StorageInvalid -from ..backends import ( +from terminal.backends import ( get_command_storage, get_multi_command_storage, SessionCommandSerializer, ) -from ..notifications import CommandAlertMessage +from terminal.notifications import CommandAlertMessage logger = get_logger(__name__) __all__ = ['CommandViewSet', 'InsecureCommandAlertAPI'] diff --git a/apps/terminal/api/session.py b/apps/terminal/api/session/session.py similarity index 96% rename from apps/terminal/api/session.py rename to apps/terminal/api/session/session.py index 25477166c..fe742024d 100644 --- a/apps/terminal/api/session.py +++ b/apps/terminal/api/session/session.py @@ -24,15 +24,16 @@ from common.drf.renders import PassthroughRenderer from orgs.mixins.api import OrgBulkModelViewSet from orgs.utils import tmp_to_root_org, tmp_to_org from users.models import User -from .. import utils -from ..utils import find_session_replay_local, download_session_replay -from ..models import Session -from .. import serializers -from terminal.utils import is_session_approver +from terminal.utils import ( + find_session_replay_local, download_session_replay, + is_session_approver, get_session_replay_url +) +from terminal.models import Session +from terminal import serializers __all__ = [ - 'SessionViewSet', 'SessionReplayViewSet', 'SessionJoinValidateAPI', - 'MySessionAPIView', + 'SessionViewSet', 'SessionReplayViewSet', + 'SessionJoinValidateAPI', 'MySessionAPIView', ] logger = get_logger(__name__) @@ -93,7 +94,7 @@ class SessionViewSet(OrgBulkModelViewSet): url_name='replay-download') def download(self, request, *args, **kwargs): session = self.get_object() - local_path, url = utils.get_session_replay_url(session) + local_path, url = get_session_replay_url(session) if local_path is None: return Response({"error": url}, status=404) file = self.prepare_offline_file(session, local_path) diff --git a/apps/terminal/api/sharing.py b/apps/terminal/api/session/sharing.py similarity index 97% rename from apps/terminal/api/sharing.py rename to apps/terminal/api/session/sharing.py index 1a9545e54..a3d324205 100644 --- a/apps/terminal/api/sharing.py +++ b/apps/terminal/api/session/sharing.py @@ -5,9 +5,8 @@ from django.conf import settings from django.utils.translation import ugettext_lazy as _ from common.const.http import PATCH -from common.permissions import IsValidUser from orgs.mixins.api import OrgModelViewSet -from .. import serializers, models +from terminal import serializers, models __all__ = ['SessionSharingViewSet', 'SessionJoinRecordsViewSet'] diff --git a/apps/terminal/api/task.py b/apps/terminal/api/session/task.py similarity index 96% rename from apps/terminal/api/task.py rename to apps/terminal/api/session/task.py index c7e1a2681..80fee6097 100644 --- a/apps/terminal/api/task.py +++ b/apps/terminal/api/session/task.py @@ -7,10 +7,10 @@ from rest_framework import status from rest_framework.permissions import IsAuthenticated from common.utils import get_object_or_none -from ..models import Session, Task -from .. import serializers -from terminal.utils import is_session_approver from orgs.utils import tmp_to_root_org +from terminal.models import Session, Task +from terminal import serializers +from terminal.utils import is_session_approver __all__ = ['TaskViewSet', 'KillSessionAPI', 'KillSessionForTicketAPI'] logger = logging.getLogger(__file__) diff --git a/apps/terminal/migrations/0054_auto_20221021_1433.py b/apps/terminal/migrations/0054_auto_20221024_1452.py similarity index 80% rename from apps/terminal/migrations/0054_auto_20221021_1433.py rename to apps/terminal/migrations/0054_auto_20221024_1452.py index 830ef897c..3ee883a8b 100644 --- a/apps/terminal/migrations/0054_auto_20221021_1433.py +++ b/apps/terminal/migrations/0054_auto_20221024_1452.py @@ -1,4 +1,4 @@ -# Generated by Django 3.2.14 on 2022-10-21 06:33 +# Generated by Django 3.2.14 on 2022-10-24 06:52 from django.db import migrations, models import django.db.models.deletion @@ -8,6 +8,7 @@ import uuid class Migration(migrations.Migration): dependencies = [ + ('assets', '0110_auto_20221021_1506'), ('terminal', '0053_auto_20220830_1244'), ] @@ -22,10 +23,13 @@ class Migration(migrations.Migration): ('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)), ('name', models.CharField(max_length=128, unique=True, verbose_name='Name')), ('version', models.CharField(max_length=16, verbose_name='Version')), - ('type', models.CharField(choices=[('app', 'App'), ('web', 'Web')], max_length=16, verbose_name='Type')), - ('icon', models.ImageField(upload_to='applet/icon', verbose_name='Icon')), ('author', models.CharField(max_length=128, verbose_name='Author')), + ('type', models.CharField(choices=[('general', 'General'), ('web', 'Web')], default='general', max_length=16, verbose_name='Type')), + ('path', models.FilePathField(verbose_name='Path')), + ('vcs_type', models.CharField(max_length=16, null=True, verbose_name='VCS type')), + ('vcs_url', models.CharField(max_length=256, null=True, verbose_name='URL')), ('protocols', models.JSONField(default=list, verbose_name='Protocol')), + ('tags', models.JSONField(default=list, verbose_name='Tags')), ('comment', models.TextField(blank=True, default='', verbose_name='Comment')), ], options={ @@ -33,14 +37,13 @@ class Migration(migrations.Migration): }, ), migrations.CreateModel( - name='AppletProvider', + name='AppletHost', fields=[ ('created_by', models.CharField(blank=True, max_length=32, null=True, verbose_name='Created by')), ('updated_by', models.CharField(blank=True, max_length=32, null=True, verbose_name='Updated by')), ('date_created', models.DateTimeField(auto_now_add=True, null=True, verbose_name='Date created')), ('date_updated', models.DateTimeField(auto_now=True, verbose_name='Date updated')), ('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)), - ('name', models.CharField(max_length=128, unique=True, verbose_name='Name')), ('comment', models.TextField(blank=True, default='', verbose_name='Comment')), ('account_automation', models.BooleanField(default=False, verbose_name='Account automation')), ('date_synced', models.DateTimeField(blank=True, null=True, verbose_name='Date synced')), @@ -50,22 +53,6 @@ class Migration(migrations.Migration): 'abstract': False, }, ), - migrations.CreateModel( - name='ProviderDeployment', - fields=[ - ('created_by', models.CharField(blank=True, max_length=32, null=True, verbose_name='Created by')), - ('updated_by', models.CharField(blank=True, max_length=32, null=True, verbose_name='Updated by')), - ('date_created', models.DateTimeField(auto_now_add=True, null=True, verbose_name='Date created')), - ('date_updated', models.DateTimeField(auto_now=True, verbose_name='Date updated')), - ('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)), - ('status', models.CharField(max_length=16, verbose_name='Status')), - ('comment', models.TextField(blank=True, default='', verbose_name='Comment')), - ('provider', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='terminal.appletprovider', verbose_name='Provider')), - ], - options={ - 'abstract': False, - }, - ), migrations.CreateModel( name='AppletPublication', fields=[ @@ -77,20 +64,36 @@ class Migration(migrations.Migration): ('status', models.CharField(max_length=16, verbose_name='Status')), ('comment', models.TextField(blank=True, default='', verbose_name='Comment')), ('applet', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='terminal.applet', verbose_name='Applet')), - ('provider', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='terminal.appletprovider', verbose_name='Provider')), + ('host', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='terminal.applethost', verbose_name='Host')), ], options={ - 'unique_together': {('applet', 'provider')}, + 'unique_together': {('applet', 'host')}, + }, + ), + migrations.CreateModel( + name='AppletHostDeployment', + fields=[ + ('created_by', models.CharField(blank=True, max_length=32, null=True, verbose_name='Created by')), + ('updated_by', models.CharField(blank=True, max_length=32, null=True, verbose_name='Updated by')), + ('date_created', models.DateTimeField(auto_now_add=True, null=True, verbose_name='Date created')), + ('date_updated', models.DateTimeField(auto_now=True, verbose_name='Date updated')), + ('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)), + ('status', models.CharField(max_length=16, verbose_name='Status')), + ('comment', models.TextField(blank=True, default='', verbose_name='Comment')), + ('host', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='terminal.applethost', verbose_name='Hosting')), + ], + options={ + 'abstract': False, }, ), migrations.AddField( - model_name='appletprovider', + model_name='applethost', name='applets', field=models.ManyToManyField(through='terminal.AppletPublication', to='terminal.Applet', verbose_name='Applet'), ), migrations.AddField( - model_name='appletprovider', - name='asset', - field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='assets.asset', verbose_name='Asset'), + model_name='applethost', + name='host', + field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='assets.host', verbose_name='Host'), ), ] diff --git a/apps/terminal/models/applet/__init__.py b/apps/terminal/models/applet/__init__.py index daef278e0..b2a4cac34 100644 --- a/apps/terminal/models/applet/__init__.py +++ b/apps/terminal/models/applet/__init__.py @@ -1,2 +1,2 @@ from .applet import * -from .provider import * +from .host import * diff --git a/apps/terminal/models/applet/applet.py b/apps/terminal/models/applet/applet.py index 4fc16819f..510d8da35 100644 --- a/apps/terminal/models/applet/applet.py +++ b/apps/terminal/models/applet/applet.py @@ -1,3 +1,7 @@ +import yaml +import os.path +from rest_framework.exceptions import ValidationError + from django.db import models from django.utils.translation import gettext_lazy as _ @@ -9,25 +13,69 @@ __all__ = ['Applet', 'AppletPublication'] class Applet(JMSBaseModel): class Type(models.TextChoices): - app = 'app', _('App') + general = 'general', _('General') web = 'web', _('Web') + + class VCSType(models.TextChoices): + manual = 'manual', _('Manual') + git = 'git', _('Git') + archive = 'archive', _('Remote gzip') + name = models.CharField(max_length=128, verbose_name=_('Name'), unique=True) version = models.CharField(max_length=16, verbose_name=_('Version')) - type = models.CharField(max_length=16, choices=Type.choices, verbose_name=_('Type')) - icon = models.ImageField(upload_to='applet/icon', verbose_name=_('Icon')) author = models.CharField(max_length=128, verbose_name=_('Author')) + path = models.FilePathField(verbose_name=_('Path')) + type = models.CharField(max_length=16, verbose_name=_('Type'), default='general', choices=Type.choices) + vcs_type = models.CharField(max_length=16, verbose_name=_('VCS type'), null=True) + vcs_url = models.CharField(max_length=256, verbose_name=_('URL'), null=True) protocols = models.JSONField(default=list, verbose_name=_('Protocol')) + tags = models.JSONField(default=list, verbose_name=_('Tags')) comment = models.TextField(default='', blank=True, verbose_name=_('Comment')) def __str__(self): return self.name + @property + def manifest(self): + path = os.path.join(self.path, 'manifest.yml') + if not os.path.exists(path): + return None + with open(path, 'r') as f: + return yaml.safe_load(f) + + @property + def icon(self): + path = os.path.join(self.path, 'icon.png') + if not os.path.exists(path): + return None + with open(path, 'rb') as f: + return f.read() + + @classmethod + def validate_manifest(cls, manifest): + fields = ['name', 'display_name', 'version', 'author', 'type', 'tags', 'protocols'] + for field in fields: + if field not in manifest: + raise ValidationError(f'Missing field {field}') + if manifest['type'] not in [i[0] for i in cls.Type.choices]: + raise ValidationError('Invalid type') + if not isinstance(manifest['protocols'], list): + raise ValidationError('Invalid protocols') + + @classmethod + def create_by_manifest(cls, manifest): + obj = cls() + for k, v in manifest.items(): + setattr(obj, k, v) + obj.save() + return obj + class AppletPublication(JMSBaseModel): applet = models.ForeignKey('Applet', on_delete=models.PROTECT, verbose_name=_('Applet')) - provider = models.ForeignKey('AppletProvider', on_delete=models.PROTECT, verbose_name=_('Provider')) + host = models.ForeignKey('AppletHost', on_delete=models.PROTECT, verbose_name=_('Host')) status = models.CharField(max_length=16, verbose_name=_('Status')) comment = models.TextField(default='', blank=True, verbose_name=_('Comment')) class Meta: - unique_together = ('applet', 'provider') + unique_together = ('applet', 'host') diff --git a/apps/terminal/models/applet/provider.py b/apps/terminal/models/applet/host.py similarity index 57% rename from apps/terminal/models/applet/provider.py rename to apps/terminal/models/applet/host.py index dac90850d..89b0bd576 100644 --- a/apps/terminal/models/applet/provider.py +++ b/apps/terminal/models/applet/host.py @@ -1,31 +1,34 @@ from django.db import models from django.utils.translation import gettext_lazy as _ -from celery import current_app - from common.db.models import JMSBaseModel -__all__ = ['AppletProvider', 'ProviderDeployment'] +__all__ = ['AppletHost', 'AppletHostDeployment'] -class AppletProvider(JMSBaseModel): - name = models.CharField(max_length=128, verbose_name=_('Name'), unique=True) - asset = models.ForeignKey('assets.Asset', on_delete=models.PROTECT, verbose_name=_('Asset')) +class AppletHost(JMSBaseModel): + host = models.ForeignKey('assets.Host', on_delete=models.PROTECT, verbose_name=_('Host')) comment = models.TextField(default='', blank=True, verbose_name=_('Comment')) account_automation = models.BooleanField(default=False, verbose_name=_('Account automation')) date_synced = models.DateTimeField(null=True, blank=True, verbose_name=_('Date synced')) status = models.CharField(max_length=16, verbose_name=_('Status')) applets = models.ManyToManyField( 'Applet', verbose_name=_('Applet'), - through='AppletPublication', through_fields=('provider', 'applet'), + through='AppletPublication', through_fields=('host', 'applet'), ) + def __str__(self): + return self.host.name -class ProviderDeployment(JMSBaseModel): - provider = models.ForeignKey('AppletProvider', on_delete=models.CASCADE, verbose_name=_('Provider')) + +class AppletHostDeployment(JMSBaseModel): + host = models.ForeignKey('AppletHost', on_delete=models.CASCADE, verbose_name=_('Hosting')) status = models.CharField(max_length=16, verbose_name=_('Status')) comment = models.TextField(default='', blank=True, verbose_name=_('Comment')) - def install(self): + def __str__(self): + return self.host + + def start(self): pass diff --git a/apps/terminal/serializers/__init__.py b/apps/terminal/serializers/__init__.py index e1312ebae..c14d4fba3 100644 --- a/apps/terminal/serializers/__init__.py +++ b/apps/terminal/serializers/__init__.py @@ -5,3 +5,4 @@ from .session import * from .storage import * from .sharing import * from .endpoint import * +from .applet import * diff --git a/apps/terminal/serializers/applet.py b/apps/terminal/serializers/applet.py new file mode 100644 index 000000000..3f3919776 --- /dev/null +++ b/apps/terminal/serializers/applet.py @@ -0,0 +1,65 @@ +from rest_framework import serializers + +from common.drf.fields import ObjectRelatedField +from assets.models import Host +from ..models import Applet, AppletPublication, AppletHost, AppletHostDeployment + + +__all__ = [ + 'AppletSerializer', 'AppletPublicationSerializer', + 'AppletHostSerializer', 'AppletHostDeploymentSerializer', + 'AppletUploadSerializer' +] + + +class AppletSerializer(serializers.ModelSerializer): + class Meta: + model = Applet + fields_mini = ['id', 'name'] + read_only_fields = [ + 'date_created', 'date_updated' + ] + fields = fields_mini + [ + 'version', 'author', 'type', 'protocols', 'comment' + ] + read_only_fields + + +class AppletUploadSerializer(serializers.Serializer): + file = serializers.FileField() + + +class AppletPublicationSerializer(serializers.ModelSerializer): + applet = ObjectRelatedField(queryset=Applet.objects.all()) + host = ObjectRelatedField(queryset=AppletHost.objects.all()) + + class Meta: + model = AppletPublication + fields_mini = ['id', 'applet', 'host'] + read_only_fields = ['date_created', 'date_updated'] + fields = fields_mini + [ + 'status', 'comment', + ] + read_only_fields + + +class AppletHostSerializer(serializers.ModelSerializer): + host = ObjectRelatedField(queryset=Host.objects.all()) + + class Meta: + model = AppletHost + fields_mini = ['id', 'host'] + read_only_fields = ['date_created', 'date_updated'] + fields = fields_mini + [ + 'comment', 'account_automation', 'date_synced', 'status', + ] + read_only_fields + + +class AppletHostDeploymentSerializer(serializers.ModelSerializer): + host = ObjectRelatedField(queryset=AppletHost.objects.all()) + + class Meta: + model = AppletHostDeployment + fields_mini = ['id', 'host'] + read_only_fields = ['date_created', 'date_updated'] + fields = fields_mini + [ + 'status', 'comment', + ] + read_only_fields diff --git a/apps/terminal/urls/api_urls.py b/apps/terminal/urls/api_urls.py index 3f0445350..8bed4f604 100644 --- a/apps/terminal/urls/api_urls.py +++ b/apps/terminal/urls/api_urls.py @@ -24,6 +24,11 @@ router.register(r'session-sharings', api.SessionSharingViewSet, 'session-sharing router.register(r'session-join-records', api.SessionJoinRecordsViewSet, 'session-sharing-record') router.register(r'endpoints', api.EndpointViewSet, 'endpoint') router.register(r'endpoint-rules', api.EndpointRuleViewSet, 'endpoint-rule') +router.register(r'applets', api.AppletViewSet, 'applet') +router.register(r'applet-hosts', api.AppletHostViewSet, 'applet-host') +router.register(r'applet-publication', api.AppletPublicationViewSet, 'applet-publication') +router.register(r'applet-host-deployment', api.AppletHostDeploymentViewSet, 'applet-host-deployment') + urlpatterns = [ path('my-sessions/', api.MySessionAPIView.as_view(), name='my-session'),