Merge branch 'v3' of github.com:jumpserver/jumpserver into v3

pull/8997/head
Jiangjie.Bai 2022-10-27 15:48:02 +08:00
commit 5d37d1b7b1
25 changed files with 991 additions and 623 deletions

View File

@ -210,7 +210,7 @@ class ConnectionTokenMixin:
class ConnectionTokenViewSet(ConnectionTokenMixin, RootOrgViewMixin, JMSModelViewSet): class ConnectionTokenViewSet(ConnectionTokenMixin, RootOrgViewMixin, JMSModelViewSet):
filterset_fields = ( filterset_fields = (
'type', 'user_display', 'asset_display' 'user_display', 'asset_display'
) )
search_fields = filterset_fields search_fields = filterset_fields
serializer_classes = { serializer_classes = {

View File

@ -49,5 +49,9 @@ class Migration(migrations.Migration):
migrations.RemoveField( migrations.RemoveField(
model_name='connectiontoken', model_name='connectiontoken',
name='system_user', name='system_user',
) ),
migrations.RemoveField(
model_name='connectiontoken',
name='type',
),
] ]

View File

@ -1,17 +0,0 @@
# Generated by Django 3.2.14 on 2022-10-26 08:07
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('authentication', '0012_auto_20220816_1629'),
]
operations = [
migrations.RemoveField(
model_name='connectiontoken',
name='type',
),
]

View File

