fix(apps) UI release fixes [EE-5197] (#8702)

* fix(apps) searchbar flex resizing and insights

* UI fixes

* update stacks datatable

---------

Co-authored-by: testa113 <testa113>
pull/8716/head
Ali 2023-03-23 08:20:30 +13:00 committed by GitHub
parent 3636ac5c26
commit 30248eabb4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 277 additions and 217 deletions

View File

@ -14,6 +14,7 @@ import { DockerfileDetails } from '@/react/docker/images/ItemView/DockerfileDeta
import { HealthStatus } from '@/react/docker/containers/ItemView/HealthStatus';
import { GpusList } from '@/react/docker/host/SetupView/GpusList';
import { GpusInsights } from '@/react/docker/host/SetupView/GpusInsights';
import { InsightsBox } from '@/react/components/InsightsBox';
export const componentsModule = angular
.module('portainer.docker.react.components', [])
@ -53,4 +54,15 @@ export const componentsModule = angular
'gpusList',
r2a(withControlledInput(GpusList), ['value', 'onChange'])
)
.component(
'insightsBox',
r2a(InsightsBox, [
'header',
'content',
'setHtmlContent',
'insightCloseId',
'type',
'className',
])
)
.component('gpusInsights', r2a(GpusInsights, [])).name;

View File

