Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reimplement detailed views of RSEs, DIDs and Rules #502

Merged
merged 34 commits into from
Nov 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
2db47be
Make the get RSE endpoint return all the required data
MytsV Oct 12, 2024
30bf3c2
Reimplement the RSE page view
MytsV Oct 15, 2024
4091ec1
Center the loading spinner and the error field
MytsV Oct 15, 2024
eb6a385
Delete the legacy RSE page
MytsV Oct 15, 2024
a185bad
Reimplement the meta values of DID page view
MytsV Oct 15, 2024
74258ab
Add additional padding to the meta view in the RSE details
MytsV Oct 15, 2024
259e553
Modify the get rule endpoint to return all the available data about a…
MytsV Oct 16, 2024
336c825
Express rule grouping with a separate enumerable
MytsV Oct 16, 2024
155ac67
Implement get rule feature and connect it to the endpoint
MytsV Oct 17, 2024
edff828
Connect the list locks endpoint to a feature
MytsV Nov 11, 2024
b9aec76
Sort DID details into two base columns
MytsV Nov 11, 2024
fcfc071
Implement a DID metadata table
MytsV Nov 11, 2024
f570477
Implement a tab switching component
MytsV Nov 12, 2024
3fd15a8
Implement tab switching logic for the DID details
MytsV Nov 12, 2024
efa8071
Decompose the tab switching for better readability
MytsV Nov 12, 2024
c9335cc
Handle differing tabs for different DID types
MytsV Nov 12, 2024
84e3082
Implement DID file replicas loading
MytsV Nov 13, 2024
dbd5e88
Implement DID rules loading
MytsV Nov 13, 2024
2d476c6
Implement DID parents and contents loading
MytsV Nov 13, 2024
1986311
Refactor the details DID table views
MytsV Nov 13, 2024
68cc9af
Implement contents-replicas integrated view
MytsV Nov 13, 2024
687c34a
Merge with 'master'
MytsV Nov 13, 2024
476ad81
Remove legacy components for the DID details
MytsV Nov 13, 2024
74f657e
Add titles to the single view pages
MytsV Nov 13, 2024
dc56235
Change the positioning of error field in RSE details
MytsV Nov 13, 2024
8531666
Add error handling to details DID initial meta loading
MytsV Nov 13, 2024
e6f17fb
Add an error display in case of unknown DID type
MytsV Nov 13, 2024
f8b9496
Add error handling to loading DID attribute
MytsV Nov 13, 2024
6b6036b
Merge branch 'feature-rule_locks_endpoint' into feature-472-single_di…
MytsV Nov 13, 2024
8fed81a
Update the rule details page to load actual data
MytsV Nov 13, 2024
c2f8e91
Finish rule details view
MytsV Nov 14, 2024
4557de0
Add title to the rule details page
MytsV Nov 14, 2024
25eda8e
Remove legacy rule page components
MytsV Nov 14, 2024
8dc69fa
Fix type issues
MytsV Nov 14, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
127 changes: 5 additions & 122 deletions src/app/(rucio)/did/page/[scope]/[name]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,132 +1,15 @@
'use client';
import { PageDID as PageDIDStory } from '@/component-library/pages/legacy/DID/PageDID';
import useComDOM from '@/lib/infrastructure/hooks/useComDOM';
import { useEffect, useState } from 'react';
import { HTTPRequest } from '@/lib/sdk/http';
import {
DIDDatasetReplicasViewModel,
DIDKeyValuePairsDataViewModel,
DIDMetaViewModel,
DIDRulesViewModel,
DIDViewModel,
FileReplicaStateViewModel,
} from '@/lib/infrastructure/data/view-model/did';
import { didKeyValuePairsDataQuery, didMetaQueryBase } from '@/app/(rucio)/did/queries';
import { Loading } from '@/component-library/pages/legacy/Helpers/Loading';

import { DetailsDID } from '@/component-library/pages/DID/details/DetailsDID';
import { useEffect } from 'react';

