示例#1
0
class DeviceForm(ObjectForm):
    form_type = HiddenField(default="device")
    icon = SelectField(
        "Icon",
        choices=(
            ("antenna", "Antenna"),
            ("firewall", "Firewall"),
            ("host", "Host"),
            ("optical_switch", "Optical switch"),
            ("regenerator", "Regenerator"),
            ("router", "Router"),
            ("server", "Server"),
            ("switch", "Switch"),
        ),
    )
    ip_address = StringField("IP address")
    port = IntegerField("Port", default=22)
    operating_system = StringField("Operating System")
    os_version = StringField("OS Version")
    longitude = StringField("Longitude", default=0.0)
    latitude = StringField("Latitude", default=0.0)
    napalm_driver = SelectField("NAPALM Driver",
                                choices=vs.napalm_drivers,
                                default="ios")
    netmiko_driver = SelectField("Netmiko Driver",
                                 choices=vs.netmiko_drivers,
                                 default="cisco_ios")
    scrapli_driver = SelectField(
        "Scrapli Driver",
        choices=vs.dualize(vs.scrapli_drivers),
        default="cisco_iosxe",
    )
    netconf_driver = SelectField("Netconf Driver",
                                 choices=vs.netconf_drivers,
                                 default="default")
示例#2
0
class GenericFileTransferForm(ServiceForm):
    form_type = HiddenField(default="generic_file_transfer_service")
    direction = SelectField(choices=(("get", "Get"), ("put", "Put")))
    protocol = SelectField(choices=(("scp", "SCP"), ("sftp", "SFTP")))
    source_file = StringField(validators=[InputRequired()], substitution=True)
    destination_file = StringField(validators=[InputRequired()],
                                   substitution=True)
    missing_host_key_policy = BooleanField()
    load_known_host_keys = BooleanField()
    source_file_includes_globbing = BooleanField(
        "Source file includes glob pattern")
    max_transfer_size = IntegerField(default=2**30)
    window_size = IntegerField(default=2**30)
    timeout = FloatField(default=10.0)
    credentials = SelectField(
        "Credentials",
        choices=(
            ("device", "Device Credentials"),
            ("user", "User Credentials"),
            ("custom", "Custom Credentials"),
        ),
    )
    custom_username = StringField("Custom Username", substitution=True)
    custom_password = PasswordField("Custom Password", substitution=True)

    def validate(self):
        valid_form = super().validate()
        invalid_direction = (self.source_file_includes_globbing.data
                             and self.direction.data == "get")
        if invalid_direction:
            self.direction.errors.append(
                "Globbing only works with the 'PUT' direction")
        return valid_form and not invalid_direction
示例#3
0
 def generate_filtering_forms(self):
     for model, properties in vs.properties["filtering"].items():
         relations = {}
         for related_model, relation in vs.relationships[model].items():
             if related_model in ("edges", "results"):
                 continue
             relations[related_model] = MultipleInstanceField(
                 related_model, model=related_model)
             vs.relationships[f"{model}_filtering"][
                 related_model] = relation
             filtering_key = f"{model}_relation_filtering"
             vs.relationships[filtering_key][related_model] = relation
         relation_form = {
             "template": "filtering",
             "properties": sorted(relations),
             "object_type": model,
             "form_type":
             HiddenField(default=f"{model}_relation_filtering"),
             **{
                 **relations,
                 **{
                     f"{relation}_filter": SelectField(choices=(
                         ("union", "Union"),
                         ("intersection", "Intersection"),
                         ("empty", "Empty"),
                     ))
                     for relation in relations
                 },
             },
         }
         type(f"{model}RelationshipFilteringForm", (BaseForm, ),
              relation_form)
         form, form_type = deepcopy(relation_form), f"{model}_filtering"
         for property in properties:
             vs.form_properties[form_type][f"{property}_filter"] = {
                 "type": "list"
             }
         form.update({
             "form_type": HiddenField(default=form_type),
             "properties": sorted(properties) + sorted(relations),
             **{property: StringField()
                for property in properties},
             **{
                 f"{property}_filter": SelectField(choices=(
                     ("inclusion", "Inclusion"),
                     ("equality", "Equality"),
                     ("regex", "Regular Expression"),
                     ("empty", "Empty"),
                 ))
                 for property in properties
             },
         })
         type(f"{model}FilteringForm", (BaseForm, ), form)
