Ejemplo n.º 1
0
    def __init__(self, base, frame):
        """Create 'Pools' tab and add to UI frame.

        :param base: The base class for handling pools monitoring functionality.
        :type base: :class:`.AzureBatchPools`
        :param frame: The shared plug-in UI frame.
        :type frame: :class:`.AzureBatchUI`
        """
        self.base = base
        self.label = "Pools "
        self.ready = False
        self.pools_displayed = []
        self.page = maya.form_layout(enableBackground=True)
        with utils.ScrollLayout(v_scrollbar=3, h_scrollbar=0,
                                height=520) as scroll:
            with utils.RowLayout(row_spacing=20) as sublayout:
                if not self.pools_displayed:
                    self.empty_pools = maya.text(label="Loading pool data...",
                                                 font="boldLabelFont",
                                                 parent=sublayout)
                self.pools_layout = sublayout
        with utils.Row(1, 1, 355, "center", (1, "bottom", 0)) as btn:
            self.refresh_button = utils.ProcButton("Refresh", "Refreshing...",
                                                   self.refresh)
        maya.form_layout(self.page,
                         edit=True,
                         attachForm=[(scroll, 'top', 5), (scroll, 'left', 5),
                                     (scroll, 'right', 5), (btn, 'bottom', 5),
                                     (btn, 'left', 0), (btn, 'right', 0)],
                         attachControl=(scroll, "bottom", 5, btn))
        frame.add_tab(self)
        self.is_logged_out()
Ejemplo n.º 2
0
    def __init__(self, base, frame):
        """Create 'Jobs' tab and add to UI frame.

        :param base: The base class for handling jobs monitoring functionality.
        :type base: :class:`.AzureBatchJobHistory`
        :param frame: The shared plug-in UI frame.
        :type frame: :class:`.AzureBatchUI`
        """
        self.base = base
        self.label = " Jobs "
        self.ready = False
        self.jobs_displayed = []
        self.page = maya.form_layout(enableBackground=True)

        with utils.Row(1, 1, 360, "center") as lbl:
            self.total = maya.text(label="", font="boldLabelFont")
        self.paging_display()
        with utils.ScrollLayout(v_scrollbar=3, h_scrollbar=0,
                                height=478) as scroll:
            with utils.RowLayout(row_spacing=20) as sublayout:
                if not self.jobs_displayed:
                    self.empty_jobs = maya.text(label="Loading job data...",
                                                align='left',
                                                font="boldLabelFont")
                self.jobs_layout = sublayout

        with utils.Row(1, 1, 355, "center", (1, "bottom", 0)) as btn:
            self.refresh_button = utils.ProcButton("Refresh", "Refreshing...",
                                                   self.refresh)
        maya.form_layout(self.page,
                         edit=True,
                         attachForm=[(lbl, 'top', 0), (lbl, 'left', 5),
                                     (lbl, 'right', 5),
                                     (self.first_btn, 'left', 5),
                                     (self.last_btn, 'right', 5),
                                     (scroll, 'left', 5), (scroll, 'right', 5),
                                     (btn, 'bottom', 5), (btn, 'left', 0),
                                     (btn, 'right', 0)],
                         attachControl=[(self.first_btn, "top", 5, lbl),
                                        (self.prev_btn, "top", 5, lbl),
                                        (self.last_btn, "top", 5, lbl),
                                        (self.next_btn, "top", 5, lbl),
                                        (scroll, "top", 5, self.first_btn),
                                        (scroll, "top", 5, self.next_btn),
                                        (scroll, "top", 5, self.prev_btn),
                                        (scroll, "top", 5, self.last_btn),
                                        (scroll, "bottom", 5, btn)],
                         attachPosition=[
                             (lbl, 'top', 5, 0),
                             (self.first_btn, 'right', 5, 25),
                             (self.prev_btn, 'left', 5, 25),
                             (self.prev_btn, 'right', 5, 50),
                             (self.next_btn, 'right', 5, 75),
                             (self.next_btn, 'left', 5, 50),
                             (self.last_btn, 'left', 5, 75),
                         ])
        frame.add_tab(self)
        self.is_logged_out()
