Beispiel #1
0
class AboutDialog(DialogBase):
    GITHUB_URL = 'https://github.com/ContinuumIO/anaconda-issues/issues'

    def __init__(self, *args, **kwargs):
        super(AboutDialog, self).__init__(*args, **kwargs)
        self.tracker = GATracker()
        text = """<b>Anaconda Navigator {version}</b><br>
            <br>Copyright &copy; 2016 Continuum Analytics
            <p>Created by Continuum Analytics
            <br>
            <p>For bug reports and feature requests, please visit our
            """.format(version=__version__)
        self.label_icon = QLabel()
        self.label_about = QLabel(text)
        self.button_link = ButtonLink('Issue Tracker')
        self.button_label = ButtonLabel('on GitHub.')
        self.button_ok = QPushButton('Ok')

        # Widget setup
        self.button_ok.setMinimumWidth(70)
        self.label_about.setOpenExternalLinks(True)
        self.label_icon.setPixmap(QPixmap(images.ANACONDA_ICON_64_PATH))
        self.setWindowTitle("About Anaconda Navigator")

        # Layouts
        h_layout = QHBoxLayout()
        h_layout.addWidget(self.label_icon, 0, Qt.AlignTop)
        h_layout.addSpacing(10)

        content_layout = QVBoxLayout()
        content_layout.addWidget(self.label_about, 0, Qt.AlignBottom)
        content_layout.setContentsMargins(0, 0, 0, 0)
        h_content_layout = QHBoxLayout()
        h_content_layout.addWidget(self.button_link, 0, Qt.AlignLeft)
        h_content_layout.addWidget(self.button_label, 0, Qt.AlignLeft)
        h_content_layout.addStretch(0)
        h_content_layout.setContentsMargins(0, 0, 0, 0)

        content_layout.addLayout(h_content_layout)
        h_layout.addLayout(content_layout)

        buttons_layout = QHBoxLayout()
        buttons_layout.addStretch()
        buttons_layout.addWidget(self.button_ok)

        main_layout = QVBoxLayout()
        main_layout.addLayout(h_layout)
        main_layout.addSpacing(24)
        main_layout.addLayout(buttons_layout)
        self.setLayout(main_layout)

        # Signals
        self.button_ok.clicked.connect(self.accept)
        self.button_link.clicked.connect(
            lambda: self.open_url(self.GITHUB_URL))

    def open_url(self, url):
        self.tracker.track_event('content', 'click', url)
        QDesktopServices.openUrl(QUrl(url))
Beispiel #2
0
class ListWidgetContent(QListWidget):
    """
    List Widget holding available videos in the learning tab.
    """
    sig_view_video = Signal(str, str)

    def __init__(self, *args, **kwargs):
        self._main = kwargs.pop('main', None)
        super(ListWidgetContent, self).__init__(*args, **kwargs)

        self.tracker = GATracker()
        self._items = []
        self.setObjectName('VideoListWidget')
        self.setResizeMode(QListWidget.Adjust)
        self.setMovement(QListWidget.Static)
        self.setFrameStyle(QListWidget.Plain)
        self.setSelectionMode(QAbstractItemView.NoSelection)
        self.setViewMode(QListWidget.IconMode)
        self.setFocusPolicy(Qt.NoFocus)
        self.setUniformItemSizes(True)

    def addItem(self, item):
        """
        Add a content item to the list.
        """
        super(ListWidgetContent, self).addItem(item)
        self._items.append(item)
        self.setItemWidget(item, item.widget)
        uri = item.uri
        title = item.title
        item.button_view.clicked.connect(lambda: self.launch(uri, title))

    def launch(self, uri, title):
        """
        Emit signal with youtube video identifier string.
        """
        qurl = QUrl(uri)
        QDesktopServices.openUrl(qurl)
        self.tracker.track_event('content', 'click', uri)
        self.sig_view_video.emit(uri, title)

    def update_style_sheet(self, style_sheet=None):
        """
        """
        if style_sheet is None:
            style_sheet = load_style_sheet()

        for item in self._items:
            try:
                item_widget = self.itemWidget(item)
                item_widget.setStyleSheet(style_sheet)
                item.setSizeHint(item_widget.sizeHint())
            except Exception:
                # This error is just in case the C++ object has been
                # deleted and it is not crucial to log.
                pass
        self.update()
        self.repaint()
Beispiel #3
0
class ListWidgetContent(ListWidgetBase):
    """List Widget holding available videos in the learning tab."""

    sig_view_video = Signal(str, str)

    def __init__(self, *args, **kwargs):
        """List Widget holding available videos in the learning tab."""
        self._main = kwargs.pop('main', None)  # FIXME:
        super(ListWidgetContent, self).__init__(*args, **kwargs)
        self.tracker = GATracker()
        self.setViewMode(QListWidget.IconMode)

    def ordered_widgets(self):
        """Return a list of the ordered widgets."""
        ordered_widgets = []
        for item in self.items():
            ordered_widgets += item.ordered_widgets()
        return ordered_widgets

    def setup_item(self, item):
        """Override base method."""
        max_width = (
            SASS_VARIABLES.WIDGET_CONTENT_TOTAL_WIDTH - 2 *
            SASS_VARIABLES.WIDGET_CONTENT_PADDING - 2 *
            SASS_VARIABLES.WIDGET_CONTENT_MARGIN
        )
        uri = item.uri
        title = item.title

        item.button_text.clicked.connect(lambda: self.launch(uri, title))
        item.button_text.sig_entered.connect(
            lambda: item.frame_hover.fade_in()
        )
        item.button_text.sig_entered.connect(lambda: self.scroll_to_item(item))
        item.button_text.sig_left.connect(lambda: item.frame_hover.fade_out())
        item.frame_hover.sig_clicked.connect(lambda: self.launch(uri, title))
        item.frame_hover.sig_clicked.connect(
            lambda: item.button_text.setFocus()
        )
        item.widget.setStyleSheet(self.style_sheet)
        item.label_text.setText(
            '\n' + split_text(title, item.label_text, max_width)
        )

    def launch(self, uri, title):
        """Emit signal with youtube video identifier string."""
        qurl = QUrl(uri)
        QDesktopServices.openUrl(qurl)
        self.tracker.track_event('content', 'click', uri)
        self.sig_view_video.emit(uri, title)