示例#4
0
class TaskForm(BaseForm):
    action = "eNMS.base.processData"
    form_type = HiddenField(default="task")
    id = HiddenField()
    name = StringField("Name", [InputRequired()])
    default_access = SelectField(choices=(
        ("creator", "Creator only"),
        ("public", "Public (all users)"),
        ("admin", "Admin Users only"),
    ))
    scheduling_mode = SelectField(
        "Scheduling Mode",
        choices=(("cron", "Crontab Scheduling"), ("standard",
                                                  "Standard Scheduling")),
    )
    description = StringField("Description")
    start_date = StringField("Start Date", type="date")
    end_date = StringField("End Date", type="date")
    frequency = IntegerField("Frequency", default=0)
    frequency_unit = SelectField(
        "Frequency Unit",
        choices=(
            ("seconds", "Seconds"),
            ("minutes", "Minutes"),
            ("hours", "Hours"),
            ("days", "Days"),
        ),
    )
    crontab_expression = StringField("Crontab Expression")
    initial_payload = DictField("Payload")
    devices = MultipleInstanceField("Devices", model="device")
    pools = MultipleInstanceField("Pools", model="pool")
    service = InstanceField("Service", model="service")

    def validate(self):
        valid_form = super().validate()
        if self.name.data == "Bulk Edit":
            return valid_form
        no_date = self.scheduling_mode.data == "standard" and not self.start_date.data
        if no_date:
            self.start_date.errors.append("A start date must be set.")
        no_cron_expression = (self.scheduling_mode.data == "cron"
                              and not self.crontab_expression.data)
        if no_cron_expression:
            self.crontab_expression.errors.append(
                "A crontab expression must be set.")
        no_service = not self.service.data
        if no_service:
            self.service.errors.append("No service set.")
        return valid_form and not any(
            [no_date, no_cron_expression, no_service])
示例#5
0
class UserForm(RbacForm):
    form_type = HiddenField(default="user")
    groups = StringField("Groups")
    theme = SelectField(
        "Theme",
        choices=[(theme, values["name"])
                 for theme, values in vs.themes["themes"].items()],
    )
    authentication = SelectField(
        "Authentication",
        choices=[(method, values["display_name"]) for method, values in
                 vs.settings["authentication"]["methods"].items()],
    )
    password = PasswordField("Password")
    is_admin = BooleanField(default=False)
示例#6
0
class RestartWorkflowForm(BaseForm):
    action = "eNMS.workflowBuilder.restartWorkflow"
    form_type = HiddenField(default="restart_workflow")
    start_services = HiddenField()
    restart_runtime = SelectField("Restart Runtime", validate_choice=False)
    targets = SelectField(
        "Targets",
        choices=(
            ("Manually defined", "Use the devices manually defined below."),
            ("Restart run", "Use the targets from the restart run."),
            ("Workflow", "Use the targets defined at workflow level."),
        ),
    )
    restart_devices = MultipleInstanceField("Devices", model="device")
    restart_pools = MultipleInstanceField("Pools", model="pool")
示例#7
0
 def form_init(cls):
     cls.models = ("device", "link", "service", "user")
     for model in cls.models:
         setattr(cls, f"{model}_properties",
                 vs.properties["filtering"][model])
         for property in vs.properties["filtering"][model]:
             setattr(cls, f"{model}_{property}", StringField(property))
             setattr(cls, f"{model}_{property}_invert",
                     BooleanField(property))
             vs.form_properties["pool"][f"{model}_{property}_match"] = {
                 "type": "list"
             }
             vs.form_properties["pool"][f"{model}_{property}_invert"] = {
                 "type": "bool"
             }
             setattr(
                 cls,
                 f"{model}_{property}_match",
                 SelectField(choices=(
                     ("inclusion", "Inclusion"),
                     ("equality", "Equality"),
                     ("regex", "Regular Expression"),
                     ("empty", "Empty"),
                 )),
             )
