Skip to content

Commit 51cc934

Browse files
committed
disabled new sap view for now
1 parent 0e312ff commit 51cc934

File tree

10 files changed

+256
-35
lines changed

10 files changed

+256
-35
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,3 +92,4 @@ debug_button_2.py
9292
# Build Web - track web_entry.py and requirements.txt
9393
!/build_web/web_entry.py
9494
!/build_web/requirements.txt
95+
/sap_sources

src/switchcraft/assets/lang/de.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,11 @@
142142
"context_copy": "Kopieren",
143143
"intune_util_missing": "IntuneWinAppUtil.exe fehlt.",
144144
"intune_download_activate": "Herunterladen & Aktivieren",
145+
"sap_admin_required_title": "Administrator-Rechte erforderlich",
146+
"sap_admin_required_desc": "Das SAP Installation Server Administration Tool (NwSapSetupAdmin.exe) benoetigt administrative Rechte, um Updates zusammenzufuehren und Pakete zu erstellen.",
147+
"btn_restart_admin": "SwitchCraft als Admin neu starten",
145148
"intune_downloading": "Lade Intune Tool...",
149+
"sap_arch_select": "Architektur:",
146150
"intune_success": "Intune Tool erfolgreich heruntergeladen!",
147151
"intune_ready": "Bereit",
148152
"intune_failed": "Download fehlgeschlagen. Prüfe Logs.",

src/switchcraft/assets/lang/en.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,11 @@
143143
"context_copy": "Copy",
144144
"intune_util_missing": "IntuneWinAppUtil.exe is missing.",
145145
"intune_download_activate": "Download & Activate",
146+
"sap_admin_required_title": "Administrator Privileges Required",
147+
"sap_admin_required_desc": "The SAP Installation Server Administration Tool (NwSapSetupAdmin.exe) requires administrative rights to merge updates and create packages.",
148+
"btn_restart_admin": "Restart SwitchCraft as Admin",
146149
"intune_downloading": "Downloading Intune Tool...",
150+
"sap_arch_select": "Architecture:",
147151
"intune_success": "Intune Tool downloaded successfully!",
148152
"intune_ready": "Ready",
149153
"intune_failed": "Failed to download Intune Tool. Check logs.",

src/switchcraft/gui_modern/app.py

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1076,9 +1076,6 @@ def build_ui(self):
10761076
ft.NavigationRailDestination(
10771077
icon=ft.Icons.POLICY_OUTLINED, selected_icon=ft.Icons.POLICY, label=i18n.get("settings_policies") or "Policies"
10781078
), # 21 Policies
1079-
ft.NavigationRailDestination(
1080-
icon=ft.Icons.BUSINESS, selected_icon=ft.Icons.BUSINESS, label=i18n.get("sap_card_title") or "SAP Management"
1081-
), # 22 SAP Magic
10821079
]
10831080

10841081

@@ -1782,12 +1779,6 @@ def _f():
17821779
from switchcraft.gui_modern.views.wingetcreate_view import WingetCreateView
17831780
return WingetCreateView(self.page)
17841781
load_view(_f)
1785-
elif idx == NavIndex.SAP_WIZARD:
1786-
# SAP Management
1787-
def _f():
1788-
from switchcraft.gui_modern.views.sap_wizard_view import SapWizardView
1789-
return SapWizardView(self.page)
1790-
load_view(_f)
17911782

17921783
else:
17931784
# Dynamic Addons

src/switchcraft/gui_modern/controls/sidebar.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ def __init__(self, app, destinations, on_navigate):
2727
self.categories = [
2828
(ft.Icons.DASHBOARD, i18n.get("cat_dashboard") or "Dashboard", [NavIndex.HOME, NavIndex.DASHBOARD]), # Home, Dashboard
2929
(ft.Icons.APPS, i18n.get("cat_apps_devices") or "Apps & Devices", [NavIndex.INTUNE, NavIndex.INTUNE_STORE, NavIndex.WINGET, NavIndex.LIBRARY, NavIndex.GROUP_MANAGER, NavIndex.EXCHANGE, NavIndex.STACK_MANAGER]), # Intune, Store, Winget, Library, Groups, Exchange, Stacks
30-
(ft.Icons.BUILD, i18n.get("cat_tools") or "Tools", [NavIndex.ANALYZER, NavIndex.HELPER, NavIndex.SCRIPTS, NavIndex.MACOS, NavIndex.PACKAGING_WIZARD, NavIndex.DETECTION_TESTER, NavIndex.WINGET_CREATE]), # Analyze, Generate, Scripts, MacOS, Wizard, Tester, WingetCreate
30+
(ft.Icons.BUILD, i18n.get("cat_tools") or "Tools", [NavIndex.ANALYZER, NavIndex.HELPER, NavIndex.SCRIPTS, NavIndex.MACOS, NavIndex.PACKAGING_WIZARD, NavIndex.DETECTION_TESTER, NavIndex.WINGET_CREATE, NavIndex.SAP_WIZARD]), # Analyze, Generate, Scripts, MacOS, Wizard, Tester, WingetCreate, SAP
3131
(ft.Icons.SETTINGS, i18n.get("cat_system") or "System", [NavIndex.SETTINGS, NavIndex.SETTINGS_UPDATES, NavIndex.SETTINGS_GRAPH, NavIndex.SETTINGS_POLICIES, NavIndex.HISTORY, NavIndex.SETTINGS_HELP]), # Settings, Updates, Graph, Policies, History, Help
3232
]
3333