export default function Page({ params }: { params: { scope: string; name: string } }) {
const decodedScope = decodeURIComponent(params.scope);
const decodedName = decodeURIComponent(params.name);

const [didMeta, setDIDMeta] = useState<DIDMetaViewModel>({ status: 'pending' } as DIDMetaViewModel);
const [didKeyValuePairsData, setDIDKeyValuePairsData] = useState({ status: 'pending' } as DIDKeyValuePairsDataViewModel);
const [fromDidList, setFromDidList] = useState<string>('yosearch');
useEffect(() => {
didMetaQueryBase(decodedScope, decodedName).then(setDIDMeta);
}, []);
useEffect(() => {
didKeyValuePairsDataQuery(decodedScope, decodedName).then(setDIDKeyValuePairsData);
document.title = `${decodedScope}:${decodedName} - Rucio`;
}, []);

const didParentsComDOM = useComDOM<DIDViewModel>('page-did-parents-query', [], false, Infinity, 200, true);
const didContentsComDOM = useComDOM<DIDViewModel>('page-did-contents-query', [], false, Infinity, 200, true);
const didFileReplicasComDOM = useComDOM<FileReplicaStateViewModel>('page-did-filereplicas-query', [], false, Infinity, 200, true);
const didFileReplicasDOnChange = (scope: string, name: string) => {
didFileReplicasComDOM.setRequest({
url: new URL(`${process.env.NEXT_PUBLIC_WEBUI_HOST}/api/feature/list-file-replicas`),
method: 'GET',
params: {
scope: scope,
name: name,
},
headers: new Headers({
'Content-Type': 'application/json',
} as HeadersInit),
body: null,
} as HTTPRequest);
didFileReplicasComDOM.start();
};
const didRulesComDOM = useComDOM<DIDRulesViewModel>('page-did-rules-query', [], false, Infinity, 200, true);
const didDatasetReplicasComDOM = useComDOM<DIDDatasetReplicasViewModel>('page-did-datasetreplicas-query', [], false, Infinity, 200, true);
useEffect(() => {
const setRequests = async () => {
await didContentsComDOM.setRequest({
url: new URL(`${process.env.NEXT_PUBLIC_WEBUI_HOST}/api/feature/list-did-contents`),
method: 'GET',
params: {
scope: decodedScope,
name: decodedName,
},
headers: new Headers({
'Content-Type': 'application/json',
} as HeadersInit),
body: null,
} as HTTPRequest);
await didParentsComDOM.setRequest({
url: new URL(`${process.env.NEXT_PUBLIC_WEBUI_HOST}/api/feature/list-did-parents`),
method: 'GET',
params: {
scope: decodedScope,
name: decodedName,
},
headers: new Headers({
'Content-Type': 'application/json',
} as HeadersInit),
body: null,
} as HTTPRequest);
await didFileReplicasComDOM.setRequest({
url: new URL(`${process.env.NEXT_PUBLIC_WEBUI_HOST}/api/feature/list-file-replicas`),
method: 'GET',
params: {
scope: decodedScope,
name: decodedName,
},
headers: new Headers({
'Content-Type': 'application/json',
} as HeadersInit),
body: null,
} as HTTPRequest);
await didRulesComDOM.setRequest({
url: new URL(`${process.env.NEXT_PUBLIC_WEBUI_HOST}/api/feature/list-did-rules`),
method: 'GET',
params: {
scope: decodedScope,
name: decodedName,
},
headers: new Headers({
'Content-Type': 'application/json',
} as HeadersInit),
body: null,
} as HTTPRequest);
await didDatasetReplicasComDOM.setRequest({
url: new URL(`${process.env.NEXT_PUBLIC_WEBUI_HOST}/api/feature/list-dataset-replicas`),
method: 'GET',
params: {
scope: decodedScope,
name: decodedName,
},
headers: new Headers({
'Content-Type': 'application/json',
} as HeadersInit),
body: null,
} as HTTPRequest);
};
setRequests();
}, []);
if (didMeta.status === 'pending') {
return <Loading title="View DID" subtitle={`For DID ${decodedScope}:${decodedName}`} />;
}
return (
<PageDIDStory
didMeta={didMeta}
fromDidList={fromDidList}
didParentsComDOM={didParentsComDOM}
didKeyValuePairsData={didKeyValuePairsData}
didFileReplicasComDOM={didFileReplicasComDOM}
didFileReplicasDOnChange={didFileReplicasDOnChange}
didRulesComDOM={didRulesComDOM}
didContentsComDOM={didContentsComDOM}
didDatasetReplicasComDOM={didDatasetReplicasComDOM}
/>
);
return <DetailsDID scope={decodedScope} name={decodedName} />;
}
46 changes: 6 additions & 40 deletions src/app/(rucio)/rse/page/[name]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,43 +1,9 @@
'use client';
import { Loading } from '@/component-library/pages/legacy/Helpers/Loading';
import { PageRSE as PageRSEStory } from '@/component-library/pages/legacy/RSE/PageRSE';
import { RSEBlockState } from '@/lib/core/entity/rucio';
import { RSEAttributeViewModel, RSEProtocolViewModel, RSEViewModel } from '@/lib/infrastructure/data/view-model/rse';
import { useEffect, useState } from 'react';

async function getRSE(rseName: string): Promise<RSEViewModel> {
const url = `${process.env.NEXT_PUBLIC_WEBUI_HOST}/api/feature/get-rse?` + new URLSearchParams({ rseName });
const res = await fetch(url);
return await res.json();
}

