Skip to content
Closed
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
Original file line number Diff line number Diff line change
@@ -1,12 +1,28 @@
import React from 'react'
import { Button, Progress } from 'reactstrap'
import React, { useState } from 'react'
import {
Button,
Progress,
Modal,
ModalHeader,
ModalBody,
ModalFooter,
ListGroup,
ListGroupItem
} from 'reactstrap'
import { connect } from 'react-redux'

import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { faTrashCan, faCloudArrowDown } from '@fortawesome/free-solid-svg-icons'
import {
faTrashCan,
faCloudArrowDown,
faHistory
} from '@fortawesome/free-solid-svg-icons'

function ActionCellRenderer(props) {
const app = props.data
const [showVersionsModal, setShowVersionsModal] = useState(false)
const [versions, setVersions] = useState([])
const [loadingVersions, setLoadingVersions] = useState(false)

const handleInstallClick = () => {
fetch(
Expand All @@ -18,6 +34,59 @@ function ActionCellRenderer(props) {
)
}

const handleInstallVersionClick = (version) => {
fetch(
`${window.serverRoutesPrefix}/appstore/install/${props.data.name}/${version}`,
{
method: 'POST',
credentials: 'include'
}
)
setShowVersionsModal(false)
}

const handleVersionsClick = async () => {
setLoadingVersions(true)
setShowVersionsModal(true)

try {
// Fetch versions from npm registry
const response = await fetch(
`https://registry.npmjs.org/${props.data.name}`
)
const packageData = await response.json()

if (packageData.versions) {
// Get all versions and sort them by semver (newest first)
const versionList = Object.keys(packageData.versions)
.filter((version) => version !== props.data.version) // Exclude current version
.sort((a, b) => {
// Simple version comparison - newer versions first
const aParts = a.split('.').map(Number)
const bParts = b.split('.').map(Number)

for (let i = 0; i < Math.max(aParts.length, bParts.length); i++) {
const aPart = aParts[i] || 0
const bPart = bParts[i] || 0

if (bPart !== aPart) {
return bPart - aPart
}
}
return 0
})
.slice(0, 20) // Limit to 20 versions

setVersions(versionList)
}
} catch (error) {
console.error('Failed to fetch versions:', error)
setVersions([])
} finally {
setLoadingVersions(false)
}
}

const handleRemoveClick = () => {
if (confirm(`Are you sure you want to uninstall ${props.data.name}?`)) {
fetch(`${window.serverRoutesPrefix}/appstore/remove/${props.data.name}`, {
Expand Down Expand Up @@ -86,20 +155,95 @@ function ActionCellRenderer(props) {
icon={faTrashCan}
onClick={handleRemoveClick}
/>

<FontAwesomeIcon
className="icon__versions"
icon={faHistory}
onClick={handleVersionsClick}
title="View older versions"
/>
</>
) : (
<Button
className="button__install"
color="primary"
onClick={handleInstallClick}
>
Install
</Button>
<>
<Button
className="button__install"
color="primary"
onClick={handleInstallClick}
style={{ marginRight: '8px' }}
>
Install
</Button>

<FontAwesomeIcon
className="icon__versions"
icon={faHistory}
onClick={handleVersionsClick}
title="View older versions"
/>
</>
)
}
return (
<div className="cell__renderer cell-action center">
<div>{content}</div>

{/* Versions Modal */}
<Modal
isOpen={showVersionsModal}
toggle={() => setShowVersionsModal(!showVersionsModal)}
size="md"
>
<ModalHeader toggle={() => setShowVersionsModal(!showVersionsModal)}>
Older Versions - {props.data.name}
</ModalHeader>
<ModalBody>
{loadingVersions ? (
<div className="text-center">
<div className="spinner-border text-primary" role="status">
<span className="sr-only">Loading...</span>
</div>
<p className="mt-2">Loading versions...</p>
</div>
) : versions.length > 0 ? (
<ListGroup>
{versions.map((version) => (
<ListGroupItem
key={version}
className="d-flex justify-content-between align-items-center"
>
<span>
<strong>{version}</strong>
{props.data.installedVersion === version && (
<span className="badge badge-success ml-2">
Installed
</span>
)}
</span>
<Button
size="sm"
color="primary"
onClick={() => handleInstallVersionClick(version)}
disabled={props.data.installedVersion === version}
>
{props.data.installedVersion === version
? 'Installed'
: 'Install'}
</Button>
</ListGroupItem>
))}
</ListGroup>
) : (
<p className="text-muted">
No older versions available or failed to load versions.
</p>
)}
</ModalBody>
<ModalFooter>
<Button color="secondary" onClick={() => setShowVersionsModal(false)}>
Close
</Button>
</ModalFooter>
</Modal>
</div>
)
}
Expand Down
16 changes: 16 additions & 0 deletions packages/server-admin-ui/src/views/appstore/appStore.scss
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,22 @@
color: #00cd79;
}
}

.icon__remove {
path {
color: #df6c42;
}
}

.icon__versions {
path {
color: #007bff;
}

&:hover path {
color: #0056b3;
}
}
}

.cell__renderer.center {
Expand Down