src/switchcraft/gui_modern/nav_constants.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,6 @@ class NavIndex:
4646
EXCHANGE = 20 # Exchange Online View
4747

4848
SETTINGS_POLICIES = 21 # Settings tab index 3 (Policies)
49-
SAP_WIZARD = 22 # SAP Management Wizard
5049

5150

5251
# Mapping from NavIndex to sidebar category for reference
@@ -59,7 +58,7 @@ class NavIndex:
5958
"Tools": [
6059
NavIndex.ANALYZER, NavIndex.HELPER, NavIndex.SCRIPTS,
6160
NavIndex.MACOS, NavIndex.PACKAGING_WIZARD, NavIndex.DETECTION_TESTER,
62-
NavIndex.WINGET_CREATE, NavIndex.SAP_WIZARD
61+
NavIndex.WINGET_CREATE
6362
],
6463
"System": [
6564
NavIndex.SETTINGS, NavIndex.SETTINGS_UPDATES,

src/switchcraft/gui_modern/views/sap_wizard_view.py

Lines changed: 92 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,10 @@ def __init__(self, page: ft.Page):
2424
self.update_files = []
2525
self.logo_path = ""
2626
self.use_webview2 = True
27+
self.arch_group = ft.RadioGroup(content=ft.Row([
28+
ft.Radio(value="32", label="32-bit (Win32)"),
29+
ft.Radio(value="64", label="64-bit (Win64)")
30+
]), value="64")
2731

2832
self.current_step = 1
2933
self.content_area = ft.Container(expand=True)
@@ -34,9 +38,35 @@ def __init__(self, page: ft.Page):
3438
self._build_nav_buttons()
3539
]
3640

37-
self._show_step(1)
41+
from switchcraft.utils.shell_utils import ShellUtils
42+
self.is_admin = ShellUtils.is_admin()
43+
44+
if not self.is_admin:
45+
self.content_area.content = self._build_admin_warning()
46+
# Hide nav buttons if not admin
47+
self.controls[-1].visible = False
48+
else:
49+
self._show_step(1)
50+
51+
def _build_admin_warning(self):
52+
from switchcraft.utils.shell_utils import ShellUtils
53+
return ft.Column([
54+
ft.Icon(ft.Icons.SECURITY, size=64, color="RED"),
55+
ft.Text(i18n.get("sap_admin_required_title") or "Administrator Privileges Required", size=20, weight=ft.FontWeight.BOLD),
56+
ft.Text(i18n.get("sap_admin_required_desc") or "The SAP Installation Server Administration Tool (NwSapSetupAdmin.exe) requires administrative rights to merge updates and create packages."),
57+
ft.Container(height=20),
58+
ft.ElevatedButton(
59+
i18n.get("btn_restart_admin") or "Restart SwitchCraft as Admin",
60+
icon=ft.Icons.SHIELD,
61+
on_click=lambda _: ShellUtils.restart_as_admin(),
62+
bgcolor="RED", color="WHITE"
63+
)
64+
], alignment=ft.MainAxisAlignment.CENTER, horizontal_alignment=ft.CrossAxisAlignment.CENTER, expand=True)
3865

3966
def _show_step(self, step_num):
67+
if not self.is_admin:
68+
return
69+
4070
self.current_step = step_num
4171
if step_num == 1:
4272
self.content_area.content = self._build_step_1()
@@ -67,7 +97,10 @@ def on_pick_server(e: ft.FilePickerResultEvent):
6797
ft.Row([
6898
ft.ElevatedButton(i18n.get("btn_browse_folder") or "Browse Server Folder", icon=ft.Icons.FOLDER_OPEN, on_click=lambda _: fp.get_directory_path()),
6999
path_text
70-
])
100+
]),
101+
ft.Divider(height=20, color="TRANSPARENT"),
102+
ft.Text(i18n.get("sap_arch_select") or "Architecture:", weight=ft.FontWeight.BOLD),
103+
self.arch_group
71104
])
72105

