feat(help): EE-2724 Context sensitive help (#7694)

pull/7721/head
congs 2022-09-22 13:39:36 +12:00 committed by GitHub
parent 1b0db4971f
commit f8b8d549fd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 464 additions and 13 deletions

View File

@ -504,7 +504,7 @@ angular.module('portainer.docker', ['portainer.app', reactModule]).config([
};
const dockerFeaturesConfiguration = {
name: 'docker.featuresConfiguration',
name: 'docker.host.featuresConfiguration',
url: '/feat-config',
views: {
'content@': {
@ -513,8 +513,18 @@ angular.module('portainer.docker', ['portainer.app', reactModule]).config([
},
};
const registries = {
name: 'docker.registries',
const swarmFeaturesConfiguration = {
name: 'docker.swarm.featuresConfiguration',
url: '/feat-config',
views: {
'content@': {
component: 'dockerFeaturesConfigurationView',
},
},
};
const dockerRegistries = {
name: 'docker.host.registries',
url: '/registries',
views: {
'content@': {
@ -523,8 +533,28 @@ angular.module('portainer.docker', ['portainer.app', reactModule]).config([
},
};
const registryAccess = {
name: 'docker.registries.access',
const swarmRegistries = {
name: 'docker.swarm.registries',
url: '/registries',
views: {
'content@': {
component: 'endpointRegistriesView',
},
},
};
const dockerRegistryAccess = {
name: 'docker.host.registries.access',
url: '/:id/access',
views: {
'content@': {
component: 'dockerRegistryAccessView',
},
},
};
const swarmRegistryAccess = {
name: 'docker.swarm.registries.access',
url: '/:id/access',
views: {
'content@': {
@ -577,7 +607,10 @@ angular.module('portainer.docker', ['portainer.app', reactModule]).config([
$stateRegistryProvider.register(volumeBrowse);
$stateRegistryProvider.register(volumeCreation);
$stateRegistryProvider.register(dockerFeaturesConfiguration);
$stateRegistryProvider.register(registries);
$stateRegistryProvider.register(registryAccess);
$stateRegistryProvider.register(swarmFeaturesConfiguration);
$stateRegistryProvider.register(dockerRegistries);
$stateRegistryProvider.register(swarmRegistries);
$stateRegistryProvider.register(dockerRegistryAccess);
$stateRegistryProvider.register(swarmRegistryAccess);
},
]);

View File

@ -1,5 +1,5 @@
<div ng-if="$ctrl.registry">
<page-header title="'Registry access'" breadcrumbs="[{label:'Registries', link:'docker.registries'}, $ctrl.registry.Name, 'Access management']"> </page-header>
<page-header title="'Registry access'" breadcrumbs="[{label:'Registries', link:$ctrl.registryTo,}, $ctrl.registry.Name, 'Access management']"> </page-header>
<registry-details registry="$ctrl.registry"></registry-details>

View File

@ -45,6 +45,10 @@ class DockerRegistryAccessController {
$onInit() {
return this.$async(async () => {
this.Authentication.redirectIfUnauthorized(['PortainerRegistryUpdateAccess']);
this.registryTo = window.location.hash.match(/#!\/\d+\/docker\/swarm\/registries/) ? 'docker.swarm.registries' : 'docker.host.registries';
try {
this.state = {
viewReady: false,

View File

@ -27,7 +27,7 @@ function RegistriesDatatableController($scope, $controller, $state, Authenticati
this.endpointType === PortainerEndpointTypes.AgentOnDockerEnvironment ||
this.endpointType === PortainerEndpointTypes.EdgeAgentOnDockerEnvironment
) {
$state.go('docker.registries.registry', { id: item.Id });
$state.go('docker.host.registries.registry', { id: item.Id });
} else {
$state.go('portainer.registries.registry', { id: item.Id });
}
@ -41,7 +41,11 @@ function RegistriesDatatableController($scope, $controller, $state, Authenticati
) {
$state.go('kubernetes.registries.access', { id: item.Id });
} else {
$state.go('docker.registries.access', { id: item.Id });
if (window.location.hash.endsWith('/docker/swarm/registries')) {
$state.go('docker.swarm.registries.access', { id: item.Id });
} else {
$state.go('docker.host.registries.access', { id: item.Id });
}
}
};

View File

@ -0,0 +1,5 @@
.menu-icon {
background: var(--user-menu-icon-color);
cursor: pointer;
flex-shrink: 0;
}

View File

@ -0,0 +1,27 @@
import { HelpCircle } from 'react-feather';
import clsx from 'clsx';
import { getDocURL } from '@@/PageHeader/ContextHelp/docURLs';
import './ContextHelp.css';
export function ContextHelp() {
function onHelpClick() {
const docURL = getDocURL();
window.open(docURL, '_blank');
}
return (
<div
className={clsx(
'menu-icon',
'icon-badge text-lg !p-2 mr-1',
'text-gray-8',
'th-dark:text-gray-warm-7'
)}
title="Help"
>
<HelpCircle className="feather" onClick={onHelpClick} />
</div>
);
}

View File

@ -0,0 +1,365 @@
const docURLs = [
{
desc: 'Home',
docURL: 'https://docs.portainer.io/user/home',
locationRegex: /#!\/home/,
exmaples: ['#!/home'],
},
{
desc: 'Docker or Swarm / Dashboard',
docURL: 'https://docs.portainer.io/user/docker/dashboard',
locationRegex: /#!\/\d+\/docker\/dashboard/,
exmaples: ['#!/10/docker/dashboard'],
},
{
desc: 'Docker or Swarm / Custom Templates',
docURL: 'https://docs.portainer.io/user/docker/templates/custom',
locationRegex: /#!\/\d+\/docker\/templates\/custom/,
examples: ['#!/10/docker/templates/custom', '#!/10/docker/templates/custom/new?fileContent=&type=', '#!/10/docker/templates/custom/1'],
},
{
desc: 'Docker or Swarm / App Templates',
docURL: 'https://docs.portainer.io/user/docker/templates',
locationRegex: /#!\/\d+\/docker\/templates/,
examples: ['#!/10/docker/templates'],
},
{
desc: 'Docker or Swarm / Stacks',
docURL: 'https://docs.portainer.io/user/docker/stacks',
locationRegex: /#!\/\d+\/docker\/stacks/,
examples: ['#!/10/docker/stacks', '#!/10/docker/stacks/newstack', '#!/10/docker/stacks/s4?id=3&type=1&regular=true&external=false&orphaned=false'],
},
{
desc: 'Swarm / Services',
docURL: 'https://docs.portainer.io/user/docker/services',
locationRegex: /#!\/\d+\/docker\/(services|tasks)/,
examples: [
'#!/10/docker/services',
'#!/10/docker/services/zqp46vzoz5nnf39m6c518nlt8',
'#!/10/docker/services/zqp46vzoz5nnf39m6c518nlt8/logs',
'#!/10/docker/tasks/yyll0peo7ack4uaw2wom3nxso',
'#!/10/docker/tasks/yyll0peo7ack4uaw2wom3nxso/logs',
],
},
{
desc: 'Docker or Swarm / Containers',
docURL: 'https://docs.portainer.io/user/docker/containers',
locationRegex: /#!\/\d+\/docker\/containers/,
examples: [
'#!/10/docker/containers',
'#!/10/docker/containers/new',
'#!/10/docker/containers/new?from=49ff4ae03d10c57fe375f6968c48a6169a9852a6bfbb5137cd30c615d58188c1',
'#!/10/docker/containers/49ff4ae03d10c57fe375f6968c48a6169a9852a6bfbb5137cd30c615d58188c1',
'#!/10/docker/containers/49ff4ae03d10c57fe375f6968c48a6169a9852a6bfbb5137cd30c615d58188c1/logs',
'#!/10/docker/containers/49ff4ae03d10c57fe375f6968c48a6169a9852a6bfbb5137cd30c615d58188c1/inspect',
'#!/10/docker/containers/49ff4ae03d10c57fe375f6968c48a6169a9852a6bfbb5137cd30c615d58188c1/stats',
],
},
{
desc: 'Docker or Swarm / Images',
docURL: 'https://docs.portainer.io/user/docker/images',
locationRegex: /#!\/\d+\/docker\/images/,
examples: ['#!/10/docker/images', '#!/10/docker/images/build', '#!/10/docker/images/sha256:feb5d9fea6a5e9606aa995e879d862b825965ba48de054caab5ef356dc6b3412'],
},
{
desc: 'Docker or Swarm / Networks',
docURL: 'https://docs.portainer.io/user/docker/networks',
locationRegex: /#!\/\d+\/docker\/networks/,
examples: ['#!/10/docker/networks', '#!/10/docker/networks/new', '#!/10/docker/networks/db5732ff4a2c6df70a18530dba6abd8625f8e94c5fc5daabbcbab07377ee1044'],
},
{
desc: 'Docker or Swarm / Volumes',
docURL: 'https://docs.portainer.io/user/docker/volumes',
locationRegex: /#!\/\d+\/docker\/volumes/,
examples: ['#!/10/docker/volumes', '#!/10/docker/volumes/new', '#!/10/docker/volumes/153b46162f5bab9a7c9d2c8e1675115fcedd4c0ccdf5834159400750fa6b794c'],
},
{
desc: 'Swarm / Configs',
docURL: 'https://docs.portainer.io/user/docker/configs',
locationRegex: /#!\/\d+\/docker\/configs/,
examples: ['#!/10/docker/configs', '#!/10/docker/configs/new', '#!/10/docker/configs/azd0xc805l298jrgnadbnnzyv'],
},
{
desc: 'Swarm / Secrets',
docURL: 'https://docs.portainer.io/user/docker/secrets',
locationRegex: /#!\/\d+\/docker\/secrets/,
examples: ['#!/10/docker/secrets', '#!/10/docker/secrets/new', '#!/10/docker/secrets/tsoeeh7ln7g54g5qkk67eg4xe'],
},
{
desc: 'Docker or Swarm / Swarm / Cluster visualizer',
docURL: 'https://docs.portainer.io/user/docker/swarm/cluster-visualizer',
locationRegex: /#!\/\d+\/docker\/swarm\/visualizer/,
examples: ['#!/10/docker/swarm/visualizer'],
},
{
desc: 'Docker or Swarm / Swarm / Set up',
docURL: 'https://docs.portainer.io/user/docker/swarm/setup',
locationRegex: /#!\/\d+\/docker\/swarm\/feat-config/,
examples: ['#!/10/docker/feat-config'],
},
{
desc: 'Swarm / Swarm / Registries',
docURL: 'https://docs.portainer.io/user/docker/swarm/registries',
locationRegex: /#!\/\d+\/docker\/swarm\/registries/,
examples: ['#!/10/docker/registries'],
},
{
desc: 'Swarm / Swarm',
docURL: 'https://docs.portainer.io/user/docker/swarm',
locationRegex: /#!\/\d+\/docker\/(swarm|nodes)/,
examples: ['#!/10/docker/swarm', '#!/10/docker/nodes/nd694yepzgms1j8y7kv3lpcc3'],
},
{
desc: 'Docker / Events',
docURL: 'https://docs.portainer.io/user/docker/events',
locationRegex: /#!\/\d+\/docker\/events/,
examples: ['#!/10/docker/events'],
},
{
desc: 'Docker / Host / Registries',
docURL: 'https://docs.portainer.io/user/docker/host/registries',
locationRegex: /#!\/\d+\/docker\/host\/registries/,
examples: ['#!/10/docker/registries'],
},
{
desc: 'Docker / Host / Setup',
docURL: 'https://docs.portainer.io/user/docker/host/setup',
locationRegex: /#!\/\d+\/docker\/host\/feat-config/,
examples: ['#!/10/docker/feat-config'],
},
{
desc: 'Docker / Host',
docURL: 'https://docs.portainer.io/user/docker/host',
locationRegex: /#!\/\d+\/docker\/host/,
examples: ['#!/10/docker/host'],
},
{
desc: 'Kubernetes / Dashboard',
docURL: 'https://docs.portainer.io/user/kubernetes/dashboard',
locationRegex: /#!\/\d+\/kubernetes\/dashboard/,
examples: ['#!/1/kubernetes/dashboard'],
},
{
desc: 'Kubernetes / Custom Templates',
docURL: 'https://docs.portainer.io/user/kubernetes/templates',
locationRegex: /#!\/\d+\/kubernetes\/templates\/custom/,
examples: ['#!/1/kubernetes/templates/custom', '#!/1/kubernetes/templates/custom/new?fileContent='],
},
{
desc: 'Kubernetes / Namespaces',
docURL: 'https://docs.portainer.io/user/kubernetes/namespaces',
locationRegex: /#!\/\d+\/kubernetes\/pools/,
examples: ['#!/1/kubernetes/pools', '#!/1/kubernetes/pools/new', '#!/1/kubernetes/deploy?templateId=', '#!/1/kubernetes/pools/default'],
},
{
desc: 'Kubernetes / Helm',
docURL: 'https://docs.portainer.io/user/kubernetes/helm',
locationRegex: /#!\/\d+\/kubernetes\/templates\/helm/,
examples: ['#!/1/kubernetes/templates/helm'],
},
{
desc: 'Kubernetes / Applications',
docURL: 'https://docs.portainer.io/user/kubernetes/applications',
locationRegex: /#!\/\d+\/kubernetes\/applications/,
examples: ['#!/1/kubernetes/applications', '#!/1/kubernetes/applications/new', '#!/1/kubernetes/deploy?templateId=', '#!/1/kubernetes/applications/metallb-system/controller'],
},
{
desc: 'Kubernetes / ConfigMaps & Secrets',
docURL: 'https://docs.portainer.io/user/kubernetes/configurations',
locationRegex: /#!\/\d+\/kubernetes\/configurations/,
examples: ['#!/1/kubernetes/configurations', '#!/1/kubernetes/configurations/new', '#!/1/kubernetes/configurations/metallb-system/config'],
},
{
desc: 'Kubernetes / Volumes',
docURL: 'https://docs.portainer.io/user/kubernetes/volumes',
locationRegex: /#!\/\d+\/kubernetes\/volumes/,
examples: ['#!/1/kubernetes/volumes'],
},
{
desc: 'Kubernetes / Cluster',
docURL: 'https://docs.portainer.io/user/kubernetes/cluster',
locationRegex: /#!\/\d+\/kubernetes\/cluster/,
examples: ['#!/1/kubernetes/cluster', '#!/1/kubernetes/cluster/ip-10-138-11-102', '#!/1/kubernetes/cluster/ip-10-138-11-102/stats'],
},
{
desc: 'Kubernetes / Cluster / Set up',
docURL: 'https://docs.portainer.io/user/kubernetes/cluster/setup',
locationRegex: /#!\/\d+\/kubernetes\/cluster\/configure/,
examples: ['#!/1/kubernetes/cluster/configure'],
},
{
desc: 'Kubernetes / Cluster / Security constraints',
docURL: 'https://docs.portainer.io/user/kubernetes/cluster/security',
locationRegex: /#!\/\d+\/kubernetes\/cluster\/securityConstraint/,
examples: ['#!/1/kubernetes/cluster/securityConstraint'],
},
{
desc: 'Kubernetes / Cluster / Registries',
docURL: 'https://docs.portainer.io/user/kubernetes/cluster/registries',
locationRegex: /#!\/\d+\/kubernetes\/registries/,
examples: ['#!/1/kubernetes/registries'],
},
{
desc: 'Azure ACI / Dashboard',
docURL: 'https://docs.portainer.io/user/aci/dashboard',
locationRegex: /#!\/\d+\/azure\/dashboard/,
examples: ['#!/26/azure/dashboard'],
},
{
desc: 'Azure ACI / Container instances',
docURL: 'https://docs.portainer.io/user/aci/containers',
locationRegex: /#!\/\d+\/azure\/containerinstances/,
examples: ['#!/26/azure/containerinstances'],
},
{
desc: 'Edge Compute / Edge Devices',
docURL: 'https://docs.portainer.io/user/edge/devices',
locationRegex: /#!\/edge\/devices/,
examples: ['#!/edge/devices', '#!/edge/devices/waiting-room'],
},
{
desc: 'Edge Compute / Edge Groups',
docURL: 'https://docs.portainer.io/user/edge/groups',
locationRegex: /#!\/edge\/groups/,
examples: ['#!/edge/groups', '#!/edge/groups/new'],
},
{
desc: 'Edge Compute / Edge Stacks ',
docURL: 'https://docs.portainer.io/user/edge/stacks',
locationRegex: /#!\/edge\/stacks/,
examples: ['#!/edge/stacks', '#!/edge/stacks/new'],
},
{
desc: 'Edge Compute / Edge Jobs',
docURL: 'https://docs.portainer.io/user/edge/jobs',
locationRegex: /#!\/edge\/jobs/,
examples: ['#!/edge/jobs', '#!/edge/jobs/new'],
},
{
desc: 'Nomad / Dashboard',
docURL: 'https://docs.portainer.io/user/nomad/dashboard',
locationRegex: /#!\/\d+\/nomad\/dashboard/,
examples: ['#!/2/nomad/dashboard'],
},
{
desc: 'Nomad / Nomad Jobs',
docURL: 'https://docs.portainer.io/user/nomad/jobs',
locationRegex: /#!\/\d+\/nomad\/jobs/,
examples: [
'#!/2/nomad/jobs',
'#!/2/nomad/jobs/portainer-agent/tasks/portainer-agent/allocations/acdbf08e-34af-9b8a-cc84-7dc202bf1fcf/events?namespace=default',
'#!/2/nomad/jobs/portainer-agent/tasks/portainer-agent/allocations/acdbf08e-34af-9b8a-cc84-7dc202bf1fcf/logs?namespace=default',
],
},
{
desc: 'Account Settings',
docURL: 'https://docs.portainer.io/user/account-settings',
locationRegex: /#!\/account/,
examples: ['#!/account', '#!/account/tokens/new'],
},
{
desc: 'Settings / Users',
docURL: 'https://docs.portainer.io/admin/users',
locationRegex: /#!\/users/,
examples: ['#!/users', '#!/users/1'],
},
{
desc: 'Settings / Users / Teams',
docURL: 'https://docs.portainer.io/admin/users/teams',
locationRegex: /#!\/teams/,
examples: ['#!/teams', '#!/teams/1'],
},
{
desc: 'Settings / Users / Roles',
docURL: 'https://docs.portainer.io/admin/users/roles',
locationRegex: /#!\/roles/,
examples: ['#!/roles'],
},
{
desc: 'Settings / Environments',
docURL: 'https://docs.portainer.io/admin/environments',
locationRegex: /#!\/endpoints/,
examples: ['#!/endpoints', '#!/endpoints/10', '#!/endpoints/10/access'],
},
{
desc: 'Settings / Environments / Groups',
docURL: 'https://docs.portainer.io/admin/environments/groups',
locationRegex: /#!\/groups/,
examples: ['#!/groups', '#!/groups/new', '#!/groups/3', '#!/groups/3/access'],
},
{
desc: 'Settings / Environments / Tags',
docURL: 'https://docs.portainer.io/admin/environments/tags',
locationRegex: /#!\/tags/,
examples: ['#!/tags'],
},
{
desc: 'Settings / Registries',
docURL: 'https://docs.portainer.io/admin/registries',
locationRegex: /#!\/registries/,
examples: [
'#!/registries',
'#!/registries/new',
'#!/registries/1',
'#!/registries/1/repositories',
'#!/registries/1/configure',
'#!/registries/5/portainer.demo~2Fportainerregistrytesting~2Falpine',
'#!/registries/5/portainer.demo~2Fportainerregistrytesting~2Falpine/jfadelhaye',
],
},
{
desc: 'Settings / Licenses',
docURL: 'https://docs.portainer.io/admin/licenses',
locationRegex: /#!\/licenses/,
examples: ['#!/licenses', '#!/licenses/licenses/new'],
},
{
desc: 'Settings / Authentication logs',
docURL: 'https://docs.portainer.io/admin/logs',
locationRegex: /#!\/auth-logs/,
examples: ['#!/auth-logs'],
},
{
desc: 'Settings / Authentication logs / Activity logs',
docURL: 'https://docs.portainer.io/admin/logs/activity',
locationRegex: /#!\/activity-logs/,
examples: ['#!/activity-logs'],
},
{
desc: 'Settings / Settings / Authentication',
docURL: 'https://docs.portainer.io/admin/settings/authentication',
locationRegex: /#!\/settings\/auth/,
examples: ['#!/settings/auth'],
},
{
desc: 'Settings / Settings / Cloud settings',
docURL: 'https://docs.portainer.io/admin/settings/cloud',
locationRegex: /#!\/settings\/cloud/,
examples: ['#!/settings/cloud', '#!/settings/cloud/credentials/new', '#!/settings/cloud/credentials/1'],
},
{
desc: 'Settings / Settings / Edge Compute',
docURL: 'https://docs.portainer.io/admin/settings/edge',
locationRegex: /#!\/settings\/edge/,
examples: ['#!/settings/edge'],
},
{
desc: 'Settings / Settings',
docURL: 'https://docs.portainer.io/admin/settings',
locationRegex: /#!\/settings/,
examples: ['#!/settings'],
},
];
const DEFAULT_DOC_URL = 'https://docs.portainer.io';
export function getDocURL() {
const hash = window.location.hash;
for (let i = 0; i < docURLs.length; i += 1) {
const docURL = docURLs[i];
if (hash.match(docURL.locationRegex)) {
return docURL.docURL;
}
}
return DEFAULT_DOC_URL;
}

View File

@ -0,0 +1 @@
export { ContextHelp } from './ContextHelp';

View File

@ -1,5 +1,7 @@
import { PropsWithChildren } from 'react';
import { ContextHelp } from '@@/PageHeader/ContextHelp';
import { useHeaderContext } from './HeaderContainer';
import { UserMenu } from './UserMenu';
@ -18,7 +20,10 @@ export function HeaderTitle({ title, children }: PropsWithChildren<Props>) {
</div>
{children && <span>{children}</span>}
</div>
{!window.ddExtension && <UserMenu />}
<div className="flex items-center gap-4">
<ContextHelp />
{!window.ddExtension && <UserMenu />}
</div>
</div>
);
}

View File

@ -67,6 +67,13 @@ export function DockerSidebar({ environmentId, environment }: Props) {
dataCy: 'portainerSidebar-host',
};
const featSubMenuTo = isSwarmManager
? 'docker.swarm.featuresConfiguration'
: 'docker.host.featuresConfiguration';
const registrySubMenuTo = isSwarmManager
? 'docker.swarm.registries'
: 'docker.host.registries';
return (
<>
<DashboardLink
@ -183,14 +190,14 @@ export function DockerSidebar({ environmentId, environment }: Props) {
environmentId={environmentId}
>
<SidebarItem
to="docker.featuresConfiguration"
to={featSubMenuTo}
params={{ endpointId: environmentId }}
label="Setup"
/>
</Authorized>
<SidebarItem
to="docker.registries"
to={registrySubMenuTo}
params={{ endpointId: environmentId }}
label="Registries"
/>