@ -1,132 +1,144 @@
<div class="datatable">
<!-- toolbar header actions and settings -->
<div ng-if="$ctrl.isPrimary" class="toolBar !flex-col gap-1">
<div class="toolBar vertical-center w-full flex-wrap !gap-x-5 !gap-y-1 !p-0">
<div ng-if="$ctrl.isPrimary" class="toolBar !flex-col !gap-0">
<div class="toolBar w-full !items-start !gap-x-5 !p-0">
<div class="toolBarTitle vertical-center">
<div class="widget-icon space-right">
<pr-icon icon="'box'"></pr-icon>
</div>
Applications
</div>
<div class="form-group namespaces !mb-0 !mr-0 min-w-[280px]">
<div class="input-group">
<span class="input-group-addon">
<pr-icon icon="'filter'"></pr-icon>
Namespace
</span>
<select
class="form-control"
ng-model="$ctrl.state.namespace"
ng-change="$ctrl.onChangeNamespace()"
data-cy="component-namespaceSelect"
ng-options="o.Value as (o.Name + (o.IsSystem ? ' - system' : '')) for o in $ctrl.state.namespaces"
>
</select>
</div>
</div>
<div class="searchBar vertical-center !mr-0 min-w-[280px]">
<pr-icon icon="'search'" class-name="'searchIcon'"></pr-icon>
<input
type="text"
class="searchInput"
ng-model="$ctrl.state.textFilter"
ng-change="$ctrl.onTextFilterChange()"
placeholder="Search for an application..."
auto-focus
ng-model-options="{ debounce: 300 }"
data-cy="k8sApp-searchApplicationsInput"
/>
</div>
<div class="actionBar !mr-0 !gap-3">
<button
ng-if="$ctrl.isPrimary"
type="button"
class="btn btn-sm btn-dangerlight vertical-center !ml-0 h-fit"
ng-disabled="$ctrl.state.selectedItemCount === 0"
ng-click="$ctrl.removeAction($ctrl.state.selectedItems)"
data-cy="k8sApp-removeAppButton"
>
<pr-icon icon="'trash-2'"></pr-icon>
Remove
</button>
<button
ng-if="$ctrl.isPrimary"
type="button"
class="btn btn-sm btn-secondary vertical-center !ml-0 h-fit"
ui-sref="kubernetes.applications.new"
data-cy="k8sApp-addApplicationButton"
>
<pr-icon icon="'plus'" class-name="'!h-3'"></pr-icon>Add with form
</button>
<button
ng-if="$ctrl.isPrimary"
type="button"
class="btn btn-sm btn-primary vertical-center !ml-0 h-fit"
ui-sref="kubernetes.deploy"
data-cy="k8sApp-deployFromManifestButton"
>
<pr-icon icon="'plus'" class-name="'!h-3'"></pr-icon>Create from manifest
</button>
</div>
<div class="settings" data-cy="k8sApp-tableSettings">
<span class="setting" ng-class="{ 'setting-active': $ctrl.settings.open }" uib-dropdown dropdown-append-to-body auto-close="disabled" is-open="$ctrl.settings.open">
<span uib-dropdown-toggle aria-label="Settings">
<pr-icon icon="'more-vertical'" class-name="'!mr-0 !h-4'"></pr-icon>
</span>
<div class="dropdown-menu dropdown-menu-right" uib-dropdown-menu>
<div class="tableMenu">
<div class="menuHeader"> Table settings </div>
<div class="menuContent">
<div>
<div class="md-checkbox" ng-if="$ctrl.isAdmin">
<input id="applications_setting_show_system" type="checkbox" ng-model="$ctrl.settings.showSystem" ng-change="$ctrl.onSettingsShowSystemChange()" />
<label for="applications_setting_show_system">Show system resources</label>
</div>
<div class="md-checkbox">
<input
id="setting_auto_refresh"
type="checkbox"
ng-model="$ctrl.settings.repeater.autoRefresh"
ng-change="$ctrl.onSettingsRepeaterChange()"
data-cy="k8sApp-autoRefreshCheckbox"
/>
<label for="setting_auto_refresh">Auto refresh</label>
</div>
<div ng-if="$ctrl.settings.repeater.autoRefresh">
<label for="settings_refresh_rate"> Refresh rate </label>
<select
id="settings_refresh_rate"
ng-model="$ctrl.settings.repeater.refreshRate"
ng-change="$ctrl.onSettingsRepeaterChange()"
class="small-select"
data-cy="k8sApp-refreshRateDropdown"
>
<option value="10">10s</option>
<option value="30">30s</option>
<option value="60">1min</option>
<option value="120">2min</option>
<option value="300">5min</option>
</select>
<span>
<pr-icon id="refreshRateChange" icon="'check'" mode="'success'" style="display: none"></pr-icon>
</span>
<!-- use row reverse to make the left most items wrap first to the right side in the next line -->
<div class="inline-flex flex-row-reverse flex-wrap !gap-x-5 gap-y-3">
<div class="settings" data-cy="k8sApp-tableSettings">
<span class="setting" ng-class="{ 'setting-active': $ctrl.settings.open }" uib-dropdown dropdown-append-to-body auto-close="disabled" is-open="$ctrl.settings.open">
<span uib-dropdown-toggle aria-label="Settings">
<pr-icon icon="'more-vertical'" class-name="'!mr-0 !h-4'"></pr-icon>
</span>
<div class="dropdown-menu dropdown-menu-right" uib-dropdown-menu>
<div class="tableMenu">
<div class="menuHeader"> Table settings </div>
<div class="menuContent">
<div>
<div class="md-checkbox" ng-if="$ctrl.isAdmin">
<input id="applications_setting_show_system" type="checkbox" ng-model="$ctrl.settings.showSystem" ng-change="$ctrl.onSettingsShowSystemChange()" />
<label for="applications_setting_show_system">Show system resources</label>
</div>
<div class="md-checkbox">
<input
id="setting_auto_refresh"
type="checkbox"
ng-model="$ctrl.settings.repeater.autoRefresh"
ng-change="$ctrl.onSettingsRepeaterChange()"
data-cy="k8sApp-autoRefreshCheckbox"
/>
<label for="setting_auto_refresh">Auto refresh</label>
</div>
<div ng-if="$ctrl.settings.repeater.autoRefresh">
<label for="settings_refresh_rate"> Refresh rate </label>
<select
id="settings_refresh_rate"
ng-model="$ctrl.settings.repeater.refreshRate"
ng-change="$ctrl.onSettingsRepeaterChange()"
class="small-select"
data-cy="k8sApp-refreshRateDropdown"
>
<option value="10">10s</option>
<option value="30">30s</option>
<option value="60">1min</option>
<option value="120">2min</option>
<option value="300">5min</option>
</select>
<span>
<pr-icon id="refreshRateChange" icon="'check'" mode="'success'" style="display: none"></pr-icon>
</span>
</div>
</div>
</div>
</div>
<div>
<a type="button" class="btn btn-sm btn-default btn-sm" ng-click="$ctrl.settings.open = false;" data-cy="k8sApp-tableSettingsCloseButton">Close</a>
<div>
<a type="button" class="btn btn-sm btn-default btn-sm" ng-click="$ctrl.settings.open = false;" data-cy="k8sApp-tableSettingsCloseButton">Close</a>
</div>
</div>
</div>
</span>
</div>
<div class="actionBar !mr-0 !gap-3">
<button
ng-if="$ctrl.isPrimary"
type="button"
class="btn btn-sm btn-dangerlight vertical-center !ml-0 h-fit"
ng-disabled="$ctrl.state.selectedItemCount === 0"
ng-click="$ctrl.removeAction($ctrl.state.selectedItems)"
data-cy="k8sApp-removeAppButton"
>
<pr-icon icon="'trash-2'"></pr-icon>
Remove
</button>
<button
ng-if="$ctrl.isPrimary"
hide-deployment-option="form"
type="button"
class="btn btn-sm btn-secondary vertical-center !ml-0 h-fit"
ui-sref="kubernetes.applications.new"
data-cy="k8sApp-addApplicationButton"
>
<pr-icon icon="'plus'" class-name="'!h-3'"></pr-icon>Add with form
</button>
<button
ng-if="$ctrl.isPrimary"
type="button"
class="btn btn-sm btn-primary vertical-center !ml-0 h-fit"
ui-sref="kubernetes.deploy"
data-cy="k8sApp-deployFromManifestButton"
>
<pr-icon icon="'plus'" class-name="'!h-3'"></pr-icon>Create from manifest
</button>
</div>
<div class="searchBar">
<pr-icon icon="'search'" class-name="'searchIcon'"></pr-icon>
<input
type="text"
class="searchInput"
ng-model="$ctrl.state.textFilter"
ng-change="$ctrl.onTextFilterChange()"
placeholder="Search..."
auto-focus
ng-model-options="{ debounce: 300 }"
data-cy="k8sApp-searchApplicationsInput"
/>
</div>
<div class="form-group namespaces !mb-0 !mr-0 !h-[30px] min-w-[140px]">
<div class="input-group">
<span class="input-group-addon">
<pr-icon icon="'filter'" size="'sm'"></pr-icon>
Namespace
</span>
<select
class="form-control !h-[30px] !py-1"
ng-model="$ctrl.state.namespace"
ng-change="$ctrl.onChangeNamespace()"
data-cy="component-namespaceSelect"
ng-options="o.Value as (o.Name + (o.IsSystem ? ' - system' : '')) for o in $ctrl.state.namespaces"
>
</select>
</div>
</span>
</div>
</div>
</div>
<div class="flex w-full flex-row" ng-if="$ctrl.isAdmin && !$ctrl.settings.showSystem">
<span class="small text-muted vertical-center mt-1">
<pr-icon icon="'info'" mode="'primary'" class="vertical-center"></pr-icon>
<pr-icon icon="'info'" mode="'primary'"></pr-icon>
System resources are hidden, this can be changed in the table settings.
</span>
</div>
<div class="w-full">
<div class="w-fit">
<insights-box class-name="'mt-2'" type="'slim'" header="'From 2.18 on, you can filter this view by namespace.'" insight-close-id="'k8s-namespace-filtering'"></insights-box>
</div>
</div>
</div>
<!-- data table content -->
<div ng-class="{ 'table-responsive': $ctrl.isPrimary, 'inner-datatable': !$ctrl.isPrimary }">