示例#8
0
class ConnectionForm(ServiceForm):
    form_type = HiddenField(default="connection")
    get_request_allowed = False
    abstract_service = True
    credentials = SelectField(
        "Credentials",
        choices=(
            ("device", "Device Credentials"),
            ("user", "User Credentials"),
            ("custom", "Custom Credentials"),
        ),
    )
    custom_username = StringField("Custom Username", substitution=True)
    custom_password = PasswordField("Custom Password", substitution=True)
    start_new_connection = BooleanField("Start New Connection")
    connection_name = StringField("Connection Name", default="default")
    close_connection = BooleanField("Close Connection")
    groups = {
        "Connection Parameters": {
            "commands": [
                "credentials",
                "custom_username",
                "custom_password",
                "start_new_connection",
                "connection_name",
                "close_connection",
            ],
            "default":
            "expanded",
        }
    }
示例#9
0
class NetmikoFileTransferForm(NetmikoForm):
    form_type = HiddenField(default="netmiko_file_transfer_service")
    source_file = StringField(validators=[InputRequired()], substitution=True)
    destination_file = StringField(validators=[InputRequired()],
                                   substitution=True)
    file_system = StringField()
    direction = SelectField(choices=(("put", "Upload"), ("get", "Download")))
    disable_md5 = BooleanField()
    inline_transfer = BooleanField()
    overwrite_file = BooleanField()
    groups = {
        "Main Parameters": {
            "commands": [
                "source_file",
                "destination_file",
                "file_system",
                "direction",
                "disable_md5",
                "inline_transfer",
                "overwrite_file",
            ],
            "default":
            "expanded",
        },
        **NetmikoForm.groups,
    }
示例#10
0
class WorkflowLabelForm(BaseForm):
    form_type = HiddenField(default="workflow_label")
    action = "eNMS.workflowBuilder.createLabel"
    text = StringField(widget=TextArea(), render_kw={"rows": 15})
    alignment = SelectField(
        "Text Alignment",
        choices=(("left", "Left"), ("center", "Center"), ("right", "Right")),
    )
示例#11
0
class DeviceConnectionForm(BaseForm):
    template = "device_connection"
    form_type = HiddenField(default="device_connection")
    address_choices = [("ip_address", "IP address"), ("name", "Name")] + [
        (property, values["pretty_name"])
        for property, values in vs.properties["custom"]["device"].items()
        if values.get("is_address", False)
    ]
    address = SelectField(choices=address_choices)
    username = StringField("Username")
    password = PasswordField("Password")
示例#12
0
class AddServiceForm(BaseForm):
    form_type = HiddenField(default="add_services_to_workflow")
    template = "add_services_to_workflow"
    mode = SelectField(
        "Mode",
        choices=(
            ("deep", "Deep Copy (creates a duplicate from the service)"),
            ("shallow", "Shallow Copy (creates a reference to the service)"),
        ),
    )
    search = StringField()
示例#13
0
class LoginForm(BaseForm):
    form_type = HiddenField(default="login")
    get_request_allowed = False
    authentication_method = SelectField(
        "Authentication Method",
        choices=[(method, properties["display_name"]) for method, properties in
                 vs.settings["authentication"]["methods"].items()
                 if properties["enabled"]],
    )
    username = StringField("Name", [InputRequired()])
    password = PasswordField("Password", [InputRequired()])
示例#14
0
class DebugForm(BaseForm):
    template = "debug"
    form_type = HiddenField(default="debug")
    snippets = SelectField(validate_choice=False)
    code = StringField(
        "Python Code",
        type="code",
        python=True,
        widget=TextArea(),
        render_kw={"rows": 15},
    )
    output = StringField("Output", widget=TextArea(), render_kw={"rows": 16})
示例#15
0
class NapalmConfigurationForm(NapalmForm):
    form_type = HiddenField(default="napalm_configuration_service")
    action = SelectField(
        choices=(
            ("load_merge_candidate", "Load merge"),
            ("load_replace_candidate", "Load replace"),
        )
    )
    content = StringField(widget=TextArea(), render_kw={"rows": 5}, substitution=True)
    groups = {
        "Main Parameters": {"commands": ["action", "content"], "default": "expanded"},
        **NapalmForm.groups,
    }
