Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

create troubleshooting dialog #1422

Merged
merged 4 commits into from
Jan 29, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions backend/FwLite/FwLiteMaui/FwLiteMaui.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

<TargetFrameworks>net9.0-android;net9.0-ios;net9.0-maccatalyst</TargetFrameworks>
<TargetFrameworks Condition="$([MSBuild]::IsOSPlatform('windows'))">$(TargetFrameworks);net9.0-windows10.0.19041.0</TargetFrameworks>
<LangVersion>preview</LangVersion>

<!-- Note for MacCatalyst:
The default runtime is maccatalyst-x64, except in Release config, in which case the default is maccatalyst-x64;maccatalyst-arm64.
Expand Down
26 changes: 26 additions & 0 deletions backend/FwLite/FwLiteMaui/FwLiteMauiConfig.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
using System.Diagnostics;
using System.Reflection;

namespace FwLiteMaui;

public class FwLiteMauiConfig
{
public FwLiteMauiConfig()
{
var defaultDataPath = FwLiteMauiKernel.IsPortableApp ? Directory.GetCurrentDirectory() : FileSystem.AppDataDirectory;
//when launching from a notification, the current directory may be C:\Windows\System32, so we'll use the path of the executable instead
if (defaultDataPath.StartsWith("C:\\Windows\\System32", StringComparison.OrdinalIgnoreCase))
defaultDataPath = Path.GetDirectoryName(Process.GetCurrentProcess().MainModule?.FileName ??
Assembly.GetExecutingAssembly().Location) ?? ".";
BaseDataDir = defaultDataPath;
}

public string BaseDataDir
{
get;
set => field = Path.GetFullPath(value);
}

public string AppLogFilePath => Path.Combine(BaseDataDir, "app.log");
public string AuthCacheFilePath => Path.Combine(BaseDataDir, "msal.cache");
}
17 changes: 9 additions & 8 deletions backend/FwLite/FwLiteMaui/FwLiteMauiKernel.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
using System.Diagnostics;
using System.Reflection;
using FwLiteMaui.Services;
using FwLiteShared;
using FwLiteShared.Auth;
using FwLiteShared.Services;
using LcmCrdt;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
Expand Down Expand Up @@ -88,12 +90,10 @@ public static void AddFwLiteMauiServices(this IServiceCollection services,
}
});

var defaultDataPath = IsPortableApp ? Directory.GetCurrentDirectory() : FileSystem.AppDataDirectory;
//when launching from a notification, the current directory may be C:\Windows\System32, so we'll use the path of the executable instead
if (defaultDataPath.StartsWith("C:\\Windows\\System32", StringComparison.OrdinalIgnoreCase))
defaultDataPath = Path.GetDirectoryName(Process.GetCurrentProcess().MainModule?.FileName ?? Assembly.GetExecutingAssembly().Location) ?? ".";
var baseDataPath = Path.GetFullPath(configuration.GetSection("FwLiteMaui").GetValue<string>("BaseDataDir") ??
defaultDataPath);

services.AddOptions<FwLiteMauiConfig>().BindConfiguration("FwLiteMaui");
var fwLiteMauiConfig = configuration.GetSection("FwLiteMaui").Get<FwLiteMauiConfig>() ?? new();
var baseDataPath = fwLiteMauiConfig.BaseDataDir;
logging.AddFilter("FwLiteShared.Auth.LoggerAdapter", LogLevel.Warning);
logging.AddFilter("Microsoft.EntityFrameworkCore.Database", LogLevel.Warning);
Directory.CreateDirectory(baseDataPath);
Expand All @@ -103,14 +103,15 @@ public static void AddFwLiteMauiServices(this IServiceCollection services,
});
services.Configure<AuthConfig>(config =>
{
config.CacheFileName = Path.Combine(baseDataPath, "msal.cache");
config.CacheFileName = fwLiteMauiConfig.AuthCacheFilePath;
config.SystemWebViewLogin = true;
});