async function getProtocols(rseName: string): Promise<RSEProtocolViewModel> {
const url = `${process.env.NEXT_PUBLIC_WEBUI_HOST}/api/feature/get-rse-protocols?` + new URLSearchParams({ rseName });
const res = await fetch(url);
return await res.json();
}

async function getAttributes(rseName: string): Promise<RSEAttributeViewModel> {
const url = `${process.env.NEXT_PUBLIC_WEBUI_HOST}/api/feature/get-rse-attributes?` + new URLSearchParams({ rseName });
const res = await fetch(url);
return await res.json();
}
import { DetailsRSE } from '@/component-library/pages/RSE/details/DetailsRSE';

export default function Page({ params }: { params: { name: string } }) {
const [rse, setRSE] = useState<RSEViewModel>({ status: 'pending' } as RSEViewModel);
const [protocols, setProtocols] = useState<RSEProtocolViewModel>({ status: 'pending' } as RSEProtocolViewModel);
const [attributes, setAttributes] = useState<RSEAttributeViewModel>({ status: 'pending' } as RSEAttributeViewModel);
useEffect(() => {
getRSE(params.name).then(setRSE);
}, [params.name]);
useEffect(() => {
getProtocols(params.name).then(setProtocols);
}, [params.name]);
useEffect(() => {
getAttributes(params.name).then(setAttributes);
}, [params.name]);
if (rse.status === 'pending' || protocols.status === 'pending' || attributes.status === 'pending') {
return <Loading title="View RSE" subtitle={`For RSE ${params.name}`} />;
}
return <PageRSEStory rse={rse} rseblockstate={0 as RSEBlockState} protocols={protocols} attributes={attributes} />;
return <DetailsRSE name={params.name} />;
}

export const metadata = {
title: 'RSE - Rucio',
};
59 changes: 5 additions & 54 deletions src/app/(rucio)/rule/page/[id]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,61 +1,12 @@
'use client';
import { PageRule as PageRuleStory } from '@/component-library/pages/legacy/Rule/PageRule';
import { fixtureRuleMetaViewModel } from 'test/fixtures/table-fixtures';
import { useState, useEffect } from 'react';
import useComDOM from '@/lib/infrastructure/hooks/useComDOM';
import { HTTPRequest } from '@/lib/sdk/http';
import { RuleMetaViewModel, RulePageLockEntryViewModel } from '@/lib/infrastructure/data/view-model/rule';
import { getSiteHeader } from '@/app/(rucio)/queries';
import { SiteHeaderViewModel } from '@/lib/infrastructure/data/view-model/site-header';

