diff --git a/apps/accounts/const/account.py b/apps/accounts/const/account.py index 10a681969..9a3541001 100644 --- a/apps/accounts/const/account.py +++ b/apps/accounts/const/account.py @@ -7,6 +7,7 @@ class SecretType(TextChoices): SSH_KEY = 'ssh_key', _('SSH key') ACCESS_KEY = 'access_key', _('Access key') TOKEN = 'token', _('Token') + API_KEY = 'api_key', _("API key") class AliasAccount(TextChoices): diff --git a/apps/accounts/migrations/0001_initial.py b/apps/accounts/migrations/0001_initial.py index b8fe35670..97ae584c4 100644 --- a/apps/accounts/migrations/0001_initial.py +++ b/apps/accounts/migrations/0001_initial.py @@ -1,12 +1,14 @@ # Generated by Django 3.2.14 on 2022-12-28 07:29 +import uuid + +import django.db.models.deletion +import simple_history.models +from django.conf import settings +from django.db import migrations, models + import common.db.encoder import common.db.fields -from django.conf import settings -from django.db import migrations, models -import django.db.models.deletion -import simple_history.models -import uuid class Migration(migrations.Migration): @@ -29,13 +31,16 @@ class Migration(migrations.Migration): ('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)), ('org_id', models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization')), - ('connectivity', models.CharField(choices=[('-', 'Unknown'), ('ok', 'Ok'), ('err', 'Error')], default='-', max_length=16, verbose_name='Connectivity')), + ('connectivity', + models.CharField(choices=[('-', 'Unknown'), ('ok', 'Ok'), ('err', 'Error')], default='-', + max_length=16, verbose_name='Connectivity')), ('date_verified', models.DateTimeField(null=True, verbose_name='Date verified')), ('name', models.CharField(max_length=128, verbose_name='Name')), ('username', models.CharField(blank=True, db_index=True, max_length=128, verbose_name='Username')), ('secret_type', models.CharField( choices=[('password', 'Password'), ('ssh_key', 'SSH key'), ('access_key', 'Access key'), - ('token', 'Token')], default='password', max_length=16, verbose_name='Secret type')), + ('token', 'Token'), ('api_key', 'API key')], default='password', max_length=16, + verbose_name='Secret type')), ('secret', common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='Secret')), ('privileged', models.BooleanField(default=False, verbose_name='Privileged')), ('is_active', models.BooleanField(default=True, verbose_name='Is active')), @@ -61,7 +66,8 @@ class Migration(migrations.Migration): ('id', models.UUIDField(db_index=True, default=uuid.uuid4)), ('secret_type', models.CharField( choices=[('password', 'Password'), ('ssh_key', 'SSH key'), ('access_key', 'Access key'), - ('token', 'Token')], default='password', max_length=16, verbose_name='Secret type')), + ('token', 'Token'), ('api_key', 'API key')], default='password', max_length=16, + verbose_name='Secret type')), ('secret', common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='Secret')), ('version', models.IntegerField(default=0, verbose_name='Version')), ('history_id', models.AutoField(primary_key=True, serialize=False)), @@ -96,7 +102,8 @@ class Migration(migrations.Migration): ('username', models.CharField(blank=True, db_index=True, max_length=128, verbose_name='Username')), ('secret_type', models.CharField( choices=[('password', 'Password'), ('ssh_key', 'SSH key'), ('access_key', 'Access key'), - ('token', 'Token')], default='password', max_length=16, verbose_name='Secret type')), + ('token', 'Token'), ('api_key', 'API key')], default='password', max_length=16, + verbose_name='Secret type')), ('secret', common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='Secret')), ('privileged', models.BooleanField(default=False, verbose_name='Privileged')), ('is_active', models.BooleanField(default=True, verbose_name='Is active')), diff --git a/apps/accounts/migrations/0003_automation.py b/apps/accounts/migrations/0003_automation.py index 503c766af..a341e18b2 100644 --- a/apps/accounts/migrations/0003_automation.py +++ b/apps/accounts/migrations/0003_automation.py @@ -1,11 +1,13 @@ # Generated by Django 3.2.16 on 2022-12-30 08:08 +import uuid + +import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models + import common.db.encoder import common.db.fields -from django.conf import settings -from django.db import migrations, models -import django.db.models.deletion -import uuid class Migration(migrations.Migration): @@ -53,7 +55,8 @@ class Migration(migrations.Migration): primary_key=True, serialize=False, to='assets.baseautomation')), ('secret_type', models.CharField( choices=[('password', 'Password'), ('ssh_key', 'SSH key'), ('access_key', 'Access key'), - ('token', 'Token')], default='password', max_length=16, verbose_name='Secret type')), + ('token', 'Token'), ('api_key', 'API key')], default='password', max_length=16, + verbose_name='Secret type')), ('secret_strategy', models.CharField(choices=[('specific', 'Specific password'), ('random_one', 'All assets use the same random password'), ('random_all', @@ -156,7 +159,8 @@ class Migration(migrations.Migration): primary_key=True, serialize=False, to='assets.baseautomation')), ('secret_type', models.CharField( choices=[('password', 'Password'), ('ssh_key', 'SSH key'), ('access_key', 'Access key'), - ('token', 'Token')], default='password', max_length=16, verbose_name='Secret type')), + ('token', 'Token'), ('api_key', 'API key')], default='password', max_length=16, + verbose_name='Secret type')), ('secret_strategy', models.CharField(choices=[('specific', 'Specific password'), ('random_one', 'All assets use the same random password'), ('random_all', diff --git a/apps/assets/api/asset/__init__.py b/apps/assets/api/asset/__init__.py index 0f1d81825..75c314df7 100644 --- a/apps/assets/api/asset/__init__.py +++ b/apps/assets/api/asset/__init__.py @@ -3,6 +3,7 @@ from .cloud import * from .custom import * from .database import * from .device import * +from .gpt import * from .host import * from .permission import * from .web import * diff --git a/apps/assets/api/asset/gpt.py b/apps/assets/api/asset/gpt.py new file mode 100644 index 000000000..ef9953a41 --- /dev/null +++ b/apps/assets/api/asset/gpt.py @@ -0,0 +1,16 @@ +from assets.models import GPT, Asset +from assets.serializers import GPTSerializer + +from .asset import AssetViewSet + +__all__ = ['GPTViewSet'] + + +class GPTViewSet(AssetViewSet): + model = GPT + perm_model = Asset + + def get_serializer_classes(self): + serializer_classes = super().get_serializer_classes() + serializer_classes['default'] = GPTSerializer + return serializer_classes diff --git a/apps/assets/const/base.py b/apps/assets/const/base.py index 338e02bb0..66c991caa 100644 --- a/apps/assets/const/base.py +++ b/apps/assets/const/base.py @@ -56,7 +56,7 @@ class BaseType(TextChoices): for k, v in cls.get_choices(): tp_base = {**base_default, **base.get(k, {})} tp_auto = {**automation_default, **automation.get(k, {})} - tp_protocols = {**protocols_default, **protocols.get(k, {})} + tp_protocols = {**protocols_default, **{'port_from_addr': False}, **protocols.get(k, {})} tp_protocols = cls._parse_protocols(tp_protocols, k) tp_constrains = {**tp_base, 'protocols': tp_protocols, 'automation': tp_auto} constrains[k] = tp_constrains diff --git a/apps/assets/const/category.py b/apps/assets/const/category.py index 8c4d387d8..9ccbb134e 100644 --- a/apps/assets/const/category.py +++ b/apps/assets/const/category.py @@ -12,6 +12,7 @@ class Category(ChoicesMixin, models.TextChoices): DATABASE = 'database', _("Database") CLOUD = 'cloud', _("Cloud service") WEB = 'web', _("Web") + GPT = 'gpt', "GPT" CUSTOM = 'custom', _("Custom type") @classmethod diff --git a/apps/assets/const/gpt.py b/apps/assets/const/gpt.py new file mode 100644 index 000000000..65d01ee97 --- /dev/null +++ b/apps/assets/const/gpt.py @@ -0,0 +1,54 @@ +from django.utils.translation import gettext_lazy as _ + +from .base import BaseType + + +class GPTTypes(BaseType): + CHATGPT = 'chatgpt', _('ChatGPT') + + @classmethod + def _get_base_constrains(cls) -> dict: + return { + '*': { + 'charset_enabled': False, + 'domain_enabled': False, + 'su_enabled': False, + } + } + + @classmethod + def _get_automation_constrains(cls) -> dict: + constrains = { + '*': { + 'ansible_enabled': False, + 'ping_enabled': False, + 'gather_facts_enabled': False, + 'verify_account_enabled': False, + 'change_secret_enabled': False, + 'push_account_enabled': False, + 'gather_accounts_enabled': False, + } + } + return constrains + + @classmethod + def _get_protocol_constrains(cls) -> dict: + return { + '*': { + 'choices': '__self__', + } + } + + @classmethod + def internal_platforms(cls): + return { + cls.CHATGPT: [ + {'name': 'ChatGPT'} + ], + } + + @classmethod + def get_community_types(cls): + return [ + cls.CHATGPT, + ] diff --git a/apps/assets/const/protocol.py b/apps/assets/const/protocol.py index ecb173f86..2f4f9998f 100644 --- a/apps/assets/const/protocol.py +++ b/apps/assets/const/protocol.py @@ -2,6 +2,7 @@ from django.db import models from django.utils.translation import gettext_lazy as _ from common.db.models import ChoicesMixin +from common.decorators import cached_method from .base import FillType __all__ = ['Protocol'] @@ -26,6 +27,8 @@ class Protocol(ChoicesMixin, models.TextChoices): k8s = 'k8s', 'K8S' http = 'http', 'HTTP(s)' + chatgpt = 'chatgpt', 'ChatGPT' + @classmethod def device_protocols(cls): return { @@ -149,13 +152,14 @@ class Protocol(ChoicesMixin, models.TextChoices): return { cls.k8s: { 'port': 443, + 'port_from_addr': True, 'required': True, 'secret_types': ['token'], }, cls.http: { 'port': 80, + 'port_from_addr': True, 'secret_types': ['password'], - 'label': 'HTTP(s)', 'setting': { 'autofill': { 'type': 'choice', @@ -182,11 +186,37 @@ class Protocol(ChoicesMixin, models.TextChoices): } @classmethod + def gpt_protocols(cls): + return { + cls.chatgpt: { + 'port': 443, + 'required': True, + 'port_from_addr': True, + 'secret_types': ['api_key'], + 'setting': { + 'api_mode': { + 'type': 'choice', + 'default': 'gpt-3.5-turbo', + 'label': _('API mode'), + 'choices': [ + ('gpt-3.5-turbo', 'GPT-3.5 Turbo'), + ('gpt-3.5-turbo-16k', 'GPT-3.5 Turbo 16K'), + ('gpt-4', 'GPT-4'), + ('gpt-4-32k', 'GPT-4 32K'), + ] + } + } + } + } + + @classmethod + @cached_method(ttl=600) def settings(cls): return { **cls.device_protocols(), **cls.database_protocols(), - **cls.cloud_protocols() + **cls.cloud_protocols(), + **cls.gpt_protocols(), } @classmethod diff --git a/apps/assets/const/types.py b/apps/assets/const/types.py index e9c3ae790..afa0f6d05 100644 --- a/apps/assets/const/types.py +++ b/apps/assets/const/types.py @@ -10,6 +10,7 @@ from .cloud import CloudTypes from .custom import CustomTypes from .database import DatabaseTypes from .device import DeviceTypes +from .gpt import GPTTypes from .host import HostTypes from .web import WebTypes @@ -18,7 +19,7 @@ class AllTypes(ChoicesMixin): choices: list includes = [ HostTypes, DeviceTypes, DatabaseTypes, - CloudTypes, WebTypes, CustomTypes + CloudTypes, WebTypes, CustomTypes, GPTTypes ] _category_constrains = {} @@ -147,6 +148,7 @@ class AllTypes(ChoicesMixin): (Category.DATABASE, DatabaseTypes), (Category.CLOUD, CloudTypes), (Category.WEB, WebTypes), + (Category.GPT, GPTTypes), (Category.CUSTOM, CustomTypes), ) diff --git a/apps/assets/migrations/0120_auto_20230630_1613.py b/apps/assets/migrations/0120_auto_20230630_1613.py new file mode 100644 index 000000000..aa884a217 --- /dev/null +++ b/apps/assets/migrations/0120_auto_20230630_1613.py @@ -0,0 +1,39 @@ +# Generated by Django 3.2.19 on 2023-06-30 08:13 + +import django.db.models.deletion +from django.db import migrations, models + + +def add_chatgpt_platform(apps, schema_editor): + platform_cls = apps.get_model('assets', 'Platform') + automation_cls = apps.get_model('assets', 'PlatformAutomation') + platform = platform_cls.objects.create( + name='ChatGPT', internal=True, category='gpt', type='chatgpt', + domain_enabled=False, su_enabled=False, comment='ChatGPT', + created_by='System', updated_by='System', + ) + platform.protocols.create(name='chatgpt', port=443, primary=True) + automation_cls.objects.create(ansible_enabled=False, platform=platform) + + +class Migration(migrations.Migration): + dependencies = [ + ('assets', '0119_assets_add_default_node'), + ] + + operations = [ + migrations.CreateModel( + name='GPT', + fields=[ + ('asset_ptr', + models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, + primary_key=True, serialize=False, to='assets.asset')), + ('proxy', models.CharField(blank=True, default='', max_length=128, verbose_name='Proxy')), + ], + options={ + 'verbose_name': 'Web', + }, + bases=('assets.asset',), + ), + migrations.RunPython(add_chatgpt_platform) + ] diff --git a/apps/assets/models/asset/__init__.py b/apps/assets/models/asset/__init__.py index 0004bfbb5..7541f2f2e 100644 --- a/apps/assets/models/asset/__init__.py +++ b/apps/assets/models/asset/__init__.py @@ -3,5 +3,6 @@ from .common import * from .custom import * from .database import * from .device import * +from .gpt import * from .host import * from .web import * diff --git a/apps/assets/models/asset/gpt.py b/apps/assets/models/asset/gpt.py new file mode 100644 index 000000000..4522dfe93 --- /dev/null +++ b/apps/assets/models/asset/gpt.py @@ -0,0 +1,11 @@ +from django.db import models +from django.utils.translation import gettext_lazy as _ + +from .common import Asset + + +class GPT(Asset): + proxy = models.CharField(max_length=128, blank=True, default='', verbose_name=_("Proxy")) + + class Meta: + verbose_name = _("Web") diff --git a/apps/assets/models/platform.py b/apps/assets/models/platform.py index f50177642..8fed01acf 100644 --- a/apps/assets/models/platform.py +++ b/apps/assets/models/platform.py @@ -8,6 +8,8 @@ from common.db.models import JMSBaseModel __all__ = ['Platform', 'PlatformProtocol', 'PlatformAutomation'] +from common.utils import lazyproperty + class PlatformProtocol(models.Model): name = models.CharField(max_length=32, verbose_name=_('Name')) @@ -26,6 +28,11 @@ class PlatformProtocol(models.Model): def secret_types(self): return Protocol.settings().get(self.name, {}).get('secret_types', ['password']) + @lazyproperty + def port_from_addr(self): + from assets.const.protocol import Protocol as ProtocolConst + return ProtocolConst.settings().get(self.name, {}).get('port_from_addr', False) + class PlatformAutomation(models.Model): ansible_enabled = models.BooleanField(default=False, verbose_name=_("Enabled")) diff --git a/apps/assets/serializers/asset/__init__.py b/apps/assets/serializers/asset/__init__.py index 8e3e14cf3..481e90863 100644 --- a/apps/assets/serializers/asset/__init__.py +++ b/apps/assets/serializers/asset/__init__.py @@ -4,5 +4,6 @@ from .common import * from .custom import * from .database import * from .device import * +from .gpt import * from .host import * from .web import * diff --git a/apps/assets/serializers/asset/gpt.py b/apps/assets/serializers/asset/gpt.py new file mode 100644 index 000000000..88e28ed60 --- /dev/null +++ b/apps/assets/serializers/asset/gpt.py @@ -0,0 +1,15 @@ +from assets.models import GPT +from .common import AssetSerializer + +__all__ = ['GPTSerializer'] + + +class GPTSerializer(AssetSerializer): + class Meta(AssetSerializer.Meta): + model = GPT + fields = AssetSerializer.Meta.fields + [ + 'proxy', + ] + extra_kwargs = { + **AssetSerializer.Meta.extra_kwargs, + } diff --git a/apps/assets/serializers/platform.py b/apps/assets/serializers/platform.py index 1915adcdb..8101bde23 100644 --- a/apps/assets/serializers/platform.py +++ b/apps/assets/serializers/platform.py @@ -46,13 +46,13 @@ class PlatformAutomationSerializer(serializers.ModelSerializer): class PlatformProtocolSerializer(serializers.ModelSerializer): setting = MethodSerializer(required=False, label=_("Setting")) + port_from_addr = serializers.BooleanField(label=_("Port from addr"), read_only=True) class Meta: model = PlatformProtocol fields = [ - "id", "name", "port", "primary", - "required", "default", "public", - "secret_types", "setting", + "id", "name", "port", "port_from_addr", "primary", + "required", "default", "public", "secret_types", "setting", ] extra_kwargs = { "primary": { diff --git a/apps/assets/urls/api_urls.py b/apps/assets/urls/api_urls.py index 1a1384fdf..983e077f0 100644 --- a/apps/assets/urls/api_urls.py +++ b/apps/assets/urls/api_urls.py @@ -14,6 +14,7 @@ router.register(r'devices', api.DeviceViewSet, 'device') router.register(r'databases', api.DatabaseViewSet, 'database') router.register(r'webs', api.WebViewSet, 'web') router.register(r'clouds', api.CloudViewSet, 'cloud') +router.register(r'gpts', api.GPTViewSet, 'gpt') router.register(r'customs', api.CustomViewSet, 'custom') router.register(r'platforms', api.AssetPlatformViewSet, 'platform') router.register(r'labels', api.LabelViewSet, 'label')