Beispiel #4
0
class MainWindow(QMainWindow):
    sig_logged_in = Signal()
    sig_logged_out = Signal()

    DOCS_URL = 'https://docs.continuum.io/anaconda/navigator'
    VIDEOS_URL = "http://content.continuum.io/api/videos"
    EVENTS_URL = "http://content.continuum.io/api/events"
    WEBINARS_URL = "http://content.continuum.io/api/webinars"

    def __init__(self, splash=None):
        super(MainWindow, self).__init__()
        self.tracker = None
        self.splash = splash

        # Anaconda API
        self.api = AnacondaAPI()
        self.busy = False
        self.logged = False
        self.username = ''
        self._login_text = 'Sign in to Anaconda Cloud'
        self.first_run = CONF.get('main', 'first_run')
        self.application_update_version = None

        # Widgets
        self.frame_header = FrameHeader(self)
        self.frame_body = FrameBody(self)
        self.label_logo = LabelHeaderLogo('ANACONDA NAVIGATOR')
        self.button_logged_text = ButtonLabelLogin('')
        self.button_logged_username = ButtonLinkLogin('')
        self.label_update_available = LabelHeaderUpdate('Update available!')
        self.button_update_available = ButtonHeaderUpdate('Update')
        self.button_login = ButtonLogin(self._login_text)
        self.central_widget = QWidget()
        self.statusbar = self.statusBar()
        self.progressbar = QProgressBar()

        self.stack = TabWidgetBody(self)
        self.home_tab = HomeTab(parent=self)
        self.environments_tab = EnvironmentsTab(parent=self)
        self.learning_tab = CommunityTab(
            parent=self,
            tags=['webinar', 'documentation', 'video', 'training'],
            content_urls=[self.VIDEOS_URL, self.WEBINARS_URL])
        self.community_tab = CommunityTab(parent=self,
                                          tags=['event', 'forum', 'social'],
                                          content_urls=[self.EVENTS_URL])

        #        self.projects_tab = ProjectsTab(parent=self)

        # Note: Icons are set in CSS
        self.stack.addTab(self.home_tab, text='Home')
        self.stack.addTab(self.environments_tab, text='Environments')
        self.stack.addTab(self.learning_tab, text='Learning')
        self.stack.addTab(self.community_tab, text='Community')
        #        self.stack.addTab(self.projects_tab, 'Projects')

        # Widget setup
        self.button_login.setDefault(True)
        self.label_logo.setPixmap(QPixmap(images.ANACONDA_NAVIGATOR_LOGO))
        self.setWindowTitle("Anaconda Navigator")
        self.statusbar.addPermanentWidget(self.progressbar)
        self.progressbar.setVisible(False)

        # Layout
        header_layout = QHBoxLayout()
        header_layout.addWidget(self.label_logo)
        header_layout.addSpacing(18)
        header_layout.addWidget(self.label_update_available, 0, Qt.AlignCenter)
        header_layout.addWidget(self.button_update_available, 0,
                                Qt.AlignCenter)
        header_layout.addStretch()
        header_layout.addWidget(self.button_logged_text, 0, Qt.AlignTrailing)
        header_layout.addWidget(self.button_logged_username, 0,
                                Qt.AlignTrailing)
        header_layout.addWidget(self.button_login, 0, Qt.AlignTrailing)
        header_layout.setContentsMargins(0, 0, 0, 0)
        self.frame_header.setLayout(header_layout)

        body_layout = QHBoxLayout()
        body_layout.addWidget(self.stack)
        body_layout.setContentsMargins(0, 0, 0, 0)
        self.frame_body.setLayout(body_layout)

        main_layout = QVBoxLayout()
        main_layout.addWidget(self.frame_header)
        main_layout.addWidget(self.frame_body)
        main_layout.setContentsMargins(0, 0, 0, 0)
        main_layout.setSpacing(0)
        self.central_widget.setLayout(main_layout)
        self.setContentsMargins(0, 0, 0, 0)
        self.setCentralWidget(self.central_widget)

        # Signals
        self.button_login.clicked.connect(self.login)
        self.button_logged_username.clicked.connect(self.open_login_page)
        self.button_update_available.clicked.connect(self.update_application)
        self.stack.currentChanged.connect(self._track_tab)

        # This needs to be reworked!
        #        self.projects_tab.sig_apps_updated.connect(
        #            self.home_tab.set_applications)
        #        self.projects_tab.sig_apps_changed.connect(
        #            self.home_tab.set_applications)
        #        self.projects_tab.sig_project_updated.connect(
        #            self.home_tab.set_applications)
        #        self.projects_tab.sig_status_updated.connect(self.update_status_bar)

        # Setup
        self.api.set_data_directory(CHANNELS_PATH)
        self.update_style_sheet()

    # Helpers
    # -------------------------------------------------------------------------
    def _track_tab(self, index=None):
        """
        Tracks the active tab by index, or set `Home` when no index has been
        provided.
        """
        if index is None:
            index = self.stack.currentIndex()

        text = self.stack.currentText().lower()

        if self.tracker:
            page = '/{0}'.format(text)
            self.tracker.track_page(page)

    def _metadata_updated(self, worker, path, error):
        self.set_splash('Updating repodata...')
        if error:
            logger.error(str(error))

        if path and os.path.isfile(path):
            with open(path, 'r') as f:
                data = f.read()
        try:
            self._metadata = json.loads(data)
        except Exception:
            self._metadata = {}

        channels = CONF.get('main', 'conda_channels', default=tuple())
        if not channels:
            channels = self.api.conda_get_condarc_channels()
            CONF.set('main', 'conda_channels', channels)
            CONF.set('main', 'conda_active_channels', channels)

        self.api.update_repodata(channels=channels)
        self.api.sig_repodata_updated.connect(self._repodata_updated)

    def _repodata_updated(self, paths):
        self.set_splash('Loading repodata...')
        self.api.sig_repodata_updated.disconnect(self._repodata_updated)

        if self.first_run:
            self.set_splash('Initial configuration...')
            self.api.create_default_project()

        worker = self.api.client_load_repodata(paths, self._metadata)
        worker.sig_finished.connect(self.create_application_projects)

    # --- Public API
    # -------------------------------------------------------------------------
    def setup(self):
        """
        Perform initial setup and configuration.
        """
        self.set_splash('Updating metadata...')
        user = self.api.client_set_domain()
        self.update_login_status(user)
        self.setup_toolbars()
        self.set_application_icon()
        worker = self.api.update_metadata()
        worker.sig_finished.connect(self._metadata_updated)
        statusbar = self.statusBar()
        statusbar.setVisible(False)
        statusbar.setMaximumHeight(0)
        statusbar.hide()

    def create_application_projects(self, worker, output, error):
        if error:
            logger.error(str(error))
        packages, apps = output
        self.api.create_application_projects(
            apps,
            add_project=self.first_run,
        )

        self.post_setup(apps)
        self.check_for_updates(packages)

    def post_setup(self, apps):
        CONF.set('main', 'first_run', False)
        self.set_splash('Loading applications...')
        self.home_tab.setup_tab(apps)
        #        self.set_splash('Loading projects...')
        #        self.projects_tab.setup_tab()
        self.set_splash('Loading environments...')
        self.environments_tab.setup_tab(metadata=self._metadata)
        self.set_splash('Loading content...')
        self.community_tab.setup_tab()
        self.set_splash('Loading content...')
        self.learning_tab.setup_tab()
        self.update_style_sheet()

        self.showMaximized()
        self.post_visible_setup()

    def set_application_icon(self):
        """
        """
        app = QCoreApplication.instance()
        app_icon = QIcon()
        app_icon.addFile(images.ANACONDA_ICON_16_PATH, QSize(16, 16))
        app_icon.addFile(images.ANACONDA_ICON_24_PATH, QSize(24, 24))
        app_icon.addFile(images.ANACONDA_ICON_32_PATH, QSize(32, 32))
        app_icon.addFile(images.ANACONDA_ICON_48_PATH, QSize(48, 48))
        app_icon.addFile(images.ANACONDA_ICON_256_PATH, QSize(256, 256))
        app.setWindowIcon(app_icon)

    def setup_toolbars(self):
        menubar = self.menuBar()

        file_menu = menubar.addMenu('&File')
        file_menu.addAction(
            create_action(self,
                          "&Preferences",
                          triggered=self.show_preferences,
                          shortcut="Ctrl+P"))
        file_menu.addAction(
            create_action(self,
                          "&Quit",
                          triggered=self.close,
                          shortcut="Ctrl+Q"))

        helpmenu = menubar.addMenu('&Help')
        helpmenu.addAction(
            create_action(self,
                          "&Online Documentation",
                          triggered=lambda: self.open_url(self.DOCS_URL)))
        helpmenu.addAction(
            create_action(self,
                          "&Logs viewer",
                          triggered=self.show_log_viewer,
                          shortcut="F6"))
        helpmenu.addSeparator()
        helpmenu.addAction(
            create_action(self, "&About", triggered=self.show_about))

    def post_visible_setup(self):
        if self.splash:
            self.splash.hide()

        CONF.set('main', 'first_run', False)

        # Start the tracker only after post_visible_setup
        self.tracker = GATracker()
        self._track_tab(0)  # Start tracking home
        self.fix_tab_ordering()
        self.show_welcome_screen()

    def check_for_updates(self, packages=None):
        # Check if there is an update for navigator!
        version = self.api.conda_package_version(name='root',
                                                 pkg='anaconda-navigator')
        # Temporal mock test
        # mock_versions = [version, '1.1.0']
        # packages['anaconda-navigator'] = {'versions': mock_versions}
        self.button_update_available.setVisible(False)
        self.label_update_available.setVisible(False)

        text = ''
        if packages:
            package_data = packages.get('anaconda-navigator')
            if package_data:
                versions = package_data.get('versions')
                if versions and version != versions[-1]:
                    self.application_update_version = versions[-1]
                    self.label_update_available.setText(text)
                    self.label_update_available.setVisible(True)
                    self.button_update_available.setVisible(True)

    def fix_tab_ordering(self):
        return
        for tab in [self.community_tab, self.learning_tab]:
            self.setTabOrder(self.stack.tabbar.buttons[-1],
                             tab.filter_widgets[0])
            for i in range(len(tab.filter_widgets) - 1):
                self.setTabOrder(tab.filter_widgets[i],
                                 tab.filter_widgets[i + 1])
            self.setTabOrder(tab.filter_widgets[-1], tab.text_filter)
            self.setTabOrder(tab.text_filter.button_icon, tab.list)
            self.setTabOrder(tab.list, self.button_login)