73106
def _build_step_2(self):
@@ -92,12 +125,33 @@ def _build_step_3(self):
92125

93126
def _build_step_4(self):
94127
"""Step 4: Summary & Packaging."""
128+
# Refresh packages list on entry
129+
packages = []
130+
try:
131+
packages = self.sap_service.list_packages(self.server_path)
132+
except:
133+
pass
134+
135+
opts = [ft.dropdown.Option(p['name']) for p in packages]
136+
# Default to first if available, else standard SAPGUI
137+
default_val = packages[0]['name'] if packages else "SAPGUI"
138+
139+
self.package_dd = ft.Dropdown(
140+
label=i18n.get("sap_package_select") or "Select Package to Build",
141+
options=opts,
142+
value=default_val,
143+
width=400
144+
)
145+
95146
return ft.Column([
96147
ft.Text(i18n.get("sap_step4_title") or "4. Summary & Packaging", size=18, weight=ft.FontWeight.BOLD),
97148
ft.Text(f"{i18n.get('label_server') or 'Server'}: {self.server_path}"),
149+
ft.Text(f"Architecture: {self.arch_group.value}-bit"),
98150
ft.Text(f"{i18n.get('label_custom_logo') or 'Custom Logo'}: {'Yes' if self.logo_path else 'No'}"),
99151
ft.Text(f"{i18n.get('label_webview2') or 'Edge WebView2'}: {'Enabled' if self.use_webview2 else 'Disabled'}"),
100152
ft.Divider(),
153+
self.package_dd,
154+
ft.Container(height=10),
101155
ft.ElevatedButton(i18n.get("btn_apply_build") or "Apply & Build Packaging", icon=ft.Icons.BUILD_CIRCLE, bgcolor="PRIMARY", color="WHITE", on_click=self._on_finalize)
102156
])
103157

@@ -113,8 +167,42 @@ def _on_finalize(self, _):
113167
return
114168

115169
try:
116-
self.sap_service.customize_server(self.server_path, self.logo_path, self.use_webview2)
170+
target_path = self.server_path
171+
if self.arch_group.value == "64":
172+
if "Win32" in target_path:
173+
target_path = target_path.replace("Win32", "Win64")
174+
elif "Win64" not in target_path and (Path(target_path) / "Win64").exists():
175+
target_path = str(Path(target_path) / "Win64")
176+
177+
# 1. Customize
178+
self.sap_service.customize_server(target_path, self.logo_path, self.use_webview2)
117179
self._show_snack("SAP Server customized successfully!", color="GREEN")
118-
# In a real app, we would now trigger the packaging process
180+
181+
# 2. Build
182+
pkg_name = self.package_dd.value
183+
if not pkg_name:
184+
self._show_snack("Please select a package to build.", color="RED")
185+
return
186+
187+
import tempfile
188+
# Use %TEMP%\SwitchCraft\Dist
189+
temp_dir = Path(tempfile.gettempdir()) / "SwitchCraft" / "Dist"
190+
out_dir = str(temp_dir)
191+
192+
self._show_snack(f"Building package '{pkg_name}'... Please wait...", color="BLUE")
193+
self.app_page.update()
194+
195+
# Use threading to avoid UI freeze if desired, but for now simple blocking is safer for debugging logic
196+
out_file = self.sap_service.create_single_file_installer(target_path, pkg_name, out_dir)
197+
198+
self._show_snack(f"Build Success! Installer at: {out_file}", color="GREEN", duration=5000)
199+
200+
# Auto-open in Explorer
201+
try:
202+
os.startfile(out_dir)
203+
except Exception as e:
204+
logger.error(f"Failed to open explorer: {e}")
205+
119206
except Exception as e:
207+
logger.error(f"SAP Finalize Error: {e}")
120208
self._show_snack(f"Error: {e}", color="RED")

src/switchcraft/services/sap_service.py

