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

feat: support selecting different Casbin engine endpoints and use icons to enhance UI #220

Merged
merged 6 commits into from
Jan 23, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
136 changes: 136 additions & 0 deletions app/components/editor/common/EndpointSelector.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
import React from 'react';
import { clsx } from 'clsx';
import { DEFAULT_ENDPOINT } from '@/app/components/hooks/useRemoteEnforcer';
import { useLang } from '@/app/context/LangContext';
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/app/components/ui/tooltip';

const ENDPOINTS = [DEFAULT_ENDPOINT, 'demo.casdoor.com'];

export const EndpointSelector: React.FC = () => {
const { t } = useLang();
const [isOpen, setIsOpen] = React.useState(false);
const storedEndpoint = window.localStorage.getItem('casbinEndpoint') || DEFAULT_ENDPOINT;
const [selectedEndpoint, setSelectedEndpoint] = React.useState(storedEndpoint);
const [customEndpoint, setCustomEndpoint] = React.useState(ENDPOINTS.includes(storedEndpoint) ? '' : storedEndpoint);
const dropdownRef = React.useRef<HTMLDivElement>(null);

React.useEffect(() => {
const handleClickOutside = (event: MouseEvent) => {
if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) {
setIsOpen(false);
}
};

document.addEventListener('mousedown', handleClickOutside);
return () => {
return document.removeEventListener('mousedown', handleClickOutside);
};
}, []);

const handleEndpointSelect = (value: string) => {
setSelectedEndpoint(value);
window.localStorage.setItem('casbinEndpoint', value);
setIsOpen(false);
};

const handleCustomEndpointChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const value = e.target.value;
setCustomEndpoint(value);

if (value) {
setSelectedEndpoint(value);
window.localStorage.setItem('casbinEndpoint', value);
} else {
setSelectedEndpoint(DEFAULT_ENDPOINT);
window.localStorage.setItem('casbinEndpoint', DEFAULT_ENDPOINT);
}
};