#        self.button_login.setFocus()

        self.setTabOrder(self.stack.tabbar.buttons[-1],
                         self.environments_tab.text_search)

        self.environments_tab.packages_widget.table_last_row.add_focus_widget(
            self.button_login)
        self.setTabOrder(self.environments_tab.packages_widget.table_last_row,
                         self.button_login)

    def update_style_sheet(self):
        style_sheet = load_style_sheet()
        #        self.home_tab.update_style_sheet(style_sheet)
        self.environments_tab.update_style_sheet(style_sheet)
        #        self.community_tab.update_style_sheet(style_sheet)
        #        self.learning_tab.update_style_sheet(style_sheet)
        self.setStyleSheet(style_sheet)

    def set_splash(self, message):
        """
        Set splash message.
        """
        if self.splash:
            self.splash.show_message(message)
        QApplication.processEvents()

    # --- Login
    # -------------------------------------------------------------------------
    def update_login_status(self, user_data=None):
        """
        Update login button and information.
        """
        if user_data:
            self.username = user_data.get('login', '')
            self.logged = True

        if self.logged:
            username = self.username
            anaconda_api_url = CONF.get('main', 'anaconda_api_url')
            token = self.api.client_load_token(anaconda_api_url)
            self.button_logged_text.setText('Signed in as')
            self.button_logged_username.setText(username)
            url = "{0}/{1}".format(CONF.get('main', 'conda_url'), username)
            self.button_logged_username.setToolTip(url)
            self.button_login.setText('Sign out')
            self.environments_tab.packages_widget.set_token(token)
        else:
            self.button_logged_text.setText('')
            self.button_logged_username.setText('')
            self.button_login.setText(self._login_text)
        QApplication.restoreOverrideCursor()

    def login(self):
        """
        Open up login dialog or log out depending on logged status.
        """
        if self.logged:
            QApplication.setOverrideCursor(Qt.WaitCursor)
            self.api.client_logout()
            self.api.client_remove_token()
            self.logged = False
            self.sig_logged_out.emit()
            self.tracker.track_event('authenticate',
                                     'logout',
                                     label=self.username)
        else:
            dlg = AuthenticationDialog(self.api, parent=self)

            if self.tracker:
                self.tracker.track_page('/login', pagetitle='Login dialog')

            if dlg.exec_():
                self.api.client_store_token(dlg.token)
                self.username = dlg.username
                self.logged = True
                self.sig_logged_in.emit()

                if self.tracker:
                    self.tracker.track_event('authenticate',
                                             'login',
                                             label=self.username)
            self._track_tab()

        self.update_login_status()
        logger.debug(str((self.logged, self.username)))

    # --- Dialogs
    # -------------------------------------------------------------------------
    def show_preferences(self):
        """
        Display the preferences dialog and apply the needed actions.
        """
        dlg = PreferencesDialog(self)
        self.tracker.track_page('/preferences', pagetitle='Preferences dialog')
        set_domains = self.environments_tab.packages_widget.update_domains
        set_domains = self.environments_tab.packages_widget.update_domains

        dlg.sig_urls_updated.connect(set_domains)
        dlg.sig_urls_updated.connect(lambda au, cu: self.login())
        dlg.exec_()
        self._track_tab()

    def show_about(self):
        """
        Display the `About` dialog with information on the project.
        """
        dlg = AboutDialog(self)
        self.tracker.track_page('/about', pagetitle='About dialog')
        dlg.exec_()
        self._track_tab()

    def show_log_viewer(self):
        """
        Display the logs viewer to the user
        """
        dlg = LogViewerDialog()
        self.tracker.track_page('/logs', pagetitle='Log Viewer Dialog')
        dlg.exec_()
        self._track_tab()

    def show_welcome_screen(self):
        if getattr(self, 'showme', True) and CONF.get('main', 'show_startup',
                                                      True):
            from anaconda_navigator.widgets.splash import FirstSplash

            self.showme = False
            self.splash.hide()
            dlg = FirstSplash()
            dlg.raise_()
            dlg.exec_()

    # --- Update Navigator
    # -------------------------------------------------------------------------
    def _update_application(self, worker, output, error):
        self.button_update_available.setDisabled(False)
        if error:
            text = 'Anaconda Navigator Update error:'
            dlg = MessageBoxError(text=text,
                                  error=error,
                                  title='Application Update Error')
            self.tracker.track_page('/update/error',
                                    pagetitle='Update Application Error '
                                    'Message Box')
            dlg.exec_()
        else:
            text = ('Anaconda Navigator Updated succefully.\n\n'
                    'Please restart the application')
            dlg = MessageBoxInformation(text=text, title='Application Update')
            self.tracker.track_page('/update/successful',
                                    pagetitle='Application Update Succesful '
                                    'Message Box')
            dlg.exec_()
        self._track_tab()

    def update_application(self):
        version = self.application_update_version
        if version:
            dlg = DialogUpdateApplication(version=version)
            self.tracker.track_page('/update',
                                    pagetitle='Update Application Dialog')
            reply = dlg.exec_()
            if reply:
                self.tracker.track_event('application', 'updated', version)
                self.busy = True
                pkg = 'anaconda-navigator={}'.format(version)
                worker = self.api.conda_install(name='root', pkgs=[pkg])
                worker.sig_finished.connect(self._update_application)
                self.button_update_available.setDisabled(True)
            self._track_tab()

    def update_status_bar(self, message='', timeout=0, val=-1, max_val=-1):
        """ """
        statusbar = self.statusBar()
        if val != -1 and max_val != -1:
            self.progressbar.setVisible(True)
            self.progressbar.setValue(val)
            self.progressbar.setMaximum(max_val)
        else:
            self.progressbar.setVisible(False)

        if message:
            statusbar.showMessage(message, timeout)
        else:
            statusbar.clearMessage()
        statusbar.setVisible(False)
        statusbar.setMaximumHeight(0)
        statusbar.hide()

    # --- Url handling
    # -------------------------------------------------------------------------
    def open_url(self, url):
        qurl = QUrl(url)
        QDesktopServices.openUrl(qurl)
        self.tracker.track_event('help', 'documentation', url)

    def open_login_page(self):
        """
        """
        conda_url = CONF.get('main', 'conda_url')
        url = "{0}/{1}".format(conda_url, self.username)
        qurl = QUrl(url)
        QDesktopServices.openUrl(qurl)
        self.tracker.track_event('content', 'clicked', url)

    # --- Qt methods
    # -------------------------------------------------------------------------
    def closeEvent(self, event):
        """
        Catch close event.
        """
        # TODO: check if an update is not in progress or things might break!!
        #        if self.busy:
        show_dialog = not CONF.get('main', 'hide_quit_dialog')
        if show_dialog:
            if self.tracker:
                self.tracker.track_page('/quit', pagetitle='Quit dialog')
            dlg = QuitApplicationDialog()
            reply = dlg.exec_()

            if not reply:
                event.ignore()
                self._track_tab()

    def keyPressEvent(self, event):
        """
        Qt override.
        """
        #        if event.key() in [Qt.Key_F5]:
        #            self.update_style_sheet()
        super(MainWindow, self).keyPressEvent(event)

    def paintEvent(self, event):
        """
        Qt override.

        Draw lower left border of the main Stacked Widget.
        """
        super(MainWindow, self).paintEvent(event)
        tab = self.stack.tabbar
        tab_pos = self.mapTo(self, tab.pos())
        pane_pos = self.mapTo(self, self.stack.pos())

        stack_height = self.stack.height()
        menu_height = self.menuBar().height()
        header_height = 49  # From css
        padding = 20  # From css
        left = 1  # From css
        extra = 8  # Still wondering where this extra Y delta is
        deltay = menu_height + header_height + tab.height() + padding + extra
        x0 = tab_pos.x() + tab.width() + padding - left
        y0 = tab_pos.y() + deltay
        y1 = pane_pos.y() + stack_height + deltay - tab.height() - padding

        painter = QPainter(self)
        painter.setPen(QPen(QColor('#006f43'), 1, Qt.SolidLine, Qt.RoundCap))
        painter.drawLine(x0, y0, x0, y1)