示例#16
0
class AnsiblePlaybookForm(ServiceForm):
    form_type = HiddenField(default="ansible_playbook_service")
    playbook_path = SelectField("Playbook Path", validate_choice=False)
    arguments = StringField(
        "Arguments (Ansible command line options)",
        substitution=True,
        help="ansible/arguments",
    )
    pass_device_properties = BooleanField(
        "Pass Device Inventory Properties (to be used "
        "in the playbook as {{name}} or {{ip_address}})")
    credentials = SelectField(
        "Credentials",
        choices=(
            ("device", "Device Credentials"),
            ("user", "User Credentials"),
        ),
    )
    options = DictField(
        "Options (passed to ansible as -e extra args)",
        substitution=True,
        help="ansible/options",
    )
示例#17
0
class ScrapliForm(ConnectionForm):
    form_type = HiddenField(default="scrapli_service")
    commands = StringField(substitution=True,
                           widget=TextArea(),
                           render_kw={"rows": 5})
    is_configuration = BooleanField()
    driver = SelectField(choices=vs.dualize(vs.scrapli_drivers))
    transport = SelectField(choices=vs.dualize(("system", "paramiko", "ssh2")))
    use_device_driver = BooleanField(default=True)
    groups = {
        "Main Parameters": {
            "commands": [
                "commands",
                "is_configuration",
                "driver",
                "transport",
                "use_device_driver",
            ],
            "default":
            "expanded",
        },
        **ConnectionForm.groups,
    }
示例#18
0
class ScrapliNetconfForm(ConnectionForm):
    form_type = HiddenField(default="scrapli_netconf_service")
    command = SelectField(choices=(
        ("get", "Get"),
        ("rpc", "RPC"),
        ("get_config", "Get Configuration"),
        ("edit_config", "Edit Configuration"),
        ("delete_config", "Delete Configuration"),
        ("commit", "Commit Configuration"),
        ("discard", "Discard Configuration"),
        ("lock", "Lock"),
        ("unlock", "Unlock"),
    ))
    target = SelectField(choices=(
        ("running", "Running Configuration"),
        ("startup", "Startup Configuration"),
        ("candidate", "Candidate Configuration"),
    ))
    content = StringField(substitution=True,
                          widget=TextArea(),
                          render_kw={"rows": 5})
    commit_config = BooleanField("Commit After Editing Configuration")
    strip_namespaces = BooleanField("Strip Namespaces from returned XML")
    groups = {
        "Main Parameters": {
            "commands": [
                "command",
                "target",
                "content",
                "commit_config",
                "strip_namespaces",
            ],
            "default":
            "expanded",
        },
        **ConnectionForm.groups,
    }
示例#19
0
class CredentialForm(BaseForm):
    action = "eNMS.base.processData"
    form_type = HiddenField(default="credential")
    id = HiddenField()
    name = StringField("Name", [InputRequired()])
    description = StringField(widget=TextArea(), render_kw={"rows": 6})
    role = SelectField(
        "Role",
        choices=(
            ("read-write", "Read Write"),
            ("read-only", "Read Only"),
        ),
    )
    subtype = SelectField(
        "Subtype",
        choices=(("password", "Username / Password"), ("key", "SSH Key")),
    )
    device_pools = MultipleInstanceField("Devices", model="pool")
    user_pools = MultipleInstanceField("Users", model="pool")
    priority = IntegerField("Priority", default=1)
    username = StringField("Username")
    password = PasswordField("Password")
    private_key = StringField(widget=TextArea(), render_kw={"rows": 1})
    enable_password = PasswordField("'Enable' Password")
示例#20
0
class ChangelogForm(BaseForm):
    action = "eNMS.base.processData"
    form_type = HiddenField(default="changelog")
    id = HiddenField()
    severity = SelectField(
        "Severity",
        choices=(
            ("debug", "Debug"),
            ("info", "Info"),
            ("warning", "Warning"),
            ("error", "Error"),
            ("critical", "Critical"),
        ),
    )
    content = StringField(widget=TextArea(), render_kw={"rows": 10})
