diff --git a/jans-cli-tui/cli_tui/cli_style.py b/jans-cli-tui/cli_tui/cli_style.py index 2daa3d1e190..33b33815b8e 100755 --- a/jans-cli-tui/cli_tui/cli_style.py +++ b/jans-cli-tui/cli_tui/cli_style.py @@ -1,4 +1,5 @@ from prompt_toolkit.styles import Style +from types import SimpleNamespace style = Style.from_dict( { @@ -76,6 +77,7 @@ "plugin-widget":"green", "plugin-container":"", "plugin-container.text":"green", + "plugin-black-bg": "bg: black", ## edit_client_dialog "outh-client-navbar":"#2600ff", @@ -130,9 +132,32 @@ "date-picker-time":"bg:#bab1b1", "dialog-titled-widget":"bg:#ffffff fg:green", + ####tab + "tab-nav-background": "fg:#b0e0e6 bg:#a9a9a9", + "tab-unselected": "fg:#b0e0e6 bg:#a9a9a9 underline", + "tab-selected": "fg:#000080 bg:#d3d3d3", + + ##scim + "scim-widget": "bg:black fg:white", + } ) + +def get_color_for_style(style_name:str)->SimpleNamespace: + ret_val = SimpleNamespace() + ret_val.fg = '#000000' + ret_val.bg = '#ffffff' + + for pstyle in style.class_names_and_attrs: + if pstyle[0].__contains__(style_name): + if pstyle[1].color: + ret_val.fg = '#'+pstyle[1].color + if pstyle[1].bgcolor: + ret_val.bg = '#'+pstyle[1].bgcolor + + return ret_val + ## jans nav bar main_navbar_bgcolor = "DimGray" outh_navbar_bgcolor = "#ADD8E6" diff --git a/jans-cli-tui/cli_tui/jans_cli_tui.py b/jans-cli-tui/cli_tui/jans_cli_tui.py index 0c6a7e5d6c7..7b6874f976e 100755 --- a/jans-cli-tui/cli_tui/jans_cli_tui.py +++ b/jans-cli-tui/cli_tui/jans_cli_tui.py @@ -115,6 +115,7 @@ def __init__(self): self.styles = dict(style.style_rules) self._plugins = [] self._load_plugins() + self.available_plugins = [] self.cli_object_ok = False self.pbar_text = "" self.progressing_text = "" @@ -217,9 +218,15 @@ def _load_plugins(self) -> None: self._plugins.append(plugin_object) def init_plugins(self) -> None: + """Initilizse plugins + """ for plugin in self._plugins: if hasattr(plugin, 'init_plugin'): + if getattr(plugin, 'server_side_plugin', False) and plugin.pid not in self.available_plugins: + continue + self.logger.debug('Initializing plugin {}'.format(plugin.pid)) plugin.init_plugin() + self.plugins_initialised = True def plugin_enabled(self, pid: str) -> bool: @@ -317,27 +324,31 @@ async def coroutine(): self.stop_progressing() self.cli_object_ok = True - if not self.plugins_initialised: - self.init_plugins() - self.runtime_plugins() + self.check_available_plugins() asyncio.ensure_future(coroutine()) else: self.cli_object_ok = True - if not self.plugins_initialised: - self.init_plugins() - self.runtime_plugins() + self.check_available_plugins() - def runtime_plugins(self) -> None: + def check_available_plugins(self) -> None: """Disables plugins when cli object is ready""" if self.cli_object_ok: - response = self.cli_requests({'operation_id': 'is-license-active'}) - if response.status_code == 404: - self.disable_plugin('config_api') + response = self.cli_requests({'operation_id': 'get-plugins'}) + if response.ok: + plugins = response.json() + for plugin in plugins: + self.available_plugins.append(plugin['name']) + + for pp in self._plugins: + if getattr(pp, 'server_side_plugin', False) and pp.pid not in self.available_plugins: + self.disable_plugin(pp.pid) + + self.init_plugins() def disable_plugin(self, pid) -> None: @@ -355,9 +366,6 @@ async def check_jans_cli_ini(self) -> None: else : self.create_cli() - if self.cli_object_ok and not self.plugins_initialised: - self.init_plugins() - def jans_creds_dialog(self, *params: Any) -> None: body=HSplit([ @@ -387,6 +395,7 @@ def set_keybindings(self) -> None: self.bindings.add('tab')(self.focus_next) self.bindings.add('s-tab')(self.focus_previous) self.bindings.add('c-c')(do_exit) + self.bindings.add('c-q')(do_exit) self.bindings.add('f1')(self.help) self.bindings.add('escape')(self.escape) self.bindings.add('s-up')(self.up) @@ -569,6 +578,7 @@ def getTitledText( focusable: Optional[bool] = None, width: AnyDimension = None, style: AnyFormattedText = '', + widget_style: AnyFormattedText = '', scrollbar: Optional[bool] = False, line_numbers: Optional[bool] = False, lexer: PygmentsLexer = None, @@ -583,7 +593,7 @@ def getTitledText( height=height, width=width, read_only=read_only, - style=self.styles['textarea-readonly'] if read_only else self.styles['textarea'], + style=widget_style or (self.styles['textarea-readonly'] if read_only else self.styles['textarea']), accept_handler=accept_handler, focusable=not read_only if focusable is None else focusable, scrollbar=scrollbar, @@ -598,7 +608,7 @@ def getTitledText( ta.window.jans_name = name ta.window.jans_help = jans_help - v = VSplit([Window(FormattedTextControl(title), width=len(title)+1, style=style, height=height), ta], padding=1) + v = VSplit([Window(FormattedTextControl(title), width=len(title)+1, style=style, height=height), ta]) v.me = ta return v @@ -611,6 +621,7 @@ def getTitledCheckBoxList( current_values: Optional[list] = [], jans_help: AnyFormattedText= "", style: AnyFormattedText= "", + widget_style: AnyFormattedText = '', ) -> AnyContainer: title += ': ' @@ -620,9 +631,8 @@ def getTitledCheckBoxList( cbl.current_values = current_values cbl.window.jans_name = name cbl.window.jans_help = jans_help - #li, cd, width = self.handle_long_string(title, values, cbl) - v = VSplit([Window(FormattedTextControl(title), width=len(title)+1, style=style,), cbl], padding=1) + v = VSplit([Window(FormattedTextControl(title), width=len(title)+1, style=style,), cbl], style=widget_style) v.me = cbl return v @@ -636,10 +646,15 @@ def getTitledCheckBox( on_selection_changed: Callable= None, jans_help: AnyFormattedText= "", style: AnyFormattedText= "", + widget_style: AnyFormattedText = '', ) -> AnyContainer: title += ': ' cb = Checkbox(text) + if widget_style: + cb.default_style = widget_style + cb.checked_style = widget_style + cb.selected_style = widget_style cb.checked = checked cb.window.jans_name = name cb.window.jans_help = jans_help @@ -652,9 +667,7 @@ def custom_handler(): if on_selection_changed: cb._handle_enter = custom_handler - #li, cd, width = self.handle_long_string(title, text, cb) - - v = VSplit([Window(FormattedTextControl(title), width=len(title)+1, style=style,), cb], padding=1) + v = VSplit([Window(FormattedTextControl(title), width=len(title)+1, style=style,), cb], style=widget_style) v.me = cb @@ -669,6 +682,7 @@ def getTitledRadioButton( on_selection_changed: Callable= None, jans_help: AnyFormattedText= "", style: AnyFormattedText= "", + widget_style: AnyFormattedText = '', ) -> AnyContainer: title += ': ' @@ -689,7 +703,7 @@ def custom_handler(): if on_selection_changed: rl._handle_enter = custom_handler - v = VSplit([Window(FormattedTextControl(title), width=len(title)+1, style=style,), rl], padding=1) + v = VSplit([Window(FormattedTextControl(title), width=len(title)+1, style=style,), rl]) v.me = rl @@ -715,9 +729,9 @@ def getTitledWidget( def getButton( self, - text: AnyFormattedText, - name: AnyFormattedText, - jans_help: AnyFormattedText, + text: AnyFormattedText, + name: AnyFormattedText, + jans_help: AnyFormattedText, handler: Callable= None, ) -> Button: @@ -845,15 +859,14 @@ def show_message( self.layout.focus(dialog) self.invalidate() - def show_again(self) -> None: - self.show_message(_("Again"), _("Nasted Dialogs"),) def get_confirm_dialog( - self, - message: AnyFormattedText + self, + message: AnyFormattedText, + confirm_handler: Optional[Callable]=None ) -> Dialog: body = VSplit([Label(message)], align=HorizontalAlign.CENTER) - buttons = [Button(_("No")), Button(_("Yes"))] + buttons = [Button(_("No")), Button(_("Yes"), handler=confirm_handler)] dialog = JansGDialog(self, title=_("Confirmation"), body=body, buttons=buttons) return dialog diff --git a/jans-cli-tui/cli_tui/plugins/010_auth_server/edit_client_dialog.py b/jans-cli-tui/cli_tui/plugins/010_auth_server/edit_client_dialog.py index 402dd3fb459..0f9468dac20 100755 --- a/jans-cli-tui/cli_tui/plugins/010_auth_server/edit_client_dialog.py +++ b/jans-cli-tui/cli_tui/plugins/010_auth_server/edit_client_dialog.py @@ -10,13 +10,17 @@ Button, Label, TextArea, - Dialog + Dialog, + CheckboxList ) +from prompt_toolkit.layout import Window + from prompt_toolkit.lexers import PygmentsLexer, DynamicLexer from prompt_toolkit.application.current import get_app from asyncio import Future, ensure_future from utils.static import DialogResult, cli_style from utils.multi_lang import _ +from utils.utils import common_data from wui_components.jans_dialog_with_nav import JansDialogWithNav from wui_components.jans_side_nav_bar import JansSideNavBar from wui_components.jans_cli_dialog import JansGDialog @@ -24,17 +28,22 @@ from wui_components.jans_date_picker import DateSelectWidget from utils.utils import DialogUtils from wui_components.jans_vetrical_nav import JansVerticalNav +from wui_components.jans_label_container import JansLabelContainer + from view_uma_dialog import ViewUMADialog import threading from prompt_toolkit.buffer import Buffer from prompt_toolkit.formatted_text import AnyFormattedText from typing import Optional, Sequence from typing import Callable +from prompt_toolkit.eventloop import get_event_loop +import asyncio import json ERROR_GETTING_CLIENTS = _("Error getting clients") ATTRIBUTE_SCHEMA_PATH = '#/components/schemas/ClientAttributes' +URL_SUFFIX_FORMATTER = 'inum:{}' class EditClientDialog(JansGDialog, DialogUtils): """The Main Client Dialog that contain every thing related to The Client @@ -62,14 +71,23 @@ def __init__( delete_uma_resource (method, optional): handler invoked when deleting UMA-resources """ super().__init__(parent, title, buttons) + self.save_handler = save_handler self.delete_uma_resource=delete_uma_resource self.data = data - self.title=title - self.myparent.logger.debug('self.data in init: '+str(self.data)) + self.title = title + self.nav_dialog_width = int(self.myparent.dialog_width*1.1) self.prepare_tabs() self.create_window() + + def get_scope_by_inum(self, inum:str) -> dict: + + for scope in common_data.scopes: + if scope['inum'] == inum or scope['dn'] == inum: + return scope + return {} + def save(self) -> None: """method to invoked when saving the dialog (Save button is pressed) """ @@ -78,7 +96,6 @@ def save(self) -> None: self.data['disabled'] = not self.data['disabled'] for list_key in ( 'redirectUris', - 'scopes', 'postLogoutRedirectUris', 'contacts', 'authorizedOrigins', @@ -89,11 +106,11 @@ def save(self) -> None: if self.data[list_key]: self.data[list_key] = self.data[list_key].splitlines() + self.data['scopes'] = [item[0] for item in self.client_scopes.entries] + if 'accessTokenAsJwt' in self.data: self.data['accessTokenAsJwt'] = self.data['accessTokenAsJwt'] == 'jwt' - self.myparent.logger.debug('self.data: '+str(self.data)) - if 'rptAsJwt' in self.data: self.data['rptAsJwt'] = self.data['rptAsJwt'] == 'jwt' @@ -131,7 +148,7 @@ def save(self) -> None: cfr = self.check_required_fields() - self.myparent.logger.debug('CFR: '+str(cfr)) + if not cfr: return @@ -139,11 +156,9 @@ def save(self) -> None: if ditem in self.data and self.data[ditem] is None: self.data.pop(ditem) - close_me = True if self.save_handler: - close_me = self.save_handler(self) - if close_me: - self.future.set_result(DialogResult.ACCEPT) + self.save_handler(self) + def cancel(self) -> None: """method to invoked when canceling changes in the dialog (Cancel button is pressed) @@ -168,18 +183,29 @@ def create_window(self) -> None: (self.cancel, _("Cancel")) ], height=self.myparent.dialog_height, - width=self.myparent.dialog_width, + width=self.nav_dialog_width, ) + + def fill_client_scopes(self): + self.client_scopes.entries = [] + for scope_dn in self.data.get('scopes', []): + scope = self.get_scope_by_inum(scope_dn) + if scope: + label = scope.get('displayName') or scope.get('inum') or scope_dn + self.client_scopes.add_label(scope_dn, label) + def prepare_tabs(self) -> None: """Prepare the tabs for Edil Client Dialogs """ schema = self.myparent.cli_object.get_schema_from_reference('', '#/components/schemas/Client') + self.tabs = OrderedDict() - self.tabs['Basic'] = HSplit([ + + basic_tab_widgets = [ self.myparent.getTitledText( _("Client_ID"), name='inum', @@ -286,19 +312,33 @@ def prepare_tabs(self) -> None: jans_help=self.myparent.get_help_from_schema( self.myparent.cli_object.get_schema_from_reference('', ATTRIBUTE_SCHEMA_PATH), 'redirectUrisRegex'), - style=cli_style.check_box), + style=cli_style.check_box) + ] + + add_scope_button = VSplit([Window(), self.myparent.getButton( + text=_("Add Scope"), + name='oauth:logging:save', + jans_help=_("Add Scopes"), + handler=self.add_scopes) + ]) - self.myparent.getTitledText(_("Scopes"), - name='scopes', - value='\n'.join(self.data.get('scopes', [])), - height=3, - jans_help=self.myparent.get_help_from_schema(schema, 'scopes'), - style=cli_style.check_box), - ],width=D(), - style=cli_style.tabs + self.client_scopes = JansLabelContainer( + title=_('Scopes'), + width=self.nav_dialog_width - 26, + on_display=self.myparent.data_display_dialog, + on_delete=self.delete_scope, + buttonbox=add_scope_button ) + self.fill_client_scopes() + + basic_tab_widgets.append(self.client_scopes) + + + self.tabs['Basic'] = HSplit(basic_tab_widgets, width=D(), style=cli_style.tabs) + + self.tabs['Tokens'] = HSplit([ self.myparent.getTitledRadioButton( _("Access Token Type"), @@ -620,7 +660,7 @@ def allow_spontaneous_changed(cb): style=cli_style.check_box) - self.tabs['Advanced Client Properties'] = HSplit([ + self.tabs['Advanced Client Prop.'] = HSplit([ self.myparent.getTitledCheckBox( _("Default Prompt login"), @@ -780,16 +820,63 @@ def allow_spontaneous_changed(cb): self.left_nav = list(self.tabs.keys())[0] + + def scope_exists(self, scope_dn:str) -> bool: + for item_id, item_label in self.client_scopes.entries: + if item_id == scope_dn: + return True + return False + + + def add_scopes(self) -> None: + + def add_selected_claims(dialog): + if 'scopes' not in self.data: + self.data['scopes'] = [] + + self.data['scopes'] += dialog.body.current_values + self.fill_client_scopes() + + scopes_list = [] + + for scope in common_data.scopes: + if not self.scope_exists(scope['dn']): + scopes_list.append((scope['dn'], scope.get('displayName', '') or scope['inum'])) + + scopes_list.sort(key=lambda x: x[1]) + + check_box_list = CheckboxList(values=scopes_list) + + buttons = [Button(_("Cancel")), Button(_("OK"), handler=add_selected_claims)] + dialog = JansGDialog(self.myparent, title=_("Select Scopes to add"), body=check_box_list, buttons=buttons) + self.myparent.show_jans_dialog(dialog) + + + + def delete_scope(self, scope: list) -> None: + + + def do_delete_scope(dialog): + self.data['scopes'].remove(scope[0]) + self.fill_client_scopes() + + dialog = self.myparent.get_confirm_dialog( + message=_("Are you sure want to delete Scope:\n {} ?".format(scope[1])), + confirm_handler=do_delete_scope + ) + + self.myparent.show_jans_dialog(dialog) + + def show_client_scopes(self) -> None: - client_scopes = self.data.get('scopes') - self.myparent.logger.debug('client_scopes: '+str(client_scopes)) + client_scopes = self.data.get('scopes')#[0] data = [] for i in client_scopes : try : inum = i.split(',')[0][5:] rsponse = self.myparent.cli_object.process_command_by_id( operation_id='get-oauth-scopes-by-inum', - url_suffix='inum:{}'.format(inum), + url_suffix=URL_SUFFIX_FORMATTER.format(inum), endpoint_args="", data_fn=None, data={} @@ -804,9 +891,7 @@ def show_client_scopes(self) -> None: pass if rsponse.json().get('scopeType','') == 'spontaneous': data.append(rsponse.json()) - - self.myparent.logger.debug('datadata: '+str(data)) if not data : data = "No Scope of type: Spontaneous" @@ -862,7 +947,6 @@ def oauth_update_uma_resources ( if pattern: endpoint_args +=',pattern:'+pattern - self.myparent.logger.debug('DATA endpoint_args: '+str(endpoint_args)) try : rsponse = self.myparent.cli_object.process_command_by_id( operation_id='get-oauth-uma-resources-by-clientid', @@ -897,7 +981,7 @@ def oauth_update_uma_resources ( try : scope_response = self.myparent.cli_object.process_command_by_id( operation_id='get-oauth-scopes-by-inum', - url_suffix='inum:{}'.format(inum), + url_suffix=URL_SUFFIX_FORMATTER.format(inum), endpoint_args='', data_fn=None, data={} @@ -930,7 +1014,6 @@ def oauth_update_uma_resources ( on_enter=self.view_uma_resources, on_display=self.myparent.data_display_dialog, on_delete=self.delete_uma_resource, - # selection_changed=self.data_selection_changed, selectes=0, headerColor='class:outh-client-navbar-headcolor', entriesColor='class:outh-client-navbar-entriescolor', diff --git a/jans-cli-tui/cli_tui/plugins/010_auth_server/main.py b/jans-cli-tui/cli_tui/plugins/010_auth_server/main.py index f5fba51eba2..841ac78d10f 100755 --- a/jans-cli-tui/cli_tui/plugins/010_auth_server/main.py +++ b/jans-cli-tui/cli_tui/plugins/010_auth_server/main.py @@ -23,6 +23,8 @@ from prompt_toolkit.lexers import PygmentsLexer, DynamicLexer from utils.static import DialogResult, cli_style, common_strings from utils.utils import DialogUtils +from utils.utils import common_data + from wui_components.jans_nav_bar import JansNavBar from wui_components.jans_vetrical_nav import JansVerticalNav from wui_components.jans_drop_down import DropDownWidget @@ -66,9 +68,12 @@ def init_plugin(self) -> None: self.app.create_background_task(self.get_appconfiguration()) self.schema = self.app.cli_object.get_schema_from_reference('', '#/components/schemas/AppConfiguration') + if not hasattr(common_data, 'scopes'): + self.app.create_background_task(self.retrieve_sopes()) + async def get_appconfiguration(self) -> None: 'Coroutine for getting application configuration.' - + cli_args = {'operation_id': 'get-properties'} response = await self.app.loop.run_in_executor(self.app.executor, self.app.cli_requests, cli_args) @@ -79,6 +84,16 @@ async def get_appconfiguration(self) -> None: self.app_configuration = response.json() self.oauth_logging() + + async def retrieve_sopes(self) -> None: + """asyncio corotune for retreiving scopes + """ + self.app.logger.debug("retreiving scopes") + cli_args = {'operation_id': 'get-oauth-scopes', 'endpoint_args': 'limit:200,startIndex:0'} + response = await self.app.loop.run_in_executor(self.app.executor, self.app.cli_requests, cli_args) + common_data.scopes = response.json()['entries'] + self.app.logger.debug("scopes retreived") + def process(self): """No pre-processing for this plugin. """ @@ -119,7 +134,7 @@ def oauth_prepare_containers(self) -> None: self.app.getButton(text=_("Get Clients"), name='oauth:clients:get', jans_help=_("Retreive first {} OpenID Connect clients").format(self.app.entries_per_page), handler=self.oauth_update_clients), self.app.getTitledText(_("Search"), name='oauth:clients:search', jans_help=_(common_strings.enter_to_search), accept_handler=self.search_clients,style='class:outh_containers_clients.text'), self.app.getButton(text=_("Add Client"), name='oauth:clients:add', jans_help=_("To add a new client press this button"), handler=self.add_client), - + ], padding=3, width=D(), @@ -140,15 +155,22 @@ def oauth_prepare_containers(self) -> None: self.oauth_containers['properties'] = HSplit([ VSplit([ - self.app.getTitledText( - _("Search"), - name='oauth:properties:search', - jans_help=_(common_strings.enter_to_search), - accept_handler=self.search_properties, - style='class:outh_containers_scopes.text') + self.app.getTitledText( + _("Search"), + name='oauth:properties:search', + jans_help=_(common_strings.enter_to_search), + accept_handler=self.search_properties, + style='class:outh_containers_scopes.text' + ), + self.app.getButton( + _("Add Property"), + name='oauth:properties:add', + jans_help=_("Press this button to add a missing preperty"), + handler=self.add_property + ), ], - padding=3, - width=D(), + padding=3, + width=D(), ), DynamicContainer(lambda: self.oauth_data_container['properties']) ],style='class:outh_containers_scopes') @@ -239,7 +261,7 @@ async def coroutine(): headers=['Client ID', 'Client Name', 'Grant Types', 'Subject Type'], preferred_size= [0,0,30,0], data=data, - on_enter=self.edit_client_dialog, + on_enter=self.edit_client, on_display=self.app.data_display_dialog, on_delete=self.delete_client, get_help=(self.get_help,'Client'), @@ -274,6 +296,47 @@ async def coroutine(): asyncio.ensure_future(coroutine()) + + def get_scopes(self, client_data) -> None: + + async def coroutine(): + cli_args = {'operation_id': 'get-oauth-scopes', 'endpoint_args':'limit:200,startIndex:0'} + self.app.start_progressing(_("Retreiving client Scopes...")) + response = await get_event_loop().run_in_executor(self.app.executor, self.app.cli_requests, cli_args) + self.app.stop_progressing() + + if response.status_code not in (200, 201): + self.app.show_message(_("Error getting client Scopes"), str(response.text),tobefocused=self.oauth_containers['clients']) + return + + try: + result = response.json() + except Exception: + self.app.show_message(_("Error getting client Scopes"), str(response.text),tobefocused=self.oauth_containers['clients']) + return + + data_display_name =[] + data_base_dn =[] + + for d in result.get('entries', []): + data_display_name.append(d.get('displayName',d.get('baseDn'))) + data_base_dn.append(d.get('baseDn')) + + + + for client_num in range(len(client_data)): + + for scope_dn_num in range(len(client_data[client_num]['scopes'])): + if client_data[client_num]['scopes'][scope_dn_num] in data_base_dn: + + index = data_base_dn.index(client_data[client_num]['scopes'][scope_dn_num]) + + client_data[client_num]['scopes'][scope_dn_num] = [data_display_name[index],data_base_dn[index].replace(',ou=scopes,o=jans','')] + + + asyncio.ensure_future(coroutine()) + return client_data + def delete_client(self, **kwargs: Any) -> None: """This method for the deletion of the clients data @@ -396,6 +459,50 @@ async def coroutine(): asyncio.ensure_future(coroutine()) + + def add_property(self): + missing_properties = [] + + for prop in self.schema['properties']: + if prop not in self.app_configuration: + missing_properties.append(prop) + missing_properties.sort() + missing_properties_data = [ [prop] for prop in missing_properties ] + + def add_property(**params: Any) -> None: + self.add_property_dialog.future.set_result('add_property') + prop_name = params['passed'][0] + prop_val = '' + prop_type = self.schema['properties'][prop_name]['type'] + + if prop_type == 'string': + prop_val = '' + elif prop_type == 'array': + prop_val = [] + + passed = [prop_name, prop_val] + + self.view_property(passed=passed, op_type='add') + + properties = JansVerticalNav( + myparent=self.app, + headers=['Property Name'], + preferred_size=[0], + data=missing_properties_data, + on_enter=add_property, + get_help=(self.get_help,'AppConfiguration'), + selectes=0, + headerColor=cli_style.navbar_headcolor, + entriesColor=cli_style.navbar_entriescolor, + all_data=missing_properties + ) + + body = HSplit([properties]) + buttons = [Button(_("Cancel"))] + self.add_property_dialog = JansGDialog(self.app, title=_("Select Property"), body=body, buttons=buttons) + self.app.show_jans_dialog(self.add_property_dialog) + + def oauth_update_properties( self, start_index: Optional[int]= 0, @@ -415,29 +522,16 @@ def oauth_update_properties( # ------------------------------------------------------------------------------- # # ----------------------------------- Search ------------------------------------ # # ------------------------------------------------------------------------------- # - porp_schema = self.app.cli_object.get_schema_from_reference('', '#/components/schemas/AppConfiguration') data =[] if pattern: for k in self.app_configuration: if pattern.lower() in k.lower(): - if k in porp_schema.get('properties', {}): - data.append( - [ - k, - self.app_configuration[k], - ] - ) + data.append([k, self.app_configuration[k]]) else: for d in self.app_configuration: - if d in porp_schema.get('properties', {}): - data.append( - [ - d, - self.app_configuration[d], - ] - ) + data.append([d, self.app_configuration[d]]) # ------------------------------------------------------------------------------- # # --------------------------------- View Data ----------------------------------- # @@ -445,9 +539,10 @@ def oauth_update_properties( if data: + data.sort() buttons = [] - if len(data)/20 >=1: + if len(data) > 20: if start_index!=0: handler_partial = partial(self.oauth_update_properties, start_index-1, pattern) @@ -485,7 +580,7 @@ def oauth_update_properties( if tofocus: self.app.layout.focus(properties) else: - self.app.show_message(_("Oops"), _(common_strings.no_matching_result),tobefocused = self.oauth_containers['properties']) + self.app.show_message(_("Oops"), _(common_strings.no_matching_result), tobefocused= self.oauth_containers['properties']) def properties_display_dialog(self, **params: Any) -> None: """Display the properties as Text @@ -515,7 +610,7 @@ def view_property(self, **params: Any) -> None: title = _("Edit property") - dialog = ViewProperty(app=self.app, parent=self, title=title, data=selected_line_data) + dialog = ViewProperty(app=self.app, parent=self, title=title, data=selected_line_data, op_type=params.get('op_type', 'replace')) self.app.show_jans_dialog(dialog) @@ -547,6 +642,7 @@ def oauth_update_keys(self) -> None: [ d['name'], exps, + d['kid'] ] ) @@ -554,9 +650,9 @@ def oauth_update_keys(self) -> None: keys = JansVerticalNav( myparent=self.app, - headers=['Name', 'Expiration'], + headers=['Name', 'Expiration','Kid'], data=data, - preferred_size=[0,0], + preferred_size=[0,0,0], on_display=self.app.data_display_dialog, selectes=0, headerColor=cli_style.navbar_headcolor, @@ -588,19 +684,26 @@ async def coroutine(): def edit_scope_dialog(self, **params: Any) -> None: """This Method show the scopes dialog for edit """ - selected_line_data = params['data'] + selected_line_data = params['data'] dialog = EditScopeDialog(self.app, title=_("Edit Scopes"), data=selected_line_data, save_handler=self.save_scope) self.app.show_jans_dialog(dialog) - def edit_client_dialog(self, **params: Any) -> None: + def edit_client(self, **params: Any) -> None: """This Method show the scopes dialog for edit """ - selected_line_data = params['data'] - title = _("Edit user Data (Clients)") + selected_line_data = params['data'] + title = _("Edit Clients") + + self.edit_client_dialog = EditClientDialog( + parent=self.app, + title=title, + data=selected_line_data, + save_handler=self.save_client, + delete_uma_resource=self.delete_uma_resource + ) - self.EditClientDialog = EditClientDialog(self.app, title=title, data=selected_line_data, save_handler=self.save_client, delete_uma_resource=self.delete_uma_resource) - self.app.show_jans_dialog(self.EditClientDialog) + self.app.show_jans_dialog(self.edit_client_dialog) def save_client(self, dialog: Dialog) -> None: """This method to save the client data to server @@ -612,21 +715,22 @@ def save_client(self, dialog: Dialog) -> None: _type_: bool value to check the status code response """ + async def coroutine(): + self.app.start_progressing(_("Saving clinet ...")) + operation_id='put-oauth-openid-client' if dialog.data.get('inum') else 'post-oauth-openid-client' + cli_args = {'operation_id': operation_id, 'data': dialog.data} + response = await self.app.loop.run_in_executor(self.app.executor, self.app.cli_requests, cli_args) + + dialog.future.set_result(DialogResult.ACCEPT) + self.app.stop_progressing() + + if response.status_code in (200, 201): + self.oauth_update_clients() + else: + self.app.show_message(_("Error!"), _("An error ocurred while saving client:\n") + str(response.text), tobefocused=self.app.center_frame) - response = self.app.cli_object.process_command_by_id( - operation_id='put-oauth-openid-client' if dialog.data.get('inum') else 'post-oauth-openid-client', - url_suffix='', - endpoint_args='', - data_fn='', - data=dialog.data - ) - - self.app.stop_progressing() - if response.status_code in (200, 201): - self.oauth_update_clients() - return None + asyncio.ensure_future(coroutine()) - self.app.show_message(_("Error!"), _("An error ocurred while saving client:\n") + str(response.text)) def save_scope(self, dialog: Dialog) -> None: """This method to save the client data to server @@ -652,7 +756,7 @@ async def coroutine(): asyncio.ensure_future(coroutine()) - def search_scope(self, tbuffer:Buffer,) -> None: + def search_scope(self, tbuffer:Buffer) -> None: """This method handel the search for Scopes Args: @@ -661,7 +765,7 @@ def search_scope(self, tbuffer:Buffer,) -> None: self.oauth_get_scopes(pattern=tbuffer.text) - def search_clients(self, tbuffer:Buffer,) -> None: + def search_clients(self, tbuffer:Buffer) -> None: """This method handel the search for Clients Args: @@ -743,7 +847,7 @@ async def coroutine(): try: self.app.layout.focus(focused_before) except Exception: - self.app.layout.focus(self.EditClientDialog) + self.app.layout.focus(self.edit_client_dialog) if result.lower() == 'yes': result = self.app.cli_object.process_command_by_id( @@ -753,7 +857,7 @@ async def coroutine(): data_fn=None, data={} ) - self.EditClientDialog.oauth_get_uma_resources() + self.edit_client_dialog.oauth_get_uma_resources() return result diff --git a/jans-cli-tui/cli_tui/plugins/010_auth_server/view_property.py b/jans-cli-tui/cli_tui/plugins/010_auth_server/view_property.py index 67ee4d7f09f..29731b1cf95 100644 --- a/jans-cli-tui/cli_tui/plugins/010_auth_server/view_property.py +++ b/jans-cli-tui/cli_tui/plugins/010_auth_server/view_property.py @@ -1,33 +1,32 @@ +import json import asyncio +from functools import partial +from typing import Optional, Sequence + +from prompt_toolkit.application import Application from prompt_toolkit.layout.dimension import D from prompt_toolkit.formatted_text import AnyFormattedText +from prompt_toolkit.layout.containers import HSplit +from prompt_toolkit.widgets import Button, Dialog + from cli import config_cli -from prompt_toolkit.layout.containers import ( - HSplit, - DynamicContainer, -) -from prompt_toolkit.widgets import ( - Button, - RadioList, - Dialog, - ) from utils.static import DialogResult, cli_style from utils.utils import DialogUtils from wui_components.jans_cli_dialog import JansGDialog -from typing import Optional, Sequence +from wui_components.jans_tab import JansTab + from utils.multi_lang import _ -import cli_style class ViewProperty(JansGDialog, DialogUtils): """The Main UMA-resources Dialog to view UMA Resource Details """ def __init__( self, - app, + app: Application, parent, data:tuple, - title: AnyFormattedText= "", - buttons: Optional[Sequence[Button]]= [] + title: AnyFormattedText='', + op_type: Optional[str]='replace' )-> None: """init for `ViewProperty`, inherits from two diffrent classes `JansGDialog` and `DialogUtils` @@ -39,20 +38,19 @@ def __init__( parent (widget): This is the parent widget for the dialog data (tuple): selected line data title (AnyFormattedText, optional): The Main dialog title. Defaults to "". - button_functions (list, optional): Dialog main buttons with their handlers. Defaults to []. """ - super().__init__(app, title, buttons) - self.property, self.value = data[0],data[1] + super().__init__(app) + self.property_name, self.value = data[0], data[1] self.app = app self.myparent = parent + self.op_type = op_type self.value_content = HSplit([],width=D()) - self.tabs = {} - self.selected_tab = 'tab0' - self.schema = self.app.cli_object.get_schema_from_reference('', '#/components/schemas/AppConfiguration') - + self.tab_widget = None + self.widgets = [] + self.buttons = [Button(text=_("Cancel"), handler=self.cancel), Button(text=_("Save"), handler=self.save)] self.prepare_properties() self.create_window() - + def cancel(self) -> None: """method to invoked when canceling changes in the dialog (Cancel button is pressed) """ @@ -63,328 +61,188 @@ def save(self) -> None: """method to invoked when saving the dialog (Save button is pressed) """ - data_dict = {} - list_data =[] - - if type(self.value) in [str,bool,int] : - for wid in self.value_content.children: - prop_type = self.get_item_data(wid) - data = prop_type['value'] + if len(self.widgets) == 1: + item_data = self.get_item_data(self.widgets[0]) + data = item_data['value'] - elif (type(self.value)==list and (type(self.value[0]) not in [dict,list])): - - for wid in self.value_content.children: - prop_type = self.get_item_data(wid) - - if self.get_type(prop_type['key']) != 'checkboxlist': - data = prop_type['value'].split('\n') - else: - data = prop_type['value'] - - elif type(self.value) == dict : - for wid in self.value_content.children: - for k in wid.children : - prop_type = self.get_item_data(k) - data_dict[prop_type['key']]=prop_type['value'] - data = data_dict - - elif type(self.value) == list and type(self.value[0]) == dict: - for tab in self.tabs: - data_dict = {} - for k in self.tabs[tab].children : - prop_type = self.get_item_data(k.children[0]) - data_dict[prop_type['key']]=prop_type['value'] - list_data.append(data_dict) - data = list_data - else : - self.app.logger.debug("self.value: "+str(self.value)) - self.app.logger.debug("type self.value: "+str(type(self.value))) + elif self.tab_widget: data = [] - - # ------------------------------------------------------------# - # --------------------- Patch to server ----------------------# - # ------------------------------------------------------------# - if data : - - cli_args = {'operation_id': 'patch-properties', 'data': [ {'op':'replace', 'path': self.property, 'value': data } ]} - - async def coroutine(): - self.app.start_progressing() - response = await self.app.loop.run_in_executor(self.app.executor, self.app.cli_requests, cli_args) - self.app.stop_progressing() - self.myparent.app_configuration = response - self.future.set_result(DialogResult.ACCEPT) - self.myparent.oauth_update_properties(start_index=self.myparent.oauth_update_properties_start_index) - asyncio.ensure_future(coroutine()) - - def get_type(self,prop): - """This Method get a property and get its type from schema to return the widget type to implement - - Args: - prop (str): The property name - - Returns: - str: the widget type to implement - """ - try : - proper = self.schema.get('properties', {})[prop] - - if proper['type'] == 'string': - prop_type= 'TitledText' - - elif proper['type'] == 'integer': - prop_type= 'int-TitledText' - - elif proper['type'] == 'boolean': - prop_type= 'TitledCheckBox' - - elif proper['type'] == 'object': - prop_type= 'dict' - - elif proper['type'] == 'array': - if 'enum' in proper or ('enum' in proper['items']): - prop_type= 'checkboxlist' + tabn = [] + for tab in self.tab_widget.tabs: + tabn.append(tab[0]) + if self.tab_widget.tab_content_type == 'object': + tab_data = self.make_data_from_dialog({tab[0]: tab[1]}) else: - if type(self.value[0]) == dict: - prop_type= 'list-dict' - elif type(self.value[0]) == list: - prop_type= 'list-list' - else: - prop_type= 'long-TitledText' - except Exception: - prop_type = None - - return prop_type - - def get_listValues(self,prop,type=None): - """This method get list values for properties own Enum values + tab_data_tmp = self.make_data_from_dialog({tab[0]: tab[1]}) + tab_data = tab_data_tmp[self.property_name] + data.append(tab_data) + else: + data = {} + for widget in self.widgets: + item_data = self.get_item_data(widget) + data[item_data['key']] = item_data['value'] + + cli_args = {'operation_id': 'patch-properties', 'data': [ {'op':self.op_type, 'path': self.property_name, 'value': data } ]} + + async def coroutine(): + self.app.start_progressing() + response = await self.app.loop.run_in_executor(self.app.executor, self.app.cli_requests, cli_args) + self.app.stop_progressing() + self.myparent.app_configuration = response + self.future.set_result(DialogResult.ACCEPT) + self.myparent.oauth_update_properties(start_index=self.myparent.oauth_update_properties_start_index) + asyncio.ensure_future(coroutine()) + + + def get_widgets( + self, + properties:dict, + values: dict=None, + styles: dict=None + ) -> list: + """Returns list of widgets for properties Args: - prop (str): The property name - type (_type_, optional): If the Items in Property properties had a nasted Enum. Defaults to None. + properties (dict): properties to get widget + values (dict): values of properties + styes (dict): styles for widgets + """ - Returns: - list: List of the properties enum to choose from + if not values: + values = {self.property_name: self.value} + if not styles: + styles = {'widget_style':'', 'string': cli_style.edit_text, 'boolean': cli_style.check_box} + + widgets = [] + for item_name in properties: + item = properties[item_name] + if item['type'] in ('integer', 'string'): + widgets.append( + self.app.getTitledText( + item_name, + name=item_name, + value=str(values.get(item_name,'')), + text_type=item['type'], + style=styles['string'], + widget_style=styles['widget_style'] + ) + ) + + elif item['type'] == 'boolean': + widgets.append( + self.app.getTitledCheckBox( + item_name, + name=item_name, + checked=values.get(item_name, False), + style=styles['boolean'], + widget_style=styles['widget_style'] + ) + ) + + elif item['type'] == 'array' and item['items'].get('enum'): + widgets.append( + self.app.getTitledCheckBoxList( + item_name, + name=item_name, + values=item['items']['enum'], + current_values=values.get(item_name, []), + style=styles['boolean'], + widget_style=styles['widget_style'] + ) + ) + + elif item['type'] == 'array' and item['items'].get('type') in ('string', 'integer'): + titled_text = self.app.getTitledText( + item_name, + name=item_name, + height=4, + text_type = item['items']['type'], + value='\n'.join(values.get(item_name, [])), + style=styles['string'], + widget_style=styles['widget_style'] + ) + titled_text.jans_list_type = True + widgets.append(titled_text) + + return widgets + + + def add_tab_element( + self, + properties:dict, + values: dict + ) -> None: + """Adds element to tab widget + Args: + properties (dict): properties of element to add + values (dict): values of properties """ - try : - if type !='nasted': - list_values= self.schema.get('properties', {})[prop]['items']['enum'] - else: - list_values= self.schema.get('properties', {})[prop]['items']['items']['enum'] - except Exception: - list_values = [] + tab_name = '#{}'.format(len(self.tab_widget.tabs)+1) + tab_widgets = self.get_widgets(properties, values=values, styles={'widget_style': cli_style.tab_selected, 'string': cli_style.tab_selected, 'boolean': cli_style.tab_selected}) + self.tab_widget.add_tab(tab_name, HSplit(tab_widgets, style=cli_style.tab_selected)) + if not values: + self.tab_widget.set_tab(tab_name) - return list_values + def delete_tab_element(self) -> None: + """Deletes currenlt oelemnt form tab widget + """ + cur_tab = self.tab_widget.cur_tab + cur_tab_name = self.tab_widget.tabs[cur_tab][0] + self.tab_widget.remove_tab(cur_tab_name) def prepare_properties(self): """This method build the main value_content to edit the properties """ - tab_temp = 'tab{}' - prop_type = self.get_type(self.property) - - if prop_type == 'TitledText': - self.value_content= HSplit([self.app.getTitledText( - self.property, - name=self.property, - value=self.value, - style=cli_style.edit_text - ), - ],width=D()) - - elif prop_type == 'int-TitledText': - self.value_content= HSplit([self.app.getTitledText( - self.property, - name=self.property, - value=self.value, - text_type='integer', - style=cli_style.edit_text - ), - ],width=D()) - - elif prop_type == 'long-TitledText': - self.value_content= HSplit([self.app.getTitledText( - self.property, - name=self.property, - height=3, - value='\n'.join(self.value), - style=cli_style.edit_text - ), - ],width=D()) - - elif prop_type == 'list-list': - self.value_content= HSplit([ - self.app.getTitledCheckBoxList( - self.property, - name=self.property, - values=self.get_listValues(self.property,'nasted'), - style='class:outh-client-checkboxlist'), - ],width=D()) - - elif prop_type == 'checkboxlist': - self.value_content= HSplit([ - self.app.getTitledCheckBoxList( - self.property, - name=self.property, - values=self.get_listValues(self.property), - style='class:outh-client-checkboxlist'), - ],width=D()) - - elif prop_type == 'list-dict': - tab_num = len(self.value) - tabs = [] - for i in range(tab_num) : - tabs.append((tab_temp.format(i), tab_temp.format(i))) - - - for tab in self.value: - tab_list=[] - for item in tab: - if type(tab[item]) == str: - tab_list.append(HSplit([self.app.getTitledText( - item , - name=item, - value=tab[item], - style=cli_style.edit_text - ), - ],width=D())) - - if type(tab[item]) == int : - tab_list.append(HSplit([self.app.getTitledText( - item , - name=item, - value=tab[item], - text_type='integer', - style=cli_style.edit_text - ), - ],width=D())) - - elif type(tab[item]) == list: - tab_list.append(HSplit([self.app.getTitledText( - item, - name=item, - height=3, - value='\n'.join(tab[item]), - style=cli_style.edit_text - ), - ],width=D())) - - elif type(tab[item]) == bool: - tab_list.append(HSplit([ - self.app.getTitledCheckBox( - item, - name=item, - checked= tab[item], - style=cli_style.checkbox), - ],width=D())) - - self.tabs[tab_temp.format(self.value.index(tab))] = HSplit(tab_list,width=D()) - - self.value_content=HSplit([ - self.app.getTitledRadioButton( - _("Tab Num"), - name='tabNum', - current_value=self.selected_tab, - values=tabs, - on_selection_changed=self.tab_selection_changed, - style='class:outh-scope-radiobutton'), - - DynamicContainer(lambda: self.tabs[self.selected_tab]), - - ],width=D()) - - elif prop_type == 'TitledCheckBox': - self.value_content= HSplit([ - self.app.getTitledCheckBox( - self.property, - name=self.property, - checked= self.value, - style=cli_style.checkbox), - ],width=D()) - - elif prop_type == 'dict': - dict_list=[] - for item in self.value: - if type(self.value[item]) == str: - dict_list.append(HSplit([self.app.getTitledText( - item , - name=item, - value=self.value[item], - style=cli_style.edit_text - ), - ],width=D())) - - elif type(self.value[item]) == int : - dict_list.append(HSplit([self.app.getTitledText( - item , - name=item, - value=self.value[item], - text_type='integer', - style=cli_style.edit_text - ), - ],width=D())) - - elif type(self.value[item]) == list: - dict_list.append(HSplit([self.app.getTitledText( - item, - name=item, - height=3, - value='\n'.join(self.value[item]), - style=cli_style.edit_text - ), - ],width=D())) - - elif type(self.value[item]) == bool: - dict_list.append(HSplit([ - self.app.getTitledCheckBox( - item, - name=item, - checked= self.value[item], - style=cli_style.checkbox), - ],width=D())) - - else : - dict_list.append(HSplit([self.app.getTitledText( - item, - name=item, - value="No Items Here", - style=cli_style.edit_text, - read_only=True, - ), - ],width=D())) - self.value_content= HSplit(dict_list,width=D()) - - def create_window(self): - - self.dialog = Dialog(title=self.property, - body= - HSplit([ - self.value_content, - ], padding=1,width=100,style='class:outh-uma-tabs' - ), - buttons=[ - Button( - text=_("Cancel"), - handler=self.cancel, - ) , - Button( - text=_("Save"), - handler=self.save, - ) , ], + properties = self.myparent.schema['properties'][self.property_name] + + if properties['type'] in ('string', 'integer', 'boolean'): + self.widgets = self.get_widgets({self.property_name: properties}) + + elif properties['type'] == 'array': + if properties['items'].get('type') in ('string', 'integer', 'boolean'): + self.widgets = self.get_widgets({self.property_name: properties}) + + elif properties['items'].get('type') == 'array': + self.tab_widget = JansTab(self) + self.tab_widget.tab_content_type = 'array' + item_property = {self.property_name: properties['items']} + for entry_value in self.value: + self.add_tab_element(item_property, {self.property_name: entry_value}) + + self.value_content = self.tab_widget + add_entry_partial = partial(self.add_tab_element, item_property, {}) + self.buttons.append(Button(_("Add"), handler=add_entry_partial)) + self.buttons.append(Button(_("Delete"), handler=self.delete_tab_element)) + + elif properties.get('properties'): + self.tab_widget = JansTab(self) + self.tab_widget.tab_content_type = 'object' + for entry_value in self.value: + self.add_tab_element(properties['properties'], entry_value) + self.value_content = self.tab_widget + add_entry_partial = partial(self.add_tab_element, properties['properties'], {}) + self.buttons.append(Button(_("Add"), handler=add_entry_partial)) + self.buttons.append(Button(_("Delete"), handler=self.delete_tab_element)) + + elif properties['type'] == 'object': + self.widgets = self.get_widgets(properties['properties'], values=self.value) + + if not self.tab_widget: + self.value_content = HSplit(self.widgets, width=D()) + + def create_window(self) -> None: + """Creates dialog window + """ + + self.dialog = Dialog( + title=self.property_name, + body= HSplit([self.value_content], padding=1,width=100,style='class:outh-uma-tabs'), + buttons=self.buttons, with_background=False, ) - def tab_selection_changed( - self, - cb: RadioList, - ) -> None: - """This method for properties that implemented in multi tab - - Args: - cb (RadioList): the New Value from the nasted tab - """ - self.selected_tab = cb.current_value def __pt_container__(self)-> Dialog: """The container for the dialog itself diff --git a/jans-cli-tui/cli_tui/plugins/020_fido/main.py b/jans-cli-tui/cli_tui/plugins/020_fido/main.py index 46721cef41e..d3617a775a7 100755 --- a/jans-cli-tui/cli_tui/plugins/020_fido/main.py +++ b/jans-cli-tui/cli_tui/plugins/020_fido/main.py @@ -12,6 +12,8 @@ from wui_components.jans_cli_dialog import JansGDialog from utils.multi_lang import _ from utils.utils import DialogUtils +from utils.static import cli_style + class Plugin(DialogUtils): """This is a general class for plugins @@ -26,8 +28,9 @@ def __init__( app (Generic): The main Application class """ self.app = app - self.pid = 'fido' + self.pid = 'fido2' self.name = '[F]IDO' + self.server_side_plugin = True self.page_entered = False self.data = {} self.prepare_navbar() @@ -93,12 +96,12 @@ def create_widgets(self): self.schema = self.app.cli_object.get_schema_from_reference('Fido2', '#/components/schemas/AppConfiguration') self.tabs['configuration'] = HSplit([ - self.app.getTitledText(_("Issuer"), name='issuer', value=self.data.get('issuer',''), jans_help=self.app.get_help_from_schema(self.schema, 'issuer'), style='class:outh-scope-text'), - self.app.getTitledText(_("Base Endpoint"), name='baseEndpoint', value=self.data.get('baseEndpoint',''), jans_help=self.app.get_help_from_schema(self.schema, 'baseEndpoint'), style='class:outh-scope-text'), - self.app.getTitledText(_("Clean Service Interval"), name='cleanServiceInterval', value=self.data.get('cleanServiceInterval',''), jans_help=self.app.get_help_from_schema(self.schema, 'cleanServiceInterval'), style='class:outh-scope-text', text_type='integer'), - self.app.getTitledText(_("Clean Service Batch ChunkSize"), name='cleanServiceBatchChunkSize', value=self.data.get('cleanServiceBatchChunkSize',''), jans_help=self.app.get_help_from_schema(self.schema, 'cleanServiceBatchChunkSize'), style='class:outh-scope-text', text_type='integer'), - self.app.getTitledCheckBox(_("Use Local Cache"), name='useLocalCache', checked=self.data.get('useLocalCache'), jans_help=self.app.get_help_from_schema(self.schema, 'useLocalCache'), style='class:outh-client-checkbox'), - self.app.getTitledCheckBox(_("Disable Jdk Logger"), name='disableJdkLogger', checked=self.data.get('disableJdkLogger'), jans_help=self.app.get_help_from_schema(self.schema, 'disableJdkLogger'), style='class:outh-client-checkbox'), + self.app.getTitledText(_("Issuer"), name='issuer', value=self.data.get('issuer',''), jans_help=self.app.get_help_from_schema(self.schema, 'issuer'), style='class:outh-scope-text',widget_style=cli_style.scim_widget), + self.app.getTitledText(_("Base Endpoint"), name='baseEndpoint', value=self.data.get('baseEndpoint',''), jans_help=self.app.get_help_from_schema(self.schema, 'baseEndpoint'), style='class:outh-scope-text',widget_style=cli_style.scim_widget), + self.app.getTitledText(_("Clean Service Interval"), name='cleanServiceInterval', value=self.data.get('cleanServiceInterval',''), jans_help=self.app.get_help_from_schema(self.schema, 'cleanServiceInterval'), style='class:outh-scope-text', text_type='integer',widget_style=cli_style.scim_widget), + self.app.getTitledText(_("Clean Service Batch ChunkSize"), name='cleanServiceBatchChunkSize', value=self.data.get('cleanServiceBatchChunkSize',''), jans_help=self.app.get_help_from_schema(self.schema, 'cleanServiceBatchChunkSize'), style='class:outh-scope-text', text_type='integer',widget_style=cli_style.scim_widget), + self.app.getTitledCheckBox(_("Use Local Cache"), name='useLocalCache', checked=self.data.get('useLocalCache'), jans_help=self.app.get_help_from_schema(self.schema, 'useLocalCache'), style=cli_style.check_box,widget_style=cli_style.scim_widget), + self.app.getTitledCheckBox(_("Disable Jdk Logger"), name='disableJdkLogger', checked=self.data.get('disableJdkLogger'), jans_help=self.app.get_help_from_schema(self.schema, 'disableJdkLogger'), style=cli_style.check_box,widget_style=cli_style.scim_widget), self.app.getTitledWidget( _("Logging Level"), name='loggingLevel', @@ -107,13 +110,13 @@ def create_widgets(self): value=self.data.get('loggingLevel') ), jans_help=self.app.get_help_from_schema(self.schema, 'loggingLevel'), - style='class:outh-client-dropdown' + style=cli_style.edit_text ), - self.app.getTitledText(_("Logging Layout"), name='loggingLayout', value=self.data.get('loggingLayout',''), jans_help=self.app.get_help_from_schema(self.schema, 'loggingLayout'), style='class:outh-scope-text'), - self.app.getTitledText(_("External Logger Configuration"), name='externalLoggerConfiguration', value=self.data.get('externalLoggerConfiguration',''), jans_help=self.app.get_help_from_schema(self.schema, 'externalLoggerConfiguration'), style='class:outh-scope-text'), - self.app.getTitledText(_("Metric Reporter Interval"), name='metricReporterInterval', value=self.data.get('metricReporterInterval',''), jans_help=self.app.get_help_from_schema(self.schema, 'metricReporterInterval'), style='class:outh-scope-text', text_type='integer'), - self.app.getTitledText(_("Metric Reporter Keep Data Days"), name='metricReporterKeepDataDays', value=self.data.get('metricReporterKeepDataDays',''), jans_help=self.app.get_help_from_schema(self.schema, 'metricReporterKeepDataDays'), style='class:outh-scope-text', text_type='integer'), - self.app.getTitledCheckBox(_("Metric Reporter Enabled"), name='metricReporterEnabled', checked=self.data.get('metricReporterEnabled'), jans_help=self.app.get_help_from_schema(self.schema, 'metricReporterEnabled'), style='class:outh-client-checkbox'), + self.app.getTitledText(_("Logging Layout"), name='loggingLayout', value=self.data.get('loggingLayout',''), jans_help=self.app.get_help_from_schema(self.schema, 'loggingLayout'), style='class:outh-scope-text',widget_style=cli_style.scim_widget), + self.app.getTitledText(_("External Logger Configuration"), name='externalLoggerConfiguration', value=self.data.get('externalLoggerConfiguration',''), jans_help=self.app.get_help_from_schema(self.schema, 'externalLoggerConfiguration'), style='class:outh-scope-text',widget_style=cli_style.scim_widget), + self.app.getTitledText(_("Metric Reporter Interval"), name='metricReporterInterval', value=self.data.get('metricReporterInterval',''), jans_help=self.app.get_help_from_schema(self.schema, 'metricReporterInterval'), style='class:outh-scope-text', text_type='integer',widget_style=cli_style.scim_widget), + self.app.getTitledText(_("Metric Reporter Keep Data Days"), name='metricReporterKeepDataDays', value=self.data.get('metricReporterKeepDataDays',''), jans_help=self.app.get_help_from_schema(self.schema, 'metricReporterKeepDataDays'), style='class:outh-scope-text', text_type='integer',widget_style=cli_style.scim_widget), + self.app.getTitledCheckBox(_("Metric Reporter Enabled"), name='metricReporterEnabled', checked=self.data.get('metricReporterEnabled'), jans_help=self.app.get_help_from_schema(self.schema, 'metricReporterEnabled'), style=cli_style.check_box,widget_style=cli_style.scim_widget), self.app.getTitledText( _("Person Custom Object Classes"), name='personCustomObjectClassList', @@ -121,9 +124,12 @@ def create_widgets(self): height=3, jans_help=self.app.get_help_from_schema(self.schema, 'personCustomObjectClassList'), style='class:outh-scope-text' + ,widget_style=cli_style.scim_widget ), Window(height=1), - VSplit([Window(), Button(_("Save"), handler=self.save_config), Window()]), + VSplit([Window(), + HSplit([Button(_("Save"), handler=self.save_config)]), + Window()]), ], width=D() ) @@ -160,17 +166,17 @@ def create_widgets(self): ) self.tabs['static'] = HSplit([ - self.app.getTitledText(_("Authenticator Certificates Folder"), name='authenticatorCertsFolder', value=fido2_static_config.get('authenticatorCertsFolder',''), jans_help=self.app.get_help_from_schema(static_schema, 'authenticatorCertsFolder'), style='class:outh-scope-text'), - self.app.getTitledText(_("MDS Access Token"), name='mdsAccessToken', value=fido2_static_config.get('mdsAccessToken',''), jans_help=self.app.get_help_from_schema(static_schema, 'mdsAccessToken'), style='class:outh-scope-text'), - self.app.getTitledText(_("MDS TOC Certificates Folder"), name='mdsCertsFolder', value=fido2_static_config.get('mdsCertsFolder',''), jans_help=self.app.get_help_from_schema(static_schema, 'mdsCertsFolder'), style='class:outh-scope-text'), - self.app.getTitledText(_("MDS TOC Files Folder"), name='mdsTocsFolder', value=fido2_static_config.get('mdsTocsFolder',''), jans_help=self.app.get_help_from_schema(static_schema, 'mdsTocsFolder'), style='class:outh-scope-text'), - self.app.getTitledCheckBox(_("Check U2f Attestations"), name='checkU2fAttestations', checked=fido2_static_config.get('checkU2fAttestations'), jans_help=self.app.get_help_from_schema(static_schema, 'checkU2fAttestations'), style='class:outh-client-checkbox'), - self.app.getTitledCheckBox(_("Check U2f Attestations"), name='checkU2fAttestations', checked=fido2_static_config.get('checkU2fAttestations'), jans_help=self.app.get_help_from_schema(static_schema, 'checkU2fAttestations'), style='class:outh-client-checkbox'), - self.app.getTitledText(_("Unfinished Request Expiration"), name='unfinishedRequestExpiration', value=fido2_static_config.get('unfinishedRequestExpiration',''), jans_help=self.app.get_help_from_schema(static_schema, 'unfinishedRequestExpiration'), style='class:outh-scope-text', text_type='integer'), - self.app.getTitledText(_("Authentication History Expiration"), name='authenticationHistoryExpiration', value=fido2_static_config.get('authenticationHistoryExpiration',''), jans_help=self.app.get_help_from_schema(static_schema, 'authenticationHistoryExpiration'), style='class:outh-scope-text', text_type='integer'), - self.app.getTitledText(_("Server Metadata Folder"), name='serverMetadataFolder', value=fido2_static_config.get('serverMetadataFolder',''), jans_help=self.app.get_help_from_schema(static_schema, 'serverMetadataFolder'), style='class:outh-scope-text'), - - self.app.getTitledCheckBox(_("User Auto Enrollment"), name='userAutoEnrollment', checked=fido2_static_config.get('userAutoEnrollment'), jans_help=self.app.get_help_from_schema(static_schema, 'userAutoEnrollment'), style='class:outh-client-checkbox'), + self.app.getTitledText(_("Authenticator Certificates Folder"), name='authenticatorCertsFolder', value=fido2_static_config.get('authenticatorCertsFolder',''), jans_help=self.app.get_help_from_schema(static_schema, 'authenticatorCertsFolder'), style='class:outh-scope-text',widget_style=cli_style.scim_widget), + self.app.getTitledText(_("MDS Access Token"), name='mdsAccessToken', value=fido2_static_config.get('mdsAccessToken',''), jans_help=self.app.get_help_from_schema(static_schema, 'mdsAccessToken'), style='class:outh-scope-text',widget_style=cli_style.scim_widget), + self.app.getTitledText(_("MDS TOC Certificates Folder"), name='mdsCertsFolder', value=fido2_static_config.get('mdsCertsFolder',''), jans_help=self.app.get_help_from_schema(static_schema, 'mdsCertsFolder'), style='class:outh-scope-text',widget_style=cli_style.scim_widget), + self.app.getTitledText(_("MDS TOC Files Folder"), name='mdsTocsFolder', value=fido2_static_config.get('mdsTocsFolder',''), jans_help=self.app.get_help_from_schema(static_schema, 'mdsTocsFolder'), style='class:outh-scope-text',widget_style=cli_style.scim_widget), + self.app.getTitledCheckBox(_("Check U2f Attestations"), name='checkU2fAttestations', checked=fido2_static_config.get('checkU2fAttestations'), jans_help=self.app.get_help_from_schema(static_schema, 'checkU2fAttestations'), style=cli_style.check_box,widget_style=cli_style.scim_widget), + self.app.getTitledCheckBox(_("Check U2f Attestations"), name='checkU2fAttestations', checked=fido2_static_config.get('checkU2fAttestations'), jans_help=self.app.get_help_from_schema(static_schema, 'checkU2fAttestations'), style=cli_style.check_box,widget_style=cli_style.scim_widget), + self.app.getTitledText(_("Unfinished Request Expiration"), name='unfinishedRequestExpiration', value=fido2_static_config.get('unfinishedRequestExpiration',''), jans_help=self.app.get_help_from_schema(static_schema, 'unfinishedRequestExpiration'), style='class:outh-scope-text', text_type='integer',widget_style=cli_style.scim_widget), + self.app.getTitledText(_("Authentication History Expiration"), name='authenticationHistoryExpiration', value=fido2_static_config.get('authenticationHistoryExpiration',''), jans_help=self.app.get_help_from_schema(static_schema, 'authenticationHistoryExpiration'), style='class:outh-scope-text', text_type='integer',widget_style=cli_style.scim_widget), + self.app.getTitledText(_("Server Metadata Folder"), name='serverMetadataFolder', value=fido2_static_config.get('serverMetadataFolder',''), jans_help=self.app.get_help_from_schema(static_schema, 'serverMetadataFolder'), style='class:outh-scope-text',widget_style=cli_style.scim_widget), + + self.app.getTitledCheckBox(_("User Auto Enrollment"), name='userAutoEnrollment', checked=fido2_static_config.get('userAutoEnrollment'), jans_help=self.app.get_help_from_schema(static_schema, 'userAutoEnrollment'), style=cli_style.check_box,widget_style=cli_style.scim_widget), self.app.getTitledText( _("Requested Credential Types"), name='requestedCredentialTypes', @@ -178,21 +184,27 @@ def create_widgets(self): height=3, jans_help=self.app.get_help_from_schema(static_schema, 'requestedCredentialTypes'), style='class:outh-scope-text' + ,widget_style=cli_style.scim_widget ), VSplit([ - Label(text=requested_parties_title, style='class:script-label', width=len(requested_parties_title)+1), + HSplit([Label(text=requested_parties_title, style='class:script-label', width=len(requested_parties_title)+1),]), + self.requested_parties_container, Window(width=2), HSplit([ Window(height=1), - Button(text=add_party_title, width=len(add_party_title)+4, handler=partial(self.edit_requested_party, jans_name='editRequestedPary')), + HSplit([Button(text=add_party_title, width=len(add_party_title)+4, handler=partial(self.edit_requested_party, jans_name='editRequestedPary'))]), + ]), ], height=5, width=D(), ), - VSplit([Window(), Button(_("Save"), handler=self.save_config), Window()]), + VSplit([Window(), + HSplit([Button(_("Save"), handler=self.save_config)]), + + Window()]), ], width=D() ) @@ -261,7 +273,7 @@ def nav_selection_changed( """ if selection in self.tabs: - self.main_area = self.tabs[selection] + self.main_area = HSplit([self.tabs[selection]],height=D()) else: self.main_area = self.app.not_implemented diff --git a/jans-cli-tui/cli_tui/plugins/030_scim/main.py b/jans-cli-tui/cli_tui/plugins/030_scim/main.py index c2855473e90..96127155eb4 100755 --- a/jans-cli-tui/cli_tui/plugins/030_scim/main.py +++ b/jans-cli-tui/cli_tui/plugins/030_scim/main.py @@ -5,6 +5,7 @@ from prompt_toolkit.widgets import Button, Frame from wui_components.jans_drop_down import DropDownWidget from utils.utils import DialogUtils +from utils.static import cli_style from utils.multi_lang import _ class Plugin(DialogUtils): @@ -22,6 +23,7 @@ def __init__( self.app = app self.pid = 'scim' self.name = '[S]CIM' + self.server_side_plugin = True self.app_config = {} self.widgets_ready = False self.container = Frame( @@ -42,16 +44,16 @@ def create_widgets(self) -> None: self.save_button = Button(_("Save"), handler=self.save_app_config) schema = self.app.cli_object.get_schema_from_reference('SCIM', '#/components/schemas/AppConfiguration') self.container = HSplit([ - self.app.getTitledText(_("Base DN"), name='baseDN', value=self.app_config.get('baseDN',''), jans_help=self.app.get_help_from_schema(schema, 'baseDN'), read_only=True, style='class:outh-scope-text'), - self.app.getTitledText(_("Application Url"), name='applicationUrl', value=self.app_config.get('applicationUrl',''), jans_help=self.app.get_help_from_schema(schema, 'applicationUrl'), style='class:outh-scope-text'), - self.app.getTitledText(_("Base Endpoint"), name='baseEndpoint', value=self.app_config.get('baseEndpoint',''), jans_help=self.app.get_help_from_schema(schema, 'baseEndpoint'), style='class:outh-scope-text'), - self.app.getTitledText(_("Person Custom Object Class"), name='personCustomObjectClass', value=self.app_config.get('personCustomObjectClass',''), jans_help=self.app.get_help_from_schema(schema, 'personCustomObjectClass'), style='class:outh-scope-text'), - self.app.getTitledText(_("Auth Issuer"), name='oxAuthIssuer', value=self.app_config.get('oxAuthIssuer',''), jans_help=self.app.get_help_from_schema(schema, 'oxAuthIssuer'), style='class:outh-scope-text'), - self.app.getTitledRadioButton(_("Protection Mode"), name='protectionMode', values=[('OAUTH', 'OAUTH'),('BYPASS', 'BYPASS')], current_value=self.app_config.get('protectionMode'), jans_help=self.app.get_help_from_schema(schema, 'protectionMode'), style='class:outh-client-radiobutton'), - self.app.getTitledText(_("Max Count"), name='maxCount', value=self.app_config.get('maxCount',''), jans_help=self.app.get_help_from_schema(schema, 'maxCount'), text_type='integer', style='class:outh-scope-text'), - self.app.getTitledText(_("Bulk Max Operations"), name='bulkMaxOperations', value=self.app_config.get('bulkMaxOperations',''), jans_help=self.app.get_help_from_schema(schema, 'bulkMaxOperations'), text_type='integer', style='class:outh-scope-text'), - self.app.getTitledText(_("Bulk Max Payload Size"), name='bulkMaxPayloadSize', value=self.app_config.get('bulkMaxPayloadSize',''), jans_help=self.app.get_help_from_schema(schema, 'bulkMaxPayloadSize'), text_type='integer', style='class:outh-scope-text'), - self.app.getTitledText(_("User Extension Schema URI"), name='userExtensionSchemaURI', value=self.app_config.get('userExtensionSchemaURI',''), jans_help=self.app.get_help_from_schema(schema, 'userExtensionSchemaURI'), style='class:outh-scope-text'), + self.app.getTitledText(_("Base DN"), name='baseDN', value=self.app_config.get('baseDN',''), jans_help=self.app.get_help_from_schema(schema, 'baseDN'), read_only=True, style=cli_style.edit_text, widget_style=cli_style.scim_widget), + self.app.getTitledText(_("Application Url"), name='applicationUrl', value=self.app_config.get('applicationUrl',''), jans_help=self.app.get_help_from_schema(schema, 'applicationUrl'), style=cli_style.edit_text, widget_style=cli_style.scim_widget), + self.app.getTitledText(_("Base Endpoint"), name='baseEndpoint', value=self.app_config.get('baseEndpoint',''), jans_help=self.app.get_help_from_schema(schema, 'baseEndpoint'), style=cli_style.edit_text,widget_style=cli_style.scim_widget), + self.app.getTitledText(_("Person Custom Object Class"), name='personCustomObjectClass', value=self.app_config.get('personCustomObjectClass',''), jans_help=self.app.get_help_from_schema(schema, 'personCustomObjectClass'), style=cli_style.edit_text,widget_style=cli_style.scim_widget), + self.app.getTitledText(_("Auth Issuer"), name='oxAuthIssuer', value=self.app_config.get('oxAuthIssuer',''), jans_help=self.app.get_help_from_schema(schema, 'oxAuthIssuer'), style=cli_style.edit_text,widget_style=cli_style.scim_widget), + self.app.getTitledRadioButton(_("Protection Mode"), name='protectionMode', values=[('OAUTH', 'OAUTH'),('BYPASS', 'BYPASS')], current_value=self.app_config.get('protectionMode'), jans_help=self.app.get_help_from_schema(schema, 'protectionMode'), style='class:outh-client-radiobutton',widget_style=cli_style.scim_widget), + self.app.getTitledText(_("Max Count"), name='maxCount', value=self.app_config.get('maxCount',''), jans_help=self.app.get_help_from_schema(schema, 'maxCount'), text_type='integer', style=cli_style.edit_text,widget_style=cli_style.scim_widget), + self.app.getTitledText(_("Bulk Max Operations"), name='bulkMaxOperations', value=self.app_config.get('bulkMaxOperations',''), jans_help=self.app.get_help_from_schema(schema, 'bulkMaxOperations'), text_type='integer', style=cli_style.edit_text,widget_style=cli_style.scim_widget), + self.app.getTitledText(_("Bulk Max Payload Size"), name='bulkMaxPayloadSize', value=self.app_config.get('bulkMaxPayloadSize',''), jans_help=self.app.get_help_from_schema(schema, 'bulkMaxPayloadSize'), text_type='integer', style=cli_style.edit_text,widget_style=cli_style.scim_widget), + self.app.getTitledText(_("User Extension Schema URI"), name='userExtensionSchemaURI', value=self.app_config.get('userExtensionSchemaURI',''), jans_help=self.app.get_help_from_schema(schema, 'userExtensionSchemaURI'), style=cli_style.edit_text,widget_style=cli_style.scim_widget), self.app.getTitledWidget( _("Logging Level"), name='loggingLevel', @@ -62,19 +64,19 @@ def create_widgets(self) -> None: jans_help=self.app.get_help_from_schema(schema, 'loggingLevel'), style='class:outh-client-dropdown' ), - self.app.getTitledText(_("Logging Layout"), name='loggingLayout', value=self.app_config.get('loggingLayout',''), jans_help=self.app.get_help_from_schema(schema, 'loggingLayout'), style='class:outh-scope-text'), - self.app.getTitledText(_("External Logger Configuration"), name='externalLoggerConfiguration', value=self.app_config.get('externalLoggerConfiguration',''), jans_help=self.app.get_help_from_schema(schema, 'externalLoggerConfiguration'), style='class:outh-scope-text'), - self.app.getTitledText(_("Metric Reporter Interval"), name='metricReporterInterval', value=self.app_config.get('metricReporterInterval',''), jans_help=self.app.get_help_from_schema(schema, 'metricReporterInterval'), style='class:outh-scope-text', text_type='integer'), - self.app.getTitledText(_("Metric Reporter Keep Data Days"), name='metricReporterKeepDataDays', value=self.app_config.get('metricReporterKeepDataDays',''), jans_help=self.app.get_help_from_schema(schema, 'metricReporterKeepDataDays'), style='class:outh-scope-text', text_type='integer'), - self.app.getTitledCheckBox(_("Metric Reporter Enabled"), name='metricReporterEnabled', checked=self.app_config.get('metricReporterEnabled'), jans_help=self.app.get_help_from_schema(schema, 'metricReporterEnabled'), style='class:outh-client-checkbox'), - self.app.getTitledCheckBox(_("Disable Jdk Logger"), name='disableJdkLogger', checked=self.app_config.get('disableJdkLogger'), jans_help=self.app.get_help_from_schema(schema, 'disableJdkLogger'), style='class:outh-client-checkbox'), - self.app.getTitledCheckBox(_("Use Local Cache"), name='useLocalCache', checked=self.app_config.get('useLocalCache'), jans_help=self.app.get_help_from_schema(schema, 'useLocalCache'), style='class:outh-client-checkbox'), + self.app.getTitledText(_("Logging Layout"), name='loggingLayout', value=self.app_config.get('loggingLayout',''), jans_help=self.app.get_help_from_schema(schema, 'loggingLayout'), style=cli_style.edit_text,widget_style=cli_style.scim_widget), + self.app.getTitledText(_("External Logger Configuration"), name='externalLoggerConfiguration', value=self.app_config.get('externalLoggerConfiguration',''), jans_help=self.app.get_help_from_schema(schema, 'externalLoggerConfiguration'), style=cli_style.edit_text,widget_style=cli_style.scim_widget), + self.app.getTitledText(_("Metric Reporter Interval"), name='metricReporterInterval', value=self.app_config.get('metricReporterInterval',''), jans_help=self.app.get_help_from_schema(schema, 'metricReporterInterval'), style=cli_style.edit_text, text_type='integer',widget_style=cli_style.scim_widget), + self.app.getTitledText(_("Metric Reporter Keep Data Days"), name='metricReporterKeepDataDays', value=self.app_config.get('metricReporterKeepDataDays',''), jans_help=self.app.get_help_from_schema(schema, 'metricReporterKeepDataDays'), style=cli_style.edit_text, text_type='integer',widget_style=cli_style.scim_widget), + self.app.getTitledCheckBox(_("Metric Reporter Enabled"), name='metricReporterEnabled', checked=self.app_config.get('metricReporterEnabled'), jans_help=self.app.get_help_from_schema(schema, 'metricReporterEnabled'), style=cli_style.check_box, widget_style=cli_style.scim_widget), + self.app.getTitledCheckBox(_("Disable Jdk Logger"), name='disableJdkLogger', checked=self.app_config.get('disableJdkLogger'), jans_help=self.app.get_help_from_schema(schema, 'disableJdkLogger'), style=cli_style.check_box, widget_style=cli_style.scim_widget), + self.app.getTitledCheckBox(_("Use Local Cache"), name='useLocalCache', checked=self.app_config.get('useLocalCache'), jans_help=self.app.get_help_from_schema(schema, 'useLocalCache'), style=cli_style.check_box, widget_style=cli_style.scim_widget), VSplit([Window(), self.save_button, Window()]) ], width=D() ) - self.app.center_container = self.container + self.app.center_container = HSplit([ self.container],style='bg:black', height=D()) def get_app_config(self) -> None: """Gets SCIM application configurations from server. diff --git a/jans-cli-tui/cli_tui/plugins/040_config_api/main.py b/jans-cli-tui/cli_tui/plugins/040_config_api/main.py index 397c626ff76..21352c82086 100755 --- a/jans-cli-tui/cli_tui/plugins/040_config_api/main.py +++ b/jans-cli-tui/cli_tui/plugins/040_config_api/main.py @@ -31,8 +31,9 @@ def __init__( app (Generic): The main Application class """ self.app = app - self.pid = 'config_api' + self.pid = 'admin' self.name = '[C]onfig-API' + self.server_side_plugin = True self.page_entered = False self.role_type = 'api-viewer' self.admin_ui_roles_data = {} diff --git a/jans-cli-tui/cli_tui/plugins/060_scripts/main.py b/jans-cli-tui/cli_tui/plugins/060_scripts/main.py index e90cf9f689d..1ab45fb8cbb 100755 --- a/jans-cli-tui/cli_tui/plugins/060_scripts/main.py +++ b/jans-cli-tui/cli_tui/plugins/060_scripts/main.py @@ -20,6 +20,7 @@ from edit_script_dialog import EditScriptDialog from prompt_toolkit.application import Application from utils.multi_lang import _ +from utils.static import DialogResult, cli_style, common_strings class Plugin(): """This is a general class for plugins @@ -56,7 +57,7 @@ def scripts_prepare_containers(self) -> None: VSplit([ self.app.getButton(text=_("Get Scripts"), name='scripts:get', jans_help=_("Retreive first %d Scripts") % (20), handler=self.get_scripts), self.app.getTitledText(_("Search"), name='scripts:search', jans_help=_("Press enter to perform search"), accept_handler=self.search_scripts, style='class:outh_containers_scopes.text'), - self.app.getButton(text=_("Add Sscript"), name='scripts:add', jans_help=_("To add a new scope press this button"), handler=self.add_script_dialog), + self.app.getButton(text=_("Add Script"), name='scripts:add', jans_help=_("To add a new scope press this button"), handler=self.add_script_dialog), ], padding=3, width=D(), @@ -129,8 +130,8 @@ def scripts_update_list( get_help=(self.get_help,'Scripts'), on_delete=self.delete_script, selectes=0, - headerColor='class:outh-verticalnav-headcolor', - entriesColor='class:outh-verticalnav-entriescolor', + headerColor=cli_style.navbar_headcolor, + entriesColor=cli_style.navbar_entriescolor, all_data=self.data['entries'] ) diff --git a/jans-cli-tui/cli_tui/plugins/070_users/main.py b/jans-cli-tui/cli_tui/plugins/070_users/main.py index ead042a5973..2babeb13f38 100755 --- a/jans-cli-tui/cli_tui/plugins/070_users/main.py +++ b/jans-cli-tui/cli_tui/plugins/070_users/main.py @@ -12,6 +12,9 @@ from utils.utils import DialogUtils, common_data from utils.static import DialogResult from utils.multi_lang import _ +from wui_components.jans_cli_dialog import JansGDialog +from prompt_toolkit.eventloop import get_event_loop +from utils.static import DialogResult, cli_style, common_strings common_data.users = SimpleNamespace() @@ -28,7 +31,8 @@ def __init__( app (Generic): The main Application class """ self.app = app - self.pid = 'users' + self.pid = 'user-management' + self.server_side_plugin = True self.name = '[U]sers' self.users = {} self.widgets_ready = False @@ -79,10 +83,12 @@ def update_user_list_container(self, pattern: Optional[str]='') -> None: on_display=self.app.data_display_dialog, on_delete=self.delete_user, #get_help=(self.get_help,'User'), + change_password=self.change_password, selectes=0, - headerColor='class:outh-verticalnav-headcolor', - entriesColor='class:outh-verticalnav-entriescolor', - all_data=self.users['entries'] + headerColor=cli_style.navbar_headcolor, + entriesColor=cli_style.navbar_entriescolor, + all_data=self.users['entries'], + jans_help = "Press p to change password" ) self.user_list_container = self.users_list_box @@ -142,6 +148,52 @@ def edit_user_dialog(self, **kwargs: Any) -> None: edit_user_dialog = EditUserDialog(self.app, title=title, data=data, save_handler=self.save_user) self.app.show_jans_dialog(edit_user_dialog) + + def change_password(self, **kwargs: Any) -> None: + """Method to display the edit user dialog + """ + if kwargs: + data = kwargs.get('data', {}) + else: + data = {} + + def save(dialog) -> None: + async def coroutine(): + cli_args = {'operation_id': 'patch-user-by-inum', 'endpoint_args': '', + 'url_suffix':'inum:{}'.format(data['inum']), + 'data':{"jsonPatchString": "", + "customAttributes": [ + {"name": "userPassword", + "multiValued": False, + "value": "{}".format(self.new_password.me.text)}]} + } + self.app.start_progressing(_("Changing Password ...")) + await get_event_loop().run_in_executor(self.app.executor, self.app.cli_requests, cli_args) + self.app.stop_progressing() + asyncio.ensure_future(coroutine()) + + self.new_password = self.app.getTitledText( + _('New Password'), + name='passwd', + value='', + style='class:outh-scope-text', + ) + body = HSplit([ + self.new_password + ],style='class:jans-main-datadisplay') + buttons=[ + Button( + text=_("Cancel"), + ) , + Button( + text=_("Save"), + handler=save, + ) , ] + + dialog = JansGDialog(self.app, title="Change Password for {}".format(data['userId']), body=body, buttons=buttons) + + self.app.show_jans_dialog(dialog) + def delete_user(self, **kwargs: Any) -> None: """This method for the deletion of the User """ diff --git a/jans-cli-tui/cli_tui/plugins/999_jans/main.py b/jans-cli-tui/cli_tui/plugins/999_jans/main.py index 5b877812fae..b09e7d6ff88 100755 --- a/jans-cli-tui/cli_tui/plugins/999_jans/main.py +++ b/jans-cli-tui/cli_tui/plugins/999_jans/main.py @@ -20,7 +20,7 @@ def __init__( """ self.app = app self.pid = 'jans-menu' - self.name = '[J]ans Cli' + self.name = '[J]ans CLI' self.menu_container = Frame( body=HSplit([ diff --git a/jans-cli-tui/cli_tui/utils/static.py b/jans-cli-tui/cli_tui/utils/static.py index d38b8cbe8fa..0a909300b5d 100755 --- a/jans-cli-tui/cli_tui/utils/static.py +++ b/jans-cli-tui/cli_tui/utils/static.py @@ -13,8 +13,11 @@ class cli_style: tabs = 'class:plugin-tabs' label = 'class:plugin-label' container = 'class:plugin-container' - navbar_headcolor = "class:plugin-navbar-headcolor" - navbar_entriescolor = "class:plugin-navbar-entriescolor" + navbar_headcolor = 'class:plugin-navbar-headcolor' + navbar_entriescolor = 'class:plugin-navbar-entriescolor' + tab_selected = 'class:tab-selected' + scim_widget = 'class:scim-widget' + black_bg = 'class:plugin-black-bg' class common_strings: enter_to_search = "Press enter to perform search" diff --git a/jans-cli-tui/cli_tui/utils/utils.py b/jans-cli-tui/cli_tui/utils/utils.py index f5dd69c1506..eee45e5ff1b 100755 --- a/jans-cli-tui/cli_tui/utils/utils.py +++ b/jans-cli-tui/cli_tui/utils/utils.py @@ -42,6 +42,9 @@ def get_item_data(self, item): if value_: value_ = int(value_) + if getattr(item, 'jans_list_type', False): + value_ = value_.split('\n') + return {'key':key_, 'value':value_} diff --git a/jans-cli-tui/cli_tui/wui_components/jans_cli_dialog.py b/jans-cli-tui/cli_tui/wui_components/jans_cli_dialog.py index 74210bba912..78c493e860d 100755 --- a/jans-cli-tui/cli_tui/wui_components/jans_cli_dialog.py +++ b/jans-cli-tui/cli_tui/wui_components/jans_cli_dialog.py @@ -3,9 +3,7 @@ from asyncio import Future from prompt_toolkit.widgets import Button, Dialog from typing import Optional, Sequence, Union -from prompt_toolkit.layout.containers import ( - AnyContainer, -) +from prompt_toolkit.layout.containers import AnyContainer from prompt_toolkit.layout.dimension import AnyDimension from prompt_toolkit.formatted_text import AnyFormattedText from utils.multi_lang import _ @@ -16,11 +14,11 @@ class JansGDialog: def __init__( self, parent, - body: AnyContainer, + body: Optional[AnyContainer]=None, title: Optional[str]= '', - buttons: Optional[Sequence[Button]]= [], - width: AnyDimension= None - )-> Dialog: + buttons: Optional[Sequence[Button]]=[], + width: AnyDimension=None + )-> Dialog: """init for JansGDialog diff --git a/jans-cli-tui/cli_tui/wui_components/jans_dialog_with_nav.py b/jans-cli-tui/cli_tui/wui_components/jans_dialog_with_nav.py index 3d3aafc1314..062310bd729 100755 --- a/jans-cli-tui/cli_tui/wui_components/jans_dialog_with_nav.py +++ b/jans-cli-tui/cli_tui/wui_components/jans_dialog_with_nav.py @@ -62,7 +62,7 @@ def __init__( self.button_functions = button_functions self.title = title self.height = height - self.width =width + self.width = width self.content = content self.create_window() @@ -71,7 +71,7 @@ def create_window(self)-> None: Todo: * Change `max_data_str` to be dynamic """ - max_data_str = 30 ## TODO TO BE Dynamic + max_data_str = 22 ## TODO TO BE Dynamic wwidth, wheight = get_terminal_size() height = 19 if wheight <= 30 else wheight - 11 diff --git a/jans-cli-tui/cli_tui/wui_components/jans_label_container.py b/jans-cli-tui/cli_tui/wui_components/jans_label_container.py new file mode 100644 index 00000000000..5a06f7a7aec --- /dev/null +++ b/jans-cli-tui/cli_tui/wui_components/jans_label_container.py @@ -0,0 +1,146 @@ +from typing import Callable, Optional +from prompt_toolkit.application import get_app + +from prompt_toolkit.formatted_text import HTML, AnyFormattedText, merge_formatted_text +from prompt_toolkit.key_binding import KeyBindings +from prompt_toolkit.layout import FormattedTextControl, Window +from prompt_toolkit.widgets import Label, Frame, Box, Button +from prompt_toolkit.layout.containers import HSplit + +class JansLabelContainer: + def __init__( + self, + title: Optional[str]='', + width: Optional[int]=50, + on_enter: Optional[Callable]=None, + on_delete: Optional[Callable]=None, + on_display: Optional[Callable]=None, + buttonbox: Optional[Button]=None + ) -> None: + + """Label container for Jans + + Args: + title (str, optional): title of frame + width (int, optional): sets width of container, default is 50 + on_enter (Callable, optional): When enter this function is called. + on_delete (Callable, optional): this function is called when an entry is deleted + on_display (Callable, optional): this function is called when user press d on keyboard + buttonbox (Button, optional): buntton box to be appended at the end + """ + self.width = width + self.on_enter = on_enter + self.on_delete = on_delete + self.on_display = on_display + self.height=2 + self.entries = [] + self.invalidate = False + self.selected_entry = 0 + self.body = Window( + content=FormattedTextControl( + text=self._get_formatted_text, + focusable=True, + key_bindings=self._get_key_bindings(), + ), + width=self.width-2, + height=self.height + ) + self.line_count = 1 + widgets = [self.body] + if buttonbox: + widgets.append(Window(height=1)) + widgets.append(buttonbox) + + self.container = Box(Frame(HSplit(widgets), title=title, width=self.width)) + + + def _get_formatted_text(self) -> AnyFormattedText: + """Internal function for formatting entries + + Returns: + merged formatted text. + """ + + result = [] + line_width = 0 + self.line_count = 1 + for i, entry in enumerate(self.entries): + if line_width + len(entry[1]) + 2 > self.width: + result.append('\n\n') + line_width = 0 + self.line_count += 2 + + line_width += len(entry[1]) + 2 + + if i == self.selected_entry: + result.append(HTML(''.format("white", "darkgrey", entry[1]))) + else: + result.append(HTML(''.format("black", "lightgrey", entry[1]))) + result.append(' ') + + self.body.height = self.line_count + if self.invalidate: + get_app().invalidate() + self.invalidate = False + + return merge_formatted_text(result) + + def add_label( + self, + label_id:str, + label_title:str + ) -> None: + """Adds label to container + + Args: + label_id (str): ID for label + label_title (str): Text to be displayed as label + """ + self.invalidate = True + self.entries.append((label_id, label_title)) + + + def remove_label(self, label_id: str) -> None: + """Removes label from container + + Args: + label_id (str): ID for label + label_title (str): Text to be displayed as label + """ + for entry in self.entries[:]: + if entry[0] == label_id: + self.entries.remove(entry) + self.invalidate = True + + def _get_key_bindings(self) -> None: + kb = KeyBindings() + + @kb.add("left") + def _go_up(event) -> None: + self.selected_entry = (self.selected_entry - 1) % len(self.entries) + + @kb.add("right") + def _go_up(event) -> None: + self.selected_entry = (self.selected_entry + 1) % len(self.entries) + + @kb.add("enter") + def _enter(event) -> None: + if self.on_enter: + self.on_enter(self.entries[self.selected_entry]) + + @kb.add("delete") + def _delete(event) -> None: + if self.on_delete: + self.on_delete(self.entries[self.selected_entry]) + + @kb.add('d') + def _display(event): + if self.on_display: + self.on_display(selected=self.entries[self.selected_entry], data=self.entries[self.selected_entry]) + + return kb + + def __pt_container__(self) -> Frame: + """Returns frame as container + """ + return self.container diff --git a/jans-cli-tui/cli_tui/wui_components/jans_tab.py b/jans-cli-tui/cli_tui/wui_components/jans_tab.py new file mode 100644 index 00000000000..d48e78766ac --- /dev/null +++ b/jans-cli-tui/cli_tui/wui_components/jans_tab.py @@ -0,0 +1,195 @@ +import re +import os + +from typing import Callable, Optional + +from prompt_toolkit.formatted_text import AnyFormattedText +from prompt_toolkit.key_binding.key_bindings import KeyBindings, KeyBindingsBase +from prompt_toolkit.layout.containers import Window, HSplit, VSplit, DynamicContainer, AnyContainer +from prompt_toolkit.layout.controls import FormattedTextControl +from prompt_toolkit.formatted_text import HTML, merge_formatted_text + +from cli_style import get_color_for_style + +selected_tab_style = get_color_for_style('tab-selected') +unselected_tab_style = get_color_for_style('tab-unselected') + +shortcut_re = re.compile(r'\[(.*?)\]') + +class JansTab(): + """This is a tab widget used for Jan Client TUI + """ + def __init__( + self, + myparent, + selection_changed: Optional[Callable]=None, + ) -> None: + + """init for JansTab + + Args: + myparent (application): This is the parent application for the dialog, to caluclate the size + selection_changed (_type_): Callable function when tab selection is changed + + Examples: + self.user_tab = JansTab(self) + self.user_tab.add_tab(('Users', HSplit([Label("This is user tab")]))) + self.user_tab.add_tab(('Groups', HSplit([Label("This is group tab")]))) + """ + self.myparent = myparent + self.selection_changed = selection_changed + self.cur_tab = 0 + + self.tabs = [] + self.tab_container = VSplit([]) + self.create_window() + + + def _set_nav_width(self): + """Sets tab navigation with""" + nav_bar_width = 1 + for tab in self.tabs: + nav_bar_width += len(tab[0]) + 3 + self.tab_nav.width = nav_bar_width + + def add_tab( + self, + tab_name: str, + container: AnyContainer + ) -> None: + """Adds tab + + Args: + tab_name (String): name of tab + container (AnyContainer): container of tab + """ + self.tabs.append((tab_name, container)) + self._set_nav_width() + + if len(self.tabs) == 1: + self.tab_container = container + + def remove_tab(self, tab_name: str)->None: + """Removes tab + + Args: + tab_name (String): name of tab + """ + for tab in self.tabs[:]: + if tab[0] == tab_name: + self.tabs.remove(tab) + self.tab_container = self.tabs[0][1] if self.tabs else VSplit([]) + self.cur_tab = 0 + self._set_nav_width() + + def set_tab(self, tab_name:str): + """Sets current tab + + Args: + tab_name (String): name of tab + """ + for i, tab in enumerate(self.tabs): + if tab[0] == tab_name: + self.tab_container = tab[1] + self.cur_tab = i + + def create_window(self)-> None: + """This method creat the tab widget it self + """ + + self.tab_nav = Window( + content=FormattedTextControl( + text=self._get_navbar_entries, + focusable=True, + key_bindings=self.get_nav_bar_key_bindings(), + ), + style='class:tab-nav-background', + height=1, + cursorline=False, + ) + + self.tabbed_container = HSplit([ + VSplit([self.tab_nav]), + DynamicContainer(lambda: self.tab_container) + ]) + + + def add_key_binding( + self, + shorcut_key:str, + )-> None: + """Key bindings for tab widget""" + + for binding in self.myparent.bindings.bindings: + if len(binding.keys) == 2 and binding.keys[0].value == 'escape' and binding.keys[1].lower() == shorcut_key: + return + self.myparent.bindings.add('escape', shorcut_key.lower())(self._go_tab) + + + def _get_navbar_entries(self)-> AnyFormattedText: + """Get tab navigation entries + + Returns: + merge_formatted_text: Merge (Concatenate) several pieces of formatted text together. + """ + + result = [HTML('')] + + for i, tab in enumerate(self.tabs): + + if i == self.cur_tab: + result.append(HTML(''.format(selected_tab_style.fg, selected_tab_style.bg, tab[0]))) + else: + result.append(HTML(''.format(unselected_tab_style.bg, tab[0]))) + sep_space = HTML('') + + result.append(sep_space) + + return merge_formatted_text(result) + + + def get_nav_bar_key_bindings(self)-> KeyBindingsBase: + """All key binding for the Dialog with Navigation bar + + Returns: + KeyBindings: The method according to the binding key + """ + kb = KeyBindings() + + @kb.add('left') + def _go_left(event) -> None: + if self.cur_tab > 0: + self.cur_tab -= 1 + self.tab_container = self.tabs[self.cur_tab][1] + if self.selection_changed: + self.selection_changed(self.cur_tab) + + @kb.add('right') + def _go_right(event) -> None: + if self.cur_tab < len(self.tabs) - 1: + self.cur_tab += 1 + self.tab_container = self.tabs[self.cur_tab][1] + if self.selection_changed: + self.selection_changed(self.cur_tab) + + @kb.add('c-left') + def _go_left(event) -> None: + if self.cur_tab > 0: + cur = self.tabs.pop(self.cur_tab) + self.tabs.insert(self.cur_tab-1, cur) + self.cur_tab -= 1 + self.tab_container = self.tabs[self.cur_tab][1] + + @kb.add('c-right') + def _go_right(event) -> None: + if self.cur_tab < len(self.tabs) - 1: + cur = self.tabs.pop(self.cur_tab) + self.tabs.insert(self.cur_tab+1, cur) + self.cur_tab += 1 + self.tab_container = self.tabs[self.cur_tab][1] + + + return kb + + def __pt_container__(self)-> HSplit: + return self.tabbed_container diff --git a/jans-cli-tui/cli_tui/wui_components/jans_vetrical_nav.py b/jans-cli-tui/cli_tui/wui_components/jans_vetrical_nav.py index 191a137055c..e6bbc4e8ea7 100644 --- a/jans-cli-tui/cli_tui/wui_components/jans_vetrical_nav.py +++ b/jans-cli-tui/cli_tui/wui_components/jans_vetrical_nav.py @@ -23,6 +23,7 @@ def __init__( on_enter: Callable= None, get_help: Tuple= None, on_delete: Callable= None, + change_password: Callable= None, all_data: Optional[list]= [], preferred_size: Optional[list]= [], data: Optional[list]= [], @@ -32,6 +33,7 @@ def __init__( max_width: AnyDimension = None, jans_name: Optional[str]= '', max_height: AnyDimension = None, + jans_help: Optional[str]= '', )->FloatContainer : """init for JansVerticalNav @@ -51,7 +53,7 @@ def __init__( max_width (int, optional): Maximum width of container. jans_name (str, optional): Widget name max_height (int, optional): Maximum hegight of container - + jans_help (str, optional): Status bar help message Examples: clients = JansVerticalNav( myparent=self, @@ -80,14 +82,15 @@ def __init__( self.headerColor = headerColor self.entriesColor = entriesColor self.max_height = max_height - + self.jans_help = jans_help self.on_enter = on_enter self.on_delete = on_delete self.on_display = on_display + self.change_password = change_password if get_help: self.get_help, self.scheme = get_help if self.data : - self.get_help(data=self.data[self.selectes],scheme=self.scheme) + self.get_help(data=self.data[self.selectes], scheme=self.scheme) else: self.get_help= None self.all_data=all_data @@ -95,7 +98,7 @@ def __init__( self.handle_header_spaces() self.create_window() - + def view_data( self, @@ -120,6 +123,22 @@ def view_data( def create_window(self) -> None: """This method creat the dialog it self """ + + self.list_box = Window( + content=FormattedTextControl( + text=self._get_formatted_text, + focusable=True, + key_bindings=self._get_key_bindings(), + style=self.entriesColor, + ), + style='class:select-box', + height=D(preferred=len(self.data), max=len(self.data)), + cursorline=True, + right_margins=[ScrollbarMargin(display_arrows=True), ], + ) + if self.jans_help: + self.list_box.jans_help = self.jans_help + self.container_content = [ Window( content=FormattedTextControl( @@ -132,18 +151,7 @@ def create_window(self) -> None: height=D(preferred=1, max=1), cursorline=False, ), - Window( - content=FormattedTextControl( - text=self._get_formatted_text, - focusable=True, - key_bindings=self._get_key_bindings(), - style=self.entriesColor, - ), - style='class:select-box', - height=D(preferred=len(self.data), max=len(self.data)), - cursorline=True, - right_margins=[ScrollbarMargin(display_arrows=True), ], - ), + self.list_box , ] if self.underline_headings: @@ -295,6 +303,13 @@ def _(event): if self.on_enter : self.on_enter(passed=self.data[self.selectes], event=event, size=size, data=self.all_data[self.selectes], selected=self.selectes, jans_name=self.jans_name) + @kb.add('p') + def _(event): + if not self.data: + return + if self.change_password: + self.change_password(data=self.all_data[self.selectes]) + @kb.add('d') def _(event):