diff --git a/app/assets/css/theme.css b/app/assets/css/theme.css index 318e0d9e4..baa800bbd 100644 --- a/app/assets/css/theme.css +++ b/app/assets/css/theme.css @@ -99,8 +99,6 @@ --orange-1: #e86925; - --BE-only: var(--ui-gray-6); - --text-log-viewer-color-json-grey: var(--text-log-viewer-color); --text-log-viewer-color-json-magenta: var(--text-log-viewer-color); --text-log-viewer-color-json-yellow: var(--text-log-viewer-color); @@ -269,8 +267,6 @@ /* Dark Theme */ [theme='dark'] { - --BE-only: var(--ui-gray-6); - --text-log-viewer-color-json-grey: var(--text-log-viewer-color); --text-log-viewer-color-json-magenta: var(--text-log-viewer-color); --text-log-viewer-color-json-yellow: var(--text-log-viewer-color); @@ -440,7 +436,6 @@ /* High Contrast Theme */ [theme='highcontrast'] { - --BE-only: var(--ui-gray-6); --text-log-viewer-color-json-grey: var(--text-log-viewer-color); --text-log-viewer-color-json-magenta: var(--text-log-viewer-color); --text-log-viewer-color-json-yellow: var(--text-log-viewer-color); diff --git a/app/portainer/views/init/admin/initAdmin.html b/app/portainer/views/init/admin/initAdmin.html index 3e37a4fa6..00845a5a6 100644 --- a/app/portainer/views/init/admin/initAdmin.html +++ b/app/portainer/views/init/admin/initAdmin.html @@ -137,49 +137,45 @@ - - -
- -
-
- You can upload a backup file from your computer. -
+ +
+
+ You can upload a backup file from your computer.
- - -
-
- - - {{ formValues.BackupFile.name }} - - -
-
- -
- -
- -
-
-
+ + +
+
+ + + {{ formValues.BackupFile.name }} + + +
+
+ +
+ +
+ +
+
+
diff --git a/app/portainer/views/init/admin/initAdminController.js b/app/portainer/views/init/admin/initAdminController.js index fc1bef4dd..db4c25599 100644 --- a/app/portainer/views/init/admin/initAdminController.js +++ b/app/portainer/views/init/admin/initAdminController.js @@ -1,5 +1,4 @@ import { getEnvironments } from '@/react/portainer/environments/environment.service'; -import { restoreOptions } from '@/react/portainer/init/InitAdminView/restore-options'; const REDIRECT_REASON_TIMEOUT = 'AdminInitTimeout'; @@ -14,19 +13,15 @@ angular.module('portainer.app').controller('InitAdminController', [ 'BackupService', 'StatusService', function ($scope, $state, Notifications, Authentication, StateManager, SettingsService, UserService, BackupService, StatusService) { - $scope.restoreOptions = restoreOptions; - $scope.uploadBackup = uploadBackup; $scope.logo = StateManager.getState().application.logo; - $scope.RESTORE_FORM_TYPES = { S3: 's3', FILE: 'file' }; $scope.formValues = { Username: 'admin', Password: '', ConfirmPassword: '', SetupToken: '', - restoreFormType: $scope.RESTORE_FORM_TYPES.FILE, }; $scope.state = { @@ -42,13 +37,6 @@ angular.module('portainer.app').controller('InitAdminController', [ $scope.state.showRestorePortainer = !$scope.state.showRestorePortainer; }; - $scope.onChangeRestoreType = onChangeRestoreType; - function onChangeRestoreType(value) { - $scope.$evalAsync(() => { - $scope.formValues.restoreFormType = value; - }); - } - $scope.createAdminUser = function () { var username = $scope.formValues.Username; var password = $scope.formValues.Password; diff --git a/app/react/components/BoxSelector/BoxSelectorItem.module.css b/app/react/components/BoxSelector/BoxSelectorItem.module.css index 1bb219261..7e680f69a 100644 --- a/app/react/components/BoxSelector/BoxSelectorItem.module.css +++ b/app/react/components/BoxSelector/BoxSelectorItem.module.css @@ -37,13 +37,3 @@ .content { padding-left: 20px; } - -/* used for BE teaser */ -.box-selector-item.limited.business label, -.box-selector-item.limited.business input:checked + label { - @apply border-gray-6 bg-gray-6 bg-opacity-10; - @apply th-dark:border-gray-6 th-dark:bg-gray-6 th-dark:bg-opacity-10; - @apply th-highcontrast:border-gray-6 th-highcontrast:bg-gray-6 th-highcontrast:bg-opacity-10; - - filter: none; -} diff --git a/app/react/components/HubspotForm.tsx b/app/react/components/HubspotForm.tsx deleted file mode 100644 index 74ac8dea6..000000000 --- a/app/react/components/HubspotForm.tsx +++ /dev/null @@ -1,112 +0,0 @@ -import { ReactNode, useRef } from 'react'; -import { useQuery } from '@tanstack/react-query'; - -let globalId = 0; - -interface Props { - portalId: HubSpotCreateFormOptions['portalId']; - formId: HubSpotCreateFormOptions['formId']; - region: HubSpotCreateFormOptions['region']; - - onSubmitted: () => void; - - loading?: ReactNode; -} - -export function HubspotForm({ - loading, - portalId, - region, - formId, - onSubmitted, -}: Props) { - const elRef = useRef(null); - const id = useRef(`reactHubspotForm${globalId++}`); - const { isLoading } = useHubspotForm({ - elId: id.current, - formId, - portalId, - region, - onSubmitted, - }); - - return ( - <> -
- {isLoading && loading} - - ); -} - -function useHubspotForm({ - elId, - formId, - portalId, - region, - onSubmitted, -}: { - elId: string; - portalId: HubSpotCreateFormOptions['portalId']; - formId: HubSpotCreateFormOptions['formId']; - region: HubSpotCreateFormOptions['region']; - - onSubmitted: () => void; -}) { - return useQuery( - ['hubspot', { elId, formId, portalId, region }], - async () => { - await loadHubspot(); - await createForm(`#${elId}`, { - formId, - portalId, - region, - onFormSubmitted: onSubmitted, - }); - }, - { - refetchOnWindowFocus: false, - } - ); -} - -async function loadHubspot() { - return new Promise((resolve) => { - if (window.hbspt) { - resolve(); - return; - } - - const script = document.createElement(`script`); - - script.defer = true; - script.onload = () => { - resolve(); - }; - script.src = `//js.hsforms.net/forms/v2.js`; - document.head.appendChild(script); - }); -} - -async function createForm( - target: string, - options: Omit -) { - return new Promise((resolve) => { - if (!window.hbspt) { - throw new Error('hbspt object is missing'); - } - - window.hbspt.forms.create({ - ...options, - target, - onFormReady(...rest) { - options.onFormReady?.(...rest); - resolve(); - }, - }); - }); -} diff --git a/app/react/components/Tip/TooltipWithChildren/TooltipWithChildren.module.css b/app/react/components/Tip/TooltipWithChildren/TooltipWithChildren.module.css index 39a11b676..62a9982e8 100644 --- a/app/react/components/Tip/TooltipWithChildren/TooltipWithChildren.module.css +++ b/app/react/components/Tip/TooltipWithChildren/TooltipWithChildren.module.css @@ -39,7 +39,3 @@ .tooltip-heading { font-weight: 500; } - -.tooltip-beteaser { - @apply text-blue-8 hover:text-blue-9; -} diff --git a/app/react/components/buttons/Button.css b/app/react/components/buttons/Button.css index 4358fe816..ae6d650f1 100644 --- a/app/react/components/buttons/Button.css +++ b/app/react/components/buttons/Button.css @@ -17,13 +17,6 @@ color: inherit; } -/* used for BE teaser. Dark theme specs defined by EE-5621 */ -.btn-warninglight { - @apply border-warning-5 bg-warning-2 text-black; - @apply th-dark:border-blue-8 th-dark:bg-blue-8 th-dark:bg-opacity-10 th-dark:text-white; - @apply th-highcontrast:bg-warning-5 th-highcontrast:bg-opacity-10 th-highcontrast:text-white; -} - .btn-none:active { outline: none; background-color: transparent; diff --git a/app/react/components/buttons/Button.tsx b/app/react/components/buttons/Button.tsx index 2a04cf4f2..00be35a78 100644 --- a/app/react/components/buttons/Button.tsx +++ b/app/react/components/buttons/Button.tsx @@ -22,7 +22,6 @@ type Color = | 'link' | 'light' | 'dangerlight' - | 'warninglight' | 'warning' | 'success' | 'blue' diff --git a/app/react/components/form-components/SwitchField/Switch.css b/app/react/components/form-components/SwitchField/Switch.css index 129df64e4..6beeedfd6 100644 --- a/app/react/components/form-components/SwitchField/Switch.css +++ b/app/react/components/form-components/SwitchField/Switch.css @@ -54,22 +54,6 @@ cursor: not-allowed; } -.switch.limited { - touch-action: none; -} - -.switch.limited i { - opacity: 1; - cursor: not-allowed; -} - -.switch.business i { - background-color: var(--BE-only); - box-shadow: - inset 0 0 1px rgb(0 0 0 / 50%), - inset 0 0 40px var(--BE-only); -} - .switch input[type='checkbox']:disabled + .slider { cursor: not-allowed; } diff --git a/app/react/portainer/HomeView/BackupFailedPanel.test.tsx b/app/react/portainer/HomeView/BackupFailedPanel.test.tsx deleted file mode 100644 index ce7a2a9d1..000000000 --- a/app/react/portainer/HomeView/BackupFailedPanel.test.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import { http, HttpResponse } from 'msw'; -import { render } from '@testing-library/react'; - -import { server } from '@/setup-tests/server'; -import { isoDate } from '@/portainer/filters/filters'; -import { withTestRouter } from '@/react/test-utils/withRouter'; -import { withTestQueryProvider } from '@/react/test-utils/withTestQuery'; - -import { BackupFailedPanel } from './BackupFailedPanel'; - -test('when backup failed, should show message', async () => { - const timestamp = 1500; - - const { findByText } = renderComponent({ failed: true, timestamp }); - - await expect( - findByText( - `The latest automated backup has failed at ${isoDate( - timestamp - )}. For details please see the log files and have a look at the`, - { exact: false } - ) - ).resolves.toBeVisible(); -}); - -test("when user is using less nodes then allowed he shouldn't see message", async () => { - const { findByText } = renderComponent({ failed: false }); - - await expect( - findByText('The latest automated backup has failed at', { exact: false }) - ).rejects.toBeTruthy(); -}); - -function renderComponent({ - failed, - timestamp, -}: { - failed: boolean; - timestamp?: number; -}) { - server.use( - http.get('/api/backup/s3/status', () => - HttpResponse.json({ Failed: failed, TimestampUTC: timestamp }) - ) - ); - - const Wrapped = withTestQueryProvider(withTestRouter(BackupFailedPanel)); - - return render(); -} diff --git a/app/react/portainer/HomeView/BackupFailedPanel.tsx b/app/react/portainer/HomeView/BackupFailedPanel.tsx deleted file mode 100644 index 51fe0f1a4..000000000 --- a/app/react/portainer/HomeView/BackupFailedPanel.tsx +++ /dev/null @@ -1,49 +0,0 @@ -import { useQuery } from '@tanstack/react-query'; - -import { error as notifyError } from '@/portainer/services/notifications'; -import { getBackupStatus } from '@/portainer/services/api/backup.service'; -import { isoDate } from '@/portainer/filters/filters'; - -import { InformationPanel } from '@@/InformationPanel'; -import { TextTip } from '@@/Tip/TextTip'; -import { Link } from '@@/Link'; - -export function BackupFailedPanel() { - const { status, isLoading } = useBackupStatus(); - - if (isLoading || !status || !status.Failed) { - return null; - } - - return ( -
-
- - - The latest automated backup has failed at{' '} - {isoDate(status.TimestampUTC)}. For details please see the log files - and have a look at the{' '} - - settings - {' '} - to verify the backup configuration. - - -
-
- ); -} - -function useBackupStatus() { - const { data, isLoading } = useQuery( - ['backup', 'status'], - () => getBackupStatus(), - { - onError(error) { - notifyError('Failure', error as Error, 'Failed to get license info'); - }, - } - ); - - return { status: data, isLoading }; -} diff --git a/app/react/portainer/HomeView/EnvironmentList/HomepageFilter.tsx b/app/react/portainer/HomeView/EnvironmentList/HomepageFilter.tsx deleted file mode 100644 index 05ead7d30..000000000 --- a/app/react/portainer/HomeView/EnvironmentList/HomepageFilter.tsx +++ /dev/null @@ -1,69 +0,0 @@ -import { components, OptionProps } from 'react-select'; - -import { useLocalStorage } from '@/react/hooks/useLocalStorage'; - -import { - type Option as OptionType, - PortainerSelect, -} from '@@/form-components/PortainerSelect'; - -interface Props { - filterOptions?: OptionType[]; - onChange: (value: TValue[]) => void; - placeHolder: string; - value: TValue[]; -} - -function Option(props: OptionProps, true>) { - const { isSelected, label } = props; - return ( -
- -
- null} - data-cy={`homepage-filter-option-${label}`} - /> - -
-
-
- ); -} - -export function HomepageFilter({ - filterOptions = [], - onChange, - placeHolder, - value, -}: Props) { - return ( - - placeholder={placeHolder} - options={filterOptions} - value={value} - isMulti - components={{ Option }} - onChange={(option) => onChange([...option])} - bindToBody - data-cy="homepage-filter" - /> - ); -} - -export function useHomePageFilter( - key: string, - defaultValue: T -): [T, (value: T) => void] { - const filterKey = keyBuilder(key); - return useLocalStorage(filterKey, defaultValue, sessionStorage); -} - -function keyBuilder(key: string) { - return `datatable_home_filter_type_${key}`; -} diff --git a/app/react/portainer/HomeView/HomeView.tsx b/app/react/portainer/HomeView/HomeView.tsx index 11b0d14d7..75c9428a9 100644 --- a/app/react/portainer/HomeView/HomeView.tsx +++ b/app/react/portainer/HomeView/HomeView.tsx @@ -14,8 +14,6 @@ import { buildConfirmButton } from '@@/modals/utils'; import { EnvironmentList } from './EnvironmentList'; import { EdgeLoadingSpinner } from './EdgeLoadingSpinner'; import { MotdPanel } from './MotdPanel'; -import { LicenseNodePanel } from './LicenseNodePanel'; -import { BackupFailedPanel } from './BackupFailedPanel'; import { EnvironmentHeader } from './EnvironmentHeader/EnvironmentHeader'; export function HomeView() { @@ -65,12 +63,8 @@ export function HomeView() { breadcrumbs={[{ label: 'Environments' }]} /> - {process.env.PORTAINER_EDITION !== 'CE' && } - - {process.env.PORTAINER_EDITION !== 'CE' && } - {connectingToEdgeEndpoint ? (
diff --git a/app/react/portainer/HomeView/LicenseNodePanel.test.tsx b/app/react/portainer/HomeView/LicenseNodePanel.test.tsx deleted file mode 100644 index bd862cc37..000000000 --- a/app/react/portainer/HomeView/LicenseNodePanel.test.tsx +++ /dev/null @@ -1,53 +0,0 @@ -import { http, HttpResponse } from 'msw'; -import { render } from '@testing-library/react'; - -import { server } from '@/setup-tests/server'; -import { withTestQueryProvider } from '@/react/test-utils/withTestQuery'; - -import { LicenseType } from '../licenses/types'; - -import { LicenseNodePanel } from './LicenseNodePanel'; - -test('when user is using more nodes then allowed he should see message', async () => { - const allowed = 2; - const used = 5; - server.use( - http.get('/api/licenses/info', () => - HttpResponse.json({ nodes: allowed, type: LicenseType.Subscription }) - ), - http.get('/api/system/nodes', () => HttpResponse.json({ nodes: used })) - ); - - const { findByText } = renderComponent(); - - await expect( - findByText( - /The number of nodes for your license has been exceeded. Please contact your administrator./ - ) - ).resolves.toBeVisible(); -}); - -test("when user is using less nodes then allowed he shouldn't see message", async () => { - const allowed = 5; - const used = 2; - server.use( - http.get('/api/licenses/info', () => - HttpResponse.json({ nodes: allowed, type: LicenseType.Subscription }) - ), - http.get('/api/system/nodes', () => HttpResponse.json({ nodes: used })) - ); - - const { findByText } = renderComponent(); - - await expect( - findByText( - /The number of nodes for your license has been exceeded. Please contact your administrator./ - ) - ).rejects.toBeTruthy(); -}); - -function renderComponent() { - const Wrapped = withTestQueryProvider(LicenseNodePanel); - - return render(); -} diff --git a/app/react/portainer/HomeView/LicenseNodePanel.tsx b/app/react/portainer/HomeView/LicenseNodePanel.tsx deleted file mode 100644 index e0f130dcc..000000000 --- a/app/react/portainer/HomeView/LicenseNodePanel.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import { TextTip } from '@@/Tip/TextTip'; -import { InformationPanel } from '@@/InformationPanel'; - -import { useNodesCount } from '../system/useNodesCount'; -import { useLicenseInfo } from '../licenses/use-license.service'; -import { LicenseType } from '../licenses/types'; - -export function LicenseNodePanel() { - const nodesValid = useNodesValid(); - - if (nodesValid) { - return null; - } - - return ( - - - The number of nodes for your license has been exceeded. Please contact - your administrator. - - - ); -} - -function useNodesValid() { - const { isLoading: isLoadingNodes, data: nodesCount = 0 } = useNodesCount(); - - const { isLoading: isLoadingLicense, info } = useLicenseInfo(); - if ( - isLoadingLicense || - isLoadingNodes || - !info || - info.type === LicenseType.Trial - ) { - return true; - } - - return nodesCount <= info.nodes; -} diff --git a/app/react/portainer/environments/queries/useUpdateEnvironmentsRelationsMutation.ts b/app/react/portainer/environments/queries/useUpdateEnvironmentsRelationsMutation.ts deleted file mode 100644 index cf70e302d..000000000 --- a/app/react/portainer/environments/queries/useUpdateEnvironmentsRelationsMutation.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { useMutation, useQueryClient } from '@tanstack/react-query'; - -import axios, { parseAxiosError } from '@/portainer/services/axios/axios'; -import { - mutationOptions, - withError, - withInvalidate, -} from '@/react-tools/react-query'; -import { EdgeGroup } from '@/react/edge/edge-groups/types'; -import { TagId } from '@/portainer/tags/types'; -import { queryKeys as edgeGroupQueryKeys } from '@/react/edge/edge-groups/queries/query-keys'; -import { queryKeys as groupQueryKeys } from '@/react/portainer/environments/environment-groups/queries/query-keys'; -import { tagKeys } from '@/portainer/tags/queries'; - -import { EnvironmentId, EnvironmentGroupId } from '../types'; -import { buildUrl } from '../environment.service/utils'; - -import { environmentQueryKeys } from './query-keys'; - -export function useUpdateEnvironmentsRelationsMutation() { - const queryClient = useQueryClient(); - - return useMutation( - updateEnvironmentRelations, - mutationOptions( - withInvalidate(queryClient, [ - environmentQueryKeys.base(), - edgeGroupQueryKeys.base(), - groupQueryKeys.base(), - tagKeys.all, - ]), - withError('Unable to update environment relations') - ) - ); -} - -export interface EnvironmentRelationsPayload { - edgeGroups: Array; - group: EnvironmentGroupId; - tags: Array; -} - -export async function updateEnvironmentRelations( - relations: Record -) { - try { - await axios.put(buildUrl(undefined, 'relations'), { relations }); - } catch (e) { - throw parseAxiosError(e as Error, 'Unable to update environment relations'); - } -} diff --git a/app/react/portainer/environments/wizard/EnvironmentTypeSelectView/EnvironmentTypeSelectView.tsx b/app/react/portainer/environments/wizard/EnvironmentTypeSelectView/EnvironmentTypeSelectView.tsx index c77691265..0a8af95e6 100644 --- a/app/react/portainer/environments/wizard/EnvironmentTypeSelectView/EnvironmentTypeSelectView.tsx +++ b/app/react/portainer/environments/wizard/EnvironmentTypeSelectView/EnvironmentTypeSelectView.tsx @@ -11,7 +11,6 @@ import { EnvironmentSelector } from './EnvironmentSelector'; import { EnvironmentOptionValue, existingEnvironmentTypes, - newEnvironmentTypes, } from './environment-types'; export function EnvironmentTypeSelectView() { @@ -45,16 +44,6 @@ export function EnvironmentTypeSelectView() { onChange={setTypes} options={existingEnvironmentTypes} /> -

Set up new environments

-
- ) : null, - }), -]; - -export function ActivityLogsTable({ - dataset, - currentPage, - keyword, - limit, - onChangeKeyword, - onChangeLimit, - onChangePage, - onChangeSort, - sort, - totalItems, -}: { - keyword: string; - onChangeKeyword(keyword: string): void; - sort: { id: string; desc: boolean } | undefined; - onChangeSort(sort: { id: string; desc: boolean } | undefined): void; - limit: number; - onChangeLimit(limit: number): void; - currentPage: number; - onChangePage(page: number): void; - totalItems: number; - dataset?: Array; -}) { - return ( - - title="Activity logs" - titleIcon={History} - columns={columns} - dataset={dataset || []} - isLoading={!dataset} - settingsManager={{ - pageSize: limit, - search: keyword, - setPageSize: onChangeLimit, - setSearch: onChangeKeyword, - setSortBy: (id, desc) => - onChangeSort({ id: getSortType(id) || 'Timestamp', desc }), - sortBy: sort - ? { - id: sort.id, - desc: sort.desc, - } - : undefined, - }} - page={currentPage} - onPageChange={onChangePage} - isServerSidePagination - totalCount={totalItems} - disableSelect - renderSubRow={(row) => } - data-cy="activity-logs-datatable" - /> - ); -} - -function SubRow({ item }: { item: ActivityLog }) { - return ( - - - - - - ); -} diff --git a/app/react/portainer/logs/ActivityLogsView/FilterBar.tsx b/app/react/portainer/logs/ActivityLogsView/FilterBar.tsx deleted file mode 100644 index 007db6ce6..000000000 --- a/app/react/portainer/logs/ActivityLogsView/FilterBar.tsx +++ /dev/null @@ -1,43 +0,0 @@ -import { DownloadIcon } from 'lucide-react'; - -import { Widget } from '@@/Widget'; -import { TextTip } from '@@/Tip/TextTip'; -import { Button } from '@@/buttons'; - -import { DateRangePicker } from '../components/DateRangePicker'; - -export function FilterBar({ - value, - onChange, - onExport, -}: { - value: { start: Date; end: Date | null } | undefined; - onChange: (value?: { start: Date; end: Date | null }) => void; - onExport: () => void; -}) { - return ( - - -
- - - - Portainer user activity logs have a maximum retention of 7 days. - - -
- -
- -
-
- ); -} diff --git a/app/react/portainer/logs/ActivityLogsView/types.ts b/app/react/portainer/logs/ActivityLogsView/types.ts deleted file mode 100644 index 76176e7ad..000000000 --- a/app/react/portainer/logs/ActivityLogsView/types.ts +++ /dev/null @@ -1,19 +0,0 @@ -interface BaseActivityLog { - timestamp: number; - action: string; - context: string; - id: number; - username: string; -} -export interface ActivityLogResponse extends BaseActivityLog { - payload: string; -} - -export interface ActivityLog extends BaseActivityLog { - payload: string | object; -} - -export interface ActivityLogsResponse { - logs: Array; - totalCount: number; -} diff --git a/app/react/portainer/logs/ActivityLogsView/useActivityLogs.ts b/app/react/portainer/logs/ActivityLogsView/useActivityLogs.ts deleted file mode 100644 index be8c0e2f6..000000000 --- a/app/react/portainer/logs/ActivityLogsView/useActivityLogs.ts +++ /dev/null @@ -1,85 +0,0 @@ -import { useQuery } from '@tanstack/react-query'; - -import axios, { parseAxiosError } from '@/portainer/services/axios/axios'; - -import { ActivityLogResponse, ActivityLogsResponse } from './types'; - -export const sortKeys = ['Context', 'Action', 'Timestamp', 'Username'] as const; -export type SortKey = (typeof sortKeys)[number]; -export function isSortKey(value?: string): value is SortKey { - return !!value && sortKeys.includes(value as SortKey); -} -export function getSortType(value?: string): SortKey | undefined { - return isSortKey(value) ? value : undefined; -} - -export interface Query { - offset: number; - limit: number; - sortBy?: SortKey; - sortDesc?: boolean; - keyword: string; - after?: number; - before?: number; -} - -export function useActivityLogs(query: Query) { - return useQuery({ - queryKey: ['activityLogs', query] as const, - queryFn: () => fetchActivityLogs(query), - keepPreviousData: true, - select: (data) => ({ - ...data, - logs: decorateLogs(data.logs), - }), - }); -} - -async function fetchActivityLogs(query: Query): Promise { - try { - const { data } = await axios.get( - '/useractivity/logs', - { params: query } - ); - return data; - } catch (err) { - throw parseAxiosError(err, 'Failed loading user activity logs csv'); - } -} - -/** - * Decorates logs with the payload parsed from base64 - */ -function decorateLogs(logs?: ActivityLogResponse[]) { - if (!logs || logs.length === 0) { - return []; - } - - return logs.map((log) => ({ - ...log, - payload: parseBase64AsObject(log.payload), - })); -} - -function parseBase64AsObject(value: string): string | object { - if (!value) { - return value; - } - try { - return JSON.parse(safeAtob(value)); - } catch (err) { - return safeAtob(value); - } -} - -function safeAtob(value: string) { - if (!value) { - return value; - } - try { - return window.atob(value); - } catch (err) { - // If the payload is not base64 encoded, return the original value - return value; - } -} diff --git a/app/react/portainer/logs/ActivityLogsView/useExportMutation.ts b/app/react/portainer/logs/ActivityLogsView/useExportMutation.ts deleted file mode 100644 index 1940a21e9..000000000 --- a/app/react/portainer/logs/ActivityLogsView/useExportMutation.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { useMutation } from '@tanstack/react-query'; -import { saveAs } from 'file-saver'; - -import axios, { parseAxiosError } from '@/portainer/services/axios/axios'; - -import { Query } from './useActivityLogs'; - -export function useExportMutation() { - return useMutation({ - mutationFn: exportActivityLogs, - }); -} - -async function exportActivityLogs(query: Omit) { - try { - const { data, headers } = await axios.get('/useractivity/logs.csv', { - params: { ...query, limit: 0 }, - responseType: 'blob', - headers: { - 'Content-type': 'text/csv', - }, - }); - - const contentDispositionHeader = headers['content-disposition'] || ''; - const filename = - contentDispositionHeader.replace('attachment; filename=', '').trim() || - 'logs.csv'; - saveAs(data, filename); - } catch (err) { - throw parseAxiosError(err, 'Failed loading user activity logs csv'); - } -} diff --git a/app/react/portainer/settings/SettingsView/BackupSettingsView/BackupSettingsPanel.tsx b/app/react/portainer/settings/SettingsView/BackupSettingsView/BackupSettingsPanel.tsx index fc000d2b3..ac9c6916b 100644 --- a/app/react/portainer/settings/SettingsView/BackupSettingsView/BackupSettingsPanel.tsx +++ b/app/react/portainer/settings/SettingsView/BackupSettingsView/BackupSettingsPanel.tsx @@ -1,16 +1,11 @@ import { Download } from 'lucide-react'; -import { useState } from 'react'; import { Widget, WidgetBody, WidgetTitle } from '@@/Widget'; import { FormSection } from '@@/form-components/FormSection'; -import { BoxSelector } from '@@/BoxSelector'; -import { options } from './backup-options'; import { BackupFileForm } from './BackupFileForm'; export function BackupSettingsPanel() { - const [backupType, setBackupType] = useState(options[0].value); - return ( @@ -21,13 +16,6 @@ export function BackupSettingsPanel() { This will back up your Portainer server configuration and does not include containers.
- setBackupType(v)} - radioName="backup-type" - /> diff --git a/app/react/portainer/settings/SettingsView/BackupSettingsView/backup-options.tsx b/app/react/portainer/settings/SettingsView/BackupSettingsView/backup-options.tsx deleted file mode 100644 index 8003ef941..000000000 --- a/app/react/portainer/settings/SettingsView/BackupSettingsView/backup-options.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import { DownloadCloud } from 'lucide-react'; - -import { BadgeIcon } from '@@/BadgeIcon'; - -export enum BackupFormType { - S3 = 's3', - File = 'file', -} - -export const options = [ - { - id: 'backup_file', - icon: , - label: 'Download backup file', - value: BackupFormType.File, - }, -]; diff --git a/app/react/portainer/settings/SettingsView/ExperimentalFeatures/ExperimentalFeatures.tsx b/app/react/portainer/settings/SettingsView/ExperimentalFeatures/ExperimentalFeatures.tsx deleted file mode 100644 index 519b558ba..000000000 --- a/app/react/portainer/settings/SettingsView/ExperimentalFeatures/ExperimentalFeatures.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import { FlaskConical } from 'lucide-react'; - -import { useExperimentalSettings } from '@/react/portainer/settings/queries'; - -import { Widget, WidgetBody, WidgetTitle } from '@@/Widget'; - -import { ExperimentalFeaturesSettingsForm } from './ExperimentalFeaturesForm'; - -export function ExperimentalFeatures() { - const settingsQuery = useExperimentalSettings(); - - if (!settingsQuery.data) { - return null; - } - - const settings = settingsQuery.data; - - return ( - - - - - - - ); -} diff --git a/app/react/portainer/settings/SettingsView/ExperimentalFeatures/ExperimentalFeaturesForm.tsx b/app/react/portainer/settings/SettingsView/ExperimentalFeatures/ExperimentalFeaturesForm.tsx deleted file mode 100644 index 8d24df95e..000000000 --- a/app/react/portainer/settings/SettingsView/ExperimentalFeatures/ExperimentalFeaturesForm.tsx +++ /dev/null @@ -1,87 +0,0 @@ -import { Form, Formik } from 'formik'; -import * as yup from 'yup'; -import { useCallback } from 'react'; -import { FlaskConical } from 'lucide-react'; - -import { notifySuccess } from '@/portainer/services/notifications'; -import { ExperimentalFeatures } from '@/react/portainer/settings/types'; -import { useUpdateExperimentalSettingsMutation } from '@/react/portainer/settings/queries'; - -import { LoadingButton } from '@@/buttons/LoadingButton'; -import { TextTip } from '@@/Tip/TextTip'; - -// eslint-disable-next-line @typescript-eslint/no-empty-object-type -interface FormValues {} - -const validation = yup.object({}); - -interface Props { - settings: ExperimentalFeatures; -} - -export function ExperimentalFeaturesSettingsForm({ settings }: Props) { - const initialValues: FormValues = settings; - - const mutation = useUpdateExperimentalSettingsMutation(); - - const { mutate: updateSettings } = mutation; - - const handleSubmit = useCallback(() => { - updateSettings( - {}, - { - onSuccess() { - notifySuccess( - 'Success', - 'Successfully updated experimental features settings' - ); - }, - } - ); - }, [updateSettings]); - - return ( - - initialValues={initialValues} - onSubmit={handleSubmit} - validationSchema={validation} - validateOnMount - enableReinitialize - > - {({ isValid, dirty }) => ( -
- - Experimental features may be discontinued without notice. - - -
-
- -
- In Portainer releases, we may introduce features that we're - experimenting with. These will be items in the early phases of - development with limited testing. -
- Our goal is to gain early user feedback, so we can refine, enhance - and ultimately make our features the best they can be. Disabling an - experimental feature will prevent access to it. -
- -
-
- - Save experimental settings - -
-
-
- )} - - ); -} diff --git a/app/react/portainer/settings/SettingsView/ExperimentalFeatures/index.ts b/app/react/portainer/settings/SettingsView/ExperimentalFeatures/index.ts deleted file mode 100644 index a846020a7..000000000 --- a/app/react/portainer/settings/SettingsView/ExperimentalFeatures/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { ExperimentalFeatures } from './ExperimentalFeatures'; diff --git a/app/react/portainer/settings/queries/index.ts b/app/react/portainer/settings/queries/index.ts index 6f7664308..cb647bdfb 100644 --- a/app/react/portainer/settings/queries/index.ts +++ b/app/react/portainer/settings/queries/index.ts @@ -5,5 +5,3 @@ export { useUpdateSettingsMutation, } from './useSettings'; export { usePublicSettings } from './usePublicSettings'; -export { useExperimentalSettings } from './useExperimentalSettings'; -export { useUpdateExperimentalSettingsMutation } from './useExperimentalSettingsMutation'; diff --git a/app/react/portainer/settings/queries/useExperimentalSettings.ts b/app/react/portainer/settings/queries/useExperimentalSettings.ts deleted file mode 100644 index ba6058daa..000000000 --- a/app/react/portainer/settings/queries/useExperimentalSettings.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { useQuery } from '@tanstack/react-query'; - -import axios, { parseAxiosError } from '@/portainer/services/axios/axios'; -import { withError } from '@/react-tools/react-query'; - -import { ExperimentalFeatures } from '../types'; -import { buildUrl } from '../settings.service'; - -import { queryKeys } from './queryKeys'; - -type ExperimentalFeaturesSettings = { - experimentalFeatures: ExperimentalFeatures; -}; - -export function useExperimentalSettings( - select?: (settings: ExperimentalFeaturesSettings) => T, - enabled = true -) { - return useQuery(queryKeys.experimental(), getExperimentalSettings, { - select, - enabled, - staleTime: 50, - ...withError('Unable to retrieve experimental settings'), - }); -} - -async function getExperimentalSettings() { - try { - const { data } = await axios.get( - buildUrl('experimental') - ); - return data; - } catch (e) { - throw parseAxiosError( - e as Error, - 'Unable to retrieve experimental settings' - ); - } -} diff --git a/app/react/portainer/settings/queries/useExperimentalSettingsMutation.ts b/app/react/portainer/settings/queries/useExperimentalSettingsMutation.ts deleted file mode 100644 index b43307241..000000000 --- a/app/react/portainer/settings/queries/useExperimentalSettingsMutation.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { useMutation, useQueryClient } from '@tanstack/react-query'; - -import axios, { parseAxiosError } from '@/portainer/services/axios/axios'; -import { - mutationOptions, - withError, - withInvalidate, -} from '@/react-tools/react-query'; - -import { ExperimentalFeatures } from '../types'; -import { buildUrl } from '../settings.service'; - -import { queryKeys } from './queryKeys'; - -export function useUpdateExperimentalSettingsMutation() { - const queryClient = useQueryClient(); - - return useMutation( - updateExperimentalSettings, - mutationOptions( - withInvalidate(queryClient, [queryKeys.base()]), - withError('Unable to update experimental settings') - ) - ); -} - -async function updateExperimentalSettings( - settings: Partial -) { - try { - await axios.put(buildUrl('experimental'), settings); - } catch (e) { - throw parseAxiosError(e as Error, 'Unable to update experimental settings'); - } -} diff --git a/app/react/portainer/system/useUpgradeEditionMutation.ts b/app/react/portainer/system/useUpgradeEditionMutation.ts deleted file mode 100644 index 33ce66526..000000000 --- a/app/react/portainer/system/useUpgradeEditionMutation.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { useMutation } from '@tanstack/react-query'; - -import axios, { parseAxiosError } from '@/portainer/services/axios/axios'; -import { withError } from '@/react-tools/react-query'; -import { isAxiosError } from '@/portainer/services/axios/utils/isAxiosError'; - -import { buildUrl } from './build-url'; - -export function useUpgradeEditionMutation() { - return useMutation(upgradeEdition, { - ...withError('Unable to upgrade edition'), - }); -} - -async function upgradeEdition({ license }: { license: string }) { - try { - await axios.post(buildUrl('upgrade'), { license }); - } catch (error) { - if (!isAxiosError(error)) { - throw error; - } - - // if error is because the server disconnected, then everything went well - if (!error.response || !error.response.status) { - return; - } - - throw parseAxiosError(error); - } -}