Ejemplo n.º 3
0
    def __init__(self, base, frame):
        """Create 'Assets' tab and add to UI frame.

        :param base: The base class for handling asset-related functionality.
        :type base: :class:`.AzureBatchAssets`
        :param frame: The shared plug-in UI frame.
        :type frame: :class:`.AzureBatchUI`
        """
        self.base = base
        self.label = "Assets"
        self.ready = False
        self.page = maya.form_layout(enableBackground=True)
        with utils.Row(2, 2, (70, 260), ("left", "left")) as proj:
            maya.text(label="Project:    ", align="left")
            self._asset_group = maya.text_field(height=25, enable=True)

        with utils.ScrollLayout(v_scrollbar=3, h_scrollbar=0,
                                height=450) as scroll:
            self.scroll_layout = scroll
            with utils.ColumnLayout(2) as sublayout:
                self.asset_display = sublayout
        f_btn = maya.button(label="Add Files", command=self.add_asset)
        d_btn = maya.button(label="Add Directory", command=self.add_dir)

        with utils.Row(1, 1, 355) as u_btn:
            self.upload_button = utils.ProcButton("Upload", "Uploading...",
                                                  self.upload)

        with utils.Row(1, 1, 355, "center", (1, "bottom", 0)) as r_btn:
            self.refresh_button = utils.ProcButton("Refresh", "Refreshing...",
                                                   self.refresh_btn_clicked)

        maya.form_layout(self.page,
                         edit=True,
                         attachForm=[(proj, 'left', 5), (proj, 'right', 5),
                                     (proj, 'top', 5), (scroll, 'left', 5),
                                     (scroll, 'right', 5), (f_btn, 'left', 5),
                                     (d_btn, 'right', 5), (u_btn, 'left', 0),
                                     (u_btn, 'right', 0), (r_btn, 'bottom', 5),
                                     (r_btn, 'left', 0), (r_btn, 'right', 0)],
                         attachControl=[(proj, "bottom", 5, scroll),
                                        (proj, "bottom", 5, scroll),
                                        (scroll, "bottom", 5, f_btn),
                                        (scroll, "bottom", 5, d_btn),
                                        (f_btn, "bottom", 5, u_btn),
                                        (d_btn, "bottom", 5, u_btn),
                                        (u_btn, "bottom", 5, r_btn)],
                         attachPosition=[(f_btn, 'right', 5, 50),
                                         (d_btn, 'left', 5, 50)])
        frame.add_tab(self)
        self.is_logged_out()
Ejemplo n.º 4
0
    def __init__(self, base, settings, frame):
        """Create 'Config' tab and add to UI frame.

        :param base: The base class for handling configuration and
         auth-related functionality.
        :type base: :class:`.AzureBatchConfig`
        :param frame: The shared plug-in UI frame.
        :type frame: :class:`.AzureBatchUI`
        """
        self.base = base
        self.settings = settings
        self.label = "Config"
        self.ready = False
        self.page = maya.form_layout()
        self.frame = frame
        self.subscription_row = None
        self.batch_account_row = None
        self.account_ui_elements = None
        self.subscription_ui_elements = None
        self.auth_temp_ui_elements = []
        self.batch_account_framelayout = None
        self.aad_environment_dropdown = None
        self.plugin_settings_framelayout = None

        with utils.ScrollLayout(height=475, width=375,
                                parent=self.page) as scroll_layout:
            self.scroll_layout = scroll_layout
            with utils.RowLayout(row_spacing=20, width=375) as sublayout:
                self.frames_layout = sublayout
                self.heading = maya.text(label="Loading plug-in...",
                                         align="center",
                                         font="boldLabelFont",
                                         wordWrap=True)

        frame.add_tab(self)
        maya.form_layout(self.page, edit=True, enable=False)
        maya.refresh()
Ejemplo n.º 5
0
    def __init__(self, base, frame, images, skus, licenses):
        """Create 'Env' tab and add to UI frame.

        :param base: The base class for handling Maya and plugin-related functionality.
        :type base: :class:`.AzureBatchEnvironment`
        :param frame: The shared plug-in UI frame.
        :type frame: :class:`.AzureBatchUI`
        """
        self.base = base
        self.label = " Env  "
        self.ready = False
        self.page = maya.form_layout(enableBackground=True)
        self.license_settings = {}
        with utils.ScrollLayout(v_scrollbar=3, h_scrollbar=0,
                                height=520) as scroll:

            with utils.RowLayout(row_spacing=20) as sublayout:
                with utils.FrameLayout(label="Render Node Configuration",
                                       collapsable=True,
                                       width=325):

                    with utils.ColumnLayout(2,
                                            col_width=((1, 160), (2, 160)),
                                            row_spacing=(1, 5),
                                            row_offset=((1, "top", 15),
                                                        (5, "bottom", 15))):
                        maya.text(label="Use Image: ", align='right')
                        with utils.Dropdown(self.set_image) as image_settings:
                            self._image = image_settings
                            for image in images:
                                self._image.add_item(image)
                        maya.text(label="Use VM Type: ", align='right')
                        with utils.Dropdown(self.set_sku) as sku_settings:
                            self._sku = sku_settings
                            for sku in skus:
                                self._sku.add_item(sku)
                        maya.text(label="Use licenses: ", align='right')
                        for label, checked in licenses.items():
                            self.license_settings[label] = maya.check_box(
                                label=label,
                                value=checked,
                                changeCommand=self.use_license_server)
                            maya.text(label="", align='right')

                with utils.FrameLayout(label="Environment Variables",
                                       collapsable=True,
                                       width=325,
                                       collapse=True):
                    with utils.Row(1, 1, 325):
                        self.env_vars = maya.table(
                            rows=0,
                            columns=2,
                            columnWidth=[(1, 155), (2, 155)],
                            label=[(1, "Setting"), (2, "Value")],
                            rowHeight=15,
                            editable=False,
                            selectionBehavior=1,
                            getCellCmd=self.populate_row)

                    with utils.ColumnLayout(2, col_width=((1, 160), (2, 160))):
                        self.custom_env_var = maya.text_field(
                            placeholderText='Env Variable')
                        self.custom_env_val = maya.text_field(
                            placeholderText='Value')
                        maya.button(label="Add", command=self.add_row)
                        maya.button(label="Delete", command=self.delete_row)

        with utils.Row(1, 1, 355, "center", (1, "bottom", 0)) as btn:
            self.refresh_button = utils.ProcButton("Refresh", "Refreshing...",
                                                   self.refresh)
        maya.form_layout(self.page,
                         edit=True,
                         attachForm=[(scroll, 'top', 5), (scroll, 'left', 5),
                                     (scroll, 'right', 5), (btn, 'bottom', 5),
                                     (btn, 'left', 5), (btn, 'right', 5)],
                         attachControl=(scroll, "bottom", 5, btn))
        frame.add_tab(self)
        self.is_logged_out()
Ejemplo n.º 6
0
    def __init__(self, base, frame, images, skus, licenses):
        """Create 'Env' tab and add to UI frame.

        :param base: The base class for handling Maya and plugin-related functionality.
        :type base: :class:`.AzureBatchEnvironment`
        :param frame: The shared plug-in UI frame.
        :type frame: :class:`.AzureBatchUI`
        """
        self.base = base
        self.label = " Env  "
        self.ready = False
        self.page = maya.form_layout(enableBackground=True)
        self.licenses = licenses
        self.batch_images = images
        self.poolImageFilter = PoolImageFilter(PoolImageProvider())

        self.image_config = None
        self.license_settings = {}
        self.node_sku_id = ""
        self.containerImageUI = None
        self.select_rendernode_type = PoolImageMode.MARKETPLACE_IMAGE.value

        with utils.ScrollLayout(v_scrollbar=3, h_scrollbar=0,
                                height=520) as scroll:

            with utils.RowLayout(row_spacing=20) as sublayout:
                with utils.FrameLayout(label="Render Node Configuration",
                                       collapsable=True,
                                       width=325,
                                       collapse=False) as rendernode_config:
                    self.rendernode_config = rendernode_config
                    with utils.ColumnLayout(2,
                                            col_width=((1, 100), (2, 200)),
                                            row_spacing=(1, 5),
                                            row_offset=((1, "top", 15),
                                                        (2, "bottom", 20))):
                        maya.text(label="Use VM SKU: ", align='left')
                        with utils.Dropdown(self.set_sku) as sku_settings:
                            self._sku = sku_settings
                            for sku in skus:
                                self._sku.add_item(sku)

                    with utils.Row(1, 1, 325):
                        maya.radio_group(
                            labelArray2=("Batch Provided Image",
                                         "Container Image"),
                            numberOfRadioButtons=2,
                            select=self.select_rendernode_type,
                            vertical=True,
                            onCommand1=self.set_marketplace_image_mode,
                            onCommand2=self.set_container_image_mode)

                    maya.parent()
                    self.set_marketplace_image_mode()

                with utils.FrameLayout(label="Application Licenses",
                                       collapsable=True,
                                       width=325,
                                       collapse=True):

                    with utils.ColumnLayout(2,
                                            col_width=((1, 100), (2, 200)),
                                            row_spacing=(1, 5),
                                            row_offset=((1, "top", 15))):

                        maya.text(label="Use licenses:   ", align='right')
                        for label, checked in licenses.items():
                            self.license_settings[label] = maya.check_box(
                                label=label,
                                value=checked,
                                changeCommand=self.use_license)
                            maya.text(label="", align='right')

                with utils.FrameLayout(label="Environment Variables",
                                       collapsable=True,
                                       width=325,
                                       collapse=True):

                    with utils.Row(1, 1, 325):
                        self.env_vars = maya.table(
                            height=95,
                            rows=0,
                            columns=2,
                            columnWidth=[(1, 190), (2, 190)],
                            label=[(1, "Setting"), (2, "Value")],
                            rowHeight=15,
                            editable=False,
                            selectionBehavior=1,
                            getCellCmd=self.populate_row)

                    with utils.ColumnLayout(2, col_width=((1, 200), (2, 200))):
                        self.custom_env_var = maya.text_field(
                            placeholderText='Env Variable')
                        self.custom_env_val = maya.text_field(
                            placeholderText='Value')
                        maya.button(label="Add", command=self.add_row)
                        maya.button(label="Delete", command=self.delete_row)

        with utils.Row(1, 1, 355, "center", (1, "bottom", 0)) as btn:
            self.refresh_button = utils.ProcButton("Refresh", "Refreshing...",
                                                   self.refresh_btn_clicked)
        maya.form_layout(self.page,
                         edit=True,
                         attachForm=[(scroll, 'top', 5), (scroll, 'left', 5),
                                     (scroll, 'right', 5), (btn, 'bottom', 5),
                                     (btn, 'left', 5), (btn, 'right', 5)],
                         attachControl=(scroll, "bottom", 5, btn))
        frame.add_tab(self)
        self.is_logged_out()
Ejemplo n.º 7
0
    def __init__(self, base, frame):
        """Create 'Config' tab and add to UI frame.

        :param base: The base class for handling configuration and
         auth-related functionality.
        :type base: :class:`.AzureBatchConfig`
        :param frame: The shared plug-in UI frame.
        :type frame: :class:`.AzureBatchUI`
        """
        self.base = base
        self.label = "Config"
        self.ready = False
        self.page = maya.form_layout()

        with utils.ScrollLayout(height=520, parent=self.page) as scroll:
            self.heading = maya.text(label="Authenticating plug-in...",
                                        align="center", font="boldLabelFont")
            with utils.Row(2, 2, (70,260), ("left","left"),
                           [(1, "top", 15),(2,"top",15)]):
                maya.text(label="Status: ", align="left")
                self.auth_status = maya.text(label="", align="left")
            
            with utils.Row(2, 2, (70,260), ("left","left")):
                maya.text(label="Service:    ", align="left")
                self._endpoint = maya.text_field(height=25, enable=True)
            
            #TODO: Allow set to 0 to disable threads
            with utils.Row(2, 2, (70,260), ("left","left")):
                maya.text(label="Threads:    ", align="left")
                self._threads = maya.int_field(
                    changeCommand=self.set_threads,
                    height=25,
                    minValue=1,
                    maxValue=40,
                    enable=True,
                    value=20)
            
            with utils.Row(2, 2, (70,260), ("left","center"),
                           [(1, "bottom", 20),(2,"bottom",15)]):
                maya.text(label="Logging:    ", align="left")
                with utils.Dropdown(self.set_logging) as log_settings:
                    self._logging = log_settings
                    self._logging.add_item("Debug")
                    self._logging.add_item("Info")
                    self._logging.add_item("Warning")
                    self._logging.add_item("Error")

            box_label = "Azure Authentication Settings"
            with utils.FrameLayout(label=box_label, collapsable=True):
                with utils.Row(2, 2, (140, 180), ("right","center"),
                               [(1, "top", 20),(2, "top", 15)]):
                    maya.text(label="Batch Account:   ", align="right")
                    self._account = maya.text_field(height=25, enable=True)

                with utils.Row(2, 2, (140, 180), ("right","center"),
                               [(1, "bottom", 20),(2,"bottom",15)]):
                    maya.text(label="Batch Key:   ", align="right")
                    self._key = maya.text_field(height=25, enable=True)

                with utils.Row(2, 2, (140, 180), ("right","center"),
                               [(1, "bottom", 20),(2,"bottom",15)]):
                    maya.text(label="Storage Account:   ", align="right")
                    self._storage = maya.text_field(height=25, enable=True)

                with utils.Row(2, 2, (140, 180), ("right","center"),
                               [(1, "bottom", 20),(2,"bottom",15)]):
                    maya.text(label="Storage Key:   ", align="right")
                    self._storage_key = maya.text_field(height=25, enable=True)

        with utils.Row(1, 1, 355, "center", (1, "bottom",0)) as btn:
            self._authenticate = maya.button(label="Authenticate",
                                             command=self.authenticate,
                                             enable=True)
        maya.form_layout(self.page, edit=True,
                         attachForm=[(scroll, 'top', 5),
                                     (scroll, 'left', 5), (scroll, 'right', 5),
                                     (btn, 'bottom', 5),
                                     (btn, 'left', 0), (btn, 'right', 0)],
                         attachControl=(scroll, "bottom", 5, btn))
        frame.add_tab(self)
        maya.form_layout(self.page, edit=True, enable=False)
        maya.refresh()
Ejemplo n.º 8
0
    def __init__(self, base, frame):
        """Create 'Submit' tab and add to UI frame.

        :param base: The base class for handling jobs submission functionality.
        :type base: :class:`.AzureBatchSubmission`
        :param frame: The shared plug-in UI frame.
        :type frame: :class:`.AzureBatchUI`
        """
        self.base = base
        self.label = "Submit"
        self.page = maya.form_layout(enableBackground=True)
        self.select_pool_type = self.AUTO_POOL
        self.select_dedicated_instances = 1
        self.select_low_pri_instances = 0

        with utils.ScrollLayout(height=475, parent=self.page) as scroll:
            box_label = "Pool Settings"
            with utils.FrameLayout(label=box_label,
                                   collapsable=True) as pool_settings:
                self.pool_settings = pool_settings
                maya.col_layout(numberOfColumns=2,
                                columnWidth=((1, 100), (2, 200)),
                                rowSpacing=(1, 10),
                                rowOffset=((1, "top", 20), ))
                maya.text(label="Pools:   ", align="right")
                maya.radio_group(
                    labelArray3=("Auto provision a pool for this job",
                                 "Reuse an existing persistent pool",
                                 "Create a new persistent pool"),
                    numberOfRadioButtons=3,
                    select=self.select_pool_type,
                    vertical=True,
                    onCommand1=self.set_pool_auto,
                    onCommand2=self.set_pool_reuse,
                    onCommand3=self.set_pool_new)
                maya.parent()
                self.pool_config = []
                self.pool_config.append(
                    maya.col_layout(numberOfColumns=4,
                                    columnWidth=((1, 100), (2, 50), (3, 100),
                                                 (4, 50)),
                                    rowSpacing=(1, 10),
                                    rowOffset=((1, "bottom", 20), ),
                                    parent=self.pool_settings))
                self.pool_config.append(
                    maya.text(label="Dedicated VMs:   ",
                              align="right",
                              parent=self.pool_config[0]))
                self.pool_config.append(
                    maya.int_field(
                        value=self.select_dedicated_instances,
                        minValue=1,
                        maxValue=self.base.max_pool_size,
                        changeCommand=self.set_dedicated_instances,
                        annotation="Number of dedicated VMs in pool",
                        parent=self.pool_config[0]))
                self.pool_config.append(
                    maya.text(label="Low-pri VMs:   ",
                              align="right",
                              parent=self.pool_config[0]))
                self.pool_config.append(
                    maya.int_field(
                        value=self.select_low_pri_instances,
                        minValue=0,
                        maxValue=self.base.max_pool_size,
                        changeCommand=self.set_low_pri_instances,
                        annotation="Number of low-priority VMs in pool",
                        parent=self.pool_config[0]))
                maya.parent()

            box_label = "Render Settings"
            with utils.FrameLayout(label=box_label, collapsable=True) as box:
                self.render_module = box

        with utils.Row(3, 2, (120, 200, 35)) as watch:
            self.job_watcher_ui()

        with utils.Row(1, 1, 355, "center") as s_btn:
            self.submit_button = utils.ProcButton("Submit Job",
                                                  "Submitting...", self.submit)

        with utils.Row(1, 1, 355, "center", (1, "bottom", 0)) as r_btn:
            self.refresh_button = utils.ProcButton("Refresh", "Refreshing...",
                                                   self.refresh)

        maya.form_layout(self.page,
                         edit=True,
                         attachForm=[(scroll, 'top', 5), (scroll, 'left', 5),
                                     (scroll, 'right', 5), (watch, 'left', 0),
                                     (watch, 'right', 0), (s_btn, 'left', 0),
                                     (s_btn, 'right', 0), (r_btn, 'bottom', 5),
                                     (r_btn, 'left', 0), (r_btn, 'right', 0)],
                         attachControl=[(scroll, "bottom", 5, watch),
                                        (watch, "bottom", 5, s_btn),
                                        (s_btn, "bottom", 5, r_btn)])
        frame.add_tab(self)
Ejemplo n.º 9
0
    def __init__(self, base, frame):
        """Create 'Submit' tab and add to UI frame.

        :param base: The base class for handling jobs submission functionality.
        :type base: :class:`.AzureBatchSubmission`
        :param frame: The shared plug-in UI frame.
        :type frame: :class:`.AzureBatchUI`
        """
        self.base = base
        self.frame = frame
        self.label = "Submit"
        self.page = maya.form_layout(enableBackground=True)
        self.select_dedicated_instances = 1
        self.select_low_pri_instances = 0
        self.selected_container_image = None
        self.container_image_text_row = None
        self.container_image_dropdown_row = None
        self.persistent_pool_dropdown_row = None
        self.render_node_config_row = None
        self.reused_pool_id = None
        self.container_config = []
        self.select_pool_type = self.AUTO_POOL
        with utils.ScrollLayout(height=475, parent=self.page) as scroll:
            box_label = "Pool Settings"
            with utils.FrameLayout(label=box_label, collapsable=True) as pool_settings:
                self.pool_settings = pool_settings
                with utils.ColumnLayout(
                        2, col_width=((1,100),(2,200)), row_spacing=(1,10),
                        row_offset=((1, "top", 20),(5, "bottom", 15))):
                    maya.text(label="Pools:   ", align="right")
                    maya.radio_group(
                        labelArray3=("Auto provision a pool for this job",
                                     "Reuse an existing persistent pool",
                                     "Create a new persistent pool"),
                        numberOfRadioButtons=3,
                        select=self.select_pool_type,
                        vertical=True,
                        onCommand1=self.set_pool_auto,
                        onCommand2=self.set_pool_reuse,
                        onCommand3=self.set_pool_new)
                    self.pool_config = []

            box_label = "Render Settings"
            with utils.FrameLayout(label=box_label, collapsable=True) as box:
                self.render_module = box

        with utils.Row(3, 2, (120,200,35)) as watch:
            self.job_watcher_ui()

        with utils.Row(1, 1, 355, "center") as s_btn:
            self.submit_button = utils.ProcButton(
                "Submit Job", "Submitting...", self.submit)

        with utils.Row(1, 1, 355, "center", (1,"bottom",0)) as r_btn:
            self.refresh_button = utils.ProcButton(
                "Refresh", "Refreshing...", self.refresh_btn_clicked)

        maya.form_layout(
            self.page, edit=True,
            attachForm=[(scroll, 'top', 5),
                        (scroll, 'left', 5), (scroll, 'right', 5),
                        (watch, 'left', 0), (watch, 'right', 0),
                        (s_btn, 'left', 0), (s_btn, 'right', 0),
                        (r_btn, 'bottom', 5),
                        (r_btn, 'left', 0), (r_btn, 'right', 0)],
            attachControl=[(scroll, "bottom", 5, watch),
                           (watch, "bottom" ,5, s_btn),
                           (s_btn, "bottom", 5, r_btn)])
        frame.add_tab(self)