@ -23,7 +23,7 @@ class ConnectionTokenSerializer(OrgResourceModelSerializerMixin):
class Meta: class Meta:
model = ConnectionToken model = ConnectionToken
fields_mini = ['id', 'type'] fields_mini = ['id']
fields_small = fields_mini + [ fields_small = fields_mini + [
'secret', 'date_expired', 'date_created', 'date_updated', 'secret', 'date_expired', 'date_created', 'date_updated',
'created_by', 'updated_by', 'org_id', 'org_name', 'created_by', 'updated_by', 'org_id', 'org_name',

View File

@ -10,6 +10,8 @@ class CommonConfig(AppConfig):
def ready(self): def ready(self):
from . import signal_handlers from . import signal_handlers
from .signals import django_ready from .signals import django_ready
if 'migrate' in sys.argv or 'compilemessages' in sys.argv: excludes = ['migrate', 'compilemessages', 'makemigrations']
return for i in excludes:
if i in sys.argv:
return
django_ready.send(CommonConfig) django_ready.send(CommonConfig)

View File

@ -12,7 +12,7 @@ from .mixin import BulkListSerializerMixin, BulkSerializerMixin
__all__ = [ __all__ = [
'MethodSerializer', 'EmptySerializer', 'BulkModelSerializer', 'MethodSerializer', 'EmptySerializer', 'BulkModelSerializer',
'AdaptedBulkListSerializer', 'CeleryTaskSerializer', 'AdaptedBulkListSerializer', 'CeleryTaskExecutionSerializer',
'WritableNestedModelSerializer', 'WritableNestedModelSerializer',
'GroupedChoiceSerializer', 'GroupedChoiceSerializer',
] ]
@ -73,7 +73,7 @@ class AdaptedBulkListSerializer(BulkListSerializerMixin, BulkListSerializer):
pass pass
class CeleryTaskSerializer(serializers.Serializer): class CeleryTaskExecutionSerializer(serializers.Serializer):
task = serializers.CharField(read_only=True) task = serializers.CharField(read_only=True)

File diff suppressed because it is too large Load Diff

View File

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:ab1c609cc4c83a223835be0eab2a5a5b9050c853b66ccd2b2fa480073c8fc763 oid sha256:5d945fc6151d6f6354052a0a09706a52e7a2fa2f9e02254965cf26b134d78c3a
size 103346 size 103377

File diff suppressed because it is too large Load Diff

View File

@ -5,7 +5,7 @@ from django.shortcuts import get_object_or_404
from rest_framework import viewsets, generics from rest_framework import viewsets, generics
from rest_framework.views import Response from rest_framework.views import Response
from common.drf.serializers import CeleryTaskSerializer from common.drf.serializers import CeleryTaskExecutionSerializer
from ..models import AdHoc, AdHocExecution from ..models import AdHoc, AdHocExecution
from ..serializers import ( from ..serializers import (
AdHocSerializer, AdHocSerializer,

View File

@ -4,6 +4,7 @@
import os import os
import re import re
from django.shortcuts import get_object_or_404
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from rest_framework import viewsets from rest_framework import viewsets
from celery.result import AsyncResult from celery.result import AsyncResult
@ -12,20 +13,21 @@ from django_celery_beat.models import PeriodicTask
from common.permissions import IsValidUser from common.permissions import IsValidUser
from common.api import LogTailApi from common.api import LogTailApi
from ..models import CeleryTask from ..models import CeleryTaskExecution, CeleryTask
from ..serializers import CeleryResultSerializer, CeleryPeriodTaskSerializer from ..serializers import CeleryResultSerializer, CeleryPeriodTaskSerializer
from ..celery.utils import get_celery_task_log_path from ..celery.utils import get_celery_task_log_path
from ..ansible.utils import get_ansible_task_log_path from ..ansible.utils import get_ansible_task_log_path
from common.mixins.api import CommonApiMixin from common.mixins.api import CommonApiMixin
__all__ = [ __all__ = [
'CeleryTaskLogApi', 'CeleryResultApi', 'CeleryPeriodTaskViewSet', 'CeleryTaskExecutionLogApi', 'CeleryResultApi', 'CeleryPeriodTaskViewSet',
'AnsibleTaskLogApi', 'AnsibleTaskLogApi', 'CeleryTaskViewSet', 'CeleryTaskExecutionViewSet'
] ]
from ..serializers.celery import CeleryTaskSerializer, CeleryTaskExecutionSerializer
class CeleryTaskLogApi(LogTailApi):
class CeleryTaskExecutionLogApi(LogTailApi):
permission_classes = (IsValidUser,) permission_classes = (IsValidUser,)
task = None task = None
task_id = '' task_id = ''
@ -46,8 +48,8 @@ class CeleryTaskLogApi(LogTailApi):
if new_path and os.path.isfile(new_path): if new_path and os.path.isfile(new_path):
return new_path return new_path
try: try:
task = CeleryTask.objects.get(id=self.task_id) task = CeleryTaskExecution.objects.get(id=self.task_id)
except CeleryTask.DoesNotExist: except CeleryTaskExecution.DoesNotExist:
return None return None
return task.full_log_path return task.full_log_path
@ -94,3 +96,22 @@ class CeleryPeriodTaskViewSet(CommonApiMixin, viewsets.ModelViewSet):
queryset = super().get_queryset() queryset = super().get_queryset()
queryset = queryset.exclude(description='') queryset = queryset.exclude(description='')
return queryset return queryset
class CeleryTaskViewSet(CommonApiMixin, viewsets.ReadOnlyModelViewSet):
queryset = CeleryTask.objects.all()
serializer_class = CeleryTaskSerializer
http_method_names = ('get', 'head', 'options',)
class CeleryTaskExecutionViewSet(CommonApiMixin, viewsets.ReadOnlyModelViewSet):
serializer_class = CeleryTaskExecutionSerializer
http_method_names = ('get', 'head', 'options',)
def get_queryset(self):
task_id = self.kwargs.get("task_pk")
if task_id:
task = CeleryTask.objects.get(pk=task_id)
return CeleryTaskExecution.objects.filter(name=task.name)
else:
return CeleryTaskExecution.objects.none()

View File

@ -0,0 +1,36 @@
# Generated by Django 3.2.14 on 2022-10-24 09:09
from django.db import migrations, models
import uuid
class Migration(migrations.Migration):
dependencies = [
('ops', '0026_auto_20221009_2050'),
]
operations = [
migrations.DeleteModel(name='CeleryTask'),
migrations.CreateModel(
name='CeleryTask',
fields=[
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
('name', models.CharField(max_length=1024)),
]
),
migrations.CreateModel(
name='CeleryTaskExecution',
fields=[
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
('name', models.CharField(max_length=1024)),
('args', models.JSONField(verbose_name='Args')),
('kwargs', models.JSONField(verbose_name='Kwargs')),
('state', models.CharField(max_length=16, verbose_name='State')),
('is_finished', models.BooleanField(default=False, verbose_name='Finished')),
('date_published', models.DateTimeField(auto_now_add=True)),
('date_start', models.DateTimeField(null=True)),
('date_finished', models.DateTimeField(null=True)),
],
),
]

View File

@ -7,8 +7,27 @@ from django.utils.translation import gettext_lazy as _
from django.conf import settings from django.conf import settings
from django.db import models from django.db import models
from ops.celery import app
class CeleryTask(models.Model): class CeleryTask(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4)
name = models.CharField(max_length=1024)
@property
def verbose_name(self):
task = app.tasks.get(self.name, None)
if task:
return getattr(task, 'verbose_name', None)
@property
def description(self):
task = app.tasks.get(self.name, None)
if task:
return getattr(task, 'description', None)
class CeleryTaskExecution(models.Model):
LOG_DIR = os.path.join(settings.PROJECT_DIR, 'data', 'celery') LOG_DIR = os.path.join(settings.PROJECT_DIR, 'data', 'celery')
id = models.UUIDField(primary_key=True, default=uuid.uuid4) id = models.UUIDField(primary_key=True, default=uuid.uuid4)
name = models.CharField(max_length=1024) name = models.CharField(max_length=1024)

View File

@ -5,10 +5,12 @@ from rest_framework import serializers
from django_celery_beat.models import PeriodicTask from django_celery_beat.models import PeriodicTask
__all__ = [ __all__ = [
'CeleryResultSerializer', 'CeleryTaskSerializer', 'CeleryResultSerializer', 'CeleryTaskExecutionSerializer',
'CeleryPeriodTaskSerializer' 'CeleryPeriodTaskSerializer', 'CeleryTaskSerializer'
] ]
from ops.models import CeleryTask, CeleryTaskExecution
class CeleryResultSerializer(serializers.Serializer): class CeleryResultSerializer(serializers.Serializer):
id = serializers.UUIDField() id = serializers.UUIDField()
@ -16,10 +18,6 @@ class CeleryResultSerializer(serializers.Serializer):
state = serializers.CharField(max_length=16) state = serializers.CharField(max_length=16)
class CeleryTaskSerializer(serializers.Serializer):
pass
class CeleryPeriodTaskSerializer(serializers.ModelSerializer): class CeleryPeriodTaskSerializer(serializers.ModelSerializer):
class Meta: class Meta:
model = PeriodicTask model = PeriodicTask
@ -27,3 +25,19 @@ class CeleryPeriodTaskSerializer(serializers.ModelSerializer):
'name', 'task', 'enabled', 'description', 'name', 'task', 'enabled', 'description',
'last_run_at', 'total_run_count' 'last_run_at', 'total_run_count'
] ]
class CeleryTaskSerializer(serializers.ModelSerializer):
class Meta:
model = CeleryTask
fields = [
'id', 'name', 'verbose_name', 'description',
]
class CeleryTaskExecutionSerializer(serializers.ModelSerializer):
class Meta:
model = CeleryTaskExecution
fields = [
"id", "name", "args", "kwargs", "state", "is_finished", "date_published", "date_start", "date_finished"
]

View File

@ -1,12 +1,15 @@
import ast import ast
from django.db import transaction
from django.dispatch import receiver
from django.utils import translation, timezone from django.utils import translation, timezone
from django.core.cache import cache from django.core.cache import cache
from celery import signals from celery import signals, current_app
from common.db.utils import close_old_connections, get_logger from common.db.utils import close_old_connections, get_logger
from .models import CeleryTask from common.signals import django_ready
from .celery import app
from .models import CeleryTaskExecution, CeleryTask
logger = get_logger(__name__) logger = get_logger(__name__)
@ -14,6 +17,22 @@ TASK_LANG_CACHE_KEY = 'TASK_LANG_{}'
TASK_LANG_CACHE_TTL = 1800 TASK_LANG_CACHE_TTL = 1800
@receiver(django_ready)
def sync_registered_tasks(*args, **kwargs):
with transaction.atomic():
try:
db_tasks = CeleryTask.objects.all()
except Exception as e:
return
celery_task_names = [key for key in app.tasks]
db_task_names = db_tasks.values_list('name', flat=True)
db_tasks.exclude(name__in=celery_task_names).delete()
not_in_db_tasks = set(celery_task_names) - set(db_task_names)
tasks_to_create = [CeleryTask(name=name) for name in not_in_db_tasks]
CeleryTask.objects.bulk_create(tasks_to_create)
@signals.before_task_publish.connect @signals.before_task_publish.connect
def before_task_publish(headers=None, **kwargs): def before_task_publish(headers=None, **kwargs):
task_id = headers.get('id') task_id = headers.get('id')
@ -25,7 +44,7 @@ def before_task_publish(headers=None, **kwargs):
@signals.task_prerun.connect @signals.task_prerun.connect
def on_celery_task_pre_run(task_id='', **kwargs): def on_celery_task_pre_run(task_id='', **kwargs):
# 更新状态 # 更新状态
CeleryTask.objects.filter(id=task_id).update(state='RUNNING', date_start=timezone.now()) CeleryTaskExecution.objects.filter(id=task_id).update(state='RUNNING', date_start=timezone.now())
# 关闭之前的数据库连接 # 关闭之前的数据库连接
close_old_connections() close_old_connections()
@ -41,7 +60,7 @@ def on_celery_task_post_run(task_id='', state='', **kwargs):
close_old_connections() close_old_connections()
print("Task post run: ", task_id, state) print("Task post run: ", task_id, state)
CeleryTask.objects.filter(id=task_id).update( CeleryTaskExecution.objects.filter(id=task_id).update(
state=state, date_finished=timezone.now(), is_finished=True state=state, date_finished=timezone.now(), is_finished=True
) )
@ -72,4 +91,4 @@ def task_sent_handler(headers=None, body=None, **kwargs):
'args': args, 'args': args,
'kwargs': kwargs 'kwargs': kwargs
} }
CeleryTask.objects.create(**data) CeleryTaskExecution.objects.create(**data)

