mirror of
https://github.com/9technologygroup/patchmon.net.git
synced 2025-11-13 18:36:01 +00:00
Fixed issue with users not being updated
Re-worked setup.sh to use last 3 tags and the main branch (development latest)
This commit is contained in:
@@ -176,6 +176,8 @@ router.get(
|
|||||||
id: true,
|
id: true,
|
||||||
username: true,
|
username: true,
|
||||||
email: true,
|
email: true,
|
||||||
|
first_name: true,
|
||||||
|
last_name: true,
|
||||||
role: true,
|
role: true,
|
||||||
is_active: true,
|
is_active: true,
|
||||||
last_login: true,
|
last_login: true,
|
||||||
@@ -314,6 +316,14 @@ router.put(
|
|||||||
.isLength({ min: 3 })
|
.isLength({ min: 3 })
|
||||||
.withMessage("Username must be at least 3 characters"),
|
.withMessage("Username must be at least 3 characters"),
|
||||||
body("email").optional().isEmail().withMessage("Valid email is required"),
|
body("email").optional().isEmail().withMessage("Valid email is required"),
|
||||||
|
body("first_name")
|
||||||
|
.optional()
|
||||||
|
.isLength({ min: 1 })
|
||||||
|
.withMessage("First name must be at least 1 character"),
|
||||||
|
body("last_name")
|
||||||
|
.optional()
|
||||||
|
.isLength({ min: 1 })
|
||||||
|
.withMessage("Last name must be at least 1 character"),
|
||||||
body("role")
|
body("role")
|
||||||
.optional()
|
.optional()
|
||||||
.custom(async (value) => {
|
.custom(async (value) => {
|
||||||
@@ -326,10 +336,10 @@ router.put(
|
|||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}),
|
}),
|
||||||
body("isActive")
|
body("is_active")
|
||||||
.optional()
|
.optional()
|
||||||
.isBoolean()
|
.isBoolean()
|
||||||
.withMessage("isActive must be a boolean"),
|
.withMessage("is_active must be a boolean"),
|
||||||
],
|
],
|
||||||
async (req, res) => {
|
async (req, res) => {
|
||||||
try {
|
try {
|
||||||
@@ -340,13 +350,16 @@ router.put(
|
|||||||
return res.status(400).json({ errors: errors.array() });
|
return res.status(400).json({ errors: errors.array() });
|
||||||
}
|
}
|
||||||
|
|
||||||
const { username, email, role, isActive } = req.body;
|
const { username, email, first_name, last_name, role, is_active } =
|
||||||
|
req.body;
|
||||||
const updateData = {};
|
const updateData = {};
|
||||||
|
|
||||||
if (username) updateData.username = username;
|
if (username) updateData.username = username;
|
||||||
if (email) updateData.email = email;
|
if (email) updateData.email = email;
|
||||||
|
if (first_name !== undefined) updateData.first_name = first_name || null;
|
||||||
|
if (last_name !== undefined) updateData.last_name = last_name || null;
|
||||||
if (role) updateData.role = role;
|
if (role) updateData.role = role;
|
||||||
if (typeof isActive === "boolean") updateData.is_active = isActive;
|
if (typeof is_active === "boolean") updateData.is_active = is_active;
|
||||||
|
|
||||||
// Check if user exists
|
// Check if user exists
|
||||||
const existingUser = await prisma.users.findUnique({
|
const existingUser = await prisma.users.findUnique({
|
||||||
@@ -381,7 +394,7 @@ router.put(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Prevent deactivating the last admin
|
// Prevent deactivating the last admin
|
||||||
if (isActive === false && existingUser.role === "admin") {
|
if (is_active === false && existingUser.role === "admin") {
|
||||||
const adminCount = await prisma.users.count({
|
const adminCount = await prisma.users.count({
|
||||||
where: {
|
where: {
|
||||||
role: "admin",
|
role: "admin",
|
||||||
@@ -404,6 +417,8 @@ router.put(
|
|||||||
id: true,
|
id: true,
|
||||||
username: true,
|
username: true,
|
||||||
email: true,
|
email: true,
|
||||||
|
first_name: true,
|
||||||
|
last_name: true,
|
||||||
role: true,
|
role: true,
|
||||||
is_active: true,
|
is_active: true,
|
||||||
last_login: true,
|
last_login: true,
|
||||||
|
|||||||
@@ -92,7 +92,12 @@ const UsersTab = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleEditUser = (user) => {
|
const handleEditUser = (user) => {
|
||||||
|
// Reset editingUser first to force re-render with fresh data
|
||||||
|
setEditingUser(null);
|
||||||
|
// Use setTimeout to ensure the modal re-initializes with fresh data
|
||||||
|
setTimeout(() => {
|
||||||
setEditingUser(user);
|
setEditingUser(user);
|
||||||
|
}, 0);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleResetPassword = (user) => {
|
const handleResetPassword = (user) => {
|
||||||
@@ -314,7 +319,9 @@ const UsersTab = () => {
|
|||||||
user={editingUser}
|
user={editingUser}
|
||||||
isOpen={!!editingUser}
|
isOpen={!!editingUser}
|
||||||
onClose={() => setEditingUser(null)}
|
onClose={() => setEditingUser(null)}
|
||||||
onUserUpdated={() => updateUserMutation.mutate()}
|
onUserUpdated={() => {
|
||||||
|
queryClient.invalidateQueries(["users"]);
|
||||||
|
}}
|
||||||
roles={roles}
|
roles={roles}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
@@ -352,11 +359,29 @@ const AddUserModal = ({ isOpen, onClose, onUserCreated, roles }) => {
|
|||||||
});
|
});
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
const [error, setError] = useState("");
|
const [error, setError] = useState("");
|
||||||
|
const [success, setSuccess] = useState(false);
|
||||||
|
|
||||||
|
// Reset form when modal is closed
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isOpen) {
|
||||||
|
setFormData({
|
||||||
|
username: "",
|
||||||
|
email: "",
|
||||||
|
password: "",
|
||||||
|
first_name: "",
|
||||||
|
last_name: "",
|
||||||
|
role: "user",
|
||||||
|
});
|
||||||
|
setError("");
|
||||||
|
setSuccess(false);
|
||||||
|
}
|
||||||
|
}, [isOpen]);
|
||||||
|
|
||||||
const handleSubmit = async (e) => {
|
const handleSubmit = async (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
setError("");
|
setError("");
|
||||||
|
setSuccess(false);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Only send role if roles are available from API
|
// Only send role if roles are available from API
|
||||||
@@ -364,12 +389,19 @@ const AddUserModal = ({ isOpen, onClose, onUserCreated, roles }) => {
|
|||||||
username: formData.username,
|
username: formData.username,
|
||||||
email: formData.email,
|
email: formData.email,
|
||||||
password: formData.password,
|
password: formData.password,
|
||||||
|
first_name: formData.first_name,
|
||||||
|
last_name: formData.last_name,
|
||||||
};
|
};
|
||||||
if (roles && Array.isArray(roles) && roles.length > 0) {
|
if (roles && Array.isArray(roles) && roles.length > 0) {
|
||||||
payload.role = formData.role;
|
payload.role = formData.role;
|
||||||
}
|
}
|
||||||
await adminUsersAPI.create(payload);
|
await adminUsersAPI.create(payload);
|
||||||
|
setSuccess(true);
|
||||||
onUserCreated();
|
onUserCreated();
|
||||||
|
// Auto-close after 1.5 seconds
|
||||||
|
setTimeout(() => {
|
||||||
|
onClose();
|
||||||
|
}, 1500);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setError(err.response?.data?.error || "Failed to create user");
|
setError(err.response?.data?.error || "Failed to create user");
|
||||||
} finally {
|
} finally {
|
||||||
@@ -517,6 +549,17 @@ const AddUserModal = ({ isOpen, onClose, onUserCreated, roles }) => {
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{success && (
|
||||||
|
<div className="bg-green-50 dark:bg-green-900 border border-green-200 dark:border-green-700 rounded-md p-3">
|
||||||
|
<div className="flex items-center">
|
||||||
|
<CheckCircle className="h-4 w-4 text-green-600 dark:text-green-400 mr-2" />
|
||||||
|
<p className="text-sm text-green-700 dark:text-green-300">
|
||||||
|
User created successfully!
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{error && (
|
{error && (
|
||||||
<div className="bg-danger-50 dark:bg-danger-900 border border-danger-200 dark:border-danger-700 rounded-md p-3">
|
<div className="bg-danger-50 dark:bg-danger-900 border border-danger-200 dark:border-danger-700 rounded-md p-3">
|
||||||
<p className="text-sm text-danger-700 dark:text-danger-300">
|
<p className="text-sm text-danger-700 dark:text-danger-300">
|
||||||
@@ -566,15 +609,44 @@ const EditUserModal = ({ user, isOpen, onClose, onUserUpdated, roles }) => {
|
|||||||
});
|
});
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
const [error, setError] = useState("");
|
const [error, setError] = useState("");
|
||||||
|
const [success, setSuccess] = useState(false);
|
||||||
|
|
||||||
|
// Update formData when user prop changes or modal opens
|
||||||
|
useEffect(() => {
|
||||||
|
if (user && isOpen) {
|
||||||
|
setFormData({
|
||||||
|
username: user.username || "",
|
||||||
|
email: user.email || "",
|
||||||
|
first_name: user.first_name || "",
|
||||||
|
last_name: user.last_name || "",
|
||||||
|
role: user.role || "user",
|
||||||
|
is_active: user.is_active ?? true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [user, isOpen]);
|
||||||
|
|
||||||
|
// Reset error and success when modal closes
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isOpen) {
|
||||||
|
setError("");
|
||||||
|
setSuccess(false);
|
||||||
|
}
|
||||||
|
}, [isOpen]);
|
||||||
|
|
||||||
const handleSubmit = async (e) => {
|
const handleSubmit = async (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
setError("");
|
setError("");
|
||||||
|
setSuccess(false);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await adminUsersAPI.update(user.id, formData);
|
await adminUsersAPI.update(user.id, formData);
|
||||||
|
setSuccess(true);
|
||||||
onUserUpdated();
|
onUserUpdated();
|
||||||
|
// Auto-close after 1.5 seconds
|
||||||
|
setTimeout(() => {
|
||||||
|
onClose();
|
||||||
|
}, 1500);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setError(err.response?.data?.error || "Failed to update user");
|
setError(err.response?.data?.error || "Failed to update user");
|
||||||
} finally {
|
} finally {
|
||||||
@@ -718,6 +790,17 @@ const EditUserModal = ({ user, isOpen, onClose, onUserUpdated, roles }) => {
|
|||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{success && (
|
||||||
|
<div className="bg-green-50 dark:bg-green-900 border border-green-200 dark:border-green-700 rounded-md p-3">
|
||||||
|
<div className="flex items-center">
|
||||||
|
<CheckCircle className="h-4 w-4 text-green-600 dark:text-green-400 mr-2" />
|
||||||
|
<p className="text-sm text-green-700 dark:text-green-300">
|
||||||
|
User updated successfully!
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{error && (
|
{error && (
|
||||||
<div className="bg-danger-50 dark:bg-danger-900 border border-danger-200 dark:border-danger-700 rounded-md p-3">
|
<div className="bg-danger-50 dark:bg-danger-900 border border-danger-200 dark:border-danger-700 rounded-md p-3">
|
||||||
<p className="text-sm text-danger-700 dark:text-danger-300">
|
<p className="text-sm text-danger-700 dark:text-danger-300">
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ import {
|
|||||||
User,
|
User,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
|
|
||||||
import { useId, useState } from "react";
|
import { useEffect, useId, useState } from "react";
|
||||||
|
|
||||||
import { useAuth } from "../contexts/AuthContext";
|
import { useAuth } from "../contexts/AuthContext";
|
||||||
import { useTheme } from "../contexts/ThemeContext";
|
import { useTheme } from "../contexts/ThemeContext";
|
||||||
@@ -45,6 +45,18 @@ const Profile = () => {
|
|||||||
last_name: user?.last_name || "",
|
last_name: user?.last_name || "",
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Update profileData when user data changes
|
||||||
|
useEffect(() => {
|
||||||
|
if (user) {
|
||||||
|
setProfileData({
|
||||||
|
username: user.username || "",
|
||||||
|
email: user.email || "",
|
||||||
|
first_name: user.first_name || "",
|
||||||
|
last_name: user.last_name || "",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [user]);
|
||||||
|
|
||||||
const [passwordData, setPasswordData] = useState({
|
const [passwordData, setPasswordData] = useState({
|
||||||
currentPassword: "",
|
currentPassword: "",
|
||||||
newPassword: "",
|
newPassword: "",
|
||||||
|
|||||||
100
setup.sh
100
setup.sh
@@ -254,7 +254,7 @@ check_prerequisites() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
select_branch() {
|
select_branch() {
|
||||||
print_info "Fetching available branches from GitHub repository..."
|
print_info "Fetching available releases from GitHub repository..."
|
||||||
|
|
||||||
# Create temporary directory for git operations
|
# Create temporary directory for git operations
|
||||||
TEMP_DIR="/tmp/patchmon_branches_$$"
|
TEMP_DIR="/tmp/patchmon_branches_$$"
|
||||||
@@ -263,84 +263,88 @@ select_branch() {
|
|||||||
|
|
||||||
# Try to clone the repository normally
|
# Try to clone the repository normally
|
||||||
if git clone "$DEFAULT_GITHUB_REPO" . 2>/dev/null; then
|
if git clone "$DEFAULT_GITHUB_REPO" . 2>/dev/null; then
|
||||||
# Get list of remote branches and trim whitespace
|
# Get list of tags sorted by version (semantic versioning)
|
||||||
branches=$(git branch -r | grep -v HEAD | sed 's/origin\///' | sed 's/^[[:space:]]*//' | sed 's/[[:space:]]*$//' | sort -u)
|
# Using git tag with version sorting
|
||||||
|
tags=$(git tag -l --sort=-v:refname 2>/dev/null | head -3)
|
||||||
|
|
||||||
if [ -n "$branches" ]; then
|
if [ -n "$tags" ]; then
|
||||||
print_info "Available branches with details:"
|
print_info "Available releases and branches:"
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
# Get branch information
|
# Display last 3 release tags
|
||||||
branch_count=1
|
option_count=1
|
||||||
while IFS= read -r branch; do
|
declare -A options_map
|
||||||
if [ -n "$branch" ]; then
|
|
||||||
# Get last commit date for this branch
|
|
||||||
last_commit=$(git log -1 --format="%ci" "origin/$branch" 2>/dev/null || echo "Unknown")
|
|
||||||
|
|
||||||
# Get release tag associated with this branch (if any)
|
while IFS= read -r tag; do
|
||||||
release_tag=$(git describe --tags --exact-match "origin/$branch" 2>/dev/null || echo "")
|
if [ -n "$tag" ]; then
|
||||||
|
# Get tag date and commit info
|
||||||
|
tag_date=$(git log -1 --format="%ci" "$tag" 2>/dev/null || echo "Unknown")
|
||||||
|
|
||||||
# Format the date
|
# Format the date
|
||||||
if [ "$last_commit" != "Unknown" ]; then
|
if [ "$tag_date" != "Unknown" ]; then
|
||||||
formatted_date=$(date -d "$last_commit" "+%Y-%m-%d %H:%M" 2>/dev/null || echo "$last_commit")
|
formatted_date=$(date -d "$tag_date" "+%Y-%m-%d %H:%M" 2>/dev/null || echo "$tag_date")
|
||||||
else
|
else
|
||||||
formatted_date="Unknown"
|
formatted_date="Unknown"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Display branch info
|
# Mark the first one as latest
|
||||||
printf "%2d. %-20s" "$branch_count" "$branch"
|
if [ $option_count -eq 1 ]; then
|
||||||
printf " (Last commit: %s)" "$formatted_date"
|
printf "%2d. %-20s (Latest Release - %s)\n" "$option_count" "$tag" "$formatted_date"
|
||||||
|
else
|
||||||
if [ -n "$release_tag" ]; then
|
printf "%2d. %-20s (Release - %s)\n" "$option_count" "$tag" "$formatted_date"
|
||||||
printf " [Release: %s]" "$release_tag"
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo ""
|
# Store the tag for later selection
|
||||||
branch_count=$((branch_count + 1))
|
options_map[$option_count]="$tag"
|
||||||
|
option_count=$((option_count + 1))
|
||||||
fi
|
fi
|
||||||
done <<< "$branches"
|
done <<< "$tags"
|
||||||
|
|
||||||
|
# Add main branch as an option
|
||||||
|
main_commit=$(git log -1 --format="%ci" "origin/main" 2>/dev/null || echo "Unknown")
|
||||||
|
if [ "$main_commit" != "Unknown" ]; then
|
||||||
|
formatted_main_date=$(date -d "$main_commit" "+%Y-%m-%d %H:%M" 2>/dev/null || echo "$main_commit")
|
||||||
|
else
|
||||||
|
formatted_main_date="Unknown"
|
||||||
|
fi
|
||||||
|
printf "%2d. %-20s (Development Branch - %s)\n" "$option_count" "main" "$formatted_main_date"
|
||||||
|
options_map[$option_count]="main"
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
# Determine default selection: prefer 'main' if present
|
# Default to option 1 (latest release tag)
|
||||||
main_index=$(echo "$branches" | nl -w1 -s':' | awk -F':' '$2=="main"{print $1}' | head -1)
|
default_option=1
|
||||||
if [ -z "$main_index" ]; then
|
|
||||||
main_index=1
|
|
||||||
fi
|
|
||||||
|
|
||||||
while true; do
|
while true; do
|
||||||
read_input "Select branch number" BRANCH_NUMBER "$main_index"
|
read_input "Select version/branch number" SELECTION_NUMBER "$default_option"
|
||||||
|
|
||||||
if [[ "$BRANCH_NUMBER" =~ ^[0-9]+$ ]]; then
|
if [[ "$SELECTION_NUMBER" =~ ^[0-9]+$ ]]; then
|
||||||
selected_branch=$(echo "$branches" | sed -n "${BRANCH_NUMBER}p" | sed 's/^[[:space:]]*//' | sed 's/[[:space:]]*$//')
|
selected_option="${options_map[$SELECTION_NUMBER]}"
|
||||||
if [ -n "$selected_branch" ]; then
|
if [ -n "$selected_option" ]; then
|
||||||
DEPLOYMENT_BRANCH="$selected_branch"
|
DEPLOYMENT_BRANCH="$selected_option"
|
||||||
|
|
||||||
# Show additional info for selected branch
|
# Show confirmation
|
||||||
last_commit=$(git log -1 --format="%ci" "origin/$selected_branch" 2>/dev/null || echo "Unknown")
|
if [ "$selected_option" = "main" ]; then
|
||||||
release_tag=$(git describe --tags --exact-match "origin/$selected_branch" 2>/dev/null || echo "")
|
print_status "Selected branch: main (latest development code)"
|
||||||
|
print_info "Last commit: $formatted_main_date"
|
||||||
if [ "$last_commit" != "Unknown" ]; then
|
|
||||||
formatted_date=$(date -d "$last_commit" "+%Y-%m-%d %H:%M" 2>/dev/null || echo "$last_commit")
|
|
||||||
else
|
else
|
||||||
formatted_date="Unknown"
|
print_status "Selected release: $selected_option"
|
||||||
|
tag_date=$(git log -1 --format="%ci" "$selected_option" 2>/dev/null || echo "Unknown")
|
||||||
|
if [ "$tag_date" != "Unknown" ]; then
|
||||||
|
formatted_date=$(date -d "$tag_date" "+%Y-%m-%d %H:%M" 2>/dev/null || echo "$tag_date")
|
||||||
|
print_info "Release date: $formatted_date"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
print_status "Selected branch: $DEPLOYMENT_BRANCH"
|
|
||||||
print_info "Last commit: $formatted_date"
|
|
||||||
if [ -n "$release_tag" ]; then
|
|
||||||
print_info "Release tag: $release_tag"
|
|
||||||
fi
|
fi
|
||||||
break
|
break
|
||||||
else
|
else
|
||||||
print_error "Invalid branch number. Please try again."
|
print_error "Invalid selection number. Please try again."
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
print_error "Please enter a valid number."
|
print_error "Please enter a valid number."
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
else
|
else
|
||||||
print_warning "No branches found, using default: main"
|
print_warning "No release tags found, using default: main"
|
||||||
DEPLOYMENT_BRANCH="main"
|
DEPLOYMENT_BRANCH="main"
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
|
|||||||
Reference in New Issue
Block a user