Skip to content

Commit

Permalink
Merge pull request #106 from amosproj/ui-improvements
Browse files Browse the repository at this point in the history
UI improvements and policy function
  • Loading branch information
daku-de authored Jul 15, 2024
2 parents de5e4bf + dde4ddb commit b95da09
Show file tree
Hide file tree
Showing 12 changed files with 359 additions and 41 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/*
* Copyright (c) 2024 Fraunhofer Institute for Software and Systems Engineering
*
* This program and the accompanying materials are made available under the
* terms of the Apache License, Version 2.0 which is available at
* https://www.apache.org/licenses/LICENSE-2.0
*
* SPDX-License-Identifier: Apache-2.0
*
* Contributors:
* Fraunhofer Institute for Software and Systems Engineering - initial API and implementation
*
*/

package org.eclipse.edc.extension.policy;

import org.eclipse.edc.policy.engine.spi.AtomicConstraintFunction;
import org.eclipse.edc.policy.engine.spi.PolicyContext;
import org.eclipse.edc.policy.model.Operator;
import org.eclipse.edc.policy.model.Permission;
import org.eclipse.edc.spi.agent.ParticipantAgent;
import org.eclipse.edc.spi.monitor.Monitor;

import java.util.Objects;

import static java.lang.String.format;

public class PolicyConstraintFunction implements AtomicConstraintFunction<Permission> {

private final Monitor monitor;

public PolicyConstraintFunction(Monitor monitor) {
this.monitor = monitor;
}

@Override
public boolean evaluate(Operator operator, Object rightValue, Permission rule, PolicyContext context) {
var role = context.getContextData(ParticipantAgent.class).getClaims().get("role");

monitor.info(format("Evaluating constraint: role %s %s", operator, rightValue.toString()));

return switch (operator) {
case EQ -> Objects.equals(role, rightValue);
case NEQ -> !Objects.equals(role, rightValue);
default -> false;
};
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
* Copyright (c) 2024 Fraunhofer Institute for Software and Systems Engineering
*
* This program and the accompanying materials are made available under the
* terms of the Apache License, Version 2.0 which is available at
* https://www.apache.org/licenses/LICENSE-2.0
*
* SPDX-License-Identifier: Apache-2.0
*
* Contributors:
* Fraunhofer Institute for Software and Systems Engineering - initial API and implementation
*
*/

package org.eclipse.edc.extension.policy;

import org.eclipse.edc.policy.engine.spi.PolicyEngine;
import org.eclipse.edc.policy.engine.spi.RuleBindingRegistry;
import org.eclipse.edc.policy.model.Permission;
import org.eclipse.edc.runtime.metamodel.annotation.Inject;
import org.eclipse.edc.spi.system.ServiceExtension;
import org.eclipse.edc.spi.system.ServiceExtensionContext;

import static org.eclipse.edc.connector.controlplane.contract.spi.validation.ContractValidationService.NEGOTIATION_SCOPE;
import static org.eclipse.edc.jsonld.spi.PropertyAndTypeNames.ODRL_USE_ACTION_ATTRIBUTE;
import static org.eclipse.edc.policy.engine.spi.PolicyEngine.ALL_SCOPES;
import static org.eclipse.edc.spi.constants.CoreConstants.EDC_NAMESPACE;

public class PolicyFunctionsExtension implements ServiceExtension {
private static final String ROLE_CONSTRAINT_KEY = EDC_NAMESPACE + "role";

@Inject
private RuleBindingRegistry ruleBindingRegistry;
@Inject
private PolicyEngine policyEngine;

@Override
public String name() {
return "Policy functions";
}

@Override
public void initialize(ServiceExtensionContext context) {
var monitor = context.getMonitor();

ruleBindingRegistry.bind(ODRL_USE_ACTION_ATTRIBUTE, ALL_SCOPES);
ruleBindingRegistry.bind(ROLE_CONSTRAINT_KEY, NEGOTIATION_SCOPE);
policyEngine.registerFunction(ALL_SCOPES, Permission.class, ROLE_CONSTRAINT_KEY, new PolicyConstraintFunction(monitor));
}
}
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
org.eclipse.edc.extension.status.StatusEndpointExtension
org.eclipse.edc.extension.bootstrap.BootstrapLoaderExtension
org.eclipse.edc.extension.bootstrap.BootstrapLoaderExtension
org.eclipse.edc.extension.policy.PolicyFunctionsExtension
13 changes: 11 additions & 2 deletions src/frontend/app/dashboard/change_status.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import React, { useState, useEffect } from 'react';
import { toast } from 'react-toastify';
import { PauseCircleIcon, PlayCircleIcon, ExclamationCircleIcon } from '@heroicons/react/24/outline';

interface ConnectorStatusProps {
Expand All @@ -23,13 +24,20 @@ export default function ChangeStatusButton({ connectorName, connectorStatus, cal

try {
if (connectorStatus) {
await fetch('/api/pauseConnector');
const response = await fetch('/api/pauseConnector');
if (!response.ok) {
toast.error("There was an error pausing the connector!");
}
} else {
await fetch('/api/unpauseConnector');
const response = await fetch('/api/unpauseConnector');
if (!response.ok) {
toast.error("There was an error starting the connector!");
}
}
await callbackFunction();
} catch (error) {
console.error("There was an error changing conenctor status");
toast.error("There was an error changing conenctor status!");
}
};

Expand All @@ -55,6 +63,7 @@ export default function ChangeStatusButton({ connectorName, connectorStatus, cal
return (
<div className="flex justify-start mt-5">
<button onClick={changeStatus}
disabled={connectorStatus === null}
className={`mb-4 px-4 py-2 rounded-md hover:bg-neonBlue text-white flex items-center ${buttonColor}`}
style={{ height: '50px', width: '240px' }}>
<IconComponent className="w-6 mr-1" style={{ height: '30px', width: '30px' }}/>
Expand Down
3 changes: 2 additions & 1 deletion src/frontend/app/dashboard/connector_status.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -77,9 +77,10 @@ export default function ConnectorStatus({ connectorName }: ConnectorStatusProps)
<div className="flex items-baseline space-x-4">
<button
onClick={fetchStatus}
disabled={running === null}
className="px-4 py-2 text-black bg-white rounded hover:bg-neonBlue shadow-xl flex"
>
<ArrowPathIcon className="w-6 mr-1"/>
<ArrowPathIcon className={`w-6 mr-1 ${running === null ? "spinning" : ""}`}/>
Refresh
</button>
<div className="flex flex-col gap-4">
Expand Down
26 changes: 18 additions & 8 deletions src/frontend/app/dashboard/download/page.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
'use client';
import React, { useState, useEffect } from 'react';
import { ArrowPathIcon } from '@heroicons/react/24/outline';
import { fetchCatalogItems } from '@/actions/api';
import participants from '@/data/participants.json';
import { CatalogItem } from "@/data/interface/file";
import { toast } from 'react-toastify';

const DownloadPage: React.FC = () => {
const [connector, setConnector] = useState<string>('');
const [catalogItems, setCatalogItems] = useState<CatalogItem[]>([]);
const [errorMessage, setErrorMessage] = useState<string>('');
const [loadingItems, setLoadingItems] = useState<boolean>(false);


useEffect(() => {
Expand All @@ -22,12 +25,18 @@ const DownloadPage: React.FC = () => {

const fetchItems = async () => {
try {
setLoadingItems(true);
setErrorMessage("");
toast.dismiss();
const fetchedCatalog = await fetchCatalogItems(connector);
// Filter assets by connector if needed
setCatalogItems(fetchedCatalog);
} catch (error) {
console.error('Error fetching assets:', error);
setErrorMessage('Error fetching assets.');
toast.error("There was an error fetching the assets of " + participants.find(p => p.id === connector)?.displayName)
} finally {
setLoadingItems(false);
}
};

Expand Down Expand Up @@ -59,18 +68,19 @@ const DownloadPage: React.FC = () => {
</option>
))}
</select>
{/*<button*/}
{/* onClick={fetchAssets}*/}
{/* className="px-4 py-2 bg-green-500 text-white rounded flex items-center"*/}
{/*>*/}
{/* Fetch*/}
{/*</button>*/}
<button
onClick={fetchItems}
className="px-4 py-2 bg-neonBlue rounded flex items-center"
disabled={loadingItems}
>
<ArrowPathIcon className={`w-5 h-5 ${loadingItems ? "spinning" : ""}`} />
</button>
</div>
</div>
{connector && (
<>
<h2 className="text-lg font-medium text-gray-700 mb-4">Files for {connector}</h2>
<div className="overflow-x-auto">
<h2 className="text-lg font-medium text-gray-700 mb-4">Files for <b>{participants.find(p => p.id === connector)?.displayName}</b></h2>
<div className="overflow-x-auto overflow-y-clip">
<table className="min-w-full divide-y divide-gray-200">
<thead className="bg-gray-50">
<tr>
Expand Down
23 changes: 18 additions & 5 deletions src/frontend/app/dashboard/upload/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { createAsset, createContractDefinition, getAssets, uploadFile, getPolici
import { FileInfo, Policy, Asset, CatalogItem } from "@/data/interface/file";
import PolicyDropdown from './PolicyDropdown';
import PolicyModal from './policyModal';
import { toast } from 'react-toastify';

const MAX_FILE_SIZE_MB = 10;

Expand All @@ -22,6 +23,8 @@ const UploadPage: React.FC = () => {
const [isSubmitted, setIsSubmitted] = useState<boolean>(false);
const [hoveredItem, setHoveredItem] = useState<string | null>(null);

const [loadingAssets, setLoadingAssets] = useState<boolean>(false);

useEffect(() => {
fetchAssets();
fetchPolicies();
Expand All @@ -47,12 +50,16 @@ const UploadPage: React.FC = () => {

const fetchAssets = async () => {
try {
setLoadingAssets(true);
const assets = updateLinksForLocalhost(await getAssets());
setFiles(assets);
const ownContractDefinitions = await getContractDefinitions();
setContractDefinitions(ownContractDefinitions);
} catch (error) {
console.error('Error fetching assets:', error);
toast.error("There was an error fetching the assets");
} finally {
setLoadingAssets(false);
}
};

Expand All @@ -61,14 +68,17 @@ const UploadPage: React.FC = () => {
await deleteContractDefinition("contract-" + asset.id)
try {
await deleteAsset(asset.id);
await fetchAssets();
await deleteFile(asset.id);
toast.success("Asset has been deleted from connector and database");
} catch (err) {
console.error("Asset could not be deleted: ", err);
await fetchAssets();
toast.info("Asset could not be deleted because it is referenced by a contract agreement. However asset is no longer being offered.")
}
} catch (err) {
console.error("Contract could not be deleted: ", err);
toast.error("Contract could not be deleted.");
} finally {
await fetchAssets();
}
}

Expand All @@ -95,6 +105,7 @@ const UploadPage: React.FC = () => {
setPolicies(fetchedPolicies);
} catch (error) {
console.error('Error fetching policies:', error);
toast.error("There was an error fetching the policies");
}
};

Expand Down Expand Up @@ -156,6 +167,7 @@ const UploadPage: React.FC = () => {
} catch (error) {
console.error('Error uploading file:', error);
setErrorMessage('Error uploading file.');
toast.error("There was an error uploading the file.");
}
} else {
setErrorMessage('Please fill in all fields and select a file.');
Expand All @@ -180,10 +192,11 @@ const UploadPage: React.FC = () => {
<div className="p-6">
<div className="flex justify-end mb-4 gap-2">
<button
onClick={fetchAssets}
onClick={() => {toast.dismiss(); fetchAssets();}}
className="px-4 py-2 bg-neonBlue rounded flex items-center"
disabled={loadingAssets}
>
<ArrowPathIcon className="w-5 h-5" />
<ArrowPathIcon className={`w-5 h-5 ${loadingAssets ? "spinning" : ""}`} />
</button>
<button onClick={() => {setShowModal(true); fetchPolicies();}} className="px-4 py-2 bg-neonGreen rounded">
Upload File
Expand All @@ -192,7 +205,7 @@ const UploadPage: React.FC = () => {
Create Policy
</button>
</div>
<div className="overflow-x-auto">
<div className="overflow-x-auto overflow-y-clip">
<table className="min-w-full divide-y divide-gray-200">
<thead className="bg-gray-50">
<tr>
Expand Down
13 changes: 13 additions & 0 deletions src/frontend/app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,17 @@ body {
.text-balance {
text-wrap: balance;
}
}

.spinning {
animation: spin 1.5s infinite linear;
}

@keyframes spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
19 changes: 18 additions & 1 deletion src/frontend/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import type { Metadata } from "next";
import { Inter } from "next/font/google";
import "./globals.css";
import { ToastContainer } from 'react-toastify';
import 'react-toastify/dist/ReactToastify.css';

const inter = Inter({ subsets: ["latin"] });

Expand All @@ -16,7 +18,22 @@ export default function RootLayout({
}>) {
return (
<html lang="en">
<body className={inter.className}>{children}</body>
<body className={inter.className}>
{children}
<ToastContainer
position="bottom-center"
autoClose={5000}
limit={2}
hideProgressBar={false}
newestOnTop={false}
closeOnClick
rtl={false}
pauseOnFocusLoss
draggable
pauseOnHover
theme="colored"
/>
</body>
</html>
);
}
Loading

0 comments on commit b95da09

Please sign in to comment.