View File

@ -20,7 +20,7 @@ from .celery.utils import (
create_or_update_celery_periodic_tasks, get_celery_periodic_task, create_or_update_celery_periodic_tasks, get_celery_periodic_task,
disable_celery_periodic_task, delete_celery_periodic_task disable_celery_periodic_task, delete_celery_periodic_task
) )
from .models import CeleryTask, AdHoc, Playbook from .models import CeleryTaskExecution, AdHoc, Playbook
from .notifications import ServerPerformanceCheckUtil from .notifications import ServerPerformanceCheckUtil
logger = get_logger(__file__) logger = get_logger(__file__)
@ -94,9 +94,9 @@ def clean_celery_tasks_period():
logger.debug("Start clean celery task history") logger.debug("Start clean celery task history")
expire_days = get_log_keep_day('TASK_LOG_KEEP_DAYS') expire_days = get_log_keep_day('TASK_LOG_KEEP_DAYS')
days_ago = timezone.now() - timezone.timedelta(days=expire_days) days_ago = timezone.now() - timezone.timedelta(days=expire_days)
tasks = CeleryTask.objects.filter(date_start__lt=days_ago) tasks = CeleryTaskExecution.objects.filter(date_start__lt=days_ago)
tasks.delete() tasks.delete()
tasks = CeleryTask.objects.filter(date_start__isnull=True) tasks = CeleryTaskExecution.objects.filter(date_start__isnull=True)
tasks.delete() tasks.delete()
command = "find %s -mtime +%s -name '*.log' -type f -exec rm -f {} \\;" % ( command = "find %s -mtime +%s -name '*.log' -type f -exec rm -f {} \\;" % (
settings.CELERY_LOG_DIR, expire_days settings.CELERY_LOG_DIR, expire_days

View File

@ -4,8 +4,9 @@ from __future__ import unicode_literals
from django.urls import path from django.urls import path
from rest_framework.routers import DefaultRouter from rest_framework.routers import DefaultRouter
from rest_framework_bulk.routes import BulkRouter from rest_framework_bulk.routes import BulkRouter
from .. import api from rest_framework_nested import routers
from .. import api
app_name = "ops" app_name = "ops"
@ -16,12 +17,19 @@ router.register(r'adhoc', api.AdHocViewSet, 'adhoc')
router.register(r'adhoc-executions', api.AdHocExecutionViewSet, 'execution') router.register(r'adhoc-executions', api.AdHocExecutionViewSet, 'execution')
router.register(r'celery/period-tasks', api.CeleryPeriodTaskViewSet, 'celery-period-task') router.register(r'celery/period-tasks', api.CeleryPeriodTaskViewSet, 'celery-period-task')
urlpatterns = [ router.register(r'tasks', api.CeleryTaskViewSet, 'task')
path('celery/task/<uuid:pk>/log/', api.CeleryTaskLogApi.as_view(), name='celery-task-log'),
path('celery/task/<uuid:pk>/result/', api.CeleryResultApi.as_view(), name='celery-result'),
path('ansible/task/<uuid:pk>/log/', api.AnsibleTaskLogApi.as_view(), name='ansible-task-log'), task_router = routers.NestedDefaultRouter(router, r'tasks', lookup='task')
task_router.register(r'executions', api.CeleryTaskExecutionViewSet, 'task-execution')
urlpatterns = [
path('celery/task/<uuid:name>/task-execution/<uuid:pk>/log/', api.CeleryTaskExecutionLogApi.as_view(),
name='celery-task-execution-log'),
path('celery/task/<uuid:name>/task-execution/<uuid:pk>/result/', api.CeleryResultApi.as_view(),
name='celery-task-execution-result'),
path('ansible/task-execution/<uuid:pk>/log/', api.AnsibleTaskLogApi.as_view(), name='ansible-task-log'),
] ]
urlpatterns += router.urls urlpatterns += (router.urls + bulk_router.urls + task_router.urls)
urlpatterns += bulk_router.urls

View File

@ -0,0 +1,30 @@
# Generated by Django 3.2.14 on 2022-10-26 09:07
from django.db import migrations, models
def update_builtin_org(apps, schema_editor):
org_model = apps.get_model('orgs', 'Organization')
org_model.objects.create(
id='00000000-0000-0000-0000-000000000004',
name='SYSTEM', builtin=True
)
# 更新 Default
org_model.objects.filter(name='DEFAULT').update(builtin=True)
class Migration(migrations.Migration):
dependencies = [
('orgs', '0013_alter_organization_options'),
]
operations = [
migrations.AddField(
model_name='organization',
name='builtin',
field=models.BooleanField(default=False, verbose_name='Builtin'),
),
migrations.RunPython(update_builtin_org),
]

View File

@ -69,6 +69,7 @@ class Organization(OrgRoleMixin, models.Model):
id = models.UUIDField(default=uuid.uuid4, primary_key=True) id = models.UUIDField(default=uuid.uuid4, primary_key=True)
name = models.CharField(max_length=128, unique=True, verbose_name=_("Name")) name = models.CharField(max_length=128, unique=True, verbose_name=_("Name"))
created_by = models.CharField(max_length=32, null=True, blank=True, verbose_name=_('Created by')) created_by = models.CharField(max_length=32, null=True, blank=True, verbose_name=_('Created by'))
builtin = models.BooleanField(default=False, verbose_name=_('Builtin'))
date_created = models.DateTimeField(auto_now_add=True, null=True, blank=True, verbose_name=_('Date created')) date_created = models.DateTimeField(auto_now_add=True, null=True, blank=True, verbose_name=_('Date created'))
comment = models.TextField(default='', blank=True, verbose_name=_('Comment')) comment = models.TextField(default='', blank=True, verbose_name=_('Comment'))
members = models.ManyToManyField( members = models.ManyToManyField(
@ -139,13 +140,13 @@ class Organization(OrgRoleMixin, models.Model):
@classmethod @classmethod
def default(cls): def default(cls):
defaults = dict(id=cls.DEFAULT_ID, name=cls.DEFAULT_NAME) defaults = dict(id=cls.DEFAULT_ID, name=cls.DEFAULT_NAME)
obj, created = cls.objects.get_or_create(defaults=defaults, id=cls.DEFAULT_ID) obj, created = cls.objects.get_or_create(defaults=defaults, id=cls.DEFAULT_ID, builtin=True)
return obj return obj
@classmethod @classmethod
def root(cls): def root(cls):
name = settings.GLOBAL_ORG_DISPLAY_NAME or cls.ROOT_NAME name = settings.GLOBAL_ORG_DISPLAY_NAME or cls.ROOT_NAME
return cls(id=cls.ROOT_ID, name=name) return cls(id=cls.ROOT_ID, name=name, builtin=True)
def is_root(self): def is_root(self):
return self.id == self.ROOT_ID return self.id == self.ROOT_ID

View File

@ -4,6 +4,7 @@ import threading
from collections import defaultdict from collections import defaultdict
from functools import partial from functools import partial
import django.db.utils
from django.dispatch import receiver from django.dispatch import receiver
from django.conf import settings from django.conf import settings
from django.utils.functional import LazyObject from django.utils.functional import LazyObject
@ -45,7 +46,10 @@ def expire_orgs_mapping_for_memory(org_id):
def subscribe_orgs_mapping_expire(sender, **kwargs): def subscribe_orgs_mapping_expire(sender, **kwargs):
logger.debug("Start subscribe for expire orgs mapping from memory") logger.debug("Start subscribe for expire orgs mapping from memory")
if settings.DEBUG: if settings.DEBUG:
set_to_default_org() try:
set_to_default_org()
except django.db.utils.OperationalError:
pass
def keep_subscribe_org_mapping(): def keep_subscribe_org_mapping():
orgs_mapping_for_memory_pub_sub.subscribe( orgs_mapping_for_memory_pub_sub.subscribe(

View File

View File

@ -0,0 +1,56 @@
---
- hosts: windows
vars:
- DownloadHost: https://demo.jumpserver.org/download
- RDS_Licensing: enabled
- RDS_LicenseServer: 127.0.0.1
- RDS_LicensingMode: 4
- RDS_fSingleSessionPerUser: 0
- RDS_MaxDisconnectionTime: 60000
- RDS_RemoteAppLogoffTimeLimit: 0
tasks:
- name: Install RDS-Licensing (RDS)
ansible.windows.win_feature:
name: RDS-Licensing
state: present
include_management_tools: yes
when: RDS_Licensing == "enabled"
- name: Install RDS-RD-Server (RDS)
ansible.windows.win_feature:
name: RDS-RD-Server
state: present
include_management_tools: yes
register: win_feature
- name: Reboot if installing RDS feature requires it
ansible.windows.win_reboot:
when: win_feature.reboot_required
- name: Set RDS LicenseServer (regedit)
ansible.windows.win_regedit:
path: HKLM:\SOFTWARE\Policies\Microsoft\Windows NT\Terminal Services
name: LicenseServers
data: "{{ RDS_LicenseServer }}"
type: string
- name: Set RDS LicensingMode (regedit)
ansible.windows.win_regedit:
path: HKLM:\SOFTWARE\Policies\Microsoft\Windows NT\Terminal Services
name: LicensingMode
data: "{{ RDS_LicensingMode }}"
type: dword
- name: Set RDS fSingleSessionPerUser (regedit)
ansible.windows.win_regedit:
path: HKLM:\SOFTWARE\Policies\Microsoft\Windows NT\Terminal Services
name: fSingleSessionPerUser
data: "{{ RDS_fSingleSessionPerUser }}"
type: dword
- name: Set RDS MaxDisconnectionTime (regedit)
ansible.windows.win_regedit:
path: HKLM:\SOFTWARE\Policies\Microsoft\Windows NT\Terminal Services
name: MaxDisconnectionTime
data: "{{ RDS_MaxDisconnectionTime }}"
type: dword
when: RDS_MaxDisconnectionTime >= 60000
- name: Set RDS RemoteAppLogoffTimeLimit (regedit)
ansible.windows.win_regedit:
path: HKLM:\SOFTWARE\Policies\Microsoft\Windows NT\Terminal Services
name: RemoteAppLogoffTimeLimit
data: "{{ RDS_RemoteAppLogoffTime }}"

View File

@ -0,0 +1,26 @@
# Generated by Django 3.2.14 on 2022-10-26 08:31
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('terminal', '0054_auto_20221024_1452'),
]
operations = [
migrations.RemoveField(
model_name='applet',
name='vcs_type',
),
migrations.RemoveField(
model_name='applet',
name='vcs_url',
),
migrations.AddField(
model_name='applet',
name='is_active',
field=models.BooleanField(default=True, verbose_name='Is active'),
),
]

View File

@ -27,8 +27,7 @@ class Applet(JMSBaseModel):
version = models.CharField(max_length=16, verbose_name=_('Version')) version = models.CharField(max_length=16, verbose_name=_('Version'))
author = models.CharField(max_length=128, verbose_name=_('Author')) author = models.CharField(max_length=128, verbose_name=_('Author'))
type = models.CharField(max_length=16, verbose_name=_('Type'), default='general', choices=Type.choices) 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) is_active = models.BooleanField(default=True, verbose_name=_('Is active'))
vcs_url = models.CharField(max_length=256, verbose_name=_('URL'), null=True)
protocols = models.JSONField(default=list, verbose_name=_('Protocol')) protocols = models.JSONField(default=list, verbose_name=_('Protocol'))
tags = models.JSONField(default=list, verbose_name=_('Tags')) tags = models.JSONField(default=list, verbose_name=_('Tags'))
comment = models.TextField(default='', blank=True, verbose_name=_('Comment')) comment = models.TextField(default='', blank=True, verbose_name=_('Comment'))