@buildappolis/env-manager
Version:
Enterprise-grade environment variable management system with encryption, snapshots, and validation
488 lines (474 loc) • 21 kB
JavaScript
import { c as createComponent, d as renderComponent, r as renderTemplate } from '../chunks/astro/server_DE_7F_eO.mjs';
import { V as VersionFooter, $ as $$Layout8Bit } from '../chunks/VersionFooter_B4W5GPCR.mjs';
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
import { useState, useEffect } from 'react';
import { FolderOpen, RefreshCw, Key, GitBranch, Lock, Square, Play, Trash2 } from 'lucide-react';
/* empty css */
export { renderers } from '../renderers.mjs';
function ProjectSelector({ onProjectSelect }) {
const [projects, setProjects] = useState([]);
const [loading, setLoading] = useState(true);
const [selectedProject, setSelectedProject] = useState(null);
const [showPasswordModal, setShowPasswordModal] = useState(false);
const [passwordMode, setPasswordMode] = useState("setup");
const [passwordForm, setPasswordForm] = useState({
password: "",
newPassword: "",
confirmPassword: "",
recoveryPhrase: "",
projectPassword: ""
});
const [passwordError, setPasswordError] = useState("");
const [recoveryPhrase, setRecoveryPhrase] = useState("");
const [hasGlobalPassword, setHasGlobalPassword] = useState(false);
const [refreshing, setRefreshing] = useState(false);
useEffect(() => {
loadProjects();
checkPasswordStatus();
}, []);
const loadProjects = async () => {
try {
const response = await fetch("/api/projects");
const data = await response.json();
setProjects(data.projects || []);
} catch (error) {
console.error("Failed to load projects:", error);
} finally {
setLoading(false);
}
};
const checkPasswordStatus = async () => {
try {
const response = await fetch("/api/password");
const data = await response.json();
setHasGlobalPassword(data.hasGlobalPassword);
} catch (error) {
console.error("Failed to check password status:", error);
}
};
const handleProjectSelect = async (project) => {
setSelectedProject(project);
if (project.hasProjectPassword) {
setPasswordMode("verify");
setShowPasswordModal(true);
} else {
if (onProjectSelect) {
onProjectSelect(project);
}
}
};
const handlePasswordSubmit = async () => {
setPasswordError("");
try {
if (passwordMode === "setup") {
if (passwordForm.password !== passwordForm.confirmPassword) {
setPasswordError("Passwords do not match");
return;
}
const response = await fetch("/api/password", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
action: "setup",
password: passwordForm.password
})
});
const data = await response.json();
if (!response.ok) {
setPasswordError(data.error);
return;
}
setRecoveryPhrase(data.recoveryPhrase);
setHasGlobalPassword(true);
alert(`Password set! Your recovery phrase is:
${data.recoveryPhrase}
SAVE THIS PHRASE! You'll need it to recover your password.`);
setShowPasswordModal(false);
resetPasswordForm();
}
if (passwordMode === "verify") {
const response = await fetch("/api/password", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
action: "verify",
password: passwordForm.password,
projectPath: selectedProject?.path
})
});
const data = await response.json();
if (!response.ok || !data.valid) {
setPasswordError("Invalid password");
return;
}
if (data.requiresProjectPassword && !data.projectValid) {
setPasswordError("Project password required");
return;
}
setShowPasswordModal(false);
resetPasswordForm();
if (selectedProject && onProjectSelect) {
onProjectSelect(selectedProject);
}
}
if (passwordMode === "change") {
if (passwordForm.newPassword !== passwordForm.confirmPassword) {
setPasswordError("New passwords do not match");
return;
}
const response = await fetch("/api/password", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
action: "change",
password: passwordForm.password,
newPassword: passwordForm.newPassword
})
});
const data = await response.json();
if (!response.ok) {
setPasswordError(data.error);
return;
}
setRecoveryPhrase(data.recoveryPhrase);
alert(`Password changed! Your new recovery phrase is:
${data.recoveryPhrase}
SAVE THIS PHRASE!`);
setShowPasswordModal(false);
resetPasswordForm();
}
if (passwordMode === "recover") {
if (passwordForm.newPassword !== passwordForm.confirmPassword) {
setPasswordError("New passwords do not match");
return;
}
const response = await fetch("/api/password", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
action: "recover",
recoveryPhrase: passwordForm.recoveryPhrase,
newPassword: passwordForm.newPassword
})
});
const data = await response.json();
if (!response.ok) {
setPasswordError(data.error);
return;
}
setRecoveryPhrase(data.recoveryPhrase);
alert(`Password recovered! Your new recovery phrase is:
${data.recoveryPhrase}
SAVE THIS PHRASE!`);
setShowPasswordModal(false);
resetPasswordForm();
}
} catch (error) {
setPasswordError("Operation failed. Please try again.");
}
};
const handleProjectAction = async (project, action) => {
try {
const response = await fetch("/api/projects", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
action,
projectPath: project.path,
port: project.port
})
});
if (response.ok) {
await loadProjects();
}
} catch (error) {
console.error(`Failed to ${action} project:`, error);
}
};
const resetPasswordForm = () => {
setPasswordForm({
password: "",
newPassword: "",
confirmPassword: "",
recoveryPhrase: "",
projectPassword: ""
});
setPasswordError("");
};
const handleRefresh = async () => {
setRefreshing(true);
await loadProjects();
setTimeout(() => setRefreshing(false), 500);
};
if (loading) {
return /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center p-8", children: /* @__PURE__ */ jsx("div", { className: "animate-pulse text-yellow-400", children: "Loading projects..." }) });
}
return /* @__PURE__ */ jsxs("div", { className: "pixel-borders bg-gray-900 p-6 rounded-lg", children: [
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between mb-6", children: [
/* @__PURE__ */ jsxs("h2", { className: "text-xl text-yellow-400 flex items-center gap-2", children: [
/* @__PURE__ */ jsx(FolderOpen, { className: "w-6 h-6" }),
"Projects"
] }),
/* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
/* @__PURE__ */ jsx(
"button",
{
onClick: handleRefresh,
className: `pixel-button bg-blue-600 hover:bg-blue-700 text-white p-2 ${refreshing ? "animate-spin" : ""}`,
title: "Refresh",
children: /* @__PURE__ */ jsx(RefreshCw, { className: "w-4 h-4" })
}
),
hasGlobalPassword && /* @__PURE__ */ jsxs(
"button",
{
onClick: () => {
setPasswordMode("change");
setShowPasswordModal(true);
},
className: "pixel-button bg-purple-600 hover:bg-purple-700 text-white px-3 py-2 flex items-center gap-2",
children: [
/* @__PURE__ */ jsx(Key, { className: "w-4 h-4" }),
"Change Password"
]
}
)
] })
] }),
projects.length === 0 ? /* @__PURE__ */ jsxs("div", { className: "text-center py-8 text-gray-400", children: [
/* @__PURE__ */ jsx("p", { className: "mb-2", children: "No projects found" }),
/* @__PURE__ */ jsx("p", { className: "text-sm", children: 'Run "env-manager init" in a project directory to get started' })
] }) : /* @__PURE__ */ jsx("div", { className: "grid gap-4", children: projects.map((project) => /* @__PURE__ */ jsx(
"div",
{
className: `pixel-borders bg-gray-800 p-4 rounded-lg transition-all hover:bg-gray-700 ${project.isActive ? "ring-2 ring-yellow-400" : ""}`,
children: /* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between", children: [
/* @__PURE__ */ jsxs("div", { className: "flex-1", children: [
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 mb-2", children: [
/* @__PURE__ */ jsx("h3", { className: "text-lg text-cyan-400", children: project.packageInfo?.name || project.name }),
project.isRunning && /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-1 text-green-400 text-xs", children: [
/* @__PURE__ */ jsx("span", { className: "w-2 h-2 bg-green-400 rounded-full animate-pulse" }),
"LIVE"
] }),
project.gitBranch && /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-1 text-purple-400 text-xs", children: [
/* @__PURE__ */ jsx(GitBranch, { className: "w-3 h-3" }),
project.gitBranch
] }),
project.hasProjectPassword && /* @__PURE__ */ jsx(Lock, { className: "w-4 h-4 text-yellow-400" })
] }),
/* @__PURE__ */ jsxs("div", { className: "text-xs text-gray-400 space-y-1", children: [
project.packageInfo?.version && /* @__PURE__ */ jsxs("p", { children: [
"Version: ",
project.packageInfo.version
] }),
/* @__PURE__ */ jsxs("p", { className: "truncate", title: `Full path: ${project.path}`, children: [
"Path: ",
project.path
] }),
/* @__PURE__ */ jsxs("p", { children: [
"Port: ",
project.port
] }),
/* @__PURE__ */ jsxs("p", { children: [
"Last accessed: ",
new Date(project.lastAccessed).toLocaleDateString()
] })
] })
] }),
/* @__PURE__ */ jsx("div", { className: "flex gap-2", children: project.exists ? /* @__PURE__ */ jsxs(Fragment, { children: [
/* @__PURE__ */ jsx(
"button",
{
onClick: () => handleProjectSelect(project),
className: "pixel-button bg-green-600 hover:bg-green-700 text-white p-2",
title: "Open project",
children: /* @__PURE__ */ jsx(FolderOpen, { className: "w-4 h-4" })
}
),
project.isRunning ? /* @__PURE__ */ jsx(
"button",
{
onClick: () => handleProjectAction(project, "stop"),
className: "pixel-button bg-red-600 hover:bg-red-700 text-white p-2",
title: "Stop project",
children: /* @__PURE__ */ jsx(Square, { className: "w-4 h-4" })
}
) : /* @__PURE__ */ jsx(
"button",
{
onClick: () => handleProjectAction(project, "start"),
className: "pixel-button bg-blue-600 hover:bg-blue-700 text-white p-2",
title: "Start project",
children: /* @__PURE__ */ jsx(Play, { className: "w-4 h-4" })
}
)
] }) : /* @__PURE__ */ jsx(
"button",
{
onClick: () => handleProjectAction(project, "remove"),
className: "pixel-button bg-red-600 hover:bg-red-700 text-white p-2",
title: "Remove from list (project not found)",
children: /* @__PURE__ */ jsx(Trash2, { className: "w-4 h-4" })
}
) })
] })
},
project.id
)) }),
showPasswordModal && /* @__PURE__ */ jsx("div", { className: "fixed inset-0 bg-black bg-opacity-75 flex items-center justify-center z-50 p-4", children: /* @__PURE__ */ jsxs("div", { className: "pixel-borders bg-gray-900 p-6 rounded-lg max-w-md w-full", children: [
/* @__PURE__ */ jsxs("h3", { className: "text-xl text-yellow-400 mb-4", children: [
passwordMode === "setup" && "🔐 Setup Master Password",
passwordMode === "verify" && "🔓 Enter Password",
passwordMode === "change" && "🔄 Change Password",
passwordMode === "recover" && "🔑 Recover Password"
] }),
passwordError && /* @__PURE__ */ jsx("div", { className: "mb-4 p-2 bg-red-900 border border-red-400 rounded text-red-300 text-sm", children: passwordError }),
/* @__PURE__ */ jsxs("form", { onSubmit: (e) => {
e.preventDefault();
handlePasswordSubmit();
}, children: [
/* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
passwordMode === "recover" ? /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsxs("div", { children: [
/* @__PURE__ */ jsx("label", { className: "block text-cyan-400 text-sm mb-1", children: "Recovery Phrase" }),
/* @__PURE__ */ jsx(
"input",
{
type: "text",
value: passwordForm.recoveryPhrase,
onChange: (e) => setPasswordForm({ ...passwordForm, recoveryPhrase: e.target.value }),
className: "pixel-input w-full bg-gray-800 text-white p-2 rounded",
placeholder: "Enter your recovery phrase"
}
)
] }) }) : /* @__PURE__ */ jsx(Fragment, { children: (passwordMode === "verify" || passwordMode === "change") && /* @__PURE__ */ jsxs("div", { children: [
/* @__PURE__ */ jsx("label", { className: "block text-cyan-400 text-sm mb-1", children: passwordMode === "change" ? "Current Password" : "Password" }),
/* @__PURE__ */ jsx(
"input",
{
type: "password",
value: passwordForm.password,
onChange: (e) => setPasswordForm({ ...passwordForm, password: e.target.value }),
className: "pixel-input w-full bg-gray-800 text-white p-2 rounded",
placeholder: "Enter password"
}
)
] }) }),
(passwordMode === "setup" || passwordMode === "change" || passwordMode === "recover") && /* @__PURE__ */ jsxs(Fragment, { children: [
/* @__PURE__ */ jsxs("div", { children: [
/* @__PURE__ */ jsx("label", { className: "block text-cyan-400 text-sm mb-1", children: passwordMode === "setup" ? "Password" : "New Password" }),
/* @__PURE__ */ jsx(
"input",
{
type: "password",
value: passwordMode === "setup" ? passwordForm.password : passwordForm.newPassword,
onChange: (e) => setPasswordForm({
...passwordForm,
[passwordMode === "setup" ? "password" : "newPassword"]: e.target.value
}),
className: "pixel-input w-full bg-gray-800 text-white p-2 rounded",
placeholder: "Enter new password (min 6 chars)"
}
)
] }),
/* @__PURE__ */ jsxs("div", { children: [
/* @__PURE__ */ jsx("label", { className: "block text-cyan-400 text-sm mb-1", children: "Confirm Password" }),
/* @__PURE__ */ jsx(
"input",
{
type: "password",
value: passwordForm.confirmPassword,
onChange: (e) => setPasswordForm({ ...passwordForm, confirmPassword: e.target.value }),
className: "pixel-input w-full bg-gray-800 text-white p-2 rounded",
placeholder: "Confirm password"
}
)
] })
] }),
selectedProject?.hasProjectPassword && passwordMode === "verify" && /* @__PURE__ */ jsxs("div", { children: [
/* @__PURE__ */ jsx("label", { className: "block text-cyan-400 text-sm mb-1", children: "Project Password (if different)" }),
/* @__PURE__ */ jsx(
"input",
{
type: "password",
value: passwordForm.projectPassword,
onChange: (e) => setPasswordForm({ ...passwordForm, projectPassword: e.target.value }),
className: "pixel-input w-full bg-gray-800 text-white p-2 rounded",
placeholder: "Enter project-specific password"
}
)
] })
] }),
/* @__PURE__ */ jsxs("div", { className: "flex justify-between items-center mt-6", children: [
passwordMode === "verify" && /* @__PURE__ */ jsx(
"button",
{
onClick: () => {
setPasswordMode("recover");
resetPasswordForm();
},
className: "text-purple-400 hover:text-purple-300 text-sm",
children: "Forgot password?"
}
),
/* @__PURE__ */ jsxs("div", { className: "flex gap-2 ml-auto", children: [
passwordMode !== "setup" && /* @__PURE__ */ jsx(
"button",
{
onClick: () => {
setShowPasswordModal(false);
resetPasswordForm();
},
className: "pixel-button bg-gray-600 hover:bg-gray-700 text-white px-4 py-2",
children: "Cancel"
}
),
/* @__PURE__ */ jsxs(
"button",
{
type: "submit",
className: "pixel-button bg-green-600 hover:bg-green-700 text-white px-4 py-2",
children: [
passwordMode === "setup" && "Setup Password",
passwordMode === "verify" && "Unlock",
passwordMode === "change" && "Change Password",
passwordMode === "recover" && "Reset Password"
]
}
)
] })
] })
] })
] }) })
] });
}
function MainApp() {
const handleProjectSelect = (project) => {
const projectName = project.packageInfo?.name || project.name || "project";
const branch = project.gitBranch || "main";
const cleanProjectName = projectName.replace(/^@[^/]+\//, "").replace(/[^a-z0-9-]/gi, "-").toLowerCase();
window.location.href = `/${cleanProjectName}/${branch}`;
};
return /* @__PURE__ */ jsxs("div", { className: "min-h-screen bg-gray-900 p-4 pb-16", children: [
/* @__PURE__ */ jsxs("div", { className: "container mx-auto max-w-4xl", children: [
/* @__PURE__ */ jsxs("div", { className: "mb-8 text-center", children: [
/* @__PURE__ */ jsx("h1", { className: "text-4xl mb-2 text-yellow-400 animate-pulse", children: "🔧 ENV MANAGER" }),
/* @__PURE__ */ jsx("p", { className: "text-cyan-300 text-sm", children: "SELECT A PROJECT TO MANAGE ENVIRONMENT VARIABLES" })
] }),
/* @__PURE__ */ jsx(ProjectSelector, { onProjectSelect: handleProjectSelect })
] }),
/* @__PURE__ */ jsx(VersionFooter, {})
] });
}
const $$Index = createComponent(($$result, $$props, $$slots) => {
return renderTemplate`${renderComponent($$result, "Layout8Bit", $$Layout8Bit, { "projectName": "Env Manager", "gitBranch": null }, { "default": ($$result2) => renderTemplate` ${renderComponent($$result2, "MainApp", MainApp, { "client:load": true, "client:component-hydration": "load", "client:component-path": "/home/cory-ubuntu/coding/projects/env-manager/src/components/MainApp", "client:component-export": "default" })} ` })}`;
}, "/home/cory-ubuntu/coding/projects/env-manager/src/pages/index.astro", void 0);
const $$file = "/home/cory-ubuntu/coding/projects/env-manager/src/pages/index.astro";
const $$url = "";
const _page = /*#__PURE__*/Object.freeze(/*#__PURE__*/Object.defineProperty({
__proto__: null,
default: $$Index,
file: $$file,
url: $$url
}, Symbol.toStringTag, { value: 'Module' }));
const page = () => _page;
export { page };