return (
<div className="relative inline-block" ref={dropdownRef}>
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<button
onClick={() => {
return setIsOpen(!isOpen);
}}
className="border border-[#e13c3c] rounded px-2 py-1 text-[#e13c3c] hover:bg-[#e13c3c] hover:text-white flex items-center gap-1"
>
<svg className="w-4 h-4" viewBox="0 0 1024 1024" fill="currentColor">
{/* eslint-disable-next-line max-len */}
<path d="M607.934444 417.856853c-6.179746-6.1777-12.766768-11.746532-19.554358-16.910135l-0.01228 0.011256c-6.986111-6.719028-16.47216-10.857279-26.930349-10.857279-21.464871 0-38.864146 17.400299-38.864146 38.864146 0 9.497305 3.411703 18.196431 9.071609 24.947182l-0.001023 0c0.001023 0.001023 0.00307 0.00307 0.005117 0.004093 2.718925 3.242857 5.953595 6.03853 9.585309 8.251941 3.664459 3.021823 7.261381 5.997598 10.624988 9.361205l3.203972 3.204995c40.279379 40.229237 28.254507 109.539812-12.024871 149.820214L371.157763 796.383956c-40.278355 40.229237-105.761766 40.229237-146.042167 0l-3.229554-3.231601c-40.281425-40.278355-40.281425-105.809861 0-145.991002l75.93546-75.909877c9.742898-7.733125 15.997346-19.668968 15.997346-33.072233 0-23.312962-18.898419-42.211381-42.211381-42.211381-8.797363 0-16.963347 2.693342-23.725354 7.297197-0.021489-0.045025-0.044002-0.088004-0.066515-0.134053l-0.809435 0.757247c-2.989077 2.148943-5.691629 4.669346-8.025791 7.510044l-78.913281 73.841775c-74.178443 74.229608-74.178443 195.632609 0 269.758863l3.203972 3.202948c74.178443 74.127278 195.529255 74.127278 269.707698 0l171.829484-171.880649c74.076112-74.17435 80.357166-191.184297 6.282077-265.311575L607.934444 417.856853z" />
{/* eslint-disable-next-line max-len */}
<path d="M855.61957 165.804257l-3.203972-3.203972c-74.17742-74.178443-195.528232-74.178443-269.706675 0L410.87944 334.479911c-74.178443 74.178443-78.263481 181.296089-4.085038 255.522628l3.152806 3.104711c3.368724 3.367701 6.865361 6.54302 10.434653 9.588379 2.583848 2.885723 5.618974 5.355985 8.992815 7.309476 0.025583 0.020466 0.052189 0.041956 0.077771 0.062422l0.011256-0.010233c5.377474 3.092431 11.608386 4.870938 18.257829 4.870938 20.263509 0 36.68962-16.428158 36.68962-36.68962 0-5.719258-1.309832-11.132548-3.645017-15.95846l0 0c-4.850471-10.891048-13.930267-17.521049-20.210297-23.802102l-3.15383-3.102664c-40.278355-40.278355-24.982998-98.79612 15.295358-139.074476l171.930791-171.830507c40.179095-40.280402 105.685018-40.280402 145.965419 0l3.206018 3.152806c40.279379 40.281425 40.279379 105.838513 0 146.06775l-75.686796 75.737962c-10.296507 7.628748-16.97358 19.865443-16.97358 33.662681 0 23.12365 18.745946 41.87062 41.87062 41.87062 8.048303 0 15.563464-2.275833 21.944801-6.211469 0.048095 0.081864 0.093121 0.157589 0.141216 0.240477l1.173732-1.083681c3.616364-2.421142 6.828522-5.393847 9.529027-8.792247l79.766718-73.603345C929.798013 361.334535 929.798013 239.981676 855.61957 165.804257z" />
</svg>
</button>
</TooltipTrigger>
<TooltipContent className="bg-white text-[#e13c3c] border border-[#e13c3c]">
<p>{t('Select Endpoint')}</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>

{isOpen && (
<div
className="fixed mt-1 bg-white border border-gray-200 rounded shadow-lg z-[100]"
style={{
top: 'auto',
left: 'auto',
transform: 'translateY(2px)',
}}
>
<div className="p-2 min-w-[200px]">
{ENDPOINTS.map((endpoint) => {
const isSelected = endpoint === selectedEndpoint;
return (
<label key={endpoint} className="flex items-center gap-2 p-2 hover:bg-gray-100">
<div className="flex items-center">
<input
type="checkbox"
checked={isSelected}
onChange={() => {
return handleEndpointSelect(endpoint);
}}
className="form-checkbox h-4 w-4 text-[#e13c3c] rounded border-gray-300 focus:ring-[#e13c3c] accent-[#e13c3c]"
/>
</div>
<span className="text-sm whitespace-nowrap">{endpoint}</span>
</label>
);
})}
<div className="p-2 border-t">
<div className="flex items-center gap-2">
<div className="flex items-center">
<input
type="checkbox"
checked={
!ENDPOINTS.some((e) => {
return e === selectedEndpoint;
})
}
onChange={() => {}}
className="form-checkbox h-4 w-4 text-[#e13c3c] rounded border-gray-300 focus:ring-[#e13c3c] accent-[#e13c3c]"
/>
</div>
<input
type="text"
value={customEndpoint}
onChange={handleCustomEndpointChange}
placeholder={t('Enter custom endpoint')}
className={clsx(
'border border-gray-300 rounded',
'px-2 py-1',
'text-sm',
'flex-1',
'focus:outline-none focus:ring-2 focus:ring-[#e13c3c]',
)}
/>
</div>
</div>
</div>
</div>
)}
</div>
);
};
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React, { useState, useRef, useEffect } from 'react';
import { useLang } from '@/app/context/LangContext';

import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/app/components/ui/tooltip';