示例#21
0
文件: ping.py 项目: blackstrip/eNMS
class PingForm(ServiceForm):
    form_type = HiddenField(default="ping_service")
    protocol = SelectField(choices=(("ICMP", "ICMP Ping"), ("TCP",
                                                            "TCP Ping")))
    ports = StringField("Ports (TCP ping only)", default=22)
    count = IntegerField(default=5)
    timeout = IntegerField(default=2)
    ttl = IntegerField(default=60)
    packet_size = IntegerField(default=56)

    def validate(self):
        valid_form = super().validate()
        invalid_tcp_port = self.protocol.data == "TCP" and not self.ports.data
        if invalid_tcp_port:
            self.ports.errors.append("You must enter a port for a TCP ping.")
        return valid_form and not invalid_tcp_port
示例#22
0
class WorkflowForm(ServiceForm):
    form_type = HiddenField(default="workflow")
    close_connection = BooleanField(default=False)
    run_method = SelectField(
        "Run Method",
        choices=(
            ("per_device", "Run the workflow device by device"),
            (
                "per_service_with_workflow_targets",
                "Run the workflow service by service using workflow targets",
            ),
            (
                "per_service_with_service_targets",
                "Run the workflow service by service using service targets",
            ),
        ),
    )
    superworkflow = InstanceField("Superworkflow")
示例#23
0
class NapalmForm(ConnectionForm):
    form_type = HiddenField(default="napalm")
    get_request_allowed = False
    abstract_service = True
    driver = SelectField(choices=vs.napalm_drivers)
    use_device_driver = BooleanField(
        default=True,
        help="common/use_device_driver",
    )
    timeout = IntegerField(default=10)
    optional_args = DictField()
    groups = {
        "Napalm Parameters": {
            "commands":
            ["driver", "use_device_driver", "timeout", "optional_args"],
            "default": "expanded",
        },
        **ConnectionForm.groups,
    }
示例#24
0
class NapalmBackupForm(NapalmForm):
    form_type = HiddenField(default="napalm_backup_service")
    property = SelectField(
        "Configuration Property to Update",
        choices=list(vs.configuration_properties.items()),
    )
    getters = SelectMultipleField(choices=vs.automation["napalm"]["getters"])
    replacements = FieldList(FormField(ReplacementForm), min_entries=3)
    groups = {
        "Target Property and Getters": {
            "commands": ["property", "getters"],
            "default": "expanded",
        },
        "Search Response & Replace": {
            "commands": ["replacements"],
            "default": "expanded",
        },
        **NapalmForm.groups,
    }
示例#25
0
class DataBackupForm(NetmikoForm):
    form_type = HiddenField(default="netmiko_backup_service")
    property = SelectField(
        "Configuration Property to Update",
        choices=list(vs.configuration_properties.items()),
    )
    commands = FieldList(FormField(CommandsForm), min_entries=12)
    replacements = FieldList(FormField(ReplacementForm), min_entries=12)
    add_header = BooleanField("Add header for each ommand", default=True)
    groups = {
        "Target property and commands": {
            "commands": ["property", "add_header", "commands"],
            "default": "expanded",
        },
        "Search Response & Replace": {
            "commands": ["replacements"],
            "default": "expanded",
        },
        **NetmikoForm.groups,
    }
示例#26
0
class DataExtractionForm(ServiceForm):
    form_type = HiddenField(default="data_extraction_service")
    variable1 = StringField("Variable Name")
    query1 = StringField("Python Extraction Query", python=True)
    match_type1 = SelectField("Post Processing", choices=match_choices)
    match1 = StringField(
        "Regular Expression / TextFSM Template Text",
        widget=TextArea(),
        render_kw={"rows": 5},
    )
    operation1 = SelectField("Operation", choices=operation_choices)
    variable2 = StringField("Variable Name")
    query2 = StringField("Python Extraction Query", python=True)
    match_type2 = SelectField("Post Processing", choices=match_choices)
    match2 = StringField(
        "Regular Expression / TextFSM Template Text",
        widget=TextArea(),
        render_kw={"rows": 5},
    )
    operation2 = SelectField("Operation", choices=operation_choices)
    variable3 = StringField("Variable Name")
    query3 = StringField("Python Extraction Query", python=True)
    match_type3 = SelectField("Post Processing", choices=match_choices)
    match3 = StringField(
        "Regular Expression / TextFSM Template Text",
        widget=TextArea(),
        render_kw={"rows": 5},
    )
    operation3 = SelectField("Operation", choices=operation_choices)
    groups = {
        "Extraction 1": {
            "commands":
            ["variable1", "query1", "match_type1", "match1", "operation1"],
            "default":
            "expanded",
        },
        "Extraction 2": {
            "commands":
            ["variable2", "query2", "match_type2", "match2", "operation2"],
            "default":
            "expanded",
        },
        "Extraction 3": {
            "commands":
            ["variable3", "query3", "match_type3", "match3", "operation3"],
            "default":
            "expanded",
        },
    }