logging.AddFile(Path.Combine(baseDataPath, "app.log"));
logging.AddFile(fwLiteMauiConfig.AppLogFilePath);
services.AddSingleton<IPreferences>(Preferences.Default);
services.AddSingleton<IVersionTracking>(VersionTracking.Default);
services.AddSingleton<IConnectivity>(Connectivity.Current);
services.AddSingleton<ITroubleshootingService, MauiTroubleshootingService>();
logging.AddConsole();
#if DEBUG
logging.AddDebug();
Expand Down
48 changes: 48 additions & 0 deletions backend/FwLite/FwLiteMaui/Services/MauiTroubleshootingService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
using FwLiteShared.Services;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Microsoft.JSInterop;

namespace FwLiteMaui.Services;

public class MauiTroubleshootingService(IOptions<FwLiteMauiConfig> config, ILogger<MauiTroubleshootingService> logger) : ITroubleshootingService
{
private readonly ILauncher _launcher = Launcher.Default;
private readonly IBrowser _browser = Browser.Default;
private readonly IShare _share = Share.Default;
private FwLiteMauiConfig Config => config.Value;

[JSInvokable]
public async Task<bool> TryOpenDataDirectory()
{
try
{
//obviously intended to open a url in the browser, but on windows it just opens explorer
await _browser.OpenAsync("file://" + Config.BaseDataDir);
return true;
}
catch (Exception e)
{
logger.LogError(e, "Failed to open data directory");
return false;
}
}

[JSInvokable]
public Task<string> GetDataDirectory()
{
return Task.FromResult(Config.BaseDataDir);
}

[JSInvokable]
public async Task OpenLogFile()
{
await _launcher.OpenAsync(new OpenFileRequest("Log File", new FileResult(Config.AppLogFilePath)));
}

[JSInvokable]
public async Task ShareLogFile()
{
await _share.RequestAsync(new ShareFileRequest("FieldWorks Lite logs", new ShareFile(Config.AppLogFilePath, "text/plain")));
}
}
8 changes: 6 additions & 2 deletions backend/FwLite/FwLiteShared/Services/FwLiteProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ public class FwLiteProvider(
ImportFwdataService importFwdataService,
ILogger<FwLiteProvider> logger,
IOptions<FwLiteConfig> config,
IAppLauncher? appLauncher = null
IAppLauncher? appLauncher = null,
ITroubleshootingService? troubleshootingService = null
)
{
public const string OverrideServiceFunctionName = "setOverrideService";
Expand All @@ -36,6 +37,8 @@ public Dictionary<DotnetService, object> GetServices()
};
if (appLauncher is not null)
services[DotnetService.AppLauncher] = appLauncher;
if (troubleshootingService is not null)
services[DotnetService.TroubleshootingService] = troubleshootingService;
return services;
}

Expand Down Expand Up @@ -78,5 +81,6 @@ public enum DotnetService
FwLiteConfig,
ProjectServicesProvider,
HistoryService,
AppLauncher
AppLauncher,
TroubleshootingService
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
namespace FwLiteShared.Services;

public interface ITroubleshootingService
{
Task<bool> TryOpenDataDirectory();
Task<string> GetDataDirectory();
Task OpenLogFile();
Task ShareLogFile();
}
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ private static void ConfigureFwLiteSharedTypes(ConfigurationBuilder builder)
typeof(ProjectServicesProvider),
typeof(IAppLauncher)
], exportBuilder => exportBuilder.WithPublicMethods(b => b.AlwaysReturnPromise().OnlyJsInvokable()));
builder.ExportAsInterfaces([typeof(ITroubleshootingService)], exportBuilder => exportBuilder.WithPublicMethods(b => b.AlwaysReturnPromise()));

builder.ExportAsInterfaces([
typeof(ServerStatus),
Expand Down
1 change: 0 additions & 1 deletion frontend/viewer/src/App.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
}
}
window.svelteNavigate = (to: string) => {
console.log('svelteNavigate', to);
navigate(to, {replace: true});
};
</script>
Expand Down
14 changes: 12 additions & 2 deletions frontend/viewer/src/HomeView.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
mdiChatQuestion,
mdiChevronRight,
mdiCloud,
mdiFaceAgent,
mdiRefresh,
mdiTestTube,
} from '@mdi/js';
Expand All @@ -22,11 +23,12 @@
useAuthService,
useFwLiteConfig,
useImportFwdataService,
useProjectsService
useProjectsService, useTroubleshootingService
} from './lib/services/service-provider';
import type {ILexboxServer, IServerStatus} from '$lib/dotnet-types';
import LoginButton from '$lib/auth/LoginButton.svelte';
import AnchorListItem from '$lib/utils/AnchorListItem.svelte';
import TroubleshootDialog from '$lib/troubleshoot/TroubleshootDialog.svelte';

