diff --git a/backend/app/git/__init__.py b/backend/app/git/__init__.py index 3f7b9dc..2d88672 100644 --- a/backend/app/git/__init__.py +++ b/backend/app/git/__init__.py @@ -5,7 +5,6 @@ from .branches.manager import Branch_Manager from .operations.manager import GitOperations from .repo.unlink import unlink_repository from .repo.clone import clone_repository -from .auth.authenticate import validate_git_token from ..settings_utils import save_settings import logging @@ -18,6 +17,7 @@ REPO_PATH = '/app/data/db' branch_manager = Branch_Manager(REPO_PATH) git_operations = GitOperations(REPO_PATH) + @bp.route('/clone', methods=['POST']) def handle_clone_repository(): try: @@ -25,25 +25,21 @@ def handle_clone_repository(): logger.info(f"Received new settings: {new_settings}") # Validate required fields - required_fields = ['gitRepo', 'gitToken'] - for field in required_fields: - if field not in new_settings: - logger.error(f"Missing required field: {field}") - return jsonify({"error": f"Missing required field: {field}"}), 400 - - # Validate Git token - if not validate_git_token(new_settings['gitRepo'], new_settings['gitToken']): - logger.warning("Invalid Git token provided") - return jsonify({"error": "Invalid Git token. Please check your credentials and try again."}), 401 + if 'gitRepo' not in new_settings: + logger.error("Missing required field: gitRepo") + return jsonify({"error": "Missing required field: gitRepo"}), 400 # Attempt to clone the repository - success, message = clone_repository(new_settings['gitRepo'], REPO_PATH, new_settings['gitToken']) + success, message = clone_repository(new_settings['gitRepo'], REPO_PATH) if success: - # Only save the settings if the clone was successful - save_settings(new_settings) + # Only save the repository URL if the clone was successful + save_settings({'gitRepo': new_settings['gitRepo']}) logger.info("Settings updated and repository cloned successfully") - return jsonify({"message": "Repository cloned and settings updated successfully"}), 200 + return jsonify({ + "message": + "Repository cloned and settings updated successfully" + }), 200 else: logger.error(f"Failed to clone repository: {message}") return jsonify({"error": message}), 400 @@ -64,11 +60,13 @@ def get_status(): logger.error(f"Failed to retrieve git status: {message}") return jsonify({'success': False, 'error': message}), 400 + @bp.route('/branch', methods=['POST']) def create_branch(): branch_name = request.json.get('name') base_branch = request.json.get('base', 'main') - logger.debug(f"Received request to create branch {branch_name} from {base_branch}") + logger.debug( + f"Received request to create branch {branch_name} from {base_branch}") success, result = branch_manager.create(branch_name, base_branch) if success: logger.debug(f"Successfully created branch: {branch_name}") @@ -77,6 +75,7 @@ def create_branch(): logger.error(f"Failed to create branch: {result}") return jsonify({'success': False, 'error': result}), 400 + @bp.route('/branches', methods=['GET']) def get_branches(): logger.debug("Received request for branches") @@ -88,6 +87,7 @@ def get_branches(): logger.error(f"Failed to retrieve branches: {result}") return jsonify({'success': False, 'error': result}), 400 + @bp.route('/checkout', methods=['POST']) def checkout_branch(): branch_name = request.json.get('branch') @@ -100,6 +100,7 @@ def checkout_branch(): logger.error(f"Failed to checkout branch: {result}") return jsonify({'success': False, 'error': result}), 400 + @bp.route('/branch/', methods=['DELETE']) def delete_branch(branch_name): logger.debug(f"Received request to delete branch: {branch_name}") @@ -110,25 +111,31 @@ def delete_branch(branch_name): else: logger.error(f"Failed to delete branch: {result}") return jsonify({'success': False, 'error': result}), 400 - + + @bp.route('/branch/push', methods=['POST']) def push_branch(): data = request.json logger.debug(f"Received request to push branch: {data}") branch_name = data.get('branch') if not branch_name: - return jsonify({"success": False, "error": "Branch name is required"}), 400 - + return jsonify({ + "success": False, + "error": "Branch name is required" + }), 400 + success, result = branch_manager.push(branch_name) if success: return jsonify({"success": True, "data": result}), 200 else: return jsonify({"success": False, "error": result["error"]}), 500 + @bp.route('/push', methods=['POST']) def push_files(): files = request.json.get('files', []) - user_commit_message = request.json.get('commit_message', "Commit and push staged files") + user_commit_message = request.json.get('commit_message', + "Commit and push staged files") logger.debug(f"Received request to push files: {files}") commit_message = generate_commit_message(user_commit_message, files) success, message = git_operations.push(files, commit_message) @@ -139,11 +146,15 @@ def push_files(): logger.error(f"Error pushing files: {message}") return jsonify({'success': False, 'error': message}), 400 + @bp.route('/revert', methods=['POST']) def revert_file(): file_path = request.json.get('file_path') if not file_path: - return jsonify({'success': False, 'error': "File path is required."}), 400 + return jsonify({ + 'success': False, + 'error': "File path is required." + }), 400 success, message = git_operations.revert(file_path) if success: return jsonify({'success': True, 'message': message}), 200 @@ -151,6 +162,7 @@ def revert_file(): logger.error(f"Error reverting file: {message}") return jsonify({'success': False, 'error': message}), 400 + @bp.route('/revert-all', methods=['POST']) def revert_all(): success, message = git_operations.revert_all() @@ -160,11 +172,15 @@ def revert_all(): logger.error(f"Error reverting all changes: {message}") return jsonify({'success': False, 'error': message}), 400 + @bp.route('/file', methods=['DELETE']) def delete_file(): file_path = request.json.get('file_path') if not file_path: - return jsonify({'success': False, 'error': "File path is required."}), 400 + return jsonify({ + 'success': False, + 'error': "File path is required." + }), 400 success, message = git_operations.delete(file_path) if success: return jsonify({'success': True, 'message': message}), 200 @@ -172,6 +188,7 @@ def delete_file(): logger.error(f"Error deleting file: {message}") return jsonify({'success': False, 'error': message}), 400 + @bp.route('/pull', methods=['POST']) def pull_branch(): branch_name = request.json.get('branch') @@ -182,6 +199,7 @@ def pull_branch(): logger.error(f"Error pulling branch: {message}") return jsonify({'success': False, 'error': message}), 400 + @bp.route('/diff', methods=['POST']) def diff_file(): file_path = request.json.get('file_path') @@ -190,8 +208,13 @@ def diff_file(): logger.debug(f"Diff for file {file_path}: {diff}") return jsonify({'success': True, 'diff': diff if diff else ""}), 200 except Exception as e: - logger.error(f"Error getting diff for file {file_path}: {str(e)}", exc_info=True) - return jsonify({'success': False, 'error': f"Error getting diff for file: {str(e)}"}), 400 + logger.error(f"Error getting diff for file {file_path}: {str(e)}", + exc_info=True) + return jsonify({ + 'success': False, + 'error': f"Error getting diff for file: {str(e)}" + }), 400 + @bp.route('/stage', methods=['POST']) def handle_stage_files(): @@ -202,6 +225,7 @@ def handle_stage_files(): else: return jsonify({'success': False, 'error': message}), 400 + @bp.route('/unlink', methods=['POST']) def unlink(): data = request.get_json() @@ -212,6 +236,7 @@ def unlink(): else: return jsonify({'success': False, 'error': message}), 400 + def generate_commit_message(user_message, files): file_changes = [] for file in files: @@ -223,4 +248,4 @@ def generate_commit_message(user_message, files): file_changes.append(f"Update: {file}") commit_message = f"{user_message}\n\nChanges:\n" + "\n".join(file_changes) - return commit_message \ No newline at end of file + return commit_message diff --git a/backend/app/git/repo/clone.py b/backend/app/git/repo/clone.py index cfb5599..a50b9a9 100644 --- a/backend/app/git/repo/clone.py +++ b/backend/app/git/repo/clone.py @@ -10,24 +10,21 @@ from ..auth.authenticate import validate_git_token logger = logging.getLogger(__name__) -def clone_repository(repo_url, repo_path, git_token): - try: - if not validate_git_token(repo_url, git_token): - logger.error("Invalid Git token provided") - return False, "Invalid Git token. Please check your credentials and try again." +def clone_repository(repo_url, repo_path): + try: temp_dir = f"{repo_path}_temp" backup_dir = f"{repo_path}_backup" - + logger.info(f"Cloning repository from {repo_url} to {temp_dir}") - auth_repo_url = repo_url.replace('https://', f'https://{git_token}:x-oauth-basic@') - + try: - repo = git.Repo.clone_from(auth_repo_url, temp_dir) + repo = git.Repo.clone_from(repo_url, temp_dir) logger.info("Repository cloned successfully") except GitCommandError as e: if "remote: Repository not found" in str(e): - logger.info("Repository not found. Creating a new empty repository.") + logger.info( + "Repository not found. Creating a new empty repository.") repo = git.Repo.init(temp_dir) repo.create_remote('origin', repo_url) else: @@ -37,7 +34,8 @@ def clone_repository(repo_url, repo_path, git_token): try: repo.head.reference except ValueError: - logger.info("Repository is empty. Initializing with basic structure.") + logger.info( + "Repository is empty. Initializing with basic structure.") _initialize_empty_repo(repo) if os.path.exists(repo_path): @@ -55,11 +53,16 @@ def clone_repository(repo_url, repo_path, git_token): logger.info(f"Creating missing folder: {folder_name}") os.makedirs(folder_path) - cloned_files = [f for f in os.listdir(folder_path) if f.endswith('.yml')] + cloned_files = [ + f for f in os.listdir(folder_path) if f.endswith('.yml') + ] cloned_ids = set(int(f.split('.')[0]) for f in cloned_files) if os.path.exists(backup_folder_path): - local_files = [f for f in os.listdir(backup_folder_path) if f.endswith('.yml')] + local_files = [ + f for f in os.listdir(backup_folder_path) + if f.endswith('.yml') + ] for file_name in local_files: old_file_path = os.path.join(backup_folder_path, file_name) with open(old_file_path, 'r') as file: @@ -90,15 +93,21 @@ def clone_repository(repo_url, repo_path, git_token): shutil.move(backup_dir, repo_path) return False, f"Unexpected error: {str(e)}" + def _initialize_empty_repo(repo): # Create basic folder structure - os.makedirs(os.path.join(repo.working_tree_dir, 'regex_patterns'), exist_ok=True) - os.makedirs(os.path.join(repo.working_tree_dir, 'custom_formats'), exist_ok=True) - os.makedirs(os.path.join(repo.working_tree_dir, 'quality_profiles'), exist_ok=True) + os.makedirs(os.path.join(repo.working_tree_dir, 'regex_patterns'), + exist_ok=True) + os.makedirs(os.path.join(repo.working_tree_dir, 'custom_formats'), + exist_ok=True) + os.makedirs(os.path.join(repo.working_tree_dir, 'quality_profiles'), + exist_ok=True) # Create a README file with open(os.path.join(repo.working_tree_dir, 'README.md'), 'w') as f: - f.write("# Profilarr Repository\n\nThis repository contains regex patterns, custom formats and quality profiles.") + f.write( + "# Profilarr Repository\n\nThis repository contains regex patterns, custom formats and quality profiles." + ) repo.git.add(A=True) repo.index.commit("Initial commit: Basic repository structure") @@ -108,4 +117,6 @@ def _initialize_empty_repo(repo): origin.push('main') origin.push('main:main') - logger.info(f"Initialized empty repository with basic structure and pushed to main") \ No newline at end of file + logger.info( + f"Initialized empty repository with basic structure and pushed to main" + ) diff --git a/frontend/src/api/api.js b/frontend/src/api/api.js index fae092e..292e30e 100644 --- a/frontend/src/api/api.js +++ b/frontend/src/api/api.js @@ -3,306 +3,336 @@ import axios from 'axios'; const API_BASE_URL = 'http://localhost:5000'; export const getRegexes = async () => { - try { - const response = await axios.get(`${API_BASE_URL}/regex`); - return response.data; - } catch (error) { - console.error('Error fetching regexes:', error); - throw error; - } + try { + const response = await axios.get(`${API_BASE_URL}/regex`); + return response.data; + } catch (error) { + console.error('Error fetching regexes:', error); + throw error; + } }; -export const saveRegex = async (regex) => { - try { - const response = await axios.post(`${API_BASE_URL}/regex`, regex); - return response.data; - } catch (error) { - console.error('Error saving regex:', error); - throw error; - } +export const saveRegex = async regex => { + try { + const response = await axios.post(`${API_BASE_URL}/regex`, regex); + return response.data; + } catch (error) { + console.error('Error saving regex:', error); + throw error; + } }; export const updateRegex = async (id, regex) => { - try { - const response = await axios.put(`${API_BASE_URL}/regex/${id}`, regex); - return response.data; - } catch (error) { - console.error('Error updating regex:', error); - throw error; - } + try { + const response = await axios.put(`${API_BASE_URL}/regex/${id}`, regex); + return response.data; + } catch (error) { + console.error('Error updating regex:', error); + throw error; + } }; export const deleteRegex = async (id, force = false) => { - try { - const response = await axios.delete(`${API_BASE_URL}/regex/${id}${force ? '?force=true' : ''}`, { - validateStatus: (status) => { - return status >= 200 && status < 300 || status === 400 || status === 409; - } - }); - return response.data; - } catch (error) { - console.error('Error deleting regex:', error); - throw error; - } + try { + const response = await axios.delete( + `${API_BASE_URL}/regex/${id}${force ? '?force=true' : ''}`, + { + validateStatus: status => { + return ( + (status >= 200 && status < 300) || status === 400 || status === 409 + ); + } + } + ); + return response.data; + } catch (error) { + console.error('Error deleting regex:', error); + throw error; + } }; export const getFormats = async () => { - try { - const response = await axios.get(`${API_BASE_URL}/format`); - return response.data; - } catch (error) { - console.error('Error fetching formats:', error); - throw error; - } + try { + const response = await axios.get(`${API_BASE_URL}/format`); + return response.data; + } catch (error) { + console.error('Error fetching formats:', error); + throw error; + } }; -export const saveFormat = async (format) => { - try { - const response = await axios.post(`${API_BASE_URL}/format`, format); - return response.data; - } catch (error) { - console.error('Error saving format:', error); - throw error; - } +export const saveFormat = async format => { + try { + const response = await axios.post(`${API_BASE_URL}/format`, format); + return response.data; + } catch (error) { + console.error('Error saving format:', error); + throw error; + } }; export const updateFormat = async (id, format) => { - try { - const response = await axios.put(`${API_BASE_URL}/format/${id}`, format); - return response.data; - } catch (error) { - console.error('Error updating format:', error); - throw error; - } + try { + const response = await axios.put(`${API_BASE_URL}/format/${id}`, format); + return response.data; + } catch (error) { + console.error('Error updating format:', error); + throw error; + } }; export const deleteFormat = async (id, force = false) => { - try { - const response = await axios.delete(`${API_BASE_URL}/format/${id}${force ? '?force=true' : ''}`, { - validateStatus: (status) => { - return status >= 200 && status < 300 || status === 400 || status === 409; - } - }); - return response.data; - } catch (error) { - console.error('Error deleting format:', error); - throw error; - } + try { + const response = await axios.delete( + `${API_BASE_URL}/format/${id}${force ? '?force=true' : ''}`, + { + validateStatus: status => { + return ( + (status >= 200 && status < 300) || status === 400 || status === 409 + ); + } + } + ); + return response.data; + } catch (error) { + console.error('Error deleting format:', error); + throw error; + } }; -export const createRegex101Link = async (regexData) => { - try { - const response = await axios.post(`${API_BASE_URL}/regex/regex101`, regexData); - return response.data; - } catch (error) { - console.error('Error creating regex101 link:', error); - throw error; - } +export const createRegex101Link = async regexData => { + try { + const response = await axios.post( + `${API_BASE_URL}/regex/regex101`, + regexData + ); + return response.data; + } catch (error) { + console.error('Error creating regex101 link:', error); + throw error; + } }; export const getSettings = async () => { - try { - const response = await axios.get(`${API_BASE_URL}/settings`); - return response.data; - } catch (error) { - console.error('Error fetching settings:', error); - throw error; - } + try { + const response = await axios.get(`${API_BASE_URL}/settings`); + return response.data; + } catch (error) { + console.error('Error fetching settings:', error); + throw error; + } }; export const getGitStatus = async () => { - try { - const response = await axios.get(`${API_BASE_URL}/git/status`); - return response.data; - } catch (error) { - console.error('Error fetching Git status:', error); - throw error; - } + try { + const response = await axios.get(`${API_BASE_URL}/git/status`); + return response.data; + } catch (error) { + console.error('Error fetching Git status:', error); + throw error; + } }; export const getBranches = async () => { - try { - const response = await axios.get(`${API_BASE_URL}/git/branches`); - return response.data; - } catch (error) { - console.error('Error fetching branches:', error); - throw error; - } + try { + const response = await axios.get(`${API_BASE_URL}/git/branches`); + return response.data; + } catch (error) { + console.error('Error fetching branches:', error); + throw error; + } }; -export const checkoutBranch = async (branchName) => { - try { - const response = await axios.post(`${API_BASE_URL}/git/checkout`, { branch: branchName }); - return response.data; - } catch (error) { - console.error('Error checking out branch:', error); - throw error; - } +export const checkoutBranch = async branchName => { + try { + const response = await axios.post(`${API_BASE_URL}/git/checkout`, { + branch: branchName + }); + return response.data; + } catch (error) { + console.error('Error checking out branch:', error); + throw error; + } }; export const createBranch = async (branchName, baseBranch) => { - try { - const response = await axios.post(`${API_BASE_URL}/git/branch`, { name: branchName, base: baseBranch }); - return response.data; - } catch (error) { - console.error('Error creating branch:', error); - throw error; - } + try { + const response = await axios.post(`${API_BASE_URL}/git/branch`, { + name: branchName, + base: baseBranch + }); + return response.data; + } catch (error) { + console.error('Error creating branch:', error); + throw error; + } }; -export const deleteBranch = async (branchName) => { - try { - const response = await axios.delete(`${API_BASE_URL}/git/branch/${branchName}`); - return response.data; - } catch (error) { - console.error('Error deleting branch:', error); - throw error; - } +export const deleteBranch = async branchName => { + try { + const response = await axios.delete( + `${API_BASE_URL}/git/branch/${branchName}` + ); + return response.data; + } catch (error) { + console.error('Error deleting branch:', error); + throw error; + } }; -export const addFiles = async (files) => { - try { - const response = await axios.post(`${API_BASE_URL}/git/stage`, { files }); - return response.data; - } catch (error) { - console.error('Error staging files:', error); - throw error; - } +export const addFiles = async files => { + try { + const response = await axios.post(`${API_BASE_URL}/git/stage`, {files}); + return response.data; + } catch (error) { + console.error('Error staging files:', error); + throw error; + } }; export const pushFiles = async (files, commitMessage) => { - try { - const response = await axios.post(`${API_BASE_URL}/git/push`, { - files, - commit_message: commitMessage - }); - return response.data; - } catch (error) { - console.error('Error pushing files:', error); - throw error; - } + try { + const response = await axios.post(`${API_BASE_URL}/git/push`, { + files, + commit_message: commitMessage + }); + return response.data; + } catch (error) { + console.error('Error pushing files:', error); + throw error; + } }; -export const revertFile = async (filePath) => { - try { - const response = await axios.post(`${API_BASE_URL}/git/revert`, { - file_path: filePath - }); - return response.data; - } catch (error) { - console.error('Error reverting file:', error); - throw error; - } +export const revertFile = async filePath => { + try { + const response = await axios.post(`${API_BASE_URL}/git/revert`, { + file_path: filePath + }); + return response.data; + } catch (error) { + console.error('Error reverting file:', error); + throw error; + } }; export const revertAll = async () => { - try { - const response = await axios.post(`${API_BASE_URL}/git/revert-all`); - return response.data; - } catch (error) { - console.error('Error reverting all changes:', error); - throw error; - } + try { + const response = await axios.post(`${API_BASE_URL}/git/revert-all`); + return response.data; + } catch (error) { + console.error('Error reverting all changes:', error); + throw error; + } }; -export const deleteFile = async (filePath) => { - try { - const response = await axios.delete(`${API_BASE_URL}/git/file`, { - data: { file_path: filePath }, - }); - return response.data; - } catch (error) { - console.error('Error deleting file:', error); - return { success: false, error: 'Error deleting file' }; - } +export const deleteFile = async filePath => { + try { + const response = await axios.delete(`${API_BASE_URL}/git/file`, { + data: {file_path: filePath} + }); + return response.data; + } catch (error) { + console.error('Error deleting file:', error); + return {success: false, error: 'Error deleting file'}; + } }; -export const pullBranch = async (branchName) => { - try { - const response = await axios.post(`${API_BASE_URL}/git/pull`, { branch: branchName }); - return response.data; - } catch (error) { - console.error('Error pulling branch:', error); - throw error; - } +export const pullBranch = async branchName => { + try { + const response = await axios.post(`${API_BASE_URL}/git/pull`, { + branch: branchName + }); + return response.data; + } catch (error) { + console.error('Error pulling branch:', error); + throw error; + } }; -export const getDiff = async (filePath) => { - try { - const response = await axios.post(`${API_BASE_URL}/git/diff`, { file_path: filePath }); - return response.data; - } catch (error) { - console.error('Error fetching diff:', error); - throw error; - } +export const getDiff = async filePath => { + try { + const response = await axios.post(`${API_BASE_URL}/git/diff`, { + file_path: filePath + }); + return response.data; + } catch (error) { + console.error('Error fetching diff:', error); + throw error; + } }; -export const cloneRepo = async (gitRepo, gitToken) => { - try { - const response = await axios.post(`${API_BASE_URL}/git/clone`, { gitRepo, gitToken }); - return response.data; - } catch (error) { - console.error('Error cloning repository:', error); - throw error; - } +export const cloneRepo = async gitRepo => { + try { + const response = await axios.post(`${API_BASE_URL}/git/clone`, { + gitRepo + }); + return response.data; + } catch (error) { + console.error('Error cloning repository:', error); + throw error; + } }; export const getProfiles = async () => { - try { - const response = await axios.get(`${API_BASE_URL}/profile`); - return response.data; - } catch (error) { - console.error('Error fetching profiles:', error); - throw error; - } + try { + const response = await axios.get(`${API_BASE_URL}/profile`); + return response.data; + } catch (error) { + console.error('Error fetching profiles:', error); + throw error; + } }; -export const saveProfile = async (profile) => { - try { - const response = await axios.post(`${API_BASE_URL}/profile`, profile); - return response.data; - } catch (error) { - console.error('Error saving profile:', error); - throw error; - } +export const saveProfile = async profile => { + try { + const response = await axios.post(`${API_BASE_URL}/profile`, profile); + return response.data; + } catch (error) { + console.error('Error saving profile:', error); + throw error; + } }; export const updateProfile = async (id, profile) => { - try { - const response = await axios.put(`${API_BASE_URL}/profile/${id}`, profile); - return response.data; - } catch (error) { - console.error('Error updating profile:', error); - throw error; - } + try { + const response = await axios.put(`${API_BASE_URL}/profile/${id}`, profile); + return response.data; + } catch (error) { + console.error('Error updating profile:', error); + throw error; + } }; -export const deleteProfile = async (id) => { - try { - const response = await axios.delete(`${API_BASE_URL}/profile/${id}`); - return response.data; - } catch (error) { - console.error('Error deleting profile:', error); - throw error; - } +export const deleteProfile = async id => { + try { + const response = await axios.delete(`${API_BASE_URL}/profile/${id}`); + return response.data; + } catch (error) { + console.error('Error deleting profile:', error); + throw error; + } }; export const unlinkRepo = async (removeFiles = false) => { - try { - const response = await axios.post(`${API_BASE_URL}/git/unlink`, { removeFiles }); - return response.data; - } catch (error) { - console.error('Error unlinking repository:', error); - throw error; - } + try { + const response = await axios.post(`${API_BASE_URL}/git/unlink`, { + removeFiles + }); + return response.data; + } catch (error) { + console.error('Error unlinking repository:', error); + throw error; + } }; -export const pushBranchToRemote = async (branchName) => { - try { - const response = await axios.post(`${API_BASE_URL}/git/branch/push`, { branch: branchName }); - return response.data; - } catch (error) { - console.error('Error pushing branch to remote:', error); - throw error; - } -}; \ No newline at end of file +export const pushBranchToRemote = async branchName => { + try { + const response = await axios.post(`${API_BASE_URL}/git/branch/push`, { + branch: branchName + }); + return response.data; + } catch (error) { + console.error('Error pushing branch to remote:', error); + throw error; + } +}; diff --git a/frontend/src/components/settings/ApiKeyModal.jsx b/frontend/src/components/settings/ApiKeyModal.jsx deleted file mode 100644 index f77ed67..0000000 --- a/frontend/src/components/settings/ApiKeyModal.jsx +++ /dev/null @@ -1,74 +0,0 @@ -import React, { useState } from 'react'; -import Modal from '../ui/Modal'; -import { Loader } from 'lucide-react'; -import { cloneRepo } from '../../api/api'; -import Alert from '../ui/Alert'; - -const ApiKeyModal = ({ isOpen, onClose, onSubmit }) => { - const [gitRepo, setGitRepo] = useState(''); - const [apiKey, setApiKey] = useState(''); - const [loading, setLoading] = useState(false); - - const handleSubmit = async () => { - if (!gitRepo || !apiKey) { - Alert.error("Please fill in all fields."); - return; - } - setLoading(true); - try { - const response = await cloneRepo(gitRepo, apiKey); - Alert.success(response.message || "Repository cloned successfully!"); - onSubmit(); - } catch (error) { - Alert.error("An unexpected error occurred while cloning the repository."); - console.error("Error cloning repository:", error); - } finally { - setLoading(false); - } - }; - - return ( - -
-
- - setGitRepo(e.target.value)} - className="w-full p-2 border rounded bg-gray-900 text-gray-100 border-gray-600 focus:outline-none focus:ring-2 focus:ring-blue-500" - placeholder="https://github.com/your-repo.git" - /> -
-
- - setApiKey(e.target.value)} - className="w-full p-2 border rounded bg-gray-900 text-gray-100 border-gray-600 focus:outline-none focus:ring-2 focus:ring-blue-500" - placeholder="Your GitHub Token" - /> -
-
- -
-
-
- ); -}; - -export default ApiKeyModal; \ No newline at end of file diff --git a/frontend/src/components/settings/LinkRepoModal.jsx b/frontend/src/components/settings/LinkRepoModal.jsx new file mode 100644 index 0000000..fd86606 --- /dev/null +++ b/frontend/src/components/settings/LinkRepoModal.jsx @@ -0,0 +1,71 @@ +import React, {useState} from 'react'; +import Modal from '../ui/Modal'; +import {Loader} from 'lucide-react'; +import {cloneRepo} from '../../api/api'; +import Alert from '../ui/Alert'; + +const LinkRepoModal = ({isOpen, onClose, onSubmit}) => { + const [gitRepo, setGitRepo] = useState(''); + const [loading, setLoading] = useState(false); + + const handleSubmit = async () => { + if (!gitRepo) { + Alert.error('Please enter a repository URL.'); + return; + } + setLoading(true); + try { + const response = await cloneRepo(gitRepo); + Alert.success( + response.message || 'Repository linked successfully!' + ); + onSubmit(); + } catch (error) { + Alert.error( + 'An unexpected error occurred while linking the repository.' + ); + console.error('Error linking repository:', error); + } finally { + setLoading(false); + } + }; + + return ( + +
+
+ + setGitRepo(e.target.value)} + className='w-full p-2 border rounded bg-gray-900 text-gray-100 border-gray-600 focus:outline-none focus:ring-2 focus:ring-blue-500' + placeholder='https://github.com/your-repo.git' + /> +
+
+ +
+
+
+ ); +}; + +export default LinkRepoModal; diff --git a/frontend/src/components/settings/SettingsPage.jsx b/frontend/src/components/settings/SettingsPage.jsx index 23d16a1..7bb9b49 100644 --- a/frontend/src/components/settings/SettingsPage.jsx +++ b/frontend/src/components/settings/SettingsPage.jsx @@ -1,849 +1,761 @@ import React, {useState, useEffect} from 'react'; import { - getSettings, - getGitStatus, - addFiles, - pushFiles, - revertFile, - pullBranch, - getDiff, - unlinkRepo + getSettings, + getGitStatus, + addFiles, + pushFiles, + revertFile, + pullBranch, + getDiff, + unlinkRepo } from '../../api/api'; -import ApiKeyModal from './ApiKeyModal'; import UnlinkModal from './UnlinkModal'; import SettingsBranchModal from './SettingsBranchModal'; import { - FileText, - Code, - AlertCircle, - Plus, - MinusCircle, - Edit, - GitBranch, - Loader, - Eye, - RotateCcw, - Download, - ArrowDown, - ArrowUp, - CheckCircle, - File, - Settings, - Unlink + FileText, + Code, + AlertCircle, + Plus, + MinusCircle, + Edit, + GitBranch, + Loader, + Eye, + RotateCcw, + Download, + ArrowDown, + ArrowUp, + CheckCircle, + File, + Settings, + Unlink } from 'lucide-react'; import Alert from '../ui/Alert'; import CommitSection from './CommitSection'; import Tooltip from '../ui/Tooltip'; import DiffModal from './DiffModal'; +import LinkRepoModal from './LinkRepoModal'; const SettingsPage = () => { - const [settings, setSettings] = useState(null); - const [status, setStatus] = useState(null); - const [showModal, setShowModal] = useState(false); - const [showBranchModal, setShowBranchModal] = useState(false); - const [loadingAction, setLoadingAction] = useState(''); - const [loadingStatus, setLoadingStatus] = useState(true); - const [loadingMessage, setLoadingMessage] = useState(''); - const [commitMessage, setCommitMessage] = useState(''); - const [showUnlinkModal, setShowUnlinkModal] = useState(false); - const [selectedIncomingChanges, setSelectedIncomingChanges] = useState([]); - const [selectedOutgoingChanges, setSelectedOutgoingChanges] = useState([]); - const [showDiffModal, setShowDiffModal] = useState(false); - const [diffContent, setDiffContent] = useState(''); - const [currentChange, setCurrentChange] = useState(null); - const [loadingDiff, setLoadingDiff] = useState(false); - const [selectionType, setSelectionType] = useState(null); - const [funMessage, setFunMessage] = useState(''); - const [sortConfig, setSortConfig] = useState({ - key: 'type', - direction: 'descending' + const [settings, setSettings] = useState(null); + const [status, setStatus] = useState(null); + const [showModal, setShowModal] = useState(false); + const [showBranchModal, setShowBranchModal] = useState(false); + const [loadingAction, setLoadingAction] = useState(''); + const [loadingStatus, setLoadingStatus] = useState(true); + const [loadingMessage, setLoadingMessage] = useState(''); + const [commitMessage, setCommitMessage] = useState(''); + const [showUnlinkModal, setShowUnlinkModal] = useState(false); + const [selectedIncomingChanges, setSelectedIncomingChanges] = useState([]); + const [selectedOutgoingChanges, setSelectedOutgoingChanges] = useState([]); + const [showDiffModal, setShowDiffModal] = useState(false); + const [diffContent, setDiffContent] = useState(''); + const [currentChange, setCurrentChange] = useState(null); + const [loadingDiff, setLoadingDiff] = useState(false); + const [selectionType, setSelectionType] = useState(null); + const [funMessage, setFunMessage] = useState(''); + const [showLinkModal, setShowLinkModal] = useState(false); + const [sortConfig, setSortConfig] = useState({ + key: 'type', + direction: 'descending' + }); + + useEffect(() => { + fetchSettings(); + }, []); + + const fetchSettings = async () => { + try { + const fetchedSettings = await getSettings(); + setSettings(fetchedSettings); + if (fetchedSettings) { + await fetchGitStatus(); + } + } catch (error) { + console.error('Error fetching settings:', error); + } + }; + + const sortedChanges = changes => { + if (!sortConfig.key) return changes; + + return [...changes].sort((a, b) => { + if (a[sortConfig.key] < b[sortConfig.key]) { + return sortConfig.direction === 'ascending' ? -1 : 1; + } + if (a[sortConfig.key] > b[sortConfig.key]) { + return sortConfig.direction === 'ascending' ? 1 : -1; + } + return 0; }); + }; - useEffect(() => { - fetchSettings(); - }, []); - - const fetchSettings = async () => { - try { - const fetchedSettings = await getSettings(); - setSettings(fetchedSettings); - if (fetchedSettings) { - await fetchGitStatus(); - } - } catch (error) { - console.error('Error fetching settings:', error); - } - }; - - const sortedChanges = changes => { - if (!sortConfig.key) return changes; - - return [...changes].sort((a, b) => { - if (a[sortConfig.key] < b[sortConfig.key]) { - return sortConfig.direction === 'ascending' ? -1 : 1; - } - if (a[sortConfig.key] > b[sortConfig.key]) { - return sortConfig.direction === 'ascending' ? 1 : -1; - } - return 0; - }); - }; - - const requestSort = key => { - let direction = 'ascending'; - if (sortConfig.key === key && sortConfig.direction === 'ascending') { - direction = 'descending'; - } - setSortConfig({key, direction}); - }; - - const SortableHeader = ({children, sortKey}) => { - const isSorted = sortConfig.key === sortKey; - return ( - requestSort(sortKey)}> -
- {children} - {isSorted && - (sortConfig.direction === 'ascending' ? ( - - ) : ( - - ))} -
- - ); - }; - - const fetchGitStatus = async () => { - setLoadingStatus(true); - setLoadingMessage(getRandomLoadingMessage()); - setFunMessage(getRandomFunMessage()); - try { - const result = await getGitStatus(); - console.log( - '================ Git Status Response ================' - ); - console.log(JSON.stringify(result, null, 2)); - console.log( - '======================================================' - ); - - if (result.success) { - setStatus({ - ...result.data, - outgoing_changes: Array.isArray( - result.data.outgoing_changes - ) - ? result.data.outgoing_changes - : [], - incoming_changes: Array.isArray( - result.data.incoming_changes - ) - ? result.data.incoming_changes - : [] - }); - } - } catch (error) { - console.error('Error fetching Git status:', error); - Alert.error('Failed to fetch Git status'); - } finally { - setLoadingStatus(false); - } - }; - - const getRandomFunMessage = () => { - const funMessages = [ - 'No changes detected. Your regex is so precise, it could find a needle in a haystack... made of needles. ๐Ÿงต๐Ÿ”', - 'All quiet on the commit front. Your custom formats are so perfect, even perfectionists are jealous. ๐Ÿ†', - "No updates needed. Your media automation is running so smoothly, it's making butter jealous. ๐Ÿงˆ", - 'Zero modifications. Your torrent setup is seeding so efficiently, farmers are asking for advice. ๐ŸŒพ', - "No edits required. Your regex fu is so strong, it's bench-pressing parentheses for fun. ๐Ÿ’ช()", - 'Unchanged status. Your Plex library is so well-organized, librarians are taking notes. ๐Ÿ“š๐Ÿค“', - "No alterations found. Your file naming scheme is so consistent, it's bringing tears to OCD eyes. ๐Ÿ˜ข๐Ÿ‘€", - "All systems nominal. Your download queue is so orderly, it's making Marie Kondo question her career. ๐Ÿงนโœจ", - "No revisions necessary. Your automation scripts are so smart, they're solving captchas for fun. ๐Ÿค–๐Ÿงฉ", - 'Steady as she goes. Your media collection is so complete, Netflix is asking you for recommendations. ๐ŸŽฌ๐Ÿ‘‘' - ]; - return funMessages[Math.floor(Math.random() * funMessages.length)]; - }; - - const renderChangeTable = (changes, title, icon, isIncoming) => ( -
-

- {icon} - - {title} ({changes.length}) - -

-
- - - - - Status - - Type - Name - - - - - - {sortedChanges(changes).map((change, index) => ( - - handleSelectChange( - change.file_path, - isIncoming - ) - }> - - - - - - - ))} - -
- Actions - - Select -
-
- {getStatusIcon(change.status)} - - {change.staged - ? `${change.status} (Staged)` - : change.status} - -
-
-
- {getTypeIcon(change.type)} - - {change.type} - -
-
- {change.name || 'Unnamed'} - - - - - - e.stopPropagation()} - disabled={!isIncoming && change.staged} - /> -
-
-
- ); - - const getStageButtonTooltip = () => { - if (selectionType === 'staged') { - return 'These files are already staged'; - } - if (selectedOutgoingChanges.length === 0) { - return 'Select files to stage'; - } - return 'Stage selected files'; - }; - - const getCommitButtonTooltip = () => { - if (selectionType === 'unstaged') { - return 'You can only commit staged files'; - } - if (selectedOutgoingChanges.length === 0) { - return 'Select files to commit'; - } - if (!commitMessage.trim()) { - return 'Enter a commit message'; - } - return 'Commit selected files'; - }; - - const getRevertButtonTooltip = () => { - if (selectedOutgoingChanges.length === 0) { - return 'Select files to revert'; - } - return 'Revert selected files'; - }; - - const handleViewDiff = async change => { - setLoadingDiff(true); - try { - const response = await getDiff(change.file_path); - console.log('Diff response:', response); // Add this line to log the response - if (response.success) { - console.log('Diff content:', response.diff); // Add this line to log the diff content - setDiffContent(response.diff); - setCurrentChange(change); - setShowDiffModal(true); - } else { - Alert.error(response.error); - } - } catch (error) { - Alert.error( - 'An unexpected error occurred while fetching the diff.' - ); - console.error('Error fetching diff:', error); - } finally { - setLoadingDiff(false); - setLoadingAction(''); - } - }; - - const handleStageSelectedChanges = async () => { - if (selectedOutgoingChanges.length === 0) { - Alert.warning('Please select at least one change to stage.'); - return; - } - - setLoadingAction('stage_selected'); - try { - const response = await addFiles(selectedOutgoingChanges); - if (response.success) { - await fetchGitStatus(); - setSelectedOutgoingChanges([]); // Clear the selected changes after staging - Alert.success(response.message); - } else { - Alert.error(response.error); - } - } catch (error) { - Alert.error('An unexpected error occurred while staging changes.'); - console.error('Error staging changes:', error); - } finally { - setLoadingAction(''); - } - }; - - const handleCommitSelectedChanges = async () => { - if (selectedOutgoingChanges.length === 0) { - Alert.warning('Please select at least one change to commit.'); - return; - } - - if (!commitMessage.trim()) { - Alert.warning('Please enter a commit message.'); - return; - } - - setLoadingAction('commit_selected'); - try { - const response = await pushFiles( - selectedOutgoingChanges, - commitMessage - ); - if (response.success) { - await fetchGitStatus(); - setSelectedOutgoingChanges([]); // Clear the selected changes after committing - setCommitMessage(''); - Alert.success(response.message); - } else { - Alert.error(response.error); - } - } catch (error) { - Alert.error( - 'An unexpected error occurred while committing changes.' - ); - console.error('Error committing changes:', error); - } finally { - setLoadingAction(''); - } - }; - - const handleRevertSelectedChanges = async () => { - if (selectedOutgoingChanges.length === 0) { - Alert.warning('Please select at least one change to revert.'); - return; - } - - setLoadingAction('revert_selected'); - try { - const response = await Promise.all( - selectedOutgoingChanges.map(filePath => revertFile(filePath)) - ); - const allSuccessful = response.every(res => res.success); - if (allSuccessful) { - await fetchGitStatus(); - setSelectedOutgoingChanges([]); // Clear the selected changes after reverting - Alert.success( - 'Selected changes have been reverted successfully.' - ); - } else { - Alert.error( - 'Some changes could not be reverted. Please try again.' - ); - } - } catch (error) { - Alert.error( - 'An unexpected error occurred while reverting changes.' - ); - console.error('Error reverting changes:', error); - } finally { - setLoadingAction(''); - } - }; - - const handlePullSelectedChanges = async () => { - if (selectedIncomingChanges.length === 0) { - Alert.warning('Please select at least one change to pull.'); - return; - } - - setLoadingAction('pull_changes'); - try { - // You would need to update your backend to handle pulling specific files - const response = await pullBranch( - status.branch, - selectedIncomingChanges - ); - if (response.success) { - await fetchGitStatus(); - setSelectedIncomingChanges([]); // Clear the selected changes after pulling - Alert.success(response.message); - } else { - Alert.error(response.error); - } - } catch (error) { - Alert.error('An unexpected error occurred while pulling changes.'); - console.error('Error pulling changes:', error); - } finally { - setLoadingAction(''); - } - }; - - const handleSelectChange = (filePath, isIncoming) => { - if (isIncoming) { - setSelectedIncomingChanges(prevSelected => { - if (prevSelected.includes(filePath)) { - return prevSelected.filter(path => path !== filePath); - } else { - return [...prevSelected, filePath]; - } - }); - } else { - const change = status.outgoing_changes.find( - c => c.file_path === filePath - ); - const isStaged = change.staged; - - setSelectedOutgoingChanges(prevSelected => { - if (prevSelected.includes(filePath)) { - const newSelection = prevSelected.filter( - path => path !== filePath - ); - if (newSelection.length === 0) setSelectionType(null); - return newSelection; - } else { - if ( - prevSelected.length === 0 || - (isStaged && selectionType === 'staged') || - (!isStaged && selectionType === 'unstaged') - ) { - setSelectionType(isStaged ? 'staged' : 'unstaged'); - return [...prevSelected, filePath]; - } else { - return prevSelected; - } - } - }); - } - }; - - const loadingMessages = [ - "Checking for changes... don't blink!", - 'Syncing with the mothership...', - 'Peeking under the hood...', - 'Counting bits and bytes...', - 'Scanning for modifications...', - 'Looking for new stuff...', - 'Comparing local and remote...', - "Checking your project's pulse...", - "Analyzing your code's mood...", - "Reading the project's diary..." - ]; - - const getRandomLoadingMessage = () => { - return loadingMessages[ - Math.floor(Math.random() * loadingMessages.length) - ]; - }; - - const getStatusIcon = status => { - switch (status) { - case 'Untracked': - return ; - case 'Staged (New)': - return ; - case 'Staged (Modified)': - case 'Modified': - return ; - case 'Deleted': - return ; - case 'Deleted (Staged)': - return ; - case 'Renamed': - return ; - default: - return ; - } - }; - - const getTypeIcon = type => { - switch (type) { - case 'Regex Pattern': - return ; - case 'Custom Format': - return ; - case 'Quality Profile': - return ; - default: - return ; - } - }; - - const handleLinkRepo = async () => { - setLoadingAction(''); - setShowModal(false); - await fetchSettings(); - }; - - const handleUnlinkRepo = async removeFiles => { - setLoadingAction('unlink_repo'); - try { - const response = await unlinkRepo(removeFiles); - if (response.success) { - setSettings(null); - setStatus(null); - Alert.success('Repository unlinked successfully'); - setShowUnlinkModal(false); // Close the modal after unlinking - } else { - Alert.error(response.error || 'Failed to unlink repository'); - } - } catch (error) { - Alert.error( - 'An unexpected error occurred while unlinking the repository' - ); - console.error('Error unlinking repository:', error); - } finally { - setLoadingAction(''); - } - }; + const requestSort = key => { + let direction = 'ascending'; + if (sortConfig.key === key && sortConfig.direction === 'ascending') { + direction = 'descending'; + } + setSortConfig({key, direction}); + }; + const SortableHeader = ({children, sortKey}) => { + const isSorted = sortConfig.key === sortKey; return ( -
-

- Git Repository Settings -

- {!settings && ( - - )} - {settings && ( -
-
-

- Connected Repository -

-
- - {settings.gitRepo} - - - - -
-
- -
-

- Git Status -

- {loadingStatus ? ( -
- - - {loadingMessage} - -
- ) : ( - status && ( - <> -
-
- - - Current Branch: {status.branch} - -
- -
- - {status.incoming_changes.length > 0 && - renderChangeTable( - status.incoming_changes, - 'Incoming Changes', - , - true - )} - {status.outgoing_changes.length > 0 && - renderChangeTable( - status.outgoing_changes, - 'Outgoing Changes', - , - false - )} - - 0 - } - funMessage={funMessage} - /> - - {/* Buttons Below Commit Section */} -
- {/* Conditionally render Stage button */} - {selectedOutgoingChanges.length > 0 && - selectionType !== 'staged' && ( - - - - )} - - {/* Conditionally render Commit button */} - {selectedOutgoingChanges.length > 0 && - commitMessage.trim() && - selectionType !== 'unstaged' && ( - - - - )} - - {/* Conditionally render Revert button */} - {selectedOutgoingChanges.length > 0 && ( - - - - )} - {/* Conditionally render Pull button */} - {selectedIncomingChanges.length > 0 && ( - - - - )} -
- - ) - )} -
-
- )} - {settings && status && ( - setShowBranchModal(false)} - repoUrl={settings.gitRepo} - currentBranch={status.branch} - onBranchChange={fetchGitStatus} - /> - )} - {showDiffModal && currentChange && ( - setShowDiffModal(false)} - diffContent={diffContent} - type={currentChange.type} - name={currentChange.name} - commitMessage={currentChange.commit_message} - /> - )} - setShowModal(false)} - onSubmit={handleLinkRepo} - /> - setShowUnlinkModal(false)} - onSubmit={handleUnlinkRepo} - /> + requestSort(sortKey)}> +
+ {children} + {isSorted && + (sortConfig.direction === 'ascending' ? ( + + ) : ( + + ))}
+ ); + }; + + const fetchGitStatus = async () => { + setLoadingStatus(true); + setLoadingMessage(getRandomLoadingMessage()); + setFunMessage(getRandomFunMessage()); + try { + const result = await getGitStatus(); + console.log('================ Git Status Response ================'); + console.log(JSON.stringify(result, null, 2)); + console.log('======================================================'); + + if (result.success) { + setStatus({ + ...result.data, + outgoing_changes: Array.isArray(result.data.outgoing_changes) + ? result.data.outgoing_changes + : [], + incoming_changes: Array.isArray(result.data.incoming_changes) + ? result.data.incoming_changes + : [] + }); + } + } catch (error) { + console.error('Error fetching Git status:', error); + Alert.error('Failed to fetch Git status'); + } finally { + setLoadingStatus(false); + } + }; + + const getRandomFunMessage = () => { + const funMessages = [ + 'No changes detected. Your regex is so precise, it could find a needle in a haystack... made of needles. ๐Ÿงต๐Ÿ”', + 'All quiet on the commit front. Your custom formats are so perfect, even perfectionists are jealous. ๐Ÿ†', + "No updates needed. Your media automation is running so smoothly, it's making butter jealous. ๐Ÿงˆ", + 'Zero modifications. Your torrent setup is seeding so efficiently, farmers are asking for advice. ๐ŸŒพ', + "No edits required. Your regex fu is so strong, it's bench-pressing parentheses for fun. ๐Ÿ’ช()", + 'Unchanged status. Your Plex library is so well-organized, librarians are taking notes. ๐Ÿ“š๐Ÿค“', + "No alterations found. Your file naming scheme is so consistent, it's bringing tears to OCD eyes. ๐Ÿ˜ข๐Ÿ‘€", + "All systems nominal. Your download queue is so orderly, it's making Marie Kondo question her career. ๐Ÿงนโœจ", + "No revisions necessary. Your automation scripts are so smart, they're solving captchas for fun. ๐Ÿค–๐Ÿงฉ", + 'Steady as she goes. Your media collection is so complete, Netflix is asking you for recommendations. ๐ŸŽฌ๐Ÿ‘‘' + ]; + return funMessages[Math.floor(Math.random() * funMessages.length)]; + }; + + const renderChangeTable = (changes, title, icon, isIncoming) => ( +
+

+ {icon} + + {title} ({changes.length}) + +

+
+ + + + Status + Type + Name + + + + + + {sortedChanges(changes).map((change, index) => ( + + handleSelectChange(change.file_path, isIncoming) + }> + + + + + + + ))} + +
+ Actions + + Select +
+
+ {getStatusIcon(change.status)} + + {change.staged + ? `${change.status} (Staged)` + : change.status} + +
+
+
+ {getTypeIcon(change.type)} + {change.type} +
+
+ {change.name || 'Unnamed'} + + + + + + e.stopPropagation()} + disabled={!isIncoming && change.staged} + /> +
+
+
+ ); + + const getStageButtonTooltip = () => { + if (selectionType === 'staged') { + return 'These files are already staged'; + } + if (selectedOutgoingChanges.length === 0) { + return 'Select files to stage'; + } + return 'Stage selected files'; + }; + + const getCommitButtonTooltip = () => { + if (selectionType === 'unstaged') { + return 'You can only commit staged files'; + } + if (selectedOutgoingChanges.length === 0) { + return 'Select files to commit'; + } + if (!commitMessage.trim()) { + return 'Enter a commit message'; + } + return 'Commit selected files'; + }; + + const getRevertButtonTooltip = () => { + if (selectedOutgoingChanges.length === 0) { + return 'Select files to revert'; + } + return 'Revert selected files'; + }; + + const handleViewDiff = async change => { + setLoadingDiff(true); + try { + const response = await getDiff(change.file_path); + console.log('Diff response:', response); // Add this line to log the response + if (response.success) { + console.log('Diff content:', response.diff); // Add this line to log the diff content + setDiffContent(response.diff); + setCurrentChange(change); + setShowDiffModal(true); + } else { + Alert.error(response.error); + } + } catch (error) { + Alert.error('An unexpected error occurred while fetching the diff.'); + console.error('Error fetching diff:', error); + } finally { + setLoadingDiff(false); + setLoadingAction(''); + } + }; + + const handleStageSelectedChanges = async () => { + if (selectedOutgoingChanges.length === 0) { + Alert.warning('Please select at least one change to stage.'); + return; + } + + setLoadingAction('stage_selected'); + try { + const response = await addFiles(selectedOutgoingChanges); + if (response.success) { + await fetchGitStatus(); + setSelectedOutgoingChanges([]); // Clear the selected changes after staging + Alert.success(response.message); + } else { + Alert.error(response.error); + } + } catch (error) { + Alert.error('An unexpected error occurred while staging changes.'); + console.error('Error staging changes:', error); + } finally { + setLoadingAction(''); + } + }; + + const handleCommitSelectedChanges = async () => { + if (selectedOutgoingChanges.length === 0) { + Alert.warning('Please select at least one change to commit.'); + return; + } + + if (!commitMessage.trim()) { + Alert.warning('Please enter a commit message.'); + return; + } + + setLoadingAction('commit_selected'); + try { + const response = await pushFiles(selectedOutgoingChanges, commitMessage); + if (response.success) { + await fetchGitStatus(); + setSelectedOutgoingChanges([]); // Clear the selected changes after committing + setCommitMessage(''); + Alert.success(response.message); + } else { + Alert.error(response.error); + } + } catch (error) { + Alert.error('An unexpected error occurred while committing changes.'); + console.error('Error committing changes:', error); + } finally { + setLoadingAction(''); + } + }; + + const handleRevertSelectedChanges = async () => { + if (selectedOutgoingChanges.length === 0) { + Alert.warning('Please select at least one change to revert.'); + return; + } + + setLoadingAction('revert_selected'); + try { + const response = await Promise.all( + selectedOutgoingChanges.map(filePath => revertFile(filePath)) + ); + const allSuccessful = response.every(res => res.success); + if (allSuccessful) { + await fetchGitStatus(); + setSelectedOutgoingChanges([]); // Clear the selected changes after reverting + Alert.success('Selected changes have been reverted successfully.'); + } else { + Alert.error('Some changes could not be reverted. Please try again.'); + } + } catch (error) { + Alert.error('An unexpected error occurred while reverting changes.'); + console.error('Error reverting changes:', error); + } finally { + setLoadingAction(''); + } + }; + + const handlePullSelectedChanges = async () => { + if (selectedIncomingChanges.length === 0) { + Alert.warning('Please select at least one change to pull.'); + return; + } + + setLoadingAction('pull_changes'); + try { + // You would need to update your backend to handle pulling specific files + const response = await pullBranch(status.branch, selectedIncomingChanges); + if (response.success) { + await fetchGitStatus(); + setSelectedIncomingChanges([]); // Clear the selected changes after pulling + Alert.success(response.message); + } else { + Alert.error(response.error); + } + } catch (error) { + Alert.error('An unexpected error occurred while pulling changes.'); + console.error('Error pulling changes:', error); + } finally { + setLoadingAction(''); + } + }; + + const handleSelectChange = (filePath, isIncoming) => { + if (isIncoming) { + setSelectedIncomingChanges(prevSelected => { + if (prevSelected.includes(filePath)) { + return prevSelected.filter(path => path !== filePath); + } else { + return [...prevSelected, filePath]; + } + }); + } else { + const change = status.outgoing_changes.find( + c => c.file_path === filePath + ); + const isStaged = change.staged; + + setSelectedOutgoingChanges(prevSelected => { + if (prevSelected.includes(filePath)) { + const newSelection = prevSelected.filter(path => path !== filePath); + if (newSelection.length === 0) setSelectionType(null); + return newSelection; + } else { + if ( + prevSelected.length === 0 || + (isStaged && selectionType === 'staged') || + (!isStaged && selectionType === 'unstaged') + ) { + setSelectionType(isStaged ? 'staged' : 'unstaged'); + return [...prevSelected, filePath]; + } else { + return prevSelected; + } + } + }); + } + }; + + const loadingMessages = [ + "Checking for changes... don't blink!", + 'Syncing with the mothership...', + 'Peeking under the hood...', + 'Counting bits and bytes...', + 'Scanning for modifications...', + 'Looking for new stuff...', + 'Comparing local and remote...', + "Checking your project's pulse...", + "Analyzing your code's mood...", + "Reading the project's diary..." + ]; + + const getRandomLoadingMessage = () => { + return loadingMessages[Math.floor(Math.random() * loadingMessages.length)]; + }; + + const getStatusIcon = status => { + switch (status) { + case 'Untracked': + return ; + case 'Staged (New)': + return ; + case 'Staged (Modified)': + case 'Modified': + return ; + case 'Deleted': + return ; + case 'Deleted (Staged)': + return ; + case 'Renamed': + return ; + default: + return ; + } + }; + + const getTypeIcon = type => { + switch (type) { + case 'Regex Pattern': + return ; + case 'Custom Format': + return ; + case 'Quality Profile': + return ; + default: + return ; + } + }; + + const handleLinkRepo = async () => { + setLoadingAction(''); + setShowLinkModal(false); + await fetchSettings(); + }; + + const handleUnlinkRepo = async removeFiles => { + setLoadingAction('unlink_repo'); + try { + const response = await unlinkRepo(removeFiles); + if (response.success) { + setSettings(null); + setStatus(null); + Alert.success('Repository unlinked successfully'); + setShowUnlinkModal(false); // Close the modal after unlinking + } else { + Alert.error(response.error || 'Failed to unlink repository'); + } + } catch (error) { + Alert.error( + 'An unexpected error occurred while unlinking the repository' + ); + console.error('Error unlinking repository:', error); + } finally { + setLoadingAction(''); + } + }; + + return ( +
+

+ Git Repository Settings +

+ {!settings && ( + + )} + {settings && ( +
+
+

+ Connected Repository +

+
+ + {settings.gitRepo} + +
+ + + +
+
+
+ +
+

+ Git Status +

+ {loadingStatus ? ( +
+ + + {loadingMessage} + +
+ ) : ( + status && ( + <> +
+
+ + + Current Branch: {status.branch} + +
+ +
+ + {status.incoming_changes.length > 0 && + renderChangeTable( + status.incoming_changes, + 'Incoming Changes', + , + true + )} + {status.outgoing_changes.length > 0 && + renderChangeTable( + status.outgoing_changes, + 'Outgoing Changes', + , + false + )} + + 0} + funMessage={funMessage} + /> + + {/* Buttons Below Commit Section */} +
+ {/* Conditionally render Stage button */} + {selectedOutgoingChanges.length > 0 && + selectionType !== 'staged' && ( + + + + )} +
+ {/* Stage button */} + {selectedOutgoingChanges.length > 0 && + selectionType !== 'staged' && ( + + + + )} + + {/* Commit button */} + {selectedOutgoingChanges.length > 0 && + commitMessage.trim() && + selectionType !== 'unstaged' && ( + + + + )} + + {/* Revert button */} + {selectedOutgoingChanges.length > 0 && ( + + + + )} +
+ + {/* Pull button (always enabled) */} + {selectedIncomingChanges.length > 0 && ( + + + + )} +
+ + ) + )} +
+
+ )} + {settings && status && ( + setShowBranchModal(false)} + repoUrl={settings.gitRepo} + currentBranch={status.branch} + onBranchChange={fetchGitStatus} + /> + )} + {showDiffModal && currentChange && ( + setShowDiffModal(false)} + diffContent={diffContent} + type={currentChange.type} + name={currentChange.name} + commitMessage={currentChange.commit_message} + /> + )} + setShowLinkModal(false)} + onSubmit={handleLinkRepo} + /> + setShowUnlinkModal(false)} + onSubmit={handleUnlinkRepo} + /> +
+ ); }; export default SettingsPage;