export default function PageRule({ params }: { params: { id: string } }) {
const comDOM = useComDOM<RulePageLockEntryViewModel>('rule-page-lock-query', [], false, Infinity, 50, true);
const [isAdmin, setIsAdmin] = useState<boolean>(false);
useEffect(() => {
getSiteHeader().then((vm: SiteHeaderViewModel) => setIsAdmin(vm.activeAccount?.role === 'admin'));
}, []);
const [meta, setMeta] = useState<RuleMetaViewModel>({} as RuleMetaViewModel);
import { DetailsRule } from '@/component-library/pages/Rule/details/DetailsRule';
import { useEffect } from 'react';

export default function PageRule({ params }: { params: { id: string } }) {
useEffect(() => {
// TODO get from mock endpoint
fetch(`${process.env.NEXT_PUBLIC_WEBUI_HOST}/api/feature/mock-get-rule-meta`)
.then(res => {
if (res.ok) {
return res.json();
}
throw new Error(res.statusText);
})
.then(data => {
setMeta({ ...data, id: params.id });
})
.catch(err => {
console.error(err);
});
// setMeta({ ...fixtureRuleMetaViewModel(), id: params.id })
document.title = 'Rule - Rucio';
}, []);

useEffect(() => {
const runQuery = async () => {
const request: HTTPRequest = {
url: new URL(`${process.env.NEXT_PUBLIC_WEBUI_HOST}/api/feature/mock-list-rule-page-lock`),
method: 'GET',
headers: new Headers({
'Content-Type': 'application/json',
} as HeadersInit),
body: null,
};
await comDOM.setRequest(request);
};
runQuery();
}, []);
return (
<PageRuleStory
ruleMeta={meta}
ruleLocks={comDOM}
ruleBoostFunc={() => {
console.log('boost not implemented');
}}
ruleBoostShow={isAdmin}
/>
);
return <DetailsRule id={params.id} />;
}
29 changes: 29 additions & 0 deletions src/component-library/features/badges/DID/ReplicaStateBadge.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { ReplicaState } from '@/lib/core/entity/rucio';
import React from 'react';
import { Badge } from '@/component-library/atoms/misc/Badge';
import { cn } from '@/component-library/utils';

const stateString: Record<ReplicaState, string> = {
Available: 'Available',
Bad: 'Bad',
Being_Deleted: 'Being Deleted',
Copying: 'Copying',
Temporary_Unavailable: 'Temporary Unavailable',
Unavailable: 'Unavailable',
Unknown: 'Unknown',
};

const stateColorClasses: Record<ReplicaState, string> = {
Available: 'bg-base-success-500',
Bad: 'bg-base-error-500',
Being_Deleted: 'bg-base-error-300',
Copying: 'bg-base-info-500',
Temporary_Unavailable: 'bg-base-warning-400',
Unavailable: 'bg-neutral-0 dark:bg-neutral-800',
Unknown: 'bg-neutral-0 dark:bg-neutral-800',
};

export const ReplicaStateBadge = (props: { value: ReplicaState; className?: string }) => {
const classes = cn(stateColorClasses[props.value], props.className);
return <Badge value={stateString[props.value]} className={classes} />;
};
8 changes: 8 additions & 0 deletions src/component-library/features/badges/NullBadge.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { cn } from '@/component-library/utils';
import { Badge } from '@/component-library/atoms/misc/Badge';
import React from 'react';

export const NullBadge = (props: { className?: string }) => {
const classes = cn('bg-neutral-200 text-neutral-400 dark:bg-neutral-800 dark:text-neutral-600', props.className);
return <Badge value="null" className={classes} />;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import React from 'react';
import { Badge } from '@/component-library/atoms/misc/Badge';
import { cn } from '@/component-library/utils';

export const RSEAvailabilityBadge = (props: { operation: string; className?: string }) => {
const classes = cn('bg-neutral-200 dark:bg-neutral-800', props.className);
return <Badge value={props.operation} className={classes} />;
};
21 changes: 21 additions & 0 deletions src/component-library/features/badges/Rule/RuleGroupingBadge.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { RuleGrouping } from '@/lib/core/entity/rucio';
import React from 'react';
import { Badge } from '@/component-library/atoms/misc/Badge';
import { cn } from '@/component-library/utils';

const groupingString: Record<RuleGrouping, string> = {
A: 'All',
D: 'Dataset',
N: 'None',
};

const groupingColorClasses: Record<RuleGrouping, string> = {
A: 'bg-base-success-500',
D: 'bg-base-warning-400',
N: 'bg-base-error-500',
};

export const RuleGroupingBadge = (props: { value: RuleGrouping; className?: string }) => {
const classes = cn(groupingColorClasses[props.value], props.className);
return <Badge value={groupingString[props.value]} className={classes} />;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { RuleNotification } from '@/lib/core/entity/rucio';
import React from 'react';
import { Badge } from '@/component-library/atoms/misc/Badge';
import { cn } from '@/component-library/utils';

const notificationString: Record<RuleNotification, string> = {
C: 'Close',
N: 'No',
P: 'Progress',
Y: 'Yes',
};

const notificationColorClasses: Record<RuleNotification, string> = {
Y: 'bg-base-success-500',
P: 'bg-base-info-500',
C: 'bg-base-warning-400',
N: 'bg-base-error-500',
};

export const RuleNotificationBadge = (props: { value: RuleNotification; className?: string }) => {
const classes = cn(notificationColorClasses[props.value], props.className);
return <Badge value={notificationString[props.value]} className={classes}></Badge>;
};
2 changes: 1 addition & 1 deletion src/component-library/features/key-value/KeyValueRow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ export const KeyValueRow = (props: { name: string; children: ReactNode }) => {
return (
<div className="flex w-full items-center h-12">
<span className={cn('text-neutral-700 dark:text-neutral-300', 'min-w-[10rem] pr-3', 'font-medium text-right text-sm')}>{props.name}</span>
<span className="flex items-center">{props.children}</span>
<span className="flex items-center space-x-2">{props.children}</span>
</div>
);
};
23 changes: 23 additions & 0 deletions src/component-library/features/table/cells/AttributeCell.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { DateISO } from '@/lib/core/entity/rucio';
import Checkbox from '@/component-library/atoms/form/Checkbox';
import React from 'react';
import { formatDate } from '@/component-library/features/utils/text-formatters';
import { NullBadge } from '@/component-library/features/badges/NullBadge';

const isDateISO = (value: unknown): value is DateISO => {
if (typeof value !== 'string') return false;
const isoDateRegex = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?(Z|([+-]\d{2}:\d{2}))$/;
return isoDateRegex.test(value);
};

export const AttributeCell = ({ value }: { value: string | DateISO | number | boolean | null }) => {
if (value === null) {
return <NullBadge />;
} else if (typeof value === 'boolean') {
return <Checkbox checked={value} />;
} else if (isDateISO(value)) {
return formatDate(value);
} else {
return value;
}
};
Loading
Loading