View File

@ -1,115 +1,122 @@
<div class="datatable">
<!-- table title and action menu -->
<div class="toolBar !flex-col gap-1">
<div class="toolBar vertical-center w-full flex-wrap !gap-x-5 !gap-y-1 !p-0">
<!-- title -->
<div class="toolBar !flex-col !gap-0">
<div class="toolBar w-full !items-start !gap-x-5 !p-0">
<div class="toolBarTitle vertical-center">
<div class="widget-icon space-right">
<pr-icon icon="'list'"></pr-icon>
</div>
Stacks
</div>
<!-- actions -->
<div class="form-group namespaces !mb-0 !mr-0 min-w-[280px]">
<div class="input-group">
<span class="input-group-addon">
<pr-icon icon="'filter'"></pr-icon>
Namespace
</span>
<select
class="form-control"
ng-model="$ctrl.state.namespace"
ng-change="$ctrl.onChangeNamespace()"
data-cy="component-namespaceSelect"
ng-options="o.Value as (o.Name + (o.IsSystem ? ' - system' : '')) for o in $ctrl.state.namespaces"
<!-- use row reverse to make the left most items wrap first to the right side in the next line -->
<div class="inline-flex flex-row-reverse flex-wrap !gap-x-5 gap-y-3">
<div class="actionBar !mr-0 !gap-3">
<button
type="button"
class="btn btn-sm btn-dangerlight vertical-center !ml-0 h-fit"
ng-disabled="$ctrl.state.selectedItemCount === 0"
ng-click="$ctrl.removeAction($ctrl.state.selectedItems)"
data-cy="k8sApp-removeStackButton"
>
</select>
</div>
</div>
<div class="searchBar vertical-center">
<pr-icon icon="'search'" class-name="'!h-3'"></pr-icon>
<input
type="text"
class="searchInput min-w-min self-start"
ng-model="$ctrl.state.textFilter"
ng-change="$ctrl.onTextFilterChange()"
placeholder="Search for a stack..."
auto-focus
ng-model-options="{ debounce: 300 }"
/>
</div>
<div class="actionBar !mr-0 !gap-3">
<button
type="button"
class="btn btn-sm btn-dangerlight vertical-center !ml-0 h-fit"
ng-disabled="$ctrl.state.selectedItemCount === 0"
ng-click="$ctrl.removeAction($ctrl.state.selectedItems)"
data-cy="k8sApp-removeStackButton"
>
<pr-icon icon="'trash-2'"></pr-icon>
Remove
</button>
<div class="settings" data-cy="k8sApp-StackTableSettings">
<span class="setting" ng-class="{ 'setting-active': $ctrl.settings.open }" uib-dropdown dropdown-append-to-body auto-close="disabled" is-open="$ctrl.settings.open">
<span uib-dropdown-toggle>
<pr-icon icon="'more-vertical'" class-name="'!mr-0 !h-4'"></pr-icon>
</span>
<div class="dropdown-menu dropdown-menu-right" uib-dropdown-menu>
<div class="tableMenu">
<div class="menuHeader"> Table settings </div>
<div class="menuContent">
<div>
<div class="md-checkbox" ng-if="$ctrl.isAdmin">
<input id="applications_setting_show_system" type="checkbox" ng-model="$ctrl.settings.showSystem" ng-change="$ctrl.onSettingsShowSystemChange()" />
<label for="applications_setting_show_system">Show system resources</label>
</div>
<div class="md-checkbox">
<input
id="setting_auto_refresh"
type="checkbox"
ng-model="$ctrl.settings.repeater.autoRefresh"
ng-change="$ctrl.onSettingsRepeaterChange()"
data-cy="k8sApp-autoRefreshCheckbox-stack"
/>
<label for="setting_auto_refresh">Auto refresh</label>
</div>
<div ng-if="$ctrl.settings.repeater.autoRefresh">
<label for="settings_refresh_rate"> Refresh rate </label>
<select
id="settings_refresh_rate"
ng-model="$ctrl.settings.repeater.refreshRate"
ng-change="$ctrl.onSettingsRepeaterChange()"
class="small-select"
data-cy="k8sApp-refreshRateDropdown-stack"
>
<option value="10">10s</option>
<option value="30">30s</option>
<option value="60">1min</option>
<option value="120">2min</option>
<option value="300">5min</option>
</select>
<span>
<pr-icon id="refreshRateChange" icon="'check'" mode="'success'" style="display: none"></pr-icon>
</span>
<pr-icon icon="'trash-2'"></pr-icon>
Remove
</button>
<div class="settings" data-cy="k8sApp-StackTableSettings">
<span class="setting" ng-class="{ 'setting-active': $ctrl.settings.open }" uib-dropdown dropdown-append-to-body auto-close="disabled" is-open="$ctrl.settings.open">
<span uib-dropdown-toggle>
<pr-icon icon="'more-vertical'" class-name="'!mr-0 !h-4'"></pr-icon>
</span>
<div class="dropdown-menu dropdown-menu-right" uib-dropdown-menu>
<div class="tableMenu">
<div class="menuHeader"> Table settings </div>
<div class="menuContent">
<div>
<div class="md-checkbox" ng-if="$ctrl.isAdmin">
<input id="applications_setting_show_system" type="checkbox" ng-model="$ctrl.settings.showSystem" ng-change="$ctrl.onSettingsShowSystemChange()" />
<label for="applications_setting_show_system">Show system resources</label>
</div>
<div class="md-checkbox">
<input
id="setting_auto_refresh"
type="checkbox"
ng-model="$ctrl.settings.repeater.autoRefresh"
ng-change="$ctrl.onSettingsRepeaterChange()"
data-cy="k8sApp-autoRefreshCheckbox-stack"
/>
<label for="setting_auto_refresh">Auto refresh</label>
</div>
<div ng-if="$ctrl.settings.repeater.autoRefresh">
<label for="settings_refresh_rate"> Refresh rate </label>
<select
id="settings_refresh_rate"
ng-model="$ctrl.settings.repeater.refreshRate"
ng-change="$ctrl.onSettingsRepeaterChange()"
class="small-select"
data-cy="k8sApp-refreshRateDropdown-stack"
>
<option value="10">10s</option>
<option value="30">30s</option>
<option value="60">1min</option>
<option value="120">2min</option>
<option value="300">5min</option>
</select>
<span>
<pr-icon id="refreshRateChange" icon="'check'" mode="'success'" style="display: none"></pr-icon>
</span>
</div>
</div>
</div>
</div>
<div>
<a type="button" class="btn btn-sm btn-default btn-sm" ng-click="$ctrl.settings.open = false;" data-cy="k8sApp-tableSettingsCloseButton-stack">Close</a>
<div>
<a type="button" class="btn btn-sm btn-default btn-sm" ng-click="$ctrl.settings.open = false;" data-cy="k8sApp-tableSettingsCloseButton-stack">Close</a>
</div>
</div>
</div>
</div>
</span>
</span>
</div>
</div>
<div class="searchBar vertical-center">
<pr-icon icon="'search'" class-name="'!h-3'"></pr-icon>
<input
type="text"
class="searchInput min-w-min self-start"
ng-model="$ctrl.state.textFilter"
ng-change="$ctrl.onTextFilterChange()"
placeholder="Search..."
auto-focus
ng-model-options="{ debounce: 300 }"
/>
</div>
<div class="form-group namespaces !mb-0 !mr-0 !h-[30px] w-fit min-w-[140px]">
<div class="input-group">
<span class="input-group-addon">
<pr-icon icon="'filter'" size="'sm'"></pr-icon>
Namespace
</span>
<select
class="form-control !h-[30px] !py-1"
ng-model="$ctrl.state.namespace"
ng-change="$ctrl.onChangeNamespace()"
data-cy="component-namespaceSelect"
ng-options="o.Value as (o.Name + (o.IsSystem ? ' - system' : '')) for o in $ctrl.state.namespaces"
>
</select>
</div>
</div>
</div>
</div>
<!-- info text -->
<div class="flex w-full flex-row">
<span class="small text-muted vertical-center mt-1" ng-if="$ctrl.isAdmin && !$ctrl.settings.showSystem">
<div class="flex w-full flex-row" ng-if="$ctrl.isAdmin && !$ctrl.settings.showSystem">
<span class="small text-muted vertical-center mt-1">
<pr-icon icon="'info'" mode="'primary'"></pr-icon>
System resources are hidden, this can be changed in the table settings.
</span>
</div>
<div class="w-full">
<div class="w-fit">
<insights-box class-name="'mt-2'" type="'slim'" header="'From 2.18 on, you can filter this view by namespace.'" insight-close-id="'k8s-namespace-filtering'"></insights-box>
</div>
</div>
</div>
<div class="table-responsive">
<table class="table-hover nowrap-cells table">