Beispiel #5
0
 def _link_activated(self, url):
     QDesktopServices.openUrl(QUrl(url))
     tracker = GATracker()
     tracker.track_event('content', 'link', url)
Beispiel #6
0
class AuthenticationDialog(DialogBase):
    FORGOT_USERNAME_URL = 'https://anaconda.org/account/forgot_username'
    FORGOT_PASWORD_URL = 'https://anaconda.org/account/forgot_password'
    REGISTER_URL = 'https://anaconda.org'

    def __init__(self, api, parent=None):
        super(AuthenticationDialog, self).__init__(parent)

        self.api = api
        self._parent = parent
        self.token = None
        self.error = None
        self.tracker = GATracker()

        # Widgets
        self.label_username = QLabel('Username:'******'Password:'******'You can register ')
        self.label_signin_text = QLabel('<hr><br><b>Already a member? '
                                        'Sign in!</b><br>')
        # For styling purposes the label next to a ButtonLink is also a button
        # so they align adequately
        self.button_register_text = ButtonLabel('You can register by '
                                                'visiting the')
        self.button_register = ButtonLink('Anaconda Cloud')
        self.button_register_after_text = ButtonLabel('website.')
        self.label_information = QLabel('''
            <strong>Anaconda Cloud</strong> is where packages, notebooks,
            and <br> environments are shared. It provides powerful <br>
            collaboration and package management for open <br>
            source and private projects.<br>
            ''')
        self.label_message = QLabel('')
        self.button_forgot_username = ButtonLink('I forgot my username')
        self.button_forgot_password = ButtonLink('I forgot my password')
        self.button_login = QPushButton('Login')
        self.button_cancel = ButtonCancel('Cancel')
        self.bbox = QDialogButtonBox(Qt.Horizontal)

        # Widgets setup
        self.bbox.addButton(self.button_cancel, QDialogButtonBox.RejectRole)
        self.bbox.addButton(self.button_login, QDialogButtonBox.AcceptRole)
        self.text_username.setAttribute(Qt.WA_MacShowFocusRect, False)
        self.text_password.setAttribute(Qt.WA_MacShowFocusRect, False)

        self.setMinimumWidth(260)
        self.setWindowTitle('Sign in')

        # This allows to completely style the dialog with css using the frame
        self.text_password.setEchoMode(QLineEdit.Password)
        self.label_message.setVisible(False)

        # Layout
        grid_layout = QGridLayout()
        grid_layout.addWidget(self.label_username, 0, 0)
        grid_layout.addWidget(self.text_username, 0, 1)
        grid_layout.addWidget(self.label_password, 1, 0)
        grid_layout.addWidget(self.text_password, 1, 1)

        main_layout = QVBoxLayout()
        main_layout.addWidget(self.label_information)

        register_layout = QHBoxLayout()
        register_layout.addWidget(self.button_register_text, 0)
        register_layout.addWidget(self.button_register, 0, Qt.AlignLeft)
        register_layout.addWidget(self.button_register_after_text, 0,
                                  Qt.AlignLeft)
        register_layout.addStretch()
        register_layout.setContentsMargins(0, 0, 0, 0)
        main_layout.addLayout(register_layout)
        main_layout.addWidget(self.label_signin_text)
        main_layout.addLayout(grid_layout)
        main_layout.addSpacing(5)
        main_layout.addWidget(self.label_message)
        main_layout.addWidget(self.button_forgot_username, 0, Qt.AlignRight)
        main_layout.addWidget(self.button_forgot_password, 0, Qt.AlignRight)

        main_layout.addSpacing(15)
        main_layout.addWidget(self.bbox)

        self.setLayout(main_layout)

        # Signals
        self.button_forgot_username.clicked.connect(
            lambda: self.open_url(self.FORGOT_USERNAME_URL))
        self.button_forgot_password.clicked.connect(
            lambda: self.open_url(self.FORGOT_PASWORD_URL))
        self.button_register.clicked.connect(
            lambda: self.open_url(self.REGISTER_URL))
        self.text_username.textEdited.connect(self.check_text)
        self.text_password.textEdited.connect(self.check_text)
        self.button_login.clicked.connect(self.login)
        self.button_cancel.clicked.connect(self.reject)

        # Setup
        self.check_text()
        self.update_style_sheet()
        self.text_username.setFocus()

    @property
    def username(self):
        return self.text_username.text()

    def update_style_sheet(self, style_sheet=None):
        if style_sheet is None:
            style_sheet = load_style_sheet()
        self.setStyleSheet(style_sheet)

    def check_text(self):
        """
        Check that `username` and `password` are not empty and disabel/enable
        buttons accordingly.
        """
        username = self.text_username.text()
        password = self.text_password.text()

        if len(username) == 0 or len(password) == 0:
            self.button_login.setDisabled(True)
        else:
            self.button_login.setDisabled(False)

    def login(self):
        self.button_login.setEnabled(False)
        QApplication.setOverrideCursor(Qt.WaitCursor)
        self.label_message.setText('')
        worker = self.api.client_login(self.text_username.text(),
                                       self.text_password.text(),
                                       'Anaconda Navigator',
                                       '')
        worker.sig_finished.connect(self._finished)

    def _finished(self, worker, output, error):
        """
        Method called when the anaconda-client Api has finished a process
        that runs in a separate worker thread.
        """
        token = output

        if token:
            self.token = token
            self.accept()
        elif error:
            username = self.text_username.text()
            bold_username = '******'.format(username)

            # The error might come in (error_message, http_error) format
            try:
                error_message = eval(str(error))[0]
            except Exception:
                error_message = str(error)

            error_message = error_message.lower().capitalize()
            error_message = error_message.split(', ')[0]
            error_text = '<i>{0}</i>'.format(error_message)
            error_text = error_text.replace(username, bold_username)
            self.label_message.setText(error_text)
            self.label_message.setVisible(True)

            if error_message:
                domain = self.api.client_domain()
                label = '{0}/{1}: {2}'.format(domain, username,
                                              error_message.lower())
                self.tracker.track_event('authenticate', 'login failed',
                                         label=label)
                self.text_password.setFocus()
                self.text_password.selectAll()

        self.button_login.setDisabled(False)
        self.check_text()
        QApplication.restoreOverrideCursor()

    def open_url(self, url):
        self.tracker.track_event('content', 'click', url)
        QDesktopServices.openUrl(QUrl(url))
