Skip to content

Commit 9e450ec

Browse files
Merge pull request #287 from community-scripts/feat/250_chose_version
feat: Add alpine variant support for LXC scripts
2 parents 42fbdc8 + cf3f9a5 commit 9e450ec

File tree

4 files changed

+523
-56
lines changed

4 files changed

+523
-56
lines changed

src/app/_components/ScriptDetailModal.tsx

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { DiffViewer } from "./DiffViewer";
88
import { TextViewer } from "./TextViewer";
99
import { ExecutionModeModal } from "./ExecutionModeModal";
1010
import { ConfirmationModal } from "./ConfirmationModal";
11+
import { ScriptVersionModal } from "./ScriptVersionModal";
1112
import { TypeBadge, UpdateableBadge, PrivilegedBadge, NoteBadge } from "./Badge";
1213
import { Button } from "./ui/button";
1314
import { useRegisterModal } from './modal/ModalStackProvider';
@@ -38,6 +39,8 @@ export function ScriptDetailModal({
3839
const [selectedDiffFile, setSelectedDiffFile] = useState<string | null>(null);
3940
const [textViewerOpen, setTextViewerOpen] = useState(false);
4041
const [executionModeOpen, setExecutionModeOpen] = useState(false);
42+
const [versionModalOpen, setVersionModalOpen] = useState(false);
43+
const [selectedVersionType, setSelectedVersionType] = useState<string | null>(null);
4144
const [isDeleting, setIsDeleting] = useState(false);
4245
const [deleteConfirmOpen, setDeleteConfirmOpen] = useState(false);
4346

@@ -133,16 +136,43 @@ export function ScriptDetailModal({
133136

134137
const handleInstallScript = () => {
135138
if (!script) return;
139+
140+
// Check if script has multiple variants (default and alpine)
141+
const installMethods = script.install_methods || [];
142+
const hasMultipleVariants = installMethods.filter(method =>
143+
method.type === 'default' || method.type === 'alpine'
144+
).length > 1;
145+
146+
if (hasMultipleVariants) {
147+
// Show version selection modal first
148+
setVersionModalOpen(true);
149+
} else {
150+
// Only one variant, proceed directly to execution mode
151+
// Use the first available method or default to 'default' type
152+
const defaultMethod = installMethods.find(method => method.type === 'default');
153+
const firstMethod = installMethods[0];
154+
setSelectedVersionType(defaultMethod?.type || firstMethod?.type || 'default');
155+
setExecutionModeOpen(true);
156+
}
157+
};
158+
159+
const handleVersionSelect = (versionType: string) => {
160+
setSelectedVersionType(versionType);
161+
setVersionModalOpen(false);
136162
setExecutionModeOpen(true);
137163
};
138164

139165
const handleExecuteScript = (mode: "local" | "ssh", server?: any) => {
140166
if (!script || !onInstallScript) return;
141167

142-
// Find the script path (CT or tools)
168+
// Find the script path based on selected version type
169+
const versionType = selectedVersionType || 'default';
143170
const scriptMethod = script.install_methods?.find(
171+
(method) => method.type === versionType && method.script,
172+
) || script.install_methods?.find(
144173
(method) => method.script,
145174
);
175+
146176
if (scriptMethod?.script) {
147177
const scriptPath = `scripts/${scriptMethod.script}`;
148178
const scriptName = script.name;
@@ -799,11 +829,22 @@ export function ScriptDetailModal({
799829
?.script?.split("/")
800830
.pop() ?? `${script.slug}.sh`
801831
}
832+
script={script}
802833
isOpen={textViewerOpen}
803834
onClose={() => setTextViewerOpen(false)}
804835
/>
805836
)}
806837

838+
{/* Version Selection Modal */}
839+
{script && (
840+
<ScriptVersionModal
841+
script={script}
842+
isOpen={versionModalOpen}
843+
onClose={() => setVersionModalOpen(false)}
844+
onSelectVersion={handleVersionSelect}
845+
/>
846+
)}
847+
807848
{/* Execution Mode Modal */}
808849
{script && (
809850
<ExecutionModeModal
Lines changed: 210 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,210 @@
1+
'use client';
2+
3+
import { useState } from 'react';
4+
import type { Script, ScriptInstallMethod } from '../../types/script';
5+
import { Button } from './ui/button';
6+
import { useRegisterModal } from './modal/ModalStackProvider';
7+
8+
interface ScriptVersionModalProps {
9+
isOpen: boolean;
10+
onClose: () => void;
11+
onSelectVersion: (versionType: string) => void;
12+
script: Script | null;
13+
}
14+
15+
export function ScriptVersionModal({ isOpen, onClose, onSelectVersion, script }: ScriptVersionModalProps) {
16+
useRegisterModal(isOpen, { id: 'script-version-modal', allowEscape: true, onClose });
17+
const [selectedVersion, setSelectedVersion] = useState<string | null>(null);
18+
19+
if (!isOpen || !script) return null;
20+
21+
// Get available install methods
22+
const installMethods = script.install_methods || [];
23+
const defaultMethod = installMethods.find(method => method.type === 'default');
24+
const alpineMethod = installMethods.find(method => method.type === 'alpine');
25+
26+
const handleConfirm = () => {
27+
if (selectedVersion) {
28+
onSelectVersion(selectedVersion);
29+
onClose();
30+
}
31+
};
32+
33+
const handleVersionSelect = (versionType: string) => {
34+
setSelectedVersion(versionType);
35+
};
36+
37+
return (
38+
<div className="fixed inset-0 backdrop-blur-sm bg-black/50 flex items-center justify-center z-50 p-4">
39+
<div className="bg-card rounded-lg shadow-xl max-w-2xl w-full border border-border">
40+
{/* Header */}
41+
<div className="flex items-center justify-between p-6 border-b border-border">
42+
<h2 className="text-xl font-bold text-foreground">Select Version</h2>
43+
<Button
44+
onClick={onClose}
45+
variant="ghost"
46+
size="icon"
47+
className="text-muted-foreground hover:text-foreground"
48+
>
49+
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
50+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
51+
</svg>
52+
</Button>
53+
</div>
54+
55+
{/* Content */}
56+
<div className="p-6">
57+
<div className="mb-6">
58+
<h3 className="text-lg font-medium text-foreground mb-2">
59+
Choose a version for &quot;{script.name}&quot;
60+
</h3>
61+
<p className="text-sm text-muted-foreground">
62+
Select the version you want to install. Each version has different resource requirements.
63+
</p>
64+
</div>
65+
66+
<div className="space-y-4">
67+
{/* Default Version */}
68+
{defaultMethod && (
69+
<div
70+
onClick={() => handleVersionSelect('default')}
71+
className={`cursor-pointer rounded-lg border-2 p-4 transition-all ${
72+
selectedVersion === 'default'
73+
? 'border-primary bg-primary/10'
74+
: 'border-border bg-card hover:border-primary/50'
75+
}`}
76+
>
77+
<div className="flex items-start justify-between">
78+
<div className="flex-1">
79+
<div className="flex items-center space-x-3 mb-3">
80+
<div
81+
className={`w-5 h-5 rounded-full border-2 flex items-center justify-center ${
82+
selectedVersion === 'default'
83+
? 'border-primary bg-primary'
84+
: 'border-border'
85+
}`}
86+
>
87+
{selectedVersion === 'default' && (
88+
<svg className="w-3 h-3 text-white" fill="currentColor" viewBox="0 0 20 20">
89+
<path
90+
fillRule="evenodd"
91+
d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z"
92+
clipRule="evenodd"
93+
/>
94+
</svg>
95+
)}
96+
</div>
97+
<h4 className="text-base font-semibold text-foreground capitalize">
98+
{defaultMethod.type}
99+
</h4>
100+
</div>
101+
<div className="grid grid-cols-2 gap-3 text-sm ml-8">
102+
<div>
103+
<span className="text-muted-foreground">CPU: </span>
104+
<span className="text-foreground font-medium">{defaultMethod.resources.cpu} cores</span>
105+
</div>
106+
<div>
107+
<span className="text-muted-foreground">RAM: </span>
108+
<span className="text-foreground font-medium">{defaultMethod.resources.ram} MB</span>
109+
</div>
110+
<div>
111+
<span className="text-muted-foreground">HDD: </span>
112+
<span className="text-foreground font-medium">{defaultMethod.resources.hdd} GB</span>
113+
</div>
114+
<div>
115+
<span className="text-muted-foreground">OS: </span>
116+
<span className="text-foreground font-medium">
117+
{defaultMethod.resources.os} {defaultMethod.resources.version}
118+
</span>
119+
</div>
120+
</div>
121+
</div>
122+
</div>
123+
</div>
124+
)}
125+
126+
{/* Alpine Version */}
127+
{alpineMethod && (
128+
<div
129+
onClick={() => handleVersionSelect('alpine')}
130+
className={`cursor-pointer rounded-lg border-2 p-4 transition-all ${
131+
selectedVersion === 'alpine'
132+
? 'border-primary bg-primary/10'
133+
: 'border-border bg-card hover:border-primary/50'
134+
}`}
135+
>
136+
<div className="flex items-start justify-between">
137+
<div className="flex-1">
138+
<div className="flex items-center space-x-3 mb-3">
139+
<div
140+
className={`w-5 h-5 rounded-full border-2 flex items-center justify-center ${
141+
selectedVersion === 'alpine'
142+
? 'border-primary bg-primary'
143+
: 'border-border'
144+
}`}
145+
>
146+
{selectedVersion === 'alpine' && (
147+
<svg className="w-3 h-3 text-white" fill="currentColor" viewBox="0 0 20 20">
148+
<path
149+
fillRule="evenodd"
150+
d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z"
151+
clipRule="evenodd"
152+
/>
153+
</svg>
154+
)}
155+
</div>
156+
<h4 className="text-base font-semibold text-foreground capitalize">
157+
{alpineMethod.type}
158+
</h4>
159+
</div>
160+
<div className="grid grid-cols-2 gap-3 text-sm ml-8">
161+
<div>
162+
<span className="text-muted-foreground">CPU: </span>
163+
<span className="text-foreground font-medium">{alpineMethod.resources.cpu} cores</span>
164+
</div>
165+
<div>
166+
<span className="text-muted-foreground">RAM: </span>
167+
<span className="text-foreground font-medium">{alpineMethod.resources.ram} MB</span>
168+
</div>
169+
<div>
170+
<span className="text-muted-foreground">HDD: </span>
171+
<span className="text-foreground font-medium">{alpineMethod.resources.hdd} GB</span>
172+
</div>
173+
<div>
174+
<span className="text-muted-foreground">OS: </span>
175+
<span className="text-foreground font-medium">
176+
{alpineMethod.resources.os} {alpineMethod.resources.version}
177+
</span>
178+
</div>
179+
</div>
180+
</div>
181+
</div>
182+
</div>
183+
)}
184+
</div>
185+
186+
{/* Action Buttons */}
187+
<div className="flex justify-end space-x-3 mt-6">
188+
<Button
189+
onClick={onClose}
190+
variant="outline"
191+
size="default"
192+
>
193+
Cancel
194+
</Button>
195+
<Button
196+
onClick={handleConfirm}
197+
disabled={!selectedVersion}
198+
variant="default"
199+
size="default"
200+
className={!selectedVersion ? 'bg-muted-foreground cursor-not-allowed' : ''}
201+
>
202+
Continue
203+
</Button>
204+
</div>
205+
</div>
206+
</div>
207+
</div>
208+
);
209+
}
210+

0 commit comments

Comments
 (0)