diff --git a/api/http/handler/system/version.go b/api/http/handler/system/version.go index 5c0324882..50ad2f6af 100644 --- a/api/http/handler/system/version.go +++ b/api/http/handler/system/version.go @@ -22,6 +22,7 @@ type versionResponse struct { LatestVersion string `json:"LatestVersion" example:"2.0.0"` ServerVersion string + VersionSupport string `json:"VersionSupport" example:"STS/LTS"` ServerEdition string `json:"ServerEdition" example:"CE/EE"` DatabaseVersion string Build build.BuildInfo @@ -47,6 +48,7 @@ func (handler *Handler) version(w http.ResponseWriter, r *http.Request) *httperr result := &versionResponse{ ServerVersion: portainer.APIVersion, + VersionSupport: portainer.APIVersionSupport, DatabaseVersion: portainer.APIVersion, ServerEdition: portainer.Edition.GetEditionLabel(), Build: build.GetBuildInfo(), diff --git a/api/portainer.go b/api/portainer.go index 0b14d6462..99530febe 100644 --- a/api/portainer.go +++ b/api/portainer.go @@ -1631,6 +1631,8 @@ type ( const ( // APIVersion is the version number of the Portainer API APIVersion = "2.24.0" + // Support annotation for the API version ("STS" for Short-Term Support or "LTS" for Long-Term Support) + APIVersionSupport = "STS" // Edition is what this edition of Portainer is called Edition = PortainerCE // ComposeSyntaxMaxVersion is a maximum supported version of the docker compose syntax diff --git a/app/react/components/BoxSelector/BoxOption.tsx b/app/react/components/BoxSelector/BoxOption.tsx index 6b4485b21..d3ca5de10 100644 --- a/app/react/components/BoxSelector/BoxOption.tsx +++ b/app/react/components/BoxSelector/BoxOption.tsx @@ -1,6 +1,6 @@ import clsx from 'clsx'; import { PropsWithChildren } from 'react'; -import type { Icon } from 'lucide-react'; +import type { LucideIcon } from 'lucide-react'; import { TooltipWithChildren } from '@@/Tip/TooltipWithChildren'; @@ -16,7 +16,7 @@ interface Props { tooltip?: string; className?: string; type?: 'radio' | 'checkbox'; - checkIcon: Icon; + checkIcon: LucideIcon; } export function BoxOption({ diff --git a/app/react/components/BoxSelector/BoxSelectorItem.tsx b/app/react/components/BoxSelector/BoxSelectorItem.tsx index 9cd2d771d..d8e62bf51 100644 --- a/app/react/components/BoxSelector/BoxSelectorItem.tsx +++ b/app/react/components/BoxSelector/BoxSelectorItem.tsx @@ -1,5 +1,5 @@ import clsx from 'clsx'; -import { Icon as ReactFeatherComponentType, Check } from 'lucide-react'; +import { type LucideIcon, Check } from 'lucide-react'; import { Fragment } from 'react'; import { Icon } from '@/react/components/Icon'; @@ -22,7 +22,7 @@ type Props = { isSelected(value: T): boolean; type?: 'radio' | 'checkbox'; slim?: boolean; - checkIcon?: ReactFeatherComponentType; + checkIcon?: LucideIcon; }; export function BoxSelectorItem({ diff --git a/app/react/components/HelpLink/HelpLink.tsx b/app/react/components/HelpLink/HelpLink.tsx index 5f85b14ae..8596baf4d 100644 --- a/app/react/components/HelpLink/HelpLink.tsx +++ b/app/react/components/HelpLink/HelpLink.tsx @@ -1,6 +1,6 @@ import { ReactNode } from 'react'; -import { useDocsUrl } from '../PageHeader/ContextHelp/ContextHelp'; +import { useDocsUrl } from '@@/PageHeader/ContextHelp'; type HelpLinkProps = { docLink: string; diff --git a/app/react/components/PageHeader/AskAILink.tsx b/app/react/components/PageHeader/AskAILink.tsx new file mode 100644 index 000000000..7fc16a53a --- /dev/null +++ b/app/react/components/PageHeader/AskAILink.tsx @@ -0,0 +1,29 @@ +import { BotMessageSquare } from 'lucide-react'; +import clsx from 'clsx'; + +import headerStyles from './HeaderTitle.module.css'; + +const docsUrl = 'https://www.portainer.io/ask-the-ai'; + +export function AskAILink() { + return ( +
+ + + +
+ ); +} diff --git a/app/react/components/PageHeader/ContextHelp/ContextHelp.tsx b/app/react/components/PageHeader/ContextHelp.tsx similarity index 89% rename from app/react/components/PageHeader/ContextHelp/ContextHelp.tsx rename to app/react/components/PageHeader/ContextHelp.tsx index dbfc4203d..6f19453f0 100644 --- a/app/react/components/PageHeader/ContextHelp/ContextHelp.tsx +++ b/app/react/components/PageHeader/ContextHelp.tsx @@ -4,8 +4,7 @@ import { useCurrentStateAndParams } from '@uirouter/react'; import { useSystemVersion } from '@/react/portainer/system/useSystemVersion'; -import headerStyles from '../HeaderTitle.module.css'; -import './ContextHelp.css'; +import headerStyles from './HeaderTitle.module.css'; export function ContextHelp() { const docsUrl = useDocsUrl(); @@ -18,12 +17,11 @@ export function ContextHelp() { color="none" className={clsx( headerStyles.menuIcon, - 'menu-icon', - 'icon-badge mr-1 !p-2 text-lg', + 'icon-badge mr-1 !p-2 text-lg cursor-pointer', 'text-gray-8', 'th-dark:text-gray-warm-7' )} - title="Help" + title="Documentation" rel="noreferrer" data-cy="context-help-button" > diff --git a/app/react/components/PageHeader/ContextHelp/ContextHelp.css b/app/react/components/PageHeader/ContextHelp/ContextHelp.css deleted file mode 100644 index 7e36e1211..000000000 --- a/app/react/components/PageHeader/ContextHelp/ContextHelp.css +++ /dev/null @@ -1,5 +0,0 @@ -.menu-icon { - background: var(--user-menu-icon-color); - cursor: pointer; - flex-shrink: 0; -} diff --git a/app/react/components/PageHeader/ContextHelp/index.ts b/app/react/components/PageHeader/ContextHelp/index.ts deleted file mode 100644 index 6afd42e8b..000000000 --- a/app/react/components/PageHeader/ContextHelp/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { ContextHelp } from './ContextHelp'; diff --git a/app/react/components/PageHeader/HeaderTitle.module.css b/app/react/components/PageHeader/HeaderTitle.module.css index 62b04d651..eeae7fb48 100644 --- a/app/react/components/PageHeader/HeaderTitle.module.css +++ b/app/react/components/PageHeader/HeaderTitle.module.css @@ -10,6 +10,12 @@ .menu-icon { background: var(--user-menu-icon-color); position: relative; + flex-shrink: 0; +} + +.menu-icon:hover { + /* keep the links and button icon colors consistent on hover */ + @apply text-gray-8 th-dark:text-gray-warm-7; } .menu-list { diff --git a/app/react/components/PageHeader/HeaderTitle.tsx b/app/react/components/PageHeader/HeaderTitle.tsx index f8f457673..fa476dea1 100644 --- a/app/react/components/PageHeader/HeaderTitle.tsx +++ b/app/react/components/PageHeader/HeaderTitle.tsx @@ -5,6 +5,7 @@ import { ContextHelp } from '@@/PageHeader/ContextHelp'; import { useHeaderContext } from './HeaderContainer'; import { NotificationsMenu } from './NotificationsMenu'; import { UserMenu } from './UserMenu'; +import { AskAILink } from './AskAILink'; interface Props { title: string; @@ -25,6 +26,7 @@ export function HeaderTitle({ title, children }: PropsWithChildren) { {children && <>{children}}
+ {!window.ddExtension && } diff --git a/app/react/components/form-components/EnvironmentVariablesFieldset/StackEnvironmentVariablesPanel.tsx b/app/react/components/form-components/EnvironmentVariablesFieldset/StackEnvironmentVariablesPanel.tsx index 0566ef2e1..36c412718 100644 --- a/app/react/components/form-components/EnvironmentVariablesFieldset/StackEnvironmentVariablesPanel.tsx +++ b/app/react/components/form-components/EnvironmentVariablesFieldset/StackEnvironmentVariablesPanel.tsx @@ -1,7 +1,7 @@ import { ComponentProps } from 'react'; import { Alert } from '@@/Alert'; -import { useDocsUrl } from '@@/PageHeader/ContextHelp/ContextHelp'; +import { useDocsUrl } from '@@/PageHeader/ContextHelp'; import { EnvironmentVariablesFieldset } from './EnvironmentVariablesFieldset'; import { EnvironmentVariablesPanel } from './EnvironmentVariablesPanel'; diff --git a/app/react/edge/edge-stacks/ListView/EdgeStacksDatatable/EdgeStacksStatus.tsx b/app/react/edge/edge-stacks/ListView/EdgeStacksDatatable/EdgeStacksStatus.tsx index ef4661b6d..280ee8d77 100644 --- a/app/react/edge/edge-stacks/ListView/EdgeStacksDatatable/EdgeStacksStatus.tsx +++ b/app/react/edge/edge-stacks/ListView/EdgeStacksDatatable/EdgeStacksStatus.tsx @@ -2,7 +2,7 @@ import _ from 'lodash'; import { AlertTriangle, CheckCircle, - type Icon as IconType, + type LucideIcon, Loader2, XCircle, MinusCircle, @@ -51,7 +51,7 @@ function getStatus( hasOldVersion: boolean ): { label: string; - icon?: IconType; + icon?: LucideIcon; spin?: boolean; mode?: IconMode; tooltip?: string; diff --git a/app/react/portainer/environments/wizard/EnvironmentsCreationView/WizardKubernetes/KubeConfigTeaserForm.tsx b/app/react/portainer/environments/wizard/EnvironmentsCreationView/WizardKubernetes/KubeConfigTeaserForm.tsx index 8b534b254..ff40ec08e 100644 --- a/app/react/portainer/environments/wizard/EnvironmentsCreationView/WizardKubernetes/KubeConfigTeaserForm.tsx +++ b/app/react/portainer/environments/wizard/EnvironmentsCreationView/WizardKubernetes/KubeConfigTeaserForm.tsx @@ -7,7 +7,7 @@ import { FormSectionTitle } from '@@/form-components/FormSectionTitle'; import { Input } from '@@/form-components/Input'; import { Button } from '@@/buttons'; import { TextTip } from '@@/Tip/TextTip'; -import { useDocsUrl } from '@@/PageHeader/ContextHelp/ContextHelp'; +import { useDocsUrl } from '@@/PageHeader/ContextHelp'; const initialValues = { kubeConfig: '', diff --git a/app/react/portainer/gitops/RelativePathFieldset/RelativePathFieldset.tsx b/app/react/portainer/gitops/RelativePathFieldset/RelativePathFieldset.tsx index 2bc20d575..5f84d49aa 100644 --- a/app/react/portainer/gitops/RelativePathFieldset/RelativePathFieldset.tsx +++ b/app/react/portainer/gitops/RelativePathFieldset/RelativePathFieldset.tsx @@ -9,7 +9,7 @@ import { SwitchField } from '@@/form-components/SwitchField'; import { TextTip } from '@@/Tip/TextTip'; import { FormControl } from '@@/form-components/FormControl'; import { Input, Select } from '@@/form-components/Input'; -import { useDocsUrl } from '@@/PageHeader/ContextHelp/ContextHelp'; +import { useDocsUrl } from '@@/PageHeader/ContextHelp'; import { RelativePathModel, getPerDevConfigsFilterType } from './types'; diff --git a/app/react/portainer/settings/SettingsView/ApplicationSettingsPanel/EnableTelemetryField.tsx b/app/react/portainer/settings/SettingsView/ApplicationSettingsPanel/EnableTelemetryField.tsx index fcf215bac..a71d4791b 100644 --- a/app/react/portainer/settings/SettingsView/ApplicationSettingsPanel/EnableTelemetryField.tsx +++ b/app/react/portainer/settings/SettingsView/ApplicationSettingsPanel/EnableTelemetryField.tsx @@ -1,8 +1,10 @@ import { useField } from 'formik'; import { SwitchField } from '@@/form-components/SwitchField'; +import { useDocsUrl } from '@@/PageHeader/ContextHelp'; export function EnableTelemetryField() { + const privacyPolicy = useDocsUrl('/in-app-analytics-and-privacy-policy'); const [{ value }, , { setValue }] = useField('enableTelemetry'); return ( @@ -20,11 +22,7 @@ export function EnableTelemetryField() {
You can find more information about this in our{' '} - + privacy policy . diff --git a/app/react/portainer/settings/SettingsView/ApplicationSettingsPanel/TemplatesUrlSection.tsx b/app/react/portainer/settings/SettingsView/ApplicationSettingsPanel/TemplatesUrlSection.tsx index 949cd786a..1eb7a1205 100644 --- a/app/react/portainer/settings/SettingsView/ApplicationSettingsPanel/TemplatesUrlSection.tsx +++ b/app/react/portainer/settings/SettingsView/ApplicationSettingsPanel/TemplatesUrlSection.tsx @@ -3,7 +3,7 @@ import { useField, Field } from 'formik'; import { FormControl } from '@@/form-components/FormControl'; import { FormSection } from '@@/form-components/FormSection'; import { Input } from '@@/form-components/Input'; -import { useDocsUrl } from '@@/PageHeader/ContextHelp/ContextHelp'; +import { useDocsUrl } from '@@/PageHeader/ContextHelp'; // this value is taken from https://github.com/portainer/portainer/blob/develop/api/portainer.go#L1628 const DEFAULT_URL = diff --git a/app/react/portainer/system/useSystemVersion.ts b/app/react/portainer/system/useSystemVersion.ts index c1ef47330..ff32af7a1 100644 --- a/app/react/portainer/system/useSystemVersion.ts +++ b/app/react/portainer/system/useSystemVersion.ts @@ -13,6 +13,7 @@ export interface VersionResponse { // The latest version available LatestVersion: string; ServerVersion: string; + VersionSupport: 'STS' | 'LTS'; DatabaseVersion: string; Build: { BuildNumber: string; diff --git a/app/react/sidebar/Footer/BuildInfoModal.tsx b/app/react/sidebar/Footer/BuildInfoModal.tsx index 3839e5707..510e86653 100644 --- a/app/react/sidebar/Footer/BuildInfoModal.tsx +++ b/app/react/sidebar/Footer/BuildInfoModal.tsx @@ -23,22 +23,25 @@ import styles from './Footer.module.css'; export function BuildInfoModalButton() { const [isBuildInfoVisible, setIsBuildInfoVisible] = useState(false); const statusQuery = useSystemStatus(); + const versionQuery = useSystemVersion(); - if (!statusQuery.data) { + if (!statusQuery.data || !versionQuery.data) { return null; } const { Version } = statusQuery.data; + const { VersionSupport } = versionQuery.data; return ( <> {isBuildInfoVisible && ( setIsBuildInfoVisible(false)} /> @@ -57,8 +60,14 @@ function BuildInfoModal({ closeModal }: { closeModal: () => void }) { } const { Edition } = statusQuery.data; - const { ServerVersion, DatabaseVersion, Build, Dependencies, Runtime } = - versionQuery.data; + const { + ServerVersion, + DatabaseVersion, + Build, + Dependencies, + Runtime, + VersionSupport, + } = versionQuery.data; return ( @@ -69,13 +78,13 @@ function BuildInfoModal({ closeModal }: { closeModal: () => void }) { - + - Server Version: {ServerVersion} + Server Version: {ServerVersion} ({VersionSupport}) - + Database Version: {DatabaseVersion} @@ -83,13 +92,13 @@ function BuildInfoModal({ closeModal }: { closeModal: () => void }) { - + CI Build Number: {Build.BuildNumber} - + Image Tag: {Build.ImageTag} @@ -97,8 +106,10 @@ function BuildInfoModal({ closeModal }: { closeModal: () => void }) { - - Git Commit: {Build.GitCommit} + + + Git Commit: {Build.GitCommit} + diff --git a/app/react/sidebar/SettingsSidebar.tsx b/app/react/sidebar/SettingsSidebar.tsx index 4c8fd1ad1..10689cd21 100644 --- a/app/react/sidebar/SettingsSidebar.tsx +++ b/app/react/sidebar/SettingsSidebar.tsx @@ -205,7 +205,7 @@ export function SettingsSidebar({ isPureAdmin, isAdmin, isTeamLeader }: Props) { data-cy="portainerSidebar-edgeCompute" /> - + - Help / About + Get Help diff --git a/app/react/sidebar/SidebarItem/SidebarItem.stories.tsx b/app/react/sidebar/SidebarItem/SidebarItem.stories.tsx index 7aa73d066..d5289eabd 100644 --- a/app/react/sidebar/SidebarItem/SidebarItem.stories.tsx +++ b/app/react/sidebar/SidebarItem/SidebarItem.stories.tsx @@ -1,5 +1,5 @@ import { Meta, Story } from '@storybook/react'; -import { Clock, Icon } from 'lucide-react'; +import { Clock, type LucideIcon } from 'lucide-react'; import { SidebarItem } from '.'; @@ -10,7 +10,7 @@ const meta: Meta = { export default meta; interface StoryProps { - icon?: Icon; + icon?: LucideIcon; label: string; } diff --git a/app/react/sidebar/SidebarItem/SidebarItem.tsx b/app/react/sidebar/SidebarItem/SidebarItem.tsx index a2246a045..96fd5cbc8 100644 --- a/app/react/sidebar/SidebarItem/SidebarItem.tsx +++ b/app/react/sidebar/SidebarItem/SidebarItem.tsx @@ -1,4 +1,4 @@ -import { Icon as IconTest } from 'lucide-react'; +import { type LucideIcon } from 'lucide-react'; import clsx from 'clsx'; import { MouseEventHandler, PropsWithChildren } from 'react'; @@ -13,7 +13,7 @@ import { SidebarTooltip } from './SidebarTooltip'; import { useSidebarSrefActive } from './useSidebarSrefActive'; interface Props extends AutomationTestingProps { - icon?: IconTest; + icon?: LucideIcon; to: string; params?: object; label: string; diff --git a/package.json b/package.json index 033f17ad9..518ec4862 100644 --- a/package.json +++ b/package.json @@ -102,7 +102,7 @@ "js-yaml": "^3.14.0", "jsdom": "^24.0.0", "lodash": "^4.17.21", - "lucide-react": "^0.258.0", + "lucide-react": "^0.468.0", "moment": "^2.29.1", "moment-timezone": "^0.5.40", "mustache": "^4.2.0", diff --git a/yarn.lock b/yarn.lock index 226cf15a8..3c40250c7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -12621,10 +12621,10 @@ lru-cache@^6.0.0: resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.1.0.tgz#2098d41c2dc56500e6c88584aa656c84de7d0484" integrity sha512-/1clY/ui8CzjKFyjdvwPWJUYKiFVXG2I2cY0ssG7h4+hwk+XOIX7ZSG9Q7TW8TW3Kp3BUSqgFWBLgL4PJ+Blag== -lucide-react@^0.258.0: - version "0.258.0" - resolved "https://registry.yarnpkg.com/lucide-react/-/lucide-react-0.258.0.tgz#dbdabafad2835a0a452e8f677531a877cdd93ae8" - integrity sha512-3evnpKadBrjLr2HHJ66eDZ1y0vPS6pm8NiNDaLqhddUUyJGnA+lfDPZfbVkuAFq7Xaa1TEy7Sg17sM7mHpMKrA== +lucide-react@^0.468.0: + version "0.468.0" + resolved "https://registry.yarnpkg.com/lucide-react/-/lucide-react-0.468.0.tgz#830c1bfd905575ddd23b832baa420c87db166910" + integrity sha512-6koYRhnM2N0GGZIdXzSeiNwguv1gt/FAjZOiPl76roBi3xKEXa4WmfpxgQwTTL4KipXjefrnf3oV4IsYhi4JFA== lz-string@^1.4.4: version "1.4.4" @@ -17596,6 +17596,15 @@ wrap-ansi@^7.0.0: string-width "^4.1.0" strip-ansi "^6.0.0" +wrap-ansi@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + wrap-ansi@^8.0.1, wrap-ansi@^8.1.0: version "8.1.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214"