const projectsService = useProjectsService();
const authService = useAuthService();
Expand Down Expand Up @@ -141,6 +143,8 @@
.find(([_server, projects]) => matchesProject(projects, project))?.[0];
return authority ? serversStatus.find(s => s.server.authority == authority)?.server : undefined;
}
const supportsTroubleshooting = useTroubleshootingService();
let showTroubleshooting = false;
</script>
<AppBar title="Projects" class="bg-secondary min-h-12 shadow-md justify-between" menuIcon={null}>
<div slot="title" class="text-lg flex gap-2 items-center">
Expand All @@ -151,7 +155,7 @@
</picture>
<h3>Projects</h3>
</div>
<div slot="actions">
<div slot="actions" class="flex gap-2">
<Button
href={fwLiteConfig.feedbackUrl}
target="_blank"
Expand All @@ -160,6 +164,12 @@
icon={mdiChatQuestion}>
Feedback
</Button>
{#if supportsTroubleshooting}
<Button size="sm" variant="outline" icon={mdiFaceAgent} title="Troubleshoot" iconOnly={false}
on:click={() => showTroubleshooting = !showTroubleshooting}>
</Button>
<TroubleshootDialog bind:open={showTroubleshooting}/>
{/if}
</div>
</AppBar>
<div class="mx-auto md:w-full md:py-4 max-w-2xl">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export enum DotnetService {
FwLiteConfig = "FwLiteConfig",
ProjectServicesProvider = "ProjectServicesProvider",
HistoryService = "HistoryService",
AppLauncher = "AppLauncher"
AppLauncher = "AppLauncher",
TroubleshootingService = "TroubleshootingService"
}
/* eslint-enable */
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/* eslint-disable */
// This code was generated by a Reinforced.Typings tool.
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.

export interface ITroubleshootingService
{
tryOpenDataDirectory() : Promise<boolean>;
getDataDirectory() : Promise<string>;
openLogFile() : Promise<void>;
shareLogFile() : Promise<void>;
}
/* eslint-enable */
20 changes: 18 additions & 2 deletions frontend/viewer/src/lib/layout/AppBarMenu.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,22 @@
import AboutDialog from '$lib/about/AboutDialog.svelte';
import ActivityView from '$lib/activity/ActivityView.svelte';
import {useFeatures} from '$lib/services/feature-service';
import {mdiDotsVertical, mdiEyeSettingsOutline, mdiHistory, mdiInformationVariantCircle, mdiNoteEdit} from '@mdi/js';
import {
mdiDotsVertical,
mdiEyeSettingsOutline,
mdiFaceAgent,
mdiHistory,
mdiInformationVariantCircle,
mdiNoteEdit
} from '@mdi/js';
import {createEventDispatcher} from 'svelte';
import {Button, MenuItem, ResponsiveMenu, Toggle} from 'svelte-ux';
import {asScottyPortal} from './Scotty.svelte';
import {useProjectViewState} from '$lib/services/project-view-state-service';
import WritingSystemDialog from '$lib/writing-system/WritingSystemDialog.svelte';
import DevContent from '$lib/layout/DevContent.svelte';
import TroubleshootDialog from '$lib/troubleshoot/TroubleshootDialog.svelte';
import {useTroubleshootingService} from '$lib/services/service-provider';

const dispatch = createEventDispatcher<{
showOptionsDialog: void;
Expand All @@ -19,10 +28,12 @@

const features = useFeatures();
const projectViewState = useProjectViewState();
const supportsTroubleshooting = useTroubleshootingService();

let activityViewOpen = false;
let aboutDialogOpen = false;
let wsEditDialogOpen = false;
let troubleshootDialogOpen = false;
</script>

<!-- #key prevents rendering ugly delayed state updates -->
Expand All @@ -49,6 +60,9 @@
Edit WS
</MenuItem>
</DevContent>
{#if supportsTroubleshooting}
<MenuItem icon={mdiFaceAgent} on:click={() => troubleshootDialogOpen = true}>Troubleshoot</MenuItem>
{/if}
</button>
</ResponsiveMenu>
</Button>
Expand All @@ -61,7 +75,9 @@
{#if about}
<AboutDialog bind:open={aboutDialogOpen} text={about} />
{/if}

{#if supportsTroubleshooting}
<TroubleshootDialog bind:open={troubleshootDialogOpen}/>
{/if}
<WritingSystemDialog bind:open={wsEditDialogOpen}/>

<style lang="postcss" global>
Expand Down
10 changes: 9 additions & 1 deletion frontend/viewer/src/lib/services/service-provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ import type {
IHistoryServiceJsInvokable
} from '$lib/dotnet-types/generated-types/FwLiteShared/Services/IHistoryServiceJsInvokable';
import type {IAppLauncher} from '$lib/dotnet-types/generated-types/FwLiteShared/Services/IAppLauncher';
import type {
ITroubleshootingService
} from '$lib/dotnet-types/generated-types/FwLiteShared/Services/ITroubleshootingService';

export type ServiceKey = keyof LexboxServiceRegistry;
export type LexboxServiceRegistry = {
Expand All @@ -22,7 +25,8 @@ export type LexboxServiceRegistry = {
[DotnetService.FwLiteConfig]: IFwLiteConfig,
[DotnetService.ProjectServicesProvider]: IProjectServicesProvider,
[DotnetService.HistoryService]: IHistoryServiceJsInvokable,
[DotnetService.AppLauncher]: IAppLauncher
[DotnetService.AppLauncher]: IAppLauncher,
[DotnetService.TroubleshootingService]: ITroubleshootingService
};

export const SERVICE_KEYS = Object.values(DotnetService);
Expand Down Expand Up @@ -97,3 +101,7 @@ export function useProjectServicesProvider(): IProjectServicesProvider {
export function useAppLauncher(): IAppLauncher | undefined {
return window.lexbox.ServiceProvider.tryGetService(DotnetService.AppLauncher);
}

export function useTroubleshootingService(): ITroubleshootingService | undefined {
return window.lexbox.ServiceProvider.tryGetService(DotnetService.TroubleshootingService);
}
34 changes: 34 additions & 0 deletions frontend/viewer/src/lib/troubleshoot/TroubleshootDialog.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<script lang="ts">
import {Button, Dialog, Field} from 'svelte-ux';
import {useTroubleshootingService} from '$lib/services/service-provider';
import {AppNotification} from '$lib/notifications/notifications';
import {mdiFileExport, mdiFileEye, mdiFolderSearch} from '@mdi/js';

const service = useTroubleshootingService();
export let open = false;

async function tryOpenDataDirectory() {
if (!await service?.tryOpenDataDirectory()) {
AppNotification.display('Failed to open data directory, use the path in the text field instead', 'error');
}
}
</script>
<Dialog bind:open={open} style="height: auto">
<div slot="title">Troubleshoot</div>
<div class="flex flex-col gap-4 items-start p-4">
{#await service?.getDataDirectory() then value}
<Field label="Data Directory" {value} class="self-stretch">
<span slot="append">
<Button icon={mdiFolderSearch} title="Open Data Directory" class="text-surface-content/50 p-2" on:click={() => tryOpenDataDirectory()}/>
</span>
</Field>
{/await}
<div>
<Button variant="fill-light" icon={mdiFileEye} on:click={() => service?.openLogFile()}>Open Log file</Button>
<Button variant="fill-light" icon={mdiFileExport} on:click={() => service?.shareLogFile()}>Share Log file</Button>
</div>
</div>
<div slot="actions">
<Button on:click={() => open = false}>Close</Button>
</div>
</Dialog>
Loading