示例#27
0
class TopologyImportForm(ServiceForm):
    form_type = HiddenField(default="topology_import_service")
    import_type = SelectField(choices=(
        ("librenms", "LibreNMS"),
        ("netbox", "Netbox"),
        ("opennms", "OpenNMS"),
    ))
    netbox_address = StringField(default="http://0.0.0.0:8000")
    netbox_token = PasswordField()
    opennms_address = StringField()
    opennms_devices = StringField()
    opennms_login = StringField()
    opennms_password = PasswordField()
    librenms_address = StringField(default="http://librenms.example.com")
    librenms_token = PasswordField()
    groups = {
        "Type of Import": {
            "commands": ["import_type"],
            "default": "expanded"
        },
        "Netbox": {
            "commands": ["netbox_address", "netbox_token"],
            "default": "expanded",
        },
        "OpenNMS": {
            "commands": [
                "opennms_address",
                "opennms_devices",
                "opennms_login",
                "opennms_password",
            ],
            "default":
            "expanded",
        },
        "LibreNMS": {
            "commands": ["librenms_address", "librenms_token"],
            "default": "expanded",
        },
    }
示例#28
0
class NetmikoForm(ConnectionForm):
    form_type = HiddenField(default="netmiko")
    abstract_service = True
    driver = SelectField(choices=vs.netmiko_drivers)
    use_device_driver = BooleanField(
        default=True,
        help="common/use_device_driver",
    )
    enable_mode = BooleanField("Enable mode (run in enable mode or as root)",
                               default=True)
    config_mode = BooleanField(
        "Config mode (See Advanced Parameters to override the config mode command)",
        default=False,
    )
    fast_cli = BooleanField()
    timeout = FloatField(default=10.0)
    delay_factor = FloatField(
        ("Delay Factor (Changing from default of 1"
         " will nullify Netmiko Timeout setting)"),
        default=1.0,
    )
    global_delay_factor = FloatField(
        ("Global Delay Factor (Changing from default of 1"
         " will nullify Netmiko Timeout setting)"),
        default=1.0,
    )
    jump_on_connect = BooleanField(
        "Jump to remote device on connect",
        default=False,
        help="netmiko/jump_on_connect",
    )
    jump_command = StringField(
        label="Command that jumps to device",
        default="ssh jump_server_IP",
        substitution=True,
        help="netmiko/jump_command",
    )
    jump_username = StringField(label="Device username",
                                substitution=True,
                                help="netmiko/jump_username")
    jump_password = PasswordField(label="Device password",
                                  substitution=True,
                                  help="netmiko/jump_password")
    exit_command = StringField(
        label="Command to exit device back to original device",
        default="exit",
        substitution=True,
        help="netmiko/exit_command",
    )
    expect_username_prompt = StringField(
        "Expected username prompt",
        default="username:"******"netmiko/expect_username_prompt",
    )
    expect_password_prompt = StringField(
        "Expected password prompt",
        default="password",
        substitution=True,
        help="netmiko/expect_password_prompt",
    )
    expect_prompt = StringField(
        "Expected prompt after login",
        default="admin.*$",
        substitution=True,
        help="netmiko/expect_prompt",
    )
    groups = {
        "Netmiko Parameters": {
            "commands": [
                "driver",
                "use_device_driver",
                "enable_mode",
                "config_mode",
                "fast_cli",
                "timeout",
                "delay_factor",
                "global_delay_factor",
            ],
            "default":
            "expanded",
        },
        **ConnectionForm.groups,
        "Jump on connect Parameters": {
            "commands": [
                "jump_on_connect",
                "jump_command",
                "expect_username_prompt",
                "jump_username",
                "expect_password_prompt",
                "jump_password",
                "expect_prompt",
                "exit_command",
            ],
            "default":
            "hidden",
        },
    }
