mirror of
https://github.com/gitbutlerapp/gitbutler.git
synced 2024-12-23 17:43:47 +03:00
Use SectionCard instead of ClickableCard
- commit grew a bit in scope - lots of refactoring to settings pages - clickable card dropped
This commit is contained in:
parent
0311052f24
commit
645e076a78
12
gitbutler-ui/src/lib/backend/auth.ts
Normal file
12
gitbutler-ui/src/lib/backend/auth.ts
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import { invoke } from './ipc';
|
||||||
|
|
||||||
|
export type GitCredentialCheck = {
|
||||||
|
error?: string;
|
||||||
|
ok: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export class AuthService {
|
||||||
|
async getPublicKey() {
|
||||||
|
return await invoke<string>('get_public_key');
|
||||||
|
}
|
||||||
|
}
|
@ -16,13 +16,12 @@ import { get } from 'svelte/store';
|
|||||||
import type { Project as CloudProject } from '$lib/backend/cloud';
|
import type { Project as CloudProject } from '$lib/backend/cloud';
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
|
|
||||||
export type Key =
|
export type KeyType = 'default' | 'generated' | 'gitCredentialsHelper' | 'local';
|
||||||
| 'default'
|
export type LocalKey = {
|
||||||
| 'generated'
|
local: { private_key_path: string; passphrase?: string };
|
||||||
| 'gitCredentialsHelper'
|
};
|
||||||
| {
|
|
||||||
local: { private_key_path: string; passphrase?: string };
|
export type Key = Exclude<KeyType, 'local'> | LocalKey;
|
||||||
};
|
|
||||||
|
|
||||||
export type Project = {
|
export type Project = {
|
||||||
id: string;
|
id: string;
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import ClickableCard from './ClickableCard.svelte';
|
|
||||||
import InfoMessage from './InfoMessage.svelte';
|
import InfoMessage from './InfoMessage.svelte';
|
||||||
import Link from './Link.svelte';
|
import Link from './Link.svelte';
|
||||||
|
import SectionCard from './SectionCard.svelte';
|
||||||
import Toggle from './Toggle.svelte';
|
import Toggle from './Toggle.svelte';
|
||||||
import { appErrorReportingEnabled, appMetricsEnabled } from '$lib/config/appSettings';
|
import { appErrorReportingEnabled, appMetricsEnabled } from '$lib/config/appSettings';
|
||||||
|
|
||||||
@ -39,7 +39,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="analytics-settings__actions">
|
<div class="analytics-settings__actions">
|
||||||
<ClickableCard on:click={toggleErrorReporting}>
|
<SectionCard on:click={toggleErrorReporting} orientation="row">
|
||||||
<svelte:fragment slot="title">Error reporting</svelte:fragment>
|
<svelte:fragment slot="title">Error reporting</svelte:fragment>
|
||||||
<svelte:fragment slot="body">
|
<svelte:fragment slot="body">
|
||||||
Toggle reporting of application crashes and errors.
|
Toggle reporting of application crashes and errors.
|
||||||
@ -47,15 +47,15 @@
|
|||||||
<svelte:fragment slot="actions">
|
<svelte:fragment slot="actions">
|
||||||
<Toggle checked={$errorReportingEnabled} on:change={toggleErrorReporting} />
|
<Toggle checked={$errorReportingEnabled} on:change={toggleErrorReporting} />
|
||||||
</svelte:fragment>
|
</svelte:fragment>
|
||||||
</ClickableCard>
|
</SectionCard>
|
||||||
|
|
||||||
<ClickableCard on:click={toggleMetrics}>
|
<SectionCard on:click={toggleMetrics} orientation="row">
|
||||||
<svelte:fragment slot="title">Usage metrics</svelte:fragment>
|
<svelte:fragment slot="title">Usage metrics</svelte:fragment>
|
||||||
<svelte:fragment slot="body">Toggle sharing of usage statistics.</svelte:fragment>
|
<svelte:fragment slot="body">Toggle sharing of usage statistics.</svelte:fragment>
|
||||||
<svelte:fragment slot="actions">
|
<svelte:fragment slot="actions">
|
||||||
<Toggle checked={$metricsEnabled} on:change={toggleMetrics} />
|
<Toggle checked={$metricsEnabled} on:change={toggleMetrics} />
|
||||||
</svelte:fragment>
|
</svelte:fragment>
|
||||||
</ClickableCard>
|
</SectionCard>
|
||||||
|
|
||||||
{#if updatedTelemetrySettings}
|
{#if updatedTelemetrySettings}
|
||||||
<InfoMessage>Changes will take effect on the next application start.</InfoMessage>
|
<InfoMessage>Changes will take effect on the next application start.</InfoMessage>
|
||||||
|
@ -1,119 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import { createEventDispatcher } from 'svelte';
|
|
||||||
|
|
||||||
export let padding: string = 'var(--space-16)';
|
|
||||||
export let disabled = false;
|
|
||||||
export let checked = false;
|
|
||||||
export let hasTopRadius = true;
|
|
||||||
export let hasBottomRadius = true;
|
|
||||||
export let hasBottomLine = true;
|
|
||||||
|
|
||||||
const SLOTS = $$props.$$slots;
|
|
||||||
|
|
||||||
const dispatchClick = createEventDispatcher<{
|
|
||||||
click: void;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const dispatchChange = createEventDispatcher<{
|
|
||||||
change: boolean;
|
|
||||||
}>();
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<button
|
|
||||||
class="clickable-card"
|
|
||||||
class:has-top-radius={hasTopRadius}
|
|
||||||
class:has-bottom-radius={hasBottomRadius}
|
|
||||||
class:has-bottom-line={hasBottomLine}
|
|
||||||
style="padding: {padding}"
|
|
||||||
on:click={() => {
|
|
||||||
dispatchClick('click');
|
|
||||||
|
|
||||||
dispatchChange('change', checked);
|
|
||||||
|
|
||||||
checked = !checked;
|
|
||||||
}}
|
|
||||||
class:card-disabled={disabled}
|
|
||||||
{disabled}
|
|
||||||
>
|
|
||||||
<div class="clickable-card__content">
|
|
||||||
{#if SLOTS.title}
|
|
||||||
<h3 class="text-base-15 text-bold clickable-card__title">
|
|
||||||
<slot name="title" />
|
|
||||||
</h3>
|
|
||||||
{/if}
|
|
||||||
{#if SLOTS.body}
|
|
||||||
<p class="text-base-body-12 clickable-card__text">
|
|
||||||
<slot name="body" />
|
|
||||||
</p>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
{#if SLOTS.actions}
|
|
||||||
<div class="clickable-card__actions">
|
|
||||||
<slot name="actions" />
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<style lang="post-css">
|
|
||||||
.clickable-card {
|
|
||||||
display: flex;
|
|
||||||
gap: var(--space-16);
|
|
||||||
border-left: 1px solid var(--clr-theme-container-outline-light);
|
|
||||||
border-right: 1px solid var(--clr-theme-container-outline-light);
|
|
||||||
background-color: var(--clr-theme-container-light);
|
|
||||||
transition:
|
|
||||||
background-color var(--transition-fast),
|
|
||||||
border-color var(--transition-fast);
|
|
||||||
text-align: left;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background-color: color-mix(
|
|
||||||
in srgb,
|
|
||||||
var(--clr-theme-container-light),
|
|
||||||
var(--darken-tint-extralight)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-disabled {
|
|
||||||
opacity: 0.6;
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.clickable-card__content {
|
|
||||||
flex: 1;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: var(--space-8);
|
|
||||||
}
|
|
||||||
|
|
||||||
.clickable-card__title {
|
|
||||||
color: var(--clr-theme-scale-ntrl-0);
|
|
||||||
}
|
|
||||||
|
|
||||||
.clickable-card__text {
|
|
||||||
color: var(--clr-theme-scale-ntrl-30);
|
|
||||||
}
|
|
||||||
|
|
||||||
.clickable-card__actions {
|
|
||||||
display: flex;
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* MODIFIERS */
|
|
||||||
|
|
||||||
.has-top-radius {
|
|
||||||
border-top: 1px solid var(--clr-theme-container-outline-light);
|
|
||||||
border-top-left-radius: var(--radius-l);
|
|
||||||
border-top-right-radius: var(--radius-l);
|
|
||||||
}
|
|
||||||
|
|
||||||
.has-bottom-radius {
|
|
||||||
border-bottom-left-radius: var(--radius-l);
|
|
||||||
border-bottom-right-radius: var(--radius-l);
|
|
||||||
}
|
|
||||||
|
|
||||||
.has-bottom-line {
|
|
||||||
border-bottom: 1px solid var(--clr-theme-container-outline-light);
|
|
||||||
}
|
|
||||||
</style>
|
|
@ -1,6 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import SectionCard from './SectionCard.svelte';
|
||||||
import { getCloudApiClient, type User } from '$lib/backend/cloud';
|
import { getCloudApiClient, type User } from '$lib/backend/cloud';
|
||||||
import ClickableCard from '$lib/components/ClickableCard.svelte';
|
|
||||||
import Link from '$lib/components/Link.svelte';
|
import Link from '$lib/components/Link.svelte';
|
||||||
import Spacer from '$lib/components/Spacer.svelte';
|
import Spacer from '$lib/components/Spacer.svelte';
|
||||||
import Toggle from '$lib/components/Toggle.svelte';
|
import Toggle from '$lib/components/Toggle.svelte';
|
||||||
@ -33,7 +33,7 @@
|
|||||||
dispatch('updated', { ...project, api: { ...cloudProject, sync: project.api.sync } });
|
dispatch('updated', { ...project, api: { ...cloudProject, sync: project.api.sync } });
|
||||||
});
|
});
|
||||||
|
|
||||||
const onSyncChange = async (event: CustomEvent<boolean>) => {
|
async function onSyncChange(sync: boolean) {
|
||||||
if (!user) return;
|
if (!user) return;
|
||||||
try {
|
try {
|
||||||
const cloudProject =
|
const cloudProject =
|
||||||
@ -43,26 +43,26 @@
|
|||||||
description: project.description,
|
description: project.description,
|
||||||
uid: project.id
|
uid: project.id
|
||||||
}));
|
}));
|
||||||
dispatch('updated', { ...project, api: { ...cloudProject, sync: event.detail } });
|
dispatch('updated', { ...project, api: { ...cloudProject, sync } });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`Failed to update project sync status: ${error}`);
|
console.error(`Failed to update project sync status: ${error}`);
|
||||||
toasts.error('Failed to update project sync status');
|
toasts.error('Failed to update project sync status');
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
const aiGenToggle = () => {
|
function aiGenToggle() {
|
||||||
$aiGenEnabled = !$aiGenEnabled;
|
$aiGenEnabled = !$aiGenEnabled;
|
||||||
$aiGenAutoBranchNamingEnabled = $aiGenEnabled;
|
$aiGenAutoBranchNamingEnabled = $aiGenEnabled;
|
||||||
};
|
}
|
||||||
|
|
||||||
const aiGenBranchNamesToggle = () => {
|
function aiGenBranchNamesToggle() {
|
||||||
$aiGenAutoBranchNamingEnabled = !$aiGenAutoBranchNamingEnabled;
|
$aiGenAutoBranchNamingEnabled = !$aiGenAutoBranchNamingEnabled;
|
||||||
};
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if user}
|
{#if user}
|
||||||
<div class="aigen-wrap">
|
<div class="aigen-wrap">
|
||||||
<ClickableCard on:click={aiGenToggle}>
|
<SectionCard on:click={aiGenToggle} orientation="row">
|
||||||
<svelte:fragment slot="title">Enable branch and commit message generation</svelte:fragment>
|
<svelte:fragment slot="title">Enable branch and commit message generation</svelte:fragment>
|
||||||
<svelte:fragment slot="body">
|
<svelte:fragment slot="body">
|
||||||
Uses OpenAI's API. If enabled, diffs will sent to OpenAI's servers when pressing the
|
Uses OpenAI's API. If enabled, diffs will sent to OpenAI's servers when pressing the
|
||||||
@ -71,9 +71,9 @@
|
|||||||
<svelte:fragment slot="actions">
|
<svelte:fragment slot="actions">
|
||||||
<Toggle checked={$aiGenEnabled} on:change={aiGenToggle} />
|
<Toggle checked={$aiGenEnabled} on:change={aiGenToggle} />
|
||||||
</svelte:fragment>
|
</svelte:fragment>
|
||||||
</ClickableCard>
|
</SectionCard>
|
||||||
|
|
||||||
<ClickableCard disabled={!$aiGenEnabled} on:click={aiGenBranchNamesToggle}>
|
<SectionCard disabled={!$aiGenEnabled} on:click={aiGenBranchNamesToggle} orientation="row">
|
||||||
<svelte:fragment slot="title">Automatically generate branch names</svelte:fragment>
|
<svelte:fragment slot="title">Automatically generate branch names</svelte:fragment>
|
||||||
<svelte:fragment slot="actions">
|
<svelte:fragment slot="actions">
|
||||||
<Toggle
|
<Toggle
|
||||||
@ -82,7 +82,7 @@
|
|||||||
on:change={aiGenBranchNamesToggle}
|
on:change={aiGenBranchNamesToggle}
|
||||||
/>
|
/>
|
||||||
</svelte:fragment>
|
</svelte:fragment>
|
||||||
</ClickableCard>
|
</SectionCard>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Spacer />
|
<Spacer />
|
||||||
@ -90,14 +90,14 @@
|
|||||||
{#if user.role === 'admin'}
|
{#if user.role === 'admin'}
|
||||||
<h3 class="text-base-15 text-bold">Full data synchronization</h3>
|
<h3 class="text-base-15 text-bold">Full data synchronization</h3>
|
||||||
|
|
||||||
<ClickableCard on:change={onSyncChange}>
|
<SectionCard on:change={(e) => onSyncChange(e.detail)} orientation="row">
|
||||||
<svelte:fragment slot="body">
|
<svelte:fragment slot="body">
|
||||||
Sync my history, repository and branch data for backup, sharing and team features.
|
Sync my history, repository and branch data for backup, sharing and team features.
|
||||||
</svelte:fragment>
|
</svelte:fragment>
|
||||||
<svelte:fragment slot="actions">
|
<svelte:fragment slot="actions">
|
||||||
<Toggle checked={project.api?.sync || false} on:change={onSyncChange} />
|
<Toggle checked={project.api?.sync || false} on:change={(e) => onSyncChange(e.detail)} />
|
||||||
</svelte:fragment>
|
</svelte:fragment>
|
||||||
</ClickableCard>
|
</SectionCard>
|
||||||
|
|
||||||
{#if project.api}
|
{#if project.api}
|
||||||
<div class="api-link">
|
<div class="api-link">
|
||||||
|
@ -1,19 +1,17 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import ClickableCard from './ClickableCard.svelte';
|
|
||||||
import RadioButton from './RadioButton.svelte';
|
import RadioButton from './RadioButton.svelte';
|
||||||
import SectionCard from './SectionCard.svelte';
|
import SectionCard from './SectionCard.svelte';
|
||||||
import Spacer from './Spacer.svelte';
|
import Spacer from './Spacer.svelte';
|
||||||
import TextBox from './TextBox.svelte';
|
import TextBox from './TextBox.svelte';
|
||||||
import { invoke } from '$lib/backend/ipc';
|
|
||||||
import Button from '$lib/components/Button.svelte';
|
import Button from '$lib/components/Button.svelte';
|
||||||
import Link from '$lib/components/Link.svelte';
|
import Link from '$lib/components/Link.svelte';
|
||||||
import { copyToClipboard } from '$lib/utils/clipboard';
|
import { copyToClipboard } from '$lib/utils/clipboard';
|
||||||
import { debounce } from '$lib/utils/debounce';
|
|
||||||
import { openExternalUrl } from '$lib/utils/url';
|
import { openExternalUrl } from '$lib/utils/url';
|
||||||
import { createEventDispatcher } from 'svelte';
|
import { createEventDispatcher, onMount } from 'svelte';
|
||||||
import type { Key, Project } from '$lib/backend/projects';
|
import type { Key, KeyType, Project } from '$lib/backend/projects';
|
||||||
|
|
||||||
export let project: Project;
|
export let project: Project;
|
||||||
|
export let sshKey = '';
|
||||||
|
|
||||||
const dispatch = createEventDispatcher<{
|
const dispatch = createEventDispatcher<{
|
||||||
updated: {
|
updated: {
|
||||||
@ -21,234 +19,186 @@
|
|||||||
};
|
};
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
export function get_public_key() {
|
let selectedType: KeyType =
|
||||||
return invoke<string>('get_public_key');
|
typeof project.preferred_key == 'string' ? project.preferred_key : 'local';
|
||||||
}
|
|
||||||
|
|
||||||
let sshKey = '';
|
|
||||||
get_public_key().then((key) => {
|
|
||||||
sshKey = key;
|
|
||||||
});
|
|
||||||
|
|
||||||
let selectedOption =
|
|
||||||
project.preferred_key === 'generated'
|
|
||||||
? 'generated'
|
|
||||||
: project.preferred_key === 'default'
|
|
||||||
? 'default'
|
|
||||||
: project.preferred_key === 'gitCredentialsHelper'
|
|
||||||
? 'gitCredentialsHelper'
|
|
||||||
: 'local';
|
|
||||||
|
|
||||||
let privateKeyPath =
|
let privateKeyPath =
|
||||||
project.preferred_key === 'generated' ||
|
typeof project.preferred_key == 'string' ? '' : project.preferred_key.local.private_key_path;
|
||||||
project.preferred_key === 'default' ||
|
|
||||||
project.preferred_key === 'gitCredentialsHelper'
|
|
||||||
? ''
|
|
||||||
: project.preferred_key.local.private_key_path;
|
|
||||||
|
|
||||||
let privateKeyPassphrase =
|
let privateKeyPassphrase =
|
||||||
project.preferred_key === 'generated' ||
|
typeof project.preferred_key == 'string' ? '' : project.preferred_key.local.passphrase;
|
||||||
project.preferred_key === 'default' ||
|
|
||||||
project.preferred_key === 'gitCredentialsHelper'
|
|
||||||
? ''
|
|
||||||
: project.preferred_key.local.passphrase;
|
|
||||||
|
|
||||||
function setLocalKey() {
|
function setLocalKey() {
|
||||||
if (privateKeyPath.length) {
|
if (privateKeyPath.trim().length == 0) return;
|
||||||
dispatch('updated', {
|
dispatch('updated', {
|
||||||
preferred_key: {
|
preferred_key: {
|
||||||
local: {
|
local: {
|
||||||
private_key_path: privateKeyPath,
|
private_key_path: privateKeyPath.trim(),
|
||||||
passphrase:
|
passphrase: privateKeyPassphrase || undefined
|
||||||
privateKeyPassphrase && privateKeyPassphrase.length > 0
|
|
||||||
? privateKeyPassphrase
|
|
||||||
: undefined
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function setGitCredentialsHelperKey() {
|
|
||||||
dispatch('updated', {
|
|
||||||
preferred_key: 'gitCredentialsHelper'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function setDefaultKey() {
|
|
||||||
dispatch('updated', {
|
|
||||||
preferred_key: 'default'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function setGeneratedKey() {
|
|
||||||
dispatch('updated', {
|
|
||||||
preferred_key: 'generated'
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
let showPassphrase = false;
|
let showPassphrase = false;
|
||||||
|
|
||||||
|
let form: HTMLFormElement;
|
||||||
|
|
||||||
|
function onFormChange(form: HTMLFormElement) {
|
||||||
|
const formData = new FormData(form);
|
||||||
|
selectedType = formData.get('credentialType') as KeyType;
|
||||||
|
if (selectedType != 'local') {
|
||||||
|
dispatch('updated', { preferred_key: selectedType });
|
||||||
|
} else {
|
||||||
|
setLocalKey();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
form.credentialType.value = selectedType;
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<section class="git-auth-wrap">
|
<div class="git-auth-wrap">
|
||||||
<h3 class="text-base-15 text-bold">Git Authentication</h3>
|
<h3 class="text-base-15 text-bold">Git Authentication</h3>
|
||||||
<p class="text-base-body-12">
|
<p class="text-base-body-12">
|
||||||
Configure the authentication flow for GitButler when authenticating with your Git remote
|
Configure the authentication flow for GitButler when authenticating with your Git remote
|
||||||
provider.
|
provider.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<form>
|
<form class="git-radio" bind:this={form} on:change={(e) => onFormChange(e.currentTarget)}>
|
||||||
<fieldset class="git-radio">
|
<SectionCard roundedBottom={false} orientation="row" labelFor="credential-default">
|
||||||
<ClickableCard
|
<svelte:fragment slot="title">Auto detect</svelte:fragment>
|
||||||
hasBottomRadius={false}
|
|
||||||
on:click={() => {
|
|
||||||
if (selectedOption == 'default') return;
|
|
||||||
|
|
||||||
selectedOption = 'default';
|
<svelte:fragment slot="actions">
|
||||||
setDefaultKey();
|
<RadioButton name="credentialType" id="credential-default" value="default" />
|
||||||
}}
|
</svelte:fragment>
|
||||||
>
|
|
||||||
<svelte:fragment slot="title">Auto detect</svelte:fragment>
|
|
||||||
|
|
||||||
<svelte:fragment slot="actions">
|
<svelte:fragment slot="body">
|
||||||
<RadioButton bind:group={selectedOption} value="default" on:input={setDefaultKey} />
|
GitButler will attempt all available authentication flows automatically.
|
||||||
</svelte:fragment>
|
</svelte:fragment>
|
||||||
|
</SectionCard>
|
||||||
|
|
||||||
<svelte:fragment slot="body">
|
<SectionCard
|
||||||
GitButler will attempt all available authentication flows automatically.
|
roundedTop={false}
|
||||||
</svelte:fragment>
|
roundedBottom={false}
|
||||||
</ClickableCard>
|
bottomBorder={selectedType !== 'local'}
|
||||||
|
orientation="row"
|
||||||
|
labelFor="credential-local"
|
||||||
|
>
|
||||||
|
<svelte:fragment slot="title">Use existing SSH key</svelte:fragment>
|
||||||
|
|
||||||
<ClickableCard
|
<svelte:fragment slot="actions">
|
||||||
hasTopRadius={false}
|
<RadioButton name="credentialType" id="credential-local" value="local" />
|
||||||
hasBottomRadius={false}
|
</svelte:fragment>
|
||||||
hasBottomLine={selectedOption !== 'local'}
|
|
||||||
on:click={() => {
|
|
||||||
selectedOption = 'local';
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<svelte:fragment slot="title">Use existing SSH key</svelte:fragment>
|
|
||||||
|
|
||||||
<svelte:fragment slot="actions">
|
<svelte:fragment slot="body">
|
||||||
<RadioButton bind:group={selectedOption} value="local" />
|
Add the path to an existing SSH key that GitButler can use.
|
||||||
</svelte:fragment>
|
</svelte:fragment>
|
||||||
|
</SectionCard>
|
||||||
|
|
||||||
<svelte:fragment slot="body">
|
{#if selectedType == 'local'}
|
||||||
Add the path to an existing SSH key that GitButler can use.
|
<SectionCard hasTopRadius={false} hasBottomRadius={false} orientation="row">
|
||||||
</svelte:fragment>
|
<div class="inputs-group">
|
||||||
</ClickableCard>
|
<TextBox
|
||||||
|
label="Path to private key"
|
||||||
{#if selectedOption === 'local'}
|
placeholder="for example: ~/.ssh/id_rsa"
|
||||||
<SectionCard hasTopRadius={false} hasBottomRadius={false}>
|
bind:value={privateKeyPath}
|
||||||
<div class="inputs-group">
|
|
||||||
<TextBox
|
|
||||||
label="Path to private key"
|
|
||||||
placeholder="for example: ~/.ssh/id_rsa"
|
|
||||||
bind:value={privateKeyPath}
|
|
||||||
on:input={debounce(setLocalKey, 600)}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div class="input-with-button">
|
|
||||||
<TextBox
|
|
||||||
label="Passphrase (optional)"
|
|
||||||
type={showPassphrase ? 'text' : 'password'}
|
|
||||||
bind:value={privateKeyPassphrase}
|
|
||||||
on:input={debounce(setLocalKey, 600)}
|
|
||||||
wide
|
|
||||||
/>
|
|
||||||
<Button
|
|
||||||
size="large"
|
|
||||||
color="neutral"
|
|
||||||
kind="outlined"
|
|
||||||
icon={showPassphrase ? 'eye-shown' : 'eye-hidden'}
|
|
||||||
on:click={() => (showPassphrase = !showPassphrase)}
|
|
||||||
width={150}
|
|
||||||
>
|
|
||||||
{showPassphrase ? 'Hide passphrase' : 'Show passphrase'}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</SectionCard>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
<ClickableCard
|
|
||||||
hasTopRadius={false}
|
|
||||||
hasBottomRadius={false}
|
|
||||||
hasBottomLine={selectedOption !== 'generated'}
|
|
||||||
on:click={() => {
|
|
||||||
if (selectedOption == 'generated') return;
|
|
||||||
|
|
||||||
selectedOption = 'generated';
|
|
||||||
setGeneratedKey();
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<svelte:fragment slot="title">Use locally generated SSH key</svelte:fragment>
|
|
||||||
|
|
||||||
<svelte:fragment slot="actions">
|
|
||||||
<RadioButton bind:group={selectedOption} value="generated" on:input={setGeneratedKey} />
|
|
||||||
</svelte:fragment>
|
|
||||||
|
|
||||||
<svelte:fragment slot="body">
|
|
||||||
GitButler will use a locally generated SSH key. For this to work you need to add the
|
|
||||||
following public key to your Git remote provider:
|
|
||||||
</svelte:fragment>
|
|
||||||
</ClickableCard>
|
|
||||||
|
|
||||||
{#if selectedOption === 'generated'}
|
|
||||||
<SectionCard hasTopRadius={false} hasBottomRadius={false}>
|
|
||||||
<TextBox readonly selectall bind:value={sshKey} />
|
|
||||||
<div class="row-buttons">
|
|
||||||
<Button
|
|
||||||
kind="filled"
|
|
||||||
color="primary"
|
|
||||||
icon="copy"
|
|
||||||
on:mousedown={() => copyToClipboard(sshKey)}
|
|
||||||
>
|
|
||||||
Copy to Clipboard
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
kind="outlined"
|
|
||||||
color="neutral"
|
|
||||||
icon="open-link"
|
|
||||||
on:mousedown={() => {
|
|
||||||
openExternalUrl('https://github.com/settings/ssh/new');
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Add key to GitHub
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</SectionCard>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
<ClickableCard
|
|
||||||
hasTopRadius={false}
|
|
||||||
on:click={() => {
|
|
||||||
if (selectedOption == 'gitCredentialsHelper') return;
|
|
||||||
|
|
||||||
selectedOption = 'gitCredentialsHelper';
|
|
||||||
setGitCredentialsHelperKey();
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<svelte:fragment slot="title">Use a Git credentials helper</svelte:fragment>
|
|
||||||
|
|
||||||
<svelte:fragment slot="body">
|
|
||||||
GitButler will use the system's git credentials helper.
|
|
||||||
<Link target="_blank" rel="noreferrer" href="https://git-scm.com/doc/credential-helpers">
|
|
||||||
Learn more
|
|
||||||
</Link>
|
|
||||||
</svelte:fragment>
|
|
||||||
|
|
||||||
<svelte:fragment slot="actions">
|
|
||||||
<RadioButton
|
|
||||||
bind:group={selectedOption}
|
|
||||||
value="gitCredentialsHelper"
|
|
||||||
on:input={setGitCredentialsHelperKey}
|
|
||||||
/>
|
/>
|
||||||
</svelte:fragment>
|
|
||||||
</ClickableCard>
|
<div class="input-with-button">
|
||||||
</fieldset>
|
<TextBox
|
||||||
|
label="Passphrase (optional)"
|
||||||
|
type={showPassphrase ? 'text' : 'password'}
|
||||||
|
bind:value={privateKeyPassphrase}
|
||||||
|
wide
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
size="large"
|
||||||
|
color="neutral"
|
||||||
|
kind="outlined"
|
||||||
|
icon={showPassphrase ? 'eye-shown' : 'eye-hidden'}
|
||||||
|
on:click={() => (showPassphrase = !showPassphrase)}
|
||||||
|
width={150}
|
||||||
|
>
|
||||||
|
{showPassphrase ? 'Hide passphrase' : 'Show passphrase'}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</SectionCard>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<SectionCard
|
||||||
|
roundedTop={false}
|
||||||
|
roundedBottom={false}
|
||||||
|
bottomBorder={selectedType !== 'generated'}
|
||||||
|
orientation="row"
|
||||||
|
labelFor="credential-generated"
|
||||||
|
>
|
||||||
|
<svelte:fragment slot="title">Use locally generated SSH key</svelte:fragment>
|
||||||
|
|
||||||
|
<svelte:fragment slot="actions">
|
||||||
|
<RadioButton name="credentialType" id="credential-generated" value="generated" />
|
||||||
|
</svelte:fragment>
|
||||||
|
|
||||||
|
<svelte:fragment slot="body">
|
||||||
|
GitButler will use a locally generated SSH key. For this to work you need to add the
|
||||||
|
following public key to your Git remote provider:
|
||||||
|
</svelte:fragment>
|
||||||
|
</SectionCard>
|
||||||
|
|
||||||
|
{#if selectedType === 'generated'}
|
||||||
|
<SectionCard hasTopRadius={false} hasBottomRadius={false} orientation="row">
|
||||||
|
<TextBox readonly selectall bind:value={sshKey} />
|
||||||
|
<div class="row-buttons">
|
||||||
|
<Button
|
||||||
|
kind="filled"
|
||||||
|
color="primary"
|
||||||
|
icon="copy"
|
||||||
|
on:mousedown={() => copyToClipboard(sshKey)}
|
||||||
|
>
|
||||||
|
Copy to Clipboard
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
kind="outlined"
|
||||||
|
color="neutral"
|
||||||
|
icon="open-link"
|
||||||
|
on:mousedown={() => {
|
||||||
|
openExternalUrl('https://github.com/settings/ssh/new');
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Add key to GitHub
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</SectionCard>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<SectionCard
|
||||||
|
roundedTop={false}
|
||||||
|
roundedBottom={false}
|
||||||
|
orientation="row"
|
||||||
|
labelFor="credential-helper"
|
||||||
|
>
|
||||||
|
<svelte:fragment slot="title">Use a Git credentials helper</svelte:fragment>
|
||||||
|
|
||||||
|
<svelte:fragment slot="body">
|
||||||
|
GitButler will use the system's git credentials helper.
|
||||||
|
<Link target="_blank" rel="noreferrer" href="https://git-scm.com/doc/credential-helpers">
|
||||||
|
Learn more
|
||||||
|
</Link>
|
||||||
|
</svelte:fragment>
|
||||||
|
|
||||||
|
<svelte:fragment slot="actions">
|
||||||
|
<RadioButton name="credentialType" value="gitCredentialsHelper" id="credential-helper" />
|
||||||
|
</svelte:fragment>
|
||||||
|
</SectionCard>
|
||||||
|
<SectionCard roundedTop={false} orientation="row">
|
||||||
|
<svelte:fragment slot="body">
|
||||||
|
<Button wide>Test credentials</Button>
|
||||||
|
</svelte:fragment>
|
||||||
|
</SectionCard>
|
||||||
</form>
|
</form>
|
||||||
</section>
|
</div>
|
||||||
|
|
||||||
<Spacer />
|
<Spacer />
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import SectionCard from './SectionCard.svelte';
|
||||||
import Spacer from './Spacer.svelte';
|
import Spacer from './Spacer.svelte';
|
||||||
import ClickableCard from '$lib/components/ClickableCard.svelte';
|
|
||||||
import Toggle from '$lib/components/Toggle.svelte';
|
import Toggle from '$lib/components/Toggle.svelte';
|
||||||
import { projectRunCommitHooks } from '$lib/config/config';
|
import { projectRunCommitHooks } from '$lib/config/config';
|
||||||
import { createEventDispatcher } from 'svelte';
|
import { createEventDispatcher } from 'svelte';
|
||||||
@ -18,62 +18,48 @@
|
|||||||
omit_certificate_check?: boolean;
|
omit_certificate_check?: boolean;
|
||||||
};
|
};
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const onAllowForcePushingChange = () => {
|
|
||||||
dispatch('updated', { ok_with_force_push: allowForcePushing });
|
|
||||||
};
|
|
||||||
|
|
||||||
const onOmitCertificateCheckChange = () => {
|
|
||||||
dispatch('updated', { omit_certificate_check: omitCertificateCheck });
|
|
||||||
};
|
|
||||||
|
|
||||||
const onRunCommitHooksChange = () => {
|
|
||||||
$runCommitHooks = !$runCommitHooks;
|
|
||||||
};
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<section class="wrapper">
|
<section class="wrapper">
|
||||||
<ClickableCard
|
<SectionCard orientation="row" labelFor="allowForcePush">
|
||||||
on:click={() => {
|
|
||||||
allowForcePushing = !allowForcePushing;
|
|
||||||
onAllowForcePushingChange();
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<svelte:fragment slot="title">Allow force pushing</svelte:fragment>
|
<svelte:fragment slot="title">Allow force pushing</svelte:fragment>
|
||||||
<svelte:fragment slot="body">
|
<svelte:fragment slot="body">
|
||||||
Force pushing allows GitButler to override branches even if they were pushed to remote. We
|
Force pushing allows GitButler to override branches even if they were pushed to remote. We
|
||||||
will never force push to the trunk.
|
will never force push to the trunk.
|
||||||
</svelte:fragment>
|
</svelte:fragment>
|
||||||
<svelte:fragment slot="actions">
|
<svelte:fragment slot="actions">
|
||||||
<Toggle bind:checked={allowForcePushing} on:change={onAllowForcePushingChange} />
|
<Toggle
|
||||||
|
id="allowForcePush"
|
||||||
|
bind:checked={allowForcePushing}
|
||||||
|
on:change={() => dispatch('updated', { ok_with_force_push: allowForcePushing })}
|
||||||
|
/>
|
||||||
</svelte:fragment>
|
</svelte:fragment>
|
||||||
</ClickableCard>
|
</SectionCard>
|
||||||
|
|
||||||
<ClickableCard
|
<SectionCard orientation="row" labelFor="omitCertificateCheck">
|
||||||
on:click={() => {
|
|
||||||
omitCertificateCheck = !omitCertificateCheck;
|
|
||||||
onOmitCertificateCheckChange();
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<svelte:fragment slot="title">Ignore host certificate checks</svelte:fragment>
|
<svelte:fragment slot="title">Ignore host certificate checks</svelte:fragment>
|
||||||
<svelte:fragment slot="body">
|
<svelte:fragment slot="body">
|
||||||
Enabling this will ignore host certificate checks when authenticating with ssh.
|
Enabling this will ignore host certificate checks when authenticating with ssh.
|
||||||
</svelte:fragment>
|
</svelte:fragment>
|
||||||
<svelte:fragment slot="actions">
|
<svelte:fragment slot="actions">
|
||||||
<Toggle bind:checked={omitCertificateCheck} on:change={onOmitCertificateCheckChange} />
|
<Toggle
|
||||||
|
id="omitCertificateCheck"
|
||||||
|
bind:checked={omitCertificateCheck}
|
||||||
|
on:change={() => dispatch('updated', { omit_certificate_check: omitCertificateCheck })}
|
||||||
|
/>
|
||||||
</svelte:fragment>
|
</svelte:fragment>
|
||||||
</ClickableCard>
|
</SectionCard>
|
||||||
|
|
||||||
<ClickableCard on:click={onRunCommitHooksChange}>
|
<SectionCard labelFor="runHooks" orientation="row">
|
||||||
<svelte:fragment slot="title">Run commit hooks</svelte:fragment>
|
<svelte:fragment slot="title">Run commit hooks</svelte:fragment>
|
||||||
<svelte:fragment slot="body">
|
<svelte:fragment slot="body">
|
||||||
Enabling this will run any git pre and post commit hooks you have configured in your
|
Enabling this will run any git pre and post commit hooks you have configured in your
|
||||||
repository.
|
repository.
|
||||||
</svelte:fragment>
|
</svelte:fragment>
|
||||||
<svelte:fragment slot="actions">
|
<svelte:fragment slot="actions">
|
||||||
<Toggle bind:checked={$runCommitHooks} on:change={onRunCommitHooksChange} />
|
<Toggle id="runHooks" bind:checked={$runCommitHooks} />
|
||||||
</svelte:fragment>
|
</svelte:fragment>
|
||||||
</ClickableCard>
|
</SectionCard>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<Spacer />
|
<Spacer />
|
||||||
|
@ -3,8 +3,8 @@
|
|||||||
|
|
||||||
export let small = false;
|
export let small = false;
|
||||||
export let disabled = false;
|
export let disabled = false;
|
||||||
export let group = '';
|
|
||||||
export let value = '';
|
export let value = '';
|
||||||
|
export let id = '';
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<input
|
<input
|
||||||
@ -14,10 +14,10 @@
|
|||||||
type="radio"
|
type="radio"
|
||||||
class="radio"
|
class="radio"
|
||||||
class:small
|
class:small
|
||||||
|
{id}
|
||||||
{value}
|
{value}
|
||||||
{name}
|
{name}
|
||||||
{disabled}
|
{disabled}
|
||||||
bind:group
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<style lang="postcss">
|
<style lang="postcss">
|
||||||
|
@ -1,18 +1,32 @@
|
|||||||
|
<script lang="ts" context="module">
|
||||||
|
export type SectionCardBackground = 'loading' | 'success' | 'error' | undefined;
|
||||||
|
</script>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
export let orientation: 'row' | 'column' = 'column';
|
export let orientation: 'row' | 'column' = 'column';
|
||||||
export let hasTopRadius = true;
|
export let extraPadding = false;
|
||||||
export let hasBottomRadius = true;
|
export let roundedTop = true;
|
||||||
export let hasBottomLine = true;
|
export let roundedBottom = true;
|
||||||
|
export let bottomBorder = true;
|
||||||
|
export let background: SectionCardBackground = undefined;
|
||||||
|
export let noBorder = false;
|
||||||
|
export let labelFor = '';
|
||||||
|
|
||||||
const SLOTS = $$props.$$slots;
|
const SLOTS = $$props.$$slots;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<section
|
<label
|
||||||
|
for={labelFor}
|
||||||
class="section-card"
|
class="section-card"
|
||||||
style:flex-direction={orientation}
|
style:flex-direction={orientation}
|
||||||
class:has-top-radius={hasTopRadius}
|
class:extra-padding={extraPadding}
|
||||||
class:has-bottom-radius={hasBottomRadius}
|
class:rounded-top={roundedTop}
|
||||||
class:has-bottom-line={hasBottomLine}
|
class:rounded-bottom={roundedBottom}
|
||||||
|
class:bottom-border={bottomBorder}
|
||||||
|
class:no-border={noBorder}
|
||||||
|
class:loading={background == 'loading'}
|
||||||
|
class:success={background == 'success'}
|
||||||
|
class:error={background == 'error'}
|
||||||
>
|
>
|
||||||
{#if SLOTS.iconSide}
|
{#if SLOTS.iconSide}
|
||||||
<div class="section-card__icon-side">
|
<div class="section-card__icon-side">
|
||||||
@ -35,7 +49,12 @@
|
|||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
<slot />
|
<slot />
|
||||||
</section>
|
{#if SLOTS.actions}
|
||||||
|
<div class="clickable-card__actions">
|
||||||
|
<slot name="actions" />
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</label>
|
||||||
|
|
||||||
<style lang="post-css">
|
<style lang="post-css">
|
||||||
.section-card {
|
.section-card {
|
||||||
@ -45,17 +64,34 @@
|
|||||||
border-left: 1px solid var(--clr-theme-container-outline-light);
|
border-left: 1px solid var(--clr-theme-container-outline-light);
|
||||||
border-right: 1px solid var(--clr-theme-container-outline-light);
|
border-right: 1px solid var(--clr-theme-container-outline-light);
|
||||||
background-color: var(--clr-theme-container-light);
|
background-color: var(--clr-theme-container-light);
|
||||||
|
cursor: pointer;
|
||||||
transition:
|
transition:
|
||||||
background-color var(--transition-fast),
|
background-color var(--transition-fast),
|
||||||
border-color var(--transition-fast);
|
border-color var(--transition-fast);
|
||||||
text-align: left;
|
text-align: left;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.loading {
|
||||||
|
background: var(--clr-theme-container-pale);
|
||||||
|
}
|
||||||
|
|
||||||
|
.success {
|
||||||
|
background: var(--clr-theme-pop-container);
|
||||||
|
}
|
||||||
|
|
||||||
|
.error {
|
||||||
|
background: var(--clr-theme-warn-container);
|
||||||
|
}
|
||||||
|
.extra-padding {
|
||||||
|
padding: var(--space-20);
|
||||||
|
}
|
||||||
|
|
||||||
.section-card__content {
|
.section-card__content {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: var(--space-8);
|
gap: var(--space-8);
|
||||||
|
user-select: text;
|
||||||
}
|
}
|
||||||
|
|
||||||
.section-card__title {
|
.section-card__title {
|
||||||
@ -68,20 +104,22 @@
|
|||||||
|
|
||||||
/* MODIFIERS */
|
/* MODIFIERS */
|
||||||
|
|
||||||
.has-top-radius {
|
.rounded-top {
|
||||||
border-top: 1px solid var(--clr-theme-container-outline-light);
|
border-top: 1px solid var(--clr-theme-container-outline-light);
|
||||||
border-top-left-radius: var(--radius-m);
|
border-top-left-radius: var(--radius-m);
|
||||||
border-top-right-radius: var(--radius-m);
|
border-top-right-radius: var(--radius-m);
|
||||||
}
|
}
|
||||||
|
|
||||||
.has-bottom-radius {
|
.rounded-bottom {
|
||||||
border-bottom-left-radius: var(--radius-m);
|
border-bottom-left-radius: var(--radius-m);
|
||||||
border-bottom-right-radius: var(--radius-m);
|
border-bottom-right-radius: var(--radius-m);
|
||||||
}
|
}
|
||||||
|
|
||||||
.has-bottom-line {
|
.bottom-border {
|
||||||
border-bottom: 1px solid var(--clr-theme-container-outline-light);
|
border-bottom: 1px solid var(--clr-theme-container-outline-light);
|
||||||
user-select: text;
|
}
|
||||||
cursor: text;
|
|
||||||
|
.no-border {
|
||||||
|
border: none;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@ -9,6 +9,7 @@
|
|||||||
export let checked = false;
|
export let checked = false;
|
||||||
export let value = '';
|
export let value = '';
|
||||||
export let help = '';
|
export let help = '';
|
||||||
|
export let id = '';
|
||||||
|
|
||||||
let input: HTMLInputElement;
|
let input: HTMLInputElement;
|
||||||
const dispatch = createEventDispatcher<{ change: boolean }>();
|
const dispatch = createEventDispatcher<{ change: boolean }>();
|
||||||
@ -26,7 +27,7 @@
|
|||||||
class:small
|
class:small
|
||||||
{value}
|
{value}
|
||||||
{name}
|
{name}
|
||||||
id={name}
|
{id}
|
||||||
{disabled}
|
{disabled}
|
||||||
use:tooltip={help}
|
use:tooltip={help}
|
||||||
/>
|
/>
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { initPostHog } from '$lib/analytics/posthog';
|
import { initPostHog } from '$lib/analytics/posthog';
|
||||||
import { initSentry } from '$lib/analytics/sentry';
|
import { initSentry } from '$lib/analytics/sentry';
|
||||||
|
import { AuthService } from '$lib/backend/auth';
|
||||||
import { getCloudApiClient } from '$lib/backend/cloud';
|
import { getCloudApiClient } from '$lib/backend/cloud';
|
||||||
import { ProjectService } from '$lib/backend/projects';
|
import { ProjectService } from '$lib/backend/projects';
|
||||||
import { UpdaterService } from '$lib/backend/updater';
|
import { UpdaterService } from '$lib/backend/updater';
|
||||||
@ -33,7 +34,6 @@ export const load: LayoutLoad = async ({ fetch: realFetch }: { fetch: typeof fet
|
|||||||
if (enabled) initPostHog();
|
if (enabled) initPostHog();
|
||||||
});
|
});
|
||||||
const userService = new UserService();
|
const userService = new UserService();
|
||||||
const updaterService = new UpdaterService();
|
|
||||||
|
|
||||||
// TODO: Find a workaround to avoid this dynamic import
|
// TODO: Find a workaround to avoid this dynamic import
|
||||||
// https://github.com/sveltejs/kit/issues/905
|
// https://github.com/sveltejs/kit/issues/905
|
||||||
@ -41,9 +41,10 @@ export const load: LayoutLoad = async ({ fetch: realFetch }: { fetch: typeof fet
|
|||||||
const defaultPath = await homeDir();
|
const defaultPath = await homeDir();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
authService: new AuthService(),
|
||||||
projectService: new ProjectService(defaultPath),
|
projectService: new ProjectService(defaultPath),
|
||||||
cloud: getCloudApiClient({ fetch: realFetch }),
|
cloud: getCloudApiClient({ fetch: realFetch }),
|
||||||
updaterService,
|
updaterService: new UpdaterService(),
|
||||||
userService,
|
userService,
|
||||||
user$: userService.user$
|
user$: userService.user$
|
||||||
};
|
};
|
||||||
|
@ -11,7 +11,7 @@ import type { LayoutLoad } from './$types';
|
|||||||
export const prerender = false;
|
export const prerender = false;
|
||||||
|
|
||||||
export const load: LayoutLoad = async ({ params, parent }) => {
|
export const load: LayoutLoad = async ({ params, parent }) => {
|
||||||
const { user$, projectService, userService } = await parent();
|
const { authService, projectService, userService } = await parent();
|
||||||
const projectId = params.projectId;
|
const projectId = params.projectId;
|
||||||
const project$ = projectService.getProject(projectId);
|
const project$ = projectService.getProject(projectId);
|
||||||
const fetches$ = getFetchNotifications(projectId);
|
const fetches$ = getFetchNotifications(projectId);
|
||||||
@ -41,8 +41,11 @@ export const load: LayoutLoad = async ({ params, parent }) => {
|
|||||||
branchController
|
branchController
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const user$ = userService.user$;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
projectId,
|
projectId,
|
||||||
|
authService,
|
||||||
branchController,
|
branchController,
|
||||||
baseBranchService,
|
baseBranchService,
|
||||||
githubService,
|
githubService,
|
||||||
|
@ -8,7 +8,6 @@
|
|||||||
import SectionCard from '$lib/components/SectionCard.svelte';
|
import SectionCard from '$lib/components/SectionCard.svelte';
|
||||||
import ContentWrapper from '$lib/components/settings/ContentWrapper.svelte';
|
import ContentWrapper from '$lib/components/settings/ContentWrapper.svelte';
|
||||||
import * as toasts from '$lib/utils/toasts';
|
import * as toasts from '$lib/utils/toasts';
|
||||||
import type { UserError } from '$lib/backend/ipc';
|
|
||||||
import type { Key, Project } from '$lib/backend/projects';
|
import type { Key, Project } from '$lib/backend/projects';
|
||||||
import type { PageData } from './$types';
|
import type { PageData } from './$types';
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
@ -24,34 +23,41 @@
|
|||||||
let deleteConfirmationModal: RemoveProjectButton;
|
let deleteConfirmationModal: RemoveProjectButton;
|
||||||
let isDeleting = false;
|
let isDeleting = false;
|
||||||
|
|
||||||
const onDeleteClicked = () =>
|
async function onDeleteClicked() {
|
||||||
Promise.resolve()
|
isDeleting = true;
|
||||||
.then(() => (isDeleting = true))
|
try {
|
||||||
.then(() => projectService.deleteProject($project$?.id))
|
projectService.deleteProject($project$?.id);
|
||||||
.catch((e) => {
|
toasts.success('Project deleted');
|
||||||
console.error(e);
|
goto('/');
|
||||||
toasts.error('Failed to delete project');
|
} catch (err: any) {
|
||||||
})
|
console.error(err);
|
||||||
.then(() => toasts.success('Project deleted'))
|
toasts.error('Failed to delete project');
|
||||||
.then(() => goto('/'))
|
} finally {
|
||||||
.finally(() => {
|
isDeleting = false;
|
||||||
isDeleting = false;
|
projectService.reload();
|
||||||
projectService.reload();
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
const onKeysUpdated = (e: { detail: { preferred_key: Key } }) =>
|
async function onKeysUpdated(e: { detail: { preferred_key: Key } }) {
|
||||||
projectService
|
try {
|
||||||
.updateProject({ ...$project$, ...e.detail })
|
projectService.updateProject({ ...$project$, ...e.detail });
|
||||||
.then(() => toasts.success('Preferred key updated'))
|
toasts.success('Preferred key updated');
|
||||||
.catch((e: UserError) => {
|
} catch (err: any) {
|
||||||
toasts.error(e.message);
|
toasts.error(err.message);
|
||||||
});
|
}
|
||||||
const onCloudUpdated = (e: { detail: Project }) =>
|
}
|
||||||
|
|
||||||
|
async function onCloudUpdated(e: { detail: Project }) {
|
||||||
projectService.updateProject({ ...$project$, ...e.detail });
|
projectService.updateProject({ ...$project$, ...e.detail });
|
||||||
const onPreferencesUpdated = (e: {
|
}
|
||||||
|
|
||||||
|
async function onPreferencesUpdated(e: {
|
||||||
detail: { ok_with_force_push?: boolean; omit_certificate_check?: boolean };
|
detail: { ok_with_force_push?: boolean; omit_certificate_check?: boolean };
|
||||||
}) => projectService.updateProject({ ...$project$, ...e.detail });
|
}) {
|
||||||
const onDetailsUpdated = async (e: { detail: Project }) => {
|
await projectService.updateProject({ ...$project$, ...e.detail });
|
||||||
|
}
|
||||||
|
|
||||||
|
async function onDetailsUpdated(e: { detail: Project }) {
|
||||||
const api =
|
const api =
|
||||||
$user$ && e.detail.api
|
$user$ && e.detail.api
|
||||||
? await cloud.projects.update($user$?.access_token, e.detail.api.repository_id, {
|
? await cloud.projects.update($user$?.access_token, e.detail.api.repository_id, {
|
||||||
@ -59,12 +65,11 @@
|
|||||||
description: e.detail.description
|
description: e.detail.description
|
||||||
})
|
})
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
projectService.updateProject({
|
projectService.updateProject({
|
||||||
...e.detail,
|
...e.detail,
|
||||||
api: api ? { ...api, sync: e.detail.api?.sync || false } : undefined
|
api: api ? { ...api, sync: e.detail.api?.sync || false } : undefined
|
||||||
});
|
});
|
||||||
};
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if !$project$}
|
{#if !$project$}
|
||||||
|
@ -2,7 +2,6 @@
|
|||||||
import { deleteAllData } from '$lib/backend/data';
|
import { deleteAllData } from '$lib/backend/data';
|
||||||
import AnalyticsSettings from '$lib/components/AnalyticsSettings.svelte';
|
import AnalyticsSettings from '$lib/components/AnalyticsSettings.svelte';
|
||||||
import Button from '$lib/components/Button.svelte';
|
import Button from '$lib/components/Button.svelte';
|
||||||
import ClickableCard from '$lib/components/ClickableCard.svelte';
|
|
||||||
import GithubIntegration from '$lib/components/GithubIntegration.svelte';
|
import GithubIntegration from '$lib/components/GithubIntegration.svelte';
|
||||||
import Link from '$lib/components/Link.svelte';
|
import Link from '$lib/components/Link.svelte';
|
||||||
import Login from '$lib/components/Login.svelte';
|
import Login from '$lib/components/Login.svelte';
|
||||||
@ -19,37 +18,33 @@
|
|||||||
import * as toasts from '$lib/utils/toasts';
|
import * as toasts from '$lib/utils/toasts';
|
||||||
import { openExternalUrl } from '$lib/utils/url';
|
import { openExternalUrl } from '$lib/utils/url';
|
||||||
import { invoke } from '@tauri-apps/api/tauri';
|
import { invoke } from '@tauri-apps/api/tauri';
|
||||||
|
import { onMount } from 'svelte';
|
||||||
import type { PageData } from './$types';
|
import type { PageData } from './$types';
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
|
|
||||||
export let data: PageData;
|
export let data: PageData;
|
||||||
const { cloud, user$, userService } = data;
|
|
||||||
|
|
||||||
$: saving = false;
|
|
||||||
|
|
||||||
$: userPicture = $user$?.picture;
|
|
||||||
|
|
||||||
|
const { cloud, user$, userService, authService } = data;
|
||||||
const fileTypes = ['image/jpeg', 'image/png'];
|
const fileTypes = ['image/jpeg', 'image/png'];
|
||||||
|
|
||||||
const validFileType = (file: File) => {
|
// TODO: Maybe break these into components?
|
||||||
return fileTypes.includes(file.type);
|
let currentSection: 'profile' | 'git-stuff' | 'telemetry' | 'integrations' = 'profile';
|
||||||
};
|
|
||||||
|
|
||||||
const onPictureChange = (e: Event) => {
|
|
||||||
const target = e.target as HTMLInputElement;
|
|
||||||
const file = target.files?.[0];
|
|
||||||
|
|
||||||
if (file && validFileType(file)) {
|
|
||||||
userPicture = URL.createObjectURL(file);
|
|
||||||
} else {
|
|
||||||
userPicture = $user$?.picture;
|
|
||||||
toasts.error('Please use a valid image file');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
// TODO: Refactor such that this variable isn't needed
|
||||||
let newName = '';
|
let newName = '';
|
||||||
|
|
||||||
let loaded = false;
|
let loaded = false;
|
||||||
|
let isDeleting = false;
|
||||||
|
|
||||||
|
let signCommits = false;
|
||||||
|
let annotateCommits = true;
|
||||||
|
let sshKey = '';
|
||||||
|
|
||||||
|
let deleteConfirmationModal: Modal;
|
||||||
|
|
||||||
|
$: saving = false;
|
||||||
|
$: userPicture = $user$?.picture;
|
||||||
|
|
||||||
$: if ($user$ && !loaded) {
|
$: if ($user$ && !loaded) {
|
||||||
loaded = true;
|
loaded = true;
|
||||||
cloud.user.get($user$?.access_token).then((cloudUser) => {
|
cloud.user.get($user$?.access_token).then((cloudUser) => {
|
||||||
@ -59,7 +54,19 @@
|
|||||||
newName = $user$?.name || '';
|
newName = $user$?.name || '';
|
||||||
}
|
}
|
||||||
|
|
||||||
const onSubmit = async (e: SubmitEvent) => {
|
function onPictureChange(e: Event) {
|
||||||
|
const target = e.target as HTMLInputElement;
|
||||||
|
const file = target.files?.[0];
|
||||||
|
|
||||||
|
if (file && fileTypes.includes(file.type)) {
|
||||||
|
userPicture = URL.createObjectURL(file);
|
||||||
|
} else {
|
||||||
|
userPicture = $user$?.picture;
|
||||||
|
toasts.error('Please use a valid image file');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function onSubmit(e: SubmitEvent) {
|
||||||
if (!$user$) return;
|
if (!$user$) return;
|
||||||
saving = true;
|
saving = true;
|
||||||
|
|
||||||
@ -80,74 +87,55 @@
|
|||||||
toasts.error('Failed to update user');
|
toasts.error('Failed to update user');
|
||||||
}
|
}
|
||||||
saving = false;
|
saving = false;
|
||||||
};
|
}
|
||||||
|
|
||||||
let isDeleting = false;
|
// TODO: These kinds of functions should be implemented on an injected service
|
||||||
let deleteConfirmationModal: Modal;
|
function gitGetConfig(params: { key: string }) {
|
||||||
|
|
||||||
export function git_get_config(params: { key: string }) {
|
|
||||||
return invoke<string>('git_get_global_config', params);
|
return invoke<string>('git_get_global_config', params);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function git_set_config(params: { key: string; value: string }) {
|
function gitSetConfig(params: { key: string; value: string }) {
|
||||||
return invoke<string>('git_set_global_config', params);
|
return invoke<string>('git_set_global_config', params);
|
||||||
}
|
}
|
||||||
|
|
||||||
const setCommitterSetting = (value: boolean) => {
|
function toggleCommitterSigning() {
|
||||||
annotateCommits = value;
|
annotateCommits = !annotateCommits;
|
||||||
git_set_config({
|
gitSetConfig({
|
||||||
key: 'gitbutler.gitbutlerCommitter',
|
key: 'gitbutler.gitbutlerCommitter',
|
||||||
value: value ? '1' : '0'
|
value: annotateCommits ? '1' : '0'
|
||||||
});
|
});
|
||||||
};
|
|
||||||
|
|
||||||
const setSigningSetting = (value: boolean) => {
|
|
||||||
signCommits = value;
|
|
||||||
git_set_config({
|
|
||||||
key: 'gitbutler.signCommits',
|
|
||||||
value: value ? 'true' : 'false'
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
export function get_public_key() {
|
|
||||||
return invoke<string>('get_public_key');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let sshKey = '';
|
function toggleSigningSetting() {
|
||||||
get_public_key().then((key) => {
|
signCommits = !signCommits;
|
||||||
sshKey = key;
|
gitSetConfig({
|
||||||
});
|
key: 'gitbutler.signCommits',
|
||||||
|
value: signCommits ? 'true' : 'false'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
$: annotateCommits = true;
|
async function onDeleteClicked() {
|
||||||
$: signCommits = false;
|
isDeleting = true;
|
||||||
|
try {
|
||||||
git_get_config({ key: 'gitbutler.gitbutlerCommitter' }).then((value) => {
|
deleteAllData();
|
||||||
annotateCommits = value ? value === '1' : false;
|
await userService.logout();
|
||||||
});
|
|
||||||
|
|
||||||
git_get_config({ key: 'gitbutler.signCommits' }).then((value) => {
|
|
||||||
signCommits = value ? value === 'true' : false;
|
|
||||||
});
|
|
||||||
|
|
||||||
const onDeleteClicked = () =>
|
|
||||||
Promise.resolve()
|
|
||||||
.then(() => (isDeleting = true))
|
|
||||||
.then(() => deleteAllData())
|
|
||||||
// TODO: Delete user from observable!!!
|
// TODO: Delete user from observable!!!
|
||||||
.then(() => userService.logout())
|
toasts.success('All data deleted');
|
||||||
.then(() => toasts.success('All data deleted'))
|
goto('/', { replaceState: true, invalidateAll: true });
|
||||||
.catch((e) => {
|
} catch (err: any) {
|
||||||
console.error(e);
|
console.error(err);
|
||||||
toasts.error('Failed to delete project');
|
toasts.error('Failed to delete project');
|
||||||
})
|
} finally {
|
||||||
.then(() => deleteConfirmationModal.close())
|
deleteConfirmationModal.close();
|
||||||
.then(() => goto('/', { replaceState: true, invalidateAll: true }))
|
isDeleting = false;
|
||||||
.finally(() => (isDeleting = false));
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let currentSection: 'profile' | 'git-stuff' | 'telemetry' | 'integrations' = 'profile';
|
onMount(async () => {
|
||||||
|
sshKey = await authService.getPublicKey();
|
||||||
const toggleGBCommiter = () => setCommitterSetting(!annotateCommits);
|
annotateCommits = (await gitGetConfig({ key: 'gitbutler.gitbutlerCommitter' })) == '1';
|
||||||
const toggleGBSigner = () => setSigningSetting(!signCommits);
|
signCommits = (await gitGetConfig({ key: 'gitbutler.signCommits' })) == 'true';
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<section class="profile-page">
|
<section class="profile-page">
|
||||||
@ -233,7 +221,7 @@
|
|||||||
</ContentWrapper>
|
</ContentWrapper>
|
||||||
{:else if currentSection === 'git-stuff'}
|
{:else if currentSection === 'git-stuff'}
|
||||||
<ContentWrapper title="Git stuff">
|
<ContentWrapper title="Git stuff">
|
||||||
<ClickableCard on:click={toggleGBCommiter}>
|
<SectionCard labelFor="committerSigning" orientation="row">
|
||||||
<svelte:fragment slot="title">Credit GitButler as the Committer</svelte:fragment>
|
<svelte:fragment slot="title">Credit GitButler as the Committer</svelte:fragment>
|
||||||
<svelte:fragment slot="body">
|
<svelte:fragment slot="body">
|
||||||
By default, everything in the GitButler client is free to use. You can opt in to crediting
|
By default, everything in the GitButler client is free to use. You can opt in to crediting
|
||||||
@ -247,9 +235,13 @@
|
|||||||
</Link>
|
</Link>
|
||||||
</svelte:fragment>
|
</svelte:fragment>
|
||||||
<svelte:fragment slot="actions">
|
<svelte:fragment slot="actions">
|
||||||
<Toggle checked={annotateCommits} on:change={toggleGBCommiter} />
|
<Toggle
|
||||||
|
id="commiterSigning"
|
||||||
|
checked={annotateCommits}
|
||||||
|
on:change={toggleCommitterSigning}
|
||||||
|
/>
|
||||||
</svelte:fragment>
|
</svelte:fragment>
|
||||||
</ClickableCard>
|
</SectionCard>
|
||||||
|
|
||||||
<Spacer />
|
<Spacer />
|
||||||
|
|
||||||
@ -283,7 +275,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</SectionCard>
|
</SectionCard>
|
||||||
|
|
||||||
<ClickableCard on:click={toggleGBSigner}>
|
<SectionCard labelFor="signingSetting" orientation="row">
|
||||||
<svelte:fragment slot="title">Sign Commits with the above SSH Key</svelte:fragment>
|
<svelte:fragment slot="title">Sign Commits with the above SSH Key</svelte:fragment>
|
||||||
<svelte:fragment slot="body">
|
<svelte:fragment slot="body">
|
||||||
If you want GitButler to sign your commits with the SSH key we generated, then you can add
|
If you want GitButler to sign your commits with the SSH key we generated, then you can add
|
||||||
@ -297,9 +289,9 @@
|
|||||||
</Link>
|
</Link>
|
||||||
</svelte:fragment>
|
</svelte:fragment>
|
||||||
<svelte:fragment slot="actions">
|
<svelte:fragment slot="actions">
|
||||||
<Toggle checked={signCommits} on:change={toggleGBSigner} />
|
<Toggle id="signingSetting" checked={signCommits} on:change={toggleSigningSetting} />
|
||||||
</svelte:fragment>
|
</svelte:fragment>
|
||||||
</ClickableCard>
|
</SectionCard>
|
||||||
</ContentWrapper>
|
</ContentWrapper>
|
||||||
{:else if currentSection === 'telemetry'}
|
{:else if currentSection === 'telemetry'}
|
||||||
<ContentWrapper title="Telemetry">
|
<ContentWrapper title="Telemetry">
|
||||||
|
14
gitbutler-ui/static/images/lock.svg
Normal file
14
gitbutler-ui/static/images/lock.svg
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
<svg width="20" height="21" viewBox="0 0 20 21" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M15 14.5V7.5C15 4.73858 12.7614 2.5 10 2.5V2.5C7.23858 2.5 5 4.73858 5 7.5V14.5" stroke="#67C2BE" stroke-width="2"/>
|
||||||
|
<rect x="1" y="8.5" width="18" height="11" rx="2" fill="url(#paint0_linear_1756_54140)"/>
|
||||||
|
<rect x="1" y="8.5" width="18" height="11" rx="2" fill="#67C2BE"/>
|
||||||
|
<path d="M7 14C7 14.8284 6.32843 15.5 5.5 15.5C4.67157 15.5 4 14.8284 4 14C4 13.1716 4.67157 12.5 5.5 12.5C6.32843 12.5 7 13.1716 7 14Z" fill="black"/>
|
||||||
|
<path d="M11.5 14C11.5 14.8284 10.8284 15.5 10 15.5C9.17157 15.5 8.5 14.8284 8.5 14C8.5 13.1716 9.17157 12.5 10 12.5C10.8284 12.5 11.5 13.1716 11.5 14Z" fill="black"/>
|
||||||
|
<path d="M16 14C16 14.8284 15.3284 15.5 14.5 15.5C13.6716 15.5 13 14.8284 13 14C13 13.1716 13.6716 12.5 14.5 12.5C15.3284 12.5 16 13.1716 16 14Z" fill="black"/>
|
||||||
|
<defs>
|
||||||
|
<linearGradient id="paint0_linear_1756_54140" x1="10" y1="8.5" x2="-0.60513" y2="33.6738" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop stop-color="#FFBA52"/>
|
||||||
|
<stop offset="1" stop-color="#B8873E"/>
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.1 KiB |
Loading…
Reference in New Issue
Block a user