interface EngineVersion {
libVersion: string;
Expand Down Expand Up @@ -52,8 +52,8 @@ export const EngineSelector: React.FC<EngineSelectorProps> = ({
goVersion,
engineGithubLinks,
}) => {
const [isOpen, setIsOpen] = useState(false);
const { t } = useLang();
const [isOpen, setIsOpen] = useState(false);
const dropdownRef = useRef<HTMLDivElement>(null);
const engines = AVAILABLE_ENGINES(casbinVersion, javaVersion, goVersion);

Expand Down Expand Up @@ -88,25 +88,60 @@ export const EngineSelector: React.FC<EngineSelectorProps> = ({
<select
className="bg-transparent border border-[#e13c3c] rounded px-2 py-1 text-[#e13c3c] focus:outline-none"
value={selectedEngine}
onChange={(e) => {return handlePrimaryEngineChange(e.target.value)}}
onChange={(e) => {
return handlePrimaryEngineChange(e.target.value);
}}
>
{engines.map((engine) => {return (
<option key={engine.id} value={engine.id}>
{engine.name} {!engine.version ? '' :
typeof engine.version === 'string' ? engine.version :
`${engine.version.libVersion} | (CLI ${engine.version.engineVersion})`}
</option>
)})}
{engines.map((engine) => {
return (
<option key={engine.id} value={engine.id}>
{engine.name}{' '}
{!engine.version
? ''
: typeof engine.version === 'string'
? engine.version
: `${engine.version.libVersion} | (CLI ${engine.version.engineVersion})`}
</option>
);
})}
</select>

<button
onClick={() => {
return setIsOpen(!isOpen);
}}
className="border border-[#e13c3c] rounded px-2 py-1 text-[#e13c3c] hover:bg-[#e13c3c] hover:text-white"
>
{t('Compare')}
</button>
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<button
onClick={() => {
return setIsOpen(!isOpen);
}}
className="border border-[#e13c3c] rounded px-2 py-1 text-[#e13c3c] hover:bg-[#e13c3c] hover:text-white"
>
<svg className="w-4 h-4" viewBox="0 0 1024 1024" fill="currentColor">
{/* eslint-disable-next-line max-len */}
<path d="M116.364 837.818h279.272v93.091H23.273V93.091h372.363v93.09H116.364v651.637z m512 0h93.09v93.091h-93.09v-93.09z m139.636 0h93.09v93.091H768v-93.09z m139.636 93.091v-93.09h93.091v93.09h-93.09z m0-325.818V512h93.091v93.09h-93.09z m0 139.636v-93.09h93.091v93.09h-93.09z m0-279.272v-93.091h93.091v93.09h-93.09z m0-139.637v-93.09h93.091v93.09h-93.09z m0-139.636V93.09h93.091v93.09h-93.09z m-46.545 0H768V93.09h93.09v93.09z m-139.636 0h-93.091V93.09h93.09v93.09zM488.727 0h93.091v1024h-93.09V0z" />
</svg>
</button>
</TooltipTrigger>
<TooltipContent className="bg-white text-[#e13c3c] border border-[#e13c3c]">
<p>{t('Compare Engines')}</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>

<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<a href={engineGithubLinks[selectedEngine]} target="_blank" rel="noopener noreferrer" className="text-[#e13c3c] hover:text-[#ff4d4d]">
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="currentColor">
{/* eslint-disable-next-line max-len */}
<path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z" />
</svg>
</a>
</TooltipTrigger>
<TooltipContent className="bg-white text-[#e13c3c] border border-[#e13c3c]">
<p>{t('View Source Code')}</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>

{isOpen && (
<div className="absolute top-full right-0 mt-1 bg-white border border-gray-200 rounded shadow-lg z-50">
Expand All @@ -133,13 +168,6 @@ export const EngineSelector: React.FC<EngineSelectorProps> = ({
</div>
</div>
)}

<a href={engineGithubLinks[selectedEngine]} target="_blank" rel="noopener noreferrer" className="text-[#e13c3c] hover:text-[#ff4d4d]">
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="currentColor">
{/* eslint-disable-next-line max-len */}
<path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z" />
</svg>
</a>
</div>
);
};
49 changes: 30 additions & 19 deletions app/components/editor/common/FileUploadButton.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React, { useRef } from 'react';
import { useRef } from 'react';
import clsx from 'clsx';
import { useLang } from '@/app/context/LangContext';
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/app/components/ui/tooltip';

interface FileUploadButtonProps {
onFileContent: (content: string) => void;
Expand Down Expand Up @@ -37,24 +38,34 @@ export const FileUploadButton: React.FC<FileUploadButtonProps> = ({ onFileConten
return (
<div className={className}>
<input ref={fileInputRef} type="file" accept={accept} onChange={handleFileChange} className="hidden" />
<button
onClick={handleClick}
className={clsx(
'flex items-center gap-1',
'rounded',
'px-2',
'border border-[#453d7d]',
'text-[#453d7a]',
'bg-[#efefef]',
'hover:bg-[#453d7d] hover:text-white',
'transition-colors duration-500',
)}
>
<svg className="w-4 h-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
<path d="M3 15v4c0 1.1.9 2 2 2h14a2 2 0 0 0 2-2v-4M17 8l-5-5-5 5M12 3v12" />
</svg>
<span>{t('Load')}</span>
</button>
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<button
onClick={handleClick}
className={clsx(
'flex items-center gap-1',
'rounded',
'px-2',
'py-1',
'border border-[#453d7d]',
'text-[#453d7a]',
'bg-[#efefef]',
'hover:bg-[#453d7d] hover:text-white',
'transition-colors duration-500',
)}
>
<svg className="w-4 h-4" viewBox="0 0 1051 1024" fill="currentColor">
{/* eslint-disable-next-line max-len */}
<path d="M525.814398 698.649244a43.115789 43.115789 0 0 0 42.991894-42.991894V131.4536l180.268602 180.268602a43.983061 43.983061 0 0 0 60.709014 0 42.991894 42.991894 0 0 0 0-60.709014L582.31095 23.168542a79.293406 79.293406 0 0 0-111.506352 0L241.968784 251.013188a42.867998 42.867998 0 0 0 60.709014 60.709014L483.194192 131.4536v524.20375a42.991894 42.991894 0 0 0 42.620206 42.991894z M863.926437 117.08167a42.991894 42.991894 0 1 0 0 85.859891A102.09026 102.09026 0 0 1 966.388385 304.907925v531.26582a102.09026 102.09026 0 0 1-101.966364 101.966364H187.826255a102.09026 102.09026 0 0 1-101.966364-101.966364V304.907925a102.09026 102.09026 0 0 1 101.966364-101.966364 42.991894 42.991894 0 0 0 0-85.859891A188.074047 188.074047 0 0 0 0 304.907925v531.26582a188.074047 188.074047 0 0 0 187.826255 187.826255h676.100182a188.074047 188.074047 0 0 0 187.826255-187.826255V304.907925A188.074047 188.074047 0 0 0 863.926437 117.08167z" />
</svg>
</button>
</TooltipTrigger>
<TooltipContent className="bg-white text-[#453d7d] border border-[#453d7d]">
<p>{t('Upload File')}</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
</div>
);
};
26 changes: 11 additions & 15 deletions app/components/editor/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@ import { CasbinConfSupport } from '@/app/components/editor/casbin-mode/casbin-co
import { CasbinPolicySupport } from '@/app/components/editor/casbin-mode/casbin-csv';
import SidePanelChat from '@/app/components/editor/panels/SidePanelChat';
import { CustomConfigPanel } from '@/app/components/editor/panels/CustomConfigPanel';
import { PolicyToolbar } from '@/app/components/editor/panels/PolicyToolbar';
import { MessageWithTooltip } from '@/app/components/editor/common/MessageWithTooltip';
import { FileUploadButton } from '@/app/components/editor/common/FileUploadButton';
import { EngineSelector } from '@/app/components/editor/core/EngineSelector';
import { e, m, p, r } from '@/app/components/hooks/useSetupEnforceContext';
import useRunTest from '@/app/components/hooks/useRunTest';
import useShareInfo from '@/app/components/hooks/useShareInfo';
Expand Down Expand Up @@ -274,20 +274,16 @@ export const EditorScreen = () => {
<div className="flex-1 flex flex-col h-full overflow-hidden">
<div className="h-10 pl-2 font-bold flex items-center justify-between">
<div className={textClass}>{t('Policy')}</div>
<div className="text-right mr-4 text-sm flex items-center justify-end gap-2">
<div className="font-normal text-base">
<FileUploadButton onFileContent={setPolicyPersistent} accept=".csv" />
</div>
<EngineSelector
selectedEngine={selectedEngine}
comparisonEngines={comparisonEngines}
onEngineChange={handleEngineChange}
casbinVersion={casbinVersion}
javaVersion={javaVersion}
goVersion={goVersion}
engineGithubLinks={engineGithubLinks}
/>
</div>
<PolicyToolbar
setPolicyPersistent={setPolicyPersistent}
selectedEngine={selectedEngine}
comparisonEngines={comparisonEngines}
handleEngineChange={handleEngineChange}
casbinVersion={casbinVersion}
javaVersion={javaVersion}
goVersion={goVersion}
engineGithubLinks={engineGithubLinks}
/>
</div>
<div className="flex-grow overflow-auto h-full">
<div className="flex flex-col h-full">
Expand Down
Loading
Loading