示例#29
0
class DeviceDataForm(BaseForm):
    template = "device_data"
    form_type = HiddenField(default="device_data")
    data_type = SelectField("Display", choices=vs.configuration_properties)
示例#30
0
class ServiceForm(BaseForm):
    template = "service"
    form_type = HiddenField(default="service")
    get_request_allowed = False
    id = HiddenField()
    name = StringField("Name")
    type = StringField("Service Type")
    access_groups = StringField("Groups")
    shared = BooleanField("Shared")
    scoped_name = StringField("Scoped Name", [InputRequired()])
    description = StringField("Description")
    device_query = StringField("Device Query",
                               python=True,
                               widget=TextArea(),
                               render_kw={"rows": 2})
    device_query_property = SelectField("Query Property Type",
                                        choices=(("name", "Name"),
                                                 ("ip_address", "IP address")))
    target_devices = MultipleInstanceField("Devices", model="device")
    disable_result_creation = BooleanField("Save only failed results")
    target_pools = MultipleInstanceField("Pools", model="pool")
    update_target_pools = BooleanField("Update target pools before running")
    update_pools_after_running = BooleanField("Update pools after running")
    workflows = MultipleInstanceField("Workflows", model="workflow")
    owners = MultipleInstanceField("Owners", model="user")
    owners_access = SelectMultipleStringField(
        "Owners Access",
        choices=[("run", "Run"), ("edit", "Edit")],
    )
    waiting_time = IntegerField(
        "Time to Wait before next service is started (in seconds)", default=0)
    priority = IntegerField("Priority", default=1)
    send_notification = BooleanField("Send a notification")
    send_notification_method = SelectField(
        "Notification Method",
        choices=(("mail", "Mail"), ("slack", "Slack"), ("mattermost",
                                                        "Mattermost")),
    )
    notification_header = StringField(widget=TextArea(),
                                      render_kw={"rows": 5},
                                      substitution=True)
    include_device_results = BooleanField("Include Device Results")
    include_link_in_summary = BooleanField("Include Result Link in Summary")
    display_only_failed_nodes = BooleanField("Display only Failed Devices")
    mail_recipient = StringField("Mail Recipients (separated by comma)")
    reply_to = StringField("Reply-to Email Address")
    number_of_retries = IntegerField("Number of retries", default=0)
    time_between_retries = IntegerField("Time between retries (in seconds)",
                                        default=10)
    max_number_of_retries = IntegerField("Maximum number of retries",
                                         default=100)
    credential_type = SelectField(
        "Type of Credentials",
        choices=(
            ("any", "Any"),
            ("read-write", "Read Write"),
            ("read-only", "Read Only"),
        ),
    )
    maximum_runs = IntegerField("Maximum number of runs", default=1)
    skip_query = StringField("Skip Query (Python)",
                             python=True,
                             widget=TextArea(),
                             render_kw={"rows": 2})
    skip_value = SelectField(
        "Skip Value",
        choices=(
            ("success", "Success"),
            ("failure", "Failure"),
            ("discard", "Discard"),
        ),
    )
    vendor = StringField("Vendor")
    operating_system = StringField("Operating System")
    iteration_values = StringField("Iteration Values", python=True)
    initial_payload = DictField()
    mandatory_parametrization = BooleanField("Parameterized Form is Mandatory")
    parameterized_form = StringField(
        type="code",
        python=True,
        widget=TextArea(),
        default="\n".join(vs.automation["parameterized_form"]),
    )
    iteration_variable_name = StringField("Iteration Variable Name",
                                          default="iteration_value")
    iteration_devices = StringField("Iteration Devices", python=True)
    iteration_devices_property = SelectField(
        "Iteration Devices Property",
        choices=(("name", "Name"), ("ip_address", "IP address")),
    )
    preprocessing = StringField(type="code", python=True, widget=TextArea())
    postprocessing = StringField(type="code", python=True, widget=TextArea())
    postprocessing_mode = SelectField(choices=(
        ("success", "Run on success only"),
        ("failure", "Run on failure only"),
        ("always", "Always run"),
    ))
    default_access = SelectField(choices=(
        ("creator", "Role Based (Creator)"),
        ("public", "Public (All users)"),
        ("admin", "Admin (Admins only"),
    ))
    log_level = SelectField(
        "Logging",
        choices=((0, "Disable logging"), *enumerate(vs.log_levels, 1)),
        default=1,
    )
    multiprocessing = BooleanField("Multiprocessing")
    max_processes = IntegerField("Maximum number of processes", default=15)
    validation_condition = SelectField(choices=(
        ("none", "No validation"),
        ("success", "Run on success only"),
        ("failure", "Run on failure only"),
        ("always", "Always run"),
    ))
    conversion_method = SelectField(choices=(
        ("none", "No conversion"),
        ("text", "Text"),
        ("json", "Json dictionary"),
        ("xml", "XML dictionary"),
    ))
    validation_method = SelectField(
        "Validation Method",
        choices=(
            ("text", "Validation by text match"),
            ("dict_included", "Validation by dictionary inclusion"),
            ("dict_equal", "Validation by dictionary equality"),
        ),
    )
    validation_section = StringField("Section to Validate",
                                     default="results['result']")
    content_match = StringField("Content Match",
                                widget=TextArea(),
                                render_kw={"rows": 8},
                                substitution=True)
    content_match_regex = BooleanField(
        '"Content Match" is a regular expression')
    dict_match = DictField("Dictionary to Match Against", substitution=True)
    negative_logic = BooleanField("Negative logic")
    delete_spaces_before_matching = BooleanField(
        "Delete Spaces before Matching")
    run_method = SelectField(
        "Run Method",
        choices=(
            ("per_device", "Run the service once per device"),
            ("once", "Run the service once"),
        ),
    )

    def validate(self):
        valid_form = super().validate()
        no_recipient_error = (self.send_notification.data
                              and self.send_notification_method.data == "mail"
                              and not self.mail_recipient.data)
        if no_recipient_error:
            self.mail_recipient.errors.append(
                "Please add at least one recipient for the mail notification.")
        forbidden_name_error = self.scoped_name.data in ("Start", "End",
                                                         "Placeholder")
        if forbidden_name_error:
            self.name.errors.append("This name is not allowed.")
        conversion_validation_mismatch = self.validation_condition.data != "none" and (
            self.conversion_method.data == "text"
            and "dict" in self.validation_method.data
            or self.conversion_method.data in ("xml", "json")
            and "dict" not in self.validation_method.data)
        if conversion_validation_mismatch:
            self.conversion_method.errors.append(
                f"The conversion method is set to {self.conversion_method.data}"
                f" and the validation method to {self.validation_method.data} :"
                " these do not match.")
        empty_validation = self.validation_condition.data != "none" and (
            self.validation_method.data == "text"
            and not self.content_match.data or self.validation_method.data
            == "dict_included" and self.dict_match.data == "{}")
        if empty_validation:
            self.content_match.errors.append(
                f"The validation method is set to '{self.validation_method.data}'"
                f" and the matching value is empty: these do no match.")
        too_many_threads_error = (self.max_processes.data >
                                  vs.settings["automation"]["max_process"])
        if too_many_threads_error:
            self.max_processes.errors.append(
                "The number of threads used for multiprocessing must be "
                f"less than {vs.settings['automation']['max_process']}.")
        shared_service_error = not self.shared.data and len(
            self.workflows.data) > 1
        if shared_service_error:
            self.shared.errors.append(
                "The 'shared' property is unticked, but the service belongs"
                " to more than one workflow: this is incompatible.")
        return (valid_form and not conversion_validation_mismatch
                and not empty_validation and not forbidden_name_error
                and not no_recipient_error and not shared_service_error
                and not too_many_threads_error)