Compare commits
No commits in common. "60150f63d037dfc727bc4a33a198490e8f021b23" and "909c1977877cbe838ba178816842892d2e51e4ca" have entirely different histories.
60150f63d0
...
909c197787
@ -35,7 +35,7 @@
|
|||||||
"sources" : [
|
"sources" : [
|
||||||
{
|
{
|
||||||
"type" : "git",
|
"type" : "git",
|
||||||
"url" : "file:///home/pavelb/Projects/GnomeBuilderProjects/Roster"
|
"url" : "file:///home/pavelb/GnomeBuilderProjects/Roster"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"config-opts" : [
|
"config-opts" : [
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
project('roster',
|
project('roster',
|
||||||
version: '0.8.4',
|
version: '0.8.3',
|
||||||
meson_version: '>= 1.0.0',
|
meson_version: '>= 1.0.0',
|
||||||
default_options: [ 'warning_level=2', 'werror=false', ],
|
default_options: [ 'warning_level=2', 'werror=false', ],
|
||||||
)
|
)
|
||||||
|
|||||||
@ -18,7 +18,6 @@
|
|||||||
#
|
#
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
import re
|
|
||||||
from gi.repository import Adw, Gtk, GObject
|
from gi.repository import Adw, Gtk, GObject
|
||||||
from .widgets.variable_row import VariableRow
|
from .widgets.variable_row import VariableRow
|
||||||
from .widgets.environment_row import EnvironmentRow
|
from .widgets.environment_row import EnvironmentRow
|
||||||
@ -66,8 +65,6 @@ class EnvironmentsDialog(Adw.Dialog):
|
|||||||
self._on_environment_edit)
|
self._on_environment_edit)
|
||||||
self.header_row.connect('environment-delete-requested',
|
self.header_row.connect('environment-delete-requested',
|
||||||
self._on_environment_delete)
|
self._on_environment_delete)
|
||||||
self.header_row.connect('environment-copy-requested',
|
|
||||||
self._on_environment_copy)
|
|
||||||
self.table_container.append(self.header_row)
|
self.table_container.append(self.header_row)
|
||||||
|
|
||||||
# Add separator
|
# Add separator
|
||||||
@ -252,30 +249,6 @@ class EnvironmentsDialog(Adw.Dialog):
|
|||||||
dialog.connect("response", on_response)
|
dialog.connect("response", on_response)
|
||||||
dialog.present(self)
|
dialog.present(self)
|
||||||
|
|
||||||
def _on_environment_copy(self, widget, environment):
|
|
||||||
"""Copy an environment with a unique name."""
|
|
||||||
existing_names = {env.name for env in self.project.environments}
|
|
||||||
new_name = self._unique_env_name(environment.name, existing_names)
|
|
||||||
|
|
||||||
def on_copy_complete(new_env):
|
|
||||||
self._reload_project()
|
|
||||||
self._populate_table()
|
|
||||||
self.emit('environments-updated')
|
|
||||||
|
|
||||||
self.project_manager.copy_environment(self.project.id, environment.id, new_name, on_copy_complete)
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _unique_env_name(base_name, existing_names):
|
|
||||||
"""Generate a unique environment name by appending (1), (2), etc."""
|
|
||||||
match = re.match(r'^(.*?) \((\d+)\)$', base_name)
|
|
||||||
base = match.group(1) if match else base_name
|
|
||||||
counter = 1
|
|
||||||
while True:
|
|
||||||
candidate = f"{base} ({counter})"
|
|
||||||
if candidate not in existing_names:
|
|
||||||
return candidate
|
|
||||||
counter += 1
|
|
||||||
|
|
||||||
def _on_environment_delete(self, widget, environment):
|
def _on_environment_delete(self, widget, environment):
|
||||||
"""Delete environment with confirmation."""
|
"""Delete environment with confirmation."""
|
||||||
# Prevent deletion of last environment
|
# Prevent deletion of last environment
|
||||||
|
|||||||
@ -229,73 +229,6 @@ class ProjectManager:
|
|||||||
self.save_projects(projects)
|
self.save_projects(projects)
|
||||||
return environment
|
return environment
|
||||||
|
|
||||||
def copy_environment(self, project_id: str, source_env_id: str, new_name: str,
|
|
||||||
callback: Optional[Callable] = None):
|
|
||||||
"""Copy an environment including all variables (sensitive ones via keyring)."""
|
|
||||||
projects = self.load_projects()
|
|
||||||
secret_manager = get_secret_manager()
|
|
||||||
|
|
||||||
for p in projects:
|
|
||||||
if p.id == project_id:
|
|
||||||
source_env = next((e for e in p.environments if e.id == source_env_id), None)
|
|
||||||
if source_env is None:
|
|
||||||
if callback:
|
|
||||||
callback(None)
|
|
||||||
return
|
|
||||||
|
|
||||||
now = datetime.now(timezone.utc).isoformat()
|
|
||||||
new_env_id = str(uuid.uuid4())
|
|
||||||
new_env = Environment(
|
|
||||||
id=new_env_id,
|
|
||||||
name=new_name,
|
|
||||||
variables=dict(source_env.variables),
|
|
||||||
created_at=now
|
|
||||||
)
|
|
||||||
p.environments.append(new_env)
|
|
||||||
self.save_projects(projects)
|
|
||||||
|
|
||||||
sensitive_vars = [v for v in p.sensitive_variables if v in source_env.variables]
|
|
||||||
if not sensitive_vars:
|
|
||||||
if callback:
|
|
||||||
callback(new_env)
|
|
||||||
return
|
|
||||||
|
|
||||||
pending_count = [len(sensitive_vars)]
|
|
||||||
|
|
||||||
def make_copy_handler(var_name, project_name, env_name):
|
|
||||||
def on_store_done(success=True):
|
|
||||||
pending_count[0] -= 1
|
|
||||||
if pending_count[0] == 0 and callback:
|
|
||||||
callback(new_env)
|
|
||||||
|
|
||||||
def on_retrieve(value):
|
|
||||||
if value:
|
|
||||||
secret_manager.store_secret(
|
|
||||||
project_id=project_id,
|
|
||||||
environment_id=new_env_id,
|
|
||||||
variable_name=var_name,
|
|
||||||
value=value,
|
|
||||||
project_name=project_name,
|
|
||||||
environment_name=env_name,
|
|
||||||
callback=lambda success: on_store_done(success)
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
on_store_done()
|
|
||||||
|
|
||||||
return on_retrieve
|
|
||||||
|
|
||||||
for var_name in sensitive_vars:
|
|
||||||
secret_manager.retrieve_secret(
|
|
||||||
project_id=project_id,
|
|
||||||
environment_id=source_env_id,
|
|
||||||
variable_name=var_name,
|
|
||||||
callback=make_copy_handler(var_name, p.name, new_name)
|
|
||||||
)
|
|
||||||
return
|
|
||||||
|
|
||||||
if callback:
|
|
||||||
callback(None)
|
|
||||||
|
|
||||||
def update_environment(self, project_id: str, env_id: str, name: str = None, variables: dict = None):
|
def update_environment(self, project_id: str, env_id: str, name: str = None, variables: dict = None):
|
||||||
"""Update an environment's name and/or variables."""
|
"""Update an environment's name and/or variables."""
|
||||||
projects = self.load_projects()
|
projects = self.load_projects()
|
||||||
|
|||||||
@ -1255,7 +1255,6 @@ class RequestTabWidget(Gtk.Box):
|
|||||||
# Set flag to indicate we're programmatically changing the dropdown
|
# Set flag to indicate we're programmatically changing the dropdown
|
||||||
# This prevents the signal handler from triggering indicator updates during initialization
|
# This prevents the signal handler from triggering indicator updates during initialization
|
||||||
self._is_programmatically_changing_environment = True
|
self._is_programmatically_changing_environment = True
|
||||||
old_env_id = self.selected_environment_id
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Build string list with "None" + environment names
|
# Build string list with "None" + environment names
|
||||||
@ -1277,8 +1276,6 @@ class RequestTabWidget(Gtk.Box):
|
|||||||
index = self.environment_ids.index(self.selected_environment_id)
|
index = self.environment_ids.index(self.selected_environment_id)
|
||||||
self.environment_dropdown.set_selected(index)
|
self.environment_dropdown.set_selected(index)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
# Previously selected environment no longer exists
|
|
||||||
self.selected_environment_id = None
|
|
||||||
self.environment_dropdown.set_selected(0) # Default to "None"
|
self.environment_dropdown.set_selected(0) # Default to "None"
|
||||||
else:
|
else:
|
||||||
self.environment_dropdown.set_selected(0) # Default to "None"
|
self.environment_dropdown.set_selected(0) # Default to "None"
|
||||||
@ -1287,9 +1284,8 @@ class RequestTabWidget(Gtk.Box):
|
|||||||
# Always clear the flag
|
# Always clear the flag
|
||||||
self._is_programmatically_changing_environment = False
|
self._is_programmatically_changing_environment = False
|
||||||
|
|
||||||
# If the effective environment changed (e.g. was deleted), update indicators
|
# Note: Don't update indicators here as the request might not be loaded yet
|
||||||
if self.selected_environment_id != old_env_id:
|
# Indicators will be updated when environment changes or request is loaded
|
||||||
self._update_variable_indicators()
|
|
||||||
|
|
||||||
def _show_environment_selector(self):
|
def _show_environment_selector(self):
|
||||||
"""Show environment selector (create if it doesn't exist)."""
|
"""Show environment selector (create if it doesn't exist)."""
|
||||||
|
|||||||
@ -23,18 +23,6 @@
|
|||||||
<property name="halign">center</property>
|
<property name="halign">center</property>
|
||||||
<property name="spacing">4</property>
|
<property name="spacing">4</property>
|
||||||
|
|
||||||
<child>
|
|
||||||
<object class="GtkButton" id="copy_button">
|
|
||||||
<property name="icon-name">edit-copy-symbolic</property>
|
|
||||||
<property name="tooltip-text">Copy environment</property>
|
|
||||||
<signal name="clicked" handler="on_copy_clicked"/>
|
|
||||||
<style>
|
|
||||||
<class name="flat"/>
|
|
||||||
<class name="circular"/>
|
|
||||||
</style>
|
|
||||||
</object>
|
|
||||||
</child>
|
|
||||||
|
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkButton" id="edit_button">
|
<object class="GtkButton" id="edit_button">
|
||||||
<property name="icon-name">document-properties-symbolic</property>
|
<property name="icon-name">document-properties-symbolic</property>
|
||||||
|
|||||||
@ -28,14 +28,12 @@ class EnvironmentColumnHeader(Gtk.Box):
|
|||||||
__gtype_name__ = 'EnvironmentColumnHeader'
|
__gtype_name__ = 'EnvironmentColumnHeader'
|
||||||
|
|
||||||
name_label = Gtk.Template.Child()
|
name_label = Gtk.Template.Child()
|
||||||
copy_button = Gtk.Template.Child()
|
|
||||||
edit_button = Gtk.Template.Child()
|
edit_button = Gtk.Template.Child()
|
||||||
delete_button = Gtk.Template.Child()
|
delete_button = Gtk.Template.Child()
|
||||||
|
|
||||||
__gsignals__ = {
|
__gsignals__ = {
|
||||||
'edit-requested': (GObject.SIGNAL_RUN_FIRST, None, ()),
|
'edit-requested': (GObject.SIGNAL_RUN_FIRST, None, ()),
|
||||||
'delete-requested': (GObject.SIGNAL_RUN_FIRST, None, ()),
|
'delete-requested': (GObject.SIGNAL_RUN_FIRST, None, ()),
|
||||||
'copy-requested': (GObject.SIGNAL_RUN_FIRST, None, ()),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def __init__(self, environment):
|
def __init__(self, environment):
|
||||||
@ -43,11 +41,6 @@ class EnvironmentColumnHeader(Gtk.Box):
|
|||||||
self.environment = environment
|
self.environment = environment
|
||||||
self.name_label.set_text(environment.name)
|
self.name_label.set_text(environment.name)
|
||||||
|
|
||||||
@Gtk.Template.Callback()
|
|
||||||
def on_copy_clicked(self, button):
|
|
||||||
"""Handle copy button click."""
|
|
||||||
self.emit('copy-requested')
|
|
||||||
|
|
||||||
@Gtk.Template.Callback()
|
@Gtk.Template.Callback()
|
||||||
def on_edit_clicked(self, button):
|
def on_edit_clicked(self, button):
|
||||||
"""Handle edit button click."""
|
"""Handle edit button click."""
|
||||||
|
|||||||
@ -35,7 +35,6 @@ class EnvironmentHeaderRow(Gtk.Box):
|
|||||||
__gsignals__ = {
|
__gsignals__ = {
|
||||||
'environment-edit-requested': (GObject.SIGNAL_RUN_FIRST, None, (object,)),
|
'environment-edit-requested': (GObject.SIGNAL_RUN_FIRST, None, (object,)),
|
||||||
'environment-delete-requested': (GObject.SIGNAL_RUN_FIRST, None, (object,)),
|
'environment-delete-requested': (GObject.SIGNAL_RUN_FIRST, None, (object,)),
|
||||||
'environment-copy-requested': (GObject.SIGNAL_RUN_FIRST, None, (object,)),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def __init__(self, environments, size_group):
|
def __init__(self, environments, size_group):
|
||||||
@ -63,7 +62,6 @@ class EnvironmentHeaderRow(Gtk.Box):
|
|||||||
header = EnvironmentColumnHeader(env)
|
header = EnvironmentColumnHeader(env)
|
||||||
header.connect('edit-requested', self._on_edit_requested, env)
|
header.connect('edit-requested', self._on_edit_requested, env)
|
||||||
header.connect('delete-requested', self._on_delete_requested, env)
|
header.connect('delete-requested', self._on_delete_requested, env)
|
||||||
header.connect('copy-requested', self._on_copy_requested, env)
|
|
||||||
|
|
||||||
# Add to size group for alignment with data rows
|
# Add to size group for alignment with data rows
|
||||||
if self.size_group:
|
if self.size_group:
|
||||||
@ -80,10 +78,6 @@ class EnvironmentHeaderRow(Gtk.Box):
|
|||||||
"""Handle delete request from column header."""
|
"""Handle delete request from column header."""
|
||||||
self.emit('environment-delete-requested', environment)
|
self.emit('environment-delete-requested', environment)
|
||||||
|
|
||||||
def _on_copy_requested(self, widget, environment):
|
|
||||||
"""Handle copy request from column header."""
|
|
||||||
self.emit('environment-copy-requested', environment)
|
|
||||||
|
|
||||||
def refresh(self, environments):
|
def refresh(self, environments):
|
||||||
"""Refresh with updated environments list."""
|
"""Refresh with updated environments list."""
|
||||||
self.environments = environments
|
self.environments = environments
|
||||||
|
|||||||
@ -1048,10 +1048,6 @@ class RosterWindow(Adw.ApplicationWindow):
|
|||||||
def on_environments_updated(dlg):
|
def on_environments_updated(dlg):
|
||||||
# Reload projects to reflect changes
|
# Reload projects to reflect changes
|
||||||
self._load_projects()
|
self._load_projects()
|
||||||
# Refresh environment dropdowns in all open tabs for this project
|
|
||||||
for page, tab_widget in self.page_to_widget.items():
|
|
||||||
if tab_widget.project_id == project.id:
|
|
||||||
tab_widget._populate_environment_dropdown()
|
|
||||||
|
|
||||||
dialog.connect('environments-updated', on_environments_updated)
|
dialog.connect('environments-updated', on_environments_updated)
|
||||||
dialog.present(self)
|
dialog.present(self)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user