Beispiel #7
0
class AuthenticationDialog(DialogBase):
    """Login dialog."""

    # See https://github.com/Anaconda-Platform/anaconda-server settings
    USER_RE = QRegExp('^[A-Za-z0-9_][A-Za-z0-9_-]+$')
    FORGOT_USERNAME_URL = 'account/forgot_username'
    FORGOT_PASSWORD_URL = 'account/forgot_password'

    sig_authentication_succeeded = Signal()
    sig_authentication_failed = Signal()
    sig_url_clicked = Signal(object)

    def __init__(self, api, parent=None):
        """Login dialog."""
        super(AuthenticationDialog, self).__init__(parent)

        self._parent = parent
        self.config = CONF
        self.api = api
        self.token = None
        self.error = None
        self.tracker = GATracker()
        self.forgot_username_url = None
        self.forgot_password_url = None

        # Widgets
        self.label_username = QLabel('Username:'******'Password:'******'<hr><br><b>Already a member? '
                                        'Sign in!</b><br>')
        # For styling purposes the label next to a ButtonLink is also a button
        # so they align adequately
        self.button_register_text = ButtonLabel('You can register by '
                                                'visiting the ')
        self.button_register = ButtonLink('Anaconda Cloud')
        self.button_register_after_text = ButtonLabel('website.')
        self.label_information = QLabel('''
            <strong>Anaconda Cloud</strong> is where packages, notebooks,
            and <br> environments are shared. It provides powerful <br>
            collaboration and package management for open <br>
            source and private projects.<br>
            ''')
        self.label_message = QLabel('')
        self.button_forgot_username = ButtonLink('I forgot my username')
        self.button_forgot_password = ButtonLink('I forgot my password')
        self.button_login = ButtonPrimary('Login')
        self.button_cancel = ButtonNormal('Cancel')

        # Widgets setup
        self.button_login.setDefault(True)
        username_validator = QRegExpValidator(self.USER_RE)
        self.text_username.setValidator(username_validator)

        self.setMinimumWidth(260)
        self.setWindowTitle('Sign in')

        # This allows to completely style the dialog with css using the frame
        self.text_password.setEchoMode(QLineEdit.Password)
        self.label_message.setVisible(False)

        # Layout
        grid_layout = QVBoxLayout()
        grid_layout.addWidget(self.label_username)
        # grid_layout.addWidget(SpacerVertical())
        grid_layout.addWidget(self.text_username)
        grid_layout.addWidget(SpacerVertical())
        grid_layout.addWidget(self.label_password)
        # grid_layout.addWidget(SpacerVertical())
        grid_layout.addWidget(self.text_password)

        main_layout = QVBoxLayout()
        main_layout.addWidget(self.label_information)

        register_layout = QHBoxLayout()
        register_layout.addWidget(self.button_register_text)
        register_layout.addWidget(self.button_register)
        register_layout.addWidget(self.button_register_after_text)
        register_layout.addStretch()
        main_layout.addLayout(register_layout)
        main_layout.addWidget(self.label_signin_text)
        main_layout.addLayout(grid_layout)
        main_layout.addWidget(SpacerVertical())
        main_layout.addWidget(self.label_message)
        main_layout.addWidget(self.button_forgot_username, 0, Qt.AlignRight)
        main_layout.addWidget(self.button_forgot_password, 0, Qt.AlignRight)

        layout_buttons = QHBoxLayout()
        layout_buttons.addStretch()
        layout_buttons.addWidget(self.button_cancel)
        layout_buttons.addWidget(SpacerHorizontal())
        layout_buttons.addWidget(self.button_login)

        main_layout.addWidget(SpacerVertical())
        main_layout.addWidget(SpacerVertical())
        main_layout.addLayout(layout_buttons)

        self.setLayout(main_layout)

        # Signals
        self.text_username.textEdited.connect(self.check_text)
        self.text_password.textEdited.connect(self.check_text)
        self.button_login.clicked.connect(self.login)
        self.button_cancel.clicked.connect(self.reject)

        # Setup
        self.check_text()
        self.update_style_sheet()
        self.text_username.setFocus()
        self.setup()

    def setup(self):
        """Setup login dialog."""
        self.update_links()

    def update_links(self):
        """Update links."""
        for button in [
                self.button_forgot_username,
                self.button_forgot_password,
                self.button_register,
        ]:
            try:
                button.disconnect()
            except TypeError:  # pragma: no cover
                pass

        # TODO, get this from anaconda client directly?
        # from binstar_client.utils import get_config, set_config
        # config = get_config()
        anaconda_api_url = self.config.get('main', 'anaconda_api_url', None)
        if anaconda_api_url:
            # Remove api if using a subdomain
            base_url = anaconda_api_url.lower().replace('//api.', '//')
            self.base_url = base_url

            # Remove api if not using a subdomain
            parts = base_url.lower().split('/')
            if parts[-1] == 'api':
                base_url = '/'.join(parts[:-1])

            self.forgot_username_url = (base_url + '/' +
                                        self.FORGOT_USERNAME_URL)
            self.forgot_password_url = (base_url + '/' +
                                        self.FORGOT_PASSWORD_URL)

            self.button_register.clicked.connect(
                lambda: self.open_url(base_url))
            self.button_forgot_username.clicked.connect(
                lambda: self.open_url(self.forgot_username_url))
            self.button_forgot_password.clicked.connect(
                lambda: self.open_url(self.forgot_password_url))

    @property
    def username(self):
        """Return the logged username."""
        return self.text_username.text().lower()

    def update_style_sheet(self, style_sheet=None):
        """Update custom css style sheet."""
        if style_sheet is None:
            style_sheet = load_style_sheet()
        self.setStyleSheet(style_sheet)

    def check_text(self):
        """Check that `username` and `password` are valid.

        If not empty and disable/enable buttons accordingly.
        """
        username = self.text_username.text()
        password = self.text_password.text()

        if len(username) == 0 or len(password) == 0:
            self.button_login.setDisabled(True)
        else:
            self.button_login.setDisabled(False)

    def login(self):
        """Try to log the user in the specified anaconda api endpoint."""
        self.button_login.setEnabled(False)
        self.text_username.setText(self.text_username.text().lower())
        QApplication.setOverrideCursor(Qt.WaitCursor)
        self.label_message.setText('')
        worker = self.api.login(self.text_username.text().lower(),
                                self.text_password.text())
        worker.sig_finished.connect(self._finished)

    def _finished(self, worker, output, error):
        """Callback for the login procedure after worker has finished."""
        token = output
        if token:
            self.token = token
            self.sig_authentication_succeeded.emit()
            self.accept()
        elif error:
            username = self.text_username.text().lower()
            bold_username = '******'.format(username)

            # The error might come in (error_message, http_error) format
            try:
                error_message = ast.literal_eval(str(error))[0]
            except Exception:  # pragma: no cover
                error_message = str(error)

            error_message = error_message.lower().capitalize()
            error_message = error_message.split(', ')[0]
            error_text = '<i>{0}</i>'.format(error_message)
            error_text = error_text.replace(username, bold_username)
            self.label_message.setText(error_text)
            self.label_message.setVisible(True)

            if error_message:
                domain = self.api.client_domain()
                label = '{0}/{1}: {2}'.format(domain, username,
                                              error_message.lower())
                self.tracker.track_event(
                    'authenticate',
                    'login failed',
                    label=label,
                )
                self.text_password.setFocus()
                self.text_password.selectAll()
            self.sig_authentication_failed.emit()

        self.button_login.setDisabled(False)
        self.check_text()
        QApplication.restoreOverrideCursor()

    def open_url(self, url):
        """Open given url in the default browser and log the action."""
        self.tracker.track_event('content', 'click', url)
        self.sig_url_clicked.emit(url)
        QDesktopServices.openUrl(QUrl(url))