Lines changed: 91 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -97,28 +97,103 @@ def customize_server(self, server_path: str, logo_path: Optional[str] = None, us
9797

9898
return True
9999

100-
def create_single_file_installer(self, server_path: str, package_name: str, output_path: str) -> str:
100+
def list_packages(self, server_path: str) -> List[dict]:
101101
"""
102-
Creates a single-file installer (SFU) for a specific package.
103-
Note: NwSapSetupAdmin.exe CLI for SFU creation is semi-documented.
104-
We fallback to the command pattern often used for automation.
102+
Parses SapSetup.xml or SapGuiSetup.xml to list available products/packages.
103+
Returns list of dicts: {'name': 'Package Name', 'id': 'Guid'}
104+
"""
105+
packages = []
106+
107+
# Possible XML files defining products
108+
candidates = ["SapSetup.xml", "SapGuiSetup.xml"]
109+
110+
base_path = Path(server_path)
111+
# Handle architecture path adjustments (if user selected root, we look in Setup)
112+
setup_path = base_path / "Setup"
113+
if not setup_path.exists():
114+
setup_path = base_path # Maybe user pointed directly to Setup?
115+
116+
for xml_name in candidates:
117+
xml_path = setup_path / xml_name
118+
if not xml_path.exists():
119+
continue
120+
121+
try:
122+
tree = ET.parse(xml_path)
123+
root = tree.getroot()
124+
125+
# Look for <Product> or <SapSetupProduct>
126+
for tag in ["Product", "SapSetupProduct"]:
127+
for product in root.findall(f".//{tag}"):
128+
name = product.get("Name")
129+
guid = product.get("Guid")
130+
if name:
131+
packages.append({"name": name, "id": guid})
132+
133+
except Exception as e:
134+
logger.error(f"Failed to parse {xml_name}: {e}")
135+
136+
return packages
137+
138+
def create_single_file_installer(self, server_path: str, package_name: str, output_dir: str) -> str:
139+
"""
140+
Creates a Single File Installer (SFU) for the specified package.
141+
UsesNwSapSetupAdmin.exe from the server path.
105142
"""
106143
admin_tool = self.detect_admin_tool(server_path)
107144
if not admin_tool:
108145
raise FileNotFoundError("SAP Admin Tool not found.")
109146

110-
# Best-guess CLI for SFU creation (NwSapSetupAdmin.exe /CreateSFU)
111-
# Actual implementation might require NwSapSetup.exe /Package="name" /CreateSFU
112-
cmd = [
113-
str(admin_tool),
114-
f"/Package={package_name}",
115-
"/CreateSFU",
116-
f"/dest={output_path}"
117-
]
147+
output_path = Path(output_dir)
148+
if not output_path.exists():
149+
output_path.mkdir(parents=True, exist_ok=True)
118150

119-
logger.info(f"Creating SAP SFU: {cmd}")
120-
result = ShellUtils.run_command(cmd)
151+
# Use a temporary batch file to guarantee exact quoting behavior.
152+
import tempfile
153+
import os
154+
155+
# Ensure output_dir is the directory
156+
output_dir = Path(output_path)
157+
if not output_dir.exists():
158+
output_dir.mkdir(parents=True, exist_ok=True)
159+
160+
# Target file must be specified in /Dest for many SFU creators
161+
target_file_path = output_dir / f"{package_name}.exe"
162+
163+
# Using colon-based syntax which is often required for legacy SAP tools
164+
# /CreateSFU:"Path" /Package:"Name" /Silent
165+
bat_content = f"""
166+
@echo off
167+
"{str(admin_tool)}" /CreateSFU:"{str(target_file_path)}" /Package:"{package_name}" /Silent
168+
echo Exit Code: %ERRORLEVEL%
169+
"""
170+
fd, bat_path = tempfile.mkstemp(suffix=".bat", text=True)
171+
os.close(fd)
172+
173+
try:
174+
with open(bat_path, "w") as f:
175+
f.write(bat_content)
176+
177+
logger.info(f"Executing SAP Batch Wrapper: {bat_path}")
178+
logger.info(f"Command: \"{str(admin_tool)}\" /CreateSFU:\"{str(target_file_path)}\" /Package:\"{package_name}\" /Silent")
179+
180+
# Run the batch file
181+
result = ShellUtils.run_command([bat_path])
182+
183+
if result:
184+
logger.info(f"SAP Batch Output: {result.stdout}")
185+
if result.stderr:
186+
logger.error(f"SAP Batch Stderr: {result.stderr}")
187+
188+
finally:
189+
if os.path.exists(bat_path):
190+
os.unlink(bat_path)
191+
192+
if target_file_path.exists():
193+
return str(target_file_path)
194+
195+
# If result was OK, return dir
121196
if result and result.returncode == 0:
122-
return output_path
197+
return str(output_path)
123198

124-
raise RuntimeError(f"Failed to create SAP SFU: {result.stderr if result else 'Unknown error'}")
199+
raise RuntimeError(f"Failed to create SAP SFU. ReturnCode: {result.returncode if result else 'None'}.")

src/switchcraft/utils/__init__.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
21
from .config import SwitchCraftConfig
3-
from .app_updater import UpdateChecker
42

5-
__all__ = ['SwitchCraftConfig', 'UpdateChecker']
3+
__all__ = ['SwitchCraftConfig']

0 commit comments

Comments
 (0)