View File

@ -66,7 +66,12 @@
.toolBar .searchBar {
margin-right: 10px;
display: inline-flex;
min-height: 30px;
flex-basis: 7rem;
flex-grow: 1;
flex-shrink: 1;
align-items: center;
min-width: 7rem;
max-width: 14rem;
}
.datatable .searchBar input[type='text'] {

View File

@ -8,13 +8,21 @@ import { Button } from '@@/buttons';
import { insightStore } from './insights-store';
export type Props = {
header: string;
content: ReactNode;
header?: string;
content?: ReactNode;
setHtmlContent?: boolean;
insightCloseId?: string; // set if you want to be able to close the box and not show it again
type?: 'default' | 'slim';
className?: string;
};
export function InsightsBox({ header, content, insightCloseId }: Props) {
export function InsightsBox({
header,
content,
insightCloseId,
type = 'default',
className,
}: Props) {
// allow to close the box and not show it again in local storage with zustand
const { addInsightIDClosed, isClosed } = useStore(insightStore);
const isInsightClosed = isClosed(insightCloseId);
@ -24,26 +32,42 @@ export function InsightsBox({ header, content, insightCloseId }: Props) {
}
return (
<div className="relative flex w-full gap-1 rounded-lg bg-gray-modern-3 p-4 text-sm th-highcontrast:bg-legacy-grey-3 th-dark:bg-legacy-grey-3">
<div className="shrink-0">
<div
className={clsx(
'relative flex w-full gap-1 rounded-lg bg-gray-modern-3 p-4 text-sm th-highcontrast:bg-legacy-grey-3 th-dark:bg-legacy-grey-3',
type === 'slim' && 'p-2',
className
)}
>
<div className="mt-0.5 shrink-0">
<Lightbulb className="h-4 text-warning-7 th-highcontrast:text-warning-6 th-dark:text-warning-6" />
</div>
<div>
<p
className={clsx(
// text-[0.9em] matches .form-horizontal .control-label font-size used in many labels in portainer
'mb-2 text-[0.9em] font-medium',
insightCloseId && 'pr-4'
)}
>
{header}
</p>
<div className="small">{content}</div>
{header && (
<p
className={clsx(
// text-[0.9em] matches .form-horizontal .control-label font-size used in many labels in portainer
'align-middle text-[0.9em] font-medium',
insightCloseId && 'pr-10',
content ? 'mb-2' : 'mb-0'
)}
>
{header}
</p>
)}
{content && (
<div className={clsx('small', !header && insightCloseId && 'pr-6')}>
{content}
</div>
)}
</div>
{insightCloseId && (
<Button
icon={X}
className="absolute top-2 right-2 flex !text-gray-7 hover:!text-gray-8 th-highcontrast:!text-gray-6 th-highcontrast:hover:!text-gray-5 th-dark:!text-gray-6 th-dark:hover:!text-gray-5"
className={clsx(
'absolute top-3 right-2 flex !text-gray-7 hover:!text-gray-8 th-highcontrast:!text-gray-6 th-highcontrast:hover:!text-gray-5 th-dark:!text-gray-6 th-dark:hover:!text-gray-5',
type === 'slim' && insightCloseId && 'top-1'
)}
color="link"
size="medium"
onClick={() => addInsightIDClosed(insightCloseId)}