Beispiel #8
0
class MessageBox(DialogBase):
    """Base message box dialog."""

    QUESTION_BOX = 100
    INFORMATION_BOX = 101
    ERROR_BOX = 102
    REMOVE_BOX = 103

    sig_url_clicked = Signal(object)

    def __init__(self, type_, error='', title='', text='', learn_more=None):
        """Base message box dialog."""
        super(MessageBox, self).__init__()
        from anaconda_navigator.utils.analytics import GATracker

        self.tracker = GATracker()
        self.label_text = QLabel(to_text_string(text))
        self.textbox_error = QTextEdit()
        self.button_ok = ButtonPrimary('Ok')
        self.button_yes = ButtonPrimary('Yes')
        self.button_no = ButtonNormal('No')
        self.button_copy = ButtonNormal('Copy text')
        self.button_learn = ButtonNormal('Learn more')
        self.button_remove = ButtonDanger('Remove')
        self.button_cancel = ButtonNormal('Cancel')
        self.button_send = ButtonNormal('Report Issue', parent=self)

        self.label_text.setOpenExternalLinks(False)
        self.label_text.setWordWrap(True)
        self.label_text.linkActivated.connect(self.url_clicked)
        self.textbox_error.setReadOnly(True)
        self.textbox_error.setFrameStyle(QTextEdit.Plain)
        self.textbox_error.setFrameShape(QTextEdit.NoFrame)
        self.setMinimumWidth(260)
        self.textbox_error.verticalScrollBar().show()
        self.setWindowTitle(to_text_string(title))

        error = to_text_string(error).split('\n')
        error = '<br>'.join(error)
        self.textbox_error.setText(error)

        # Layouts
        layout = QVBoxLayout()
        layout.addWidget(self.label_text)
        layout.addWidget(SpacerVertical())
        if error:
            layout.addWidget(self.textbox_error)
            layout.addWidget(SpacerVertical())
            layout.addWidget(self.button_copy)
            layout.addWidget(SpacerVertical())
        layout.addWidget(SpacerVertical())

        layout_buttons = QHBoxLayout()
        layout_buttons.addStretch()

        layout.addLayout(layout_buttons)

        self.layout = layout
        self.setLayout(layout)

        # Signals
        self.button_copy.clicked.connect(self.copy_text)
        self.button_ok.clicked.connect(self.accept)
        self.button_yes.clicked.connect(self.accept)
        self.button_no.clicked.connect(self.reject)
        self.button_remove.clicked.connect(self.accept)
        self.button_cancel.clicked.connect(self.reject)
        self.button_send.clicked.connect(self.send)

        # Setup
        self.button_learn.setVisible(bool(learn_more))
        if bool(learn_more):
            layout_buttons.addWidget(self.button_learn)
            layout_buttons.addWidget(SpacerHorizontal())
            self.button_learn.clicked.connect(
                lambda: self.show_url(learn_more)
            )

        if type_ == self.ERROR_BOX:
            layout_buttons.addWidget(self.button_send)
            layout_buttons.addWidget(SpacerHorizontal())
            layout_buttons.addWidget(self.button_ok)
            self.button_yes.setVisible(False)
            self.button_no.setVisible(False)
            self.button_remove.setVisible(False)
            self.button_cancel.setVisible(False)
        elif type_ == self.INFORMATION_BOX:
            layout_buttons.addWidget(self.button_ok)
            self.button_yes.setVisible(False)
            self.button_no.setVisible(False)
            self.textbox_error.setVisible(False)
            self.button_copy.setVisible(False)
            self.button_remove.setVisible(False)
            self.button_cancel.setVisible(False)
        elif type_ == self.QUESTION_BOX:
            layout_buttons.addStretch()
            layout_buttons.addWidget(self.button_no)
            layout_buttons.addWidget(SpacerHorizontal())
            layout_buttons.addWidget(self.button_yes)
            layout_buttons.addWidget(SpacerHorizontal())
            self.textbox_error.setVisible(False)
            self.button_ok.setVisible(False)
            self.button_copy.setVisible(False)
            self.button_remove.setVisible(False)
            self.button_cancel.setVisible(False)
        elif type_ == self.REMOVE_BOX:
            layout_buttons.addStretch()
            layout_buttons.addWidget(self.button_cancel)
            layout_buttons.addWidget(SpacerHorizontal())
            layout_buttons.addWidget(self.button_remove)
            layout_buttons.addWidget(SpacerHorizontal())
            self.textbox_error.setVisible(False)
            self.button_ok.setVisible(False)
            self.button_copy.setVisible(False)
            self.button_yes.setVisible(False)
            self.button_no.setVisible(False)

        self.button_send.setVisible(False)
        self.layout_buttons = layout_buttons

    def url_clicked(self, url):
        """Emit url interaction."""
        self.sig_url_clicked.emit(url)

    def copy_text(self):
        """Copy all the content of the displayed error message."""
        self.textbox_error.selectAll()
        self.textbox_error.copy()

    def show_url(self, url=None):
        """Open url in default browser."""
        if url:
            qurl = QUrl(url)
            QDesktopServices.openUrl(qurl)
            self.tracker.track_event('help', 'documentation', url)

    def send(self):
        """Send error report to github and create an issue with a template."""
        import webbrowser
        from anaconda_navigator.utils.analytics import GATracker
        base = "https://github.com/ContinuumIO/anaconda-issues/issues/new?{0}"
        template = '''
## Main error
{text}
## Traceback
```
{trace}
```
## System information
```
{info}
```
'''
        info = GATracker().info
        info = '\n'.join('{}: {}'.format(k, v) for k, v in info.items())
        query = parse.urlencode(
            {
                'title': "Navigator Error",
                'labels': "tag:navigator",
                'body': template.format(
                    text=self.text, trace=self.error, info=info
                )
            }
        )
        url = base.format(query)
        webbrowser.open_new_tab(url)
Beispiel #9
0
 def _link_activated(url):
     QDesktopServices.openUrl(QUrl(url))
     from anaconda_navigator.utils.analytics import GATracker
     tracker = GATracker()
     tracker.track_event('content', 'link', url)