Add setting to disable checking for notifications

This commit is contained in:
Gregory Schier
2025-10-28 06:55:56 -07:00
parent 2095cb88c2
commit b7ad490c9b
12 changed files with 102 additions and 64 deletions

View File

@@ -38,16 +38,24 @@ use yaak_models::models::{
}; };
use yaak_models::query_manager::QueryManagerExt; use yaak_models::query_manager::QueryManagerExt;
use yaak_models::util::{BatchUpsertResult, UpdateSource, get_workspace_export_resources}; use yaak_models::util::{BatchUpsertResult, UpdateSource, get_workspace_export_resources};
use yaak_plugins::events::{CallGrpcRequestActionArgs, CallGrpcRequestActionRequest, CallHttpRequestActionArgs, CallHttpRequestActionRequest, Color, FilterResponse, GetGrpcRequestActionsResponse, GetHttpAuthenticationConfigResponse, GetHttpAuthenticationSummaryResponse, GetHttpRequestActionsResponse, GetTemplateFunctionSummaryResponse, GetTemplateFunctionConfigResponse, InternalEvent, InternalEventPayload, JsonPrimitive, PluginWindowContext, RenderPurpose, ShowToastRequest}; use yaak_plugins::events::{
CallGrpcRequestActionArgs, CallGrpcRequestActionRequest, CallHttpRequestActionArgs,
CallHttpRequestActionRequest, Color, FilterResponse, GetGrpcRequestActionsResponse,
GetHttpAuthenticationConfigResponse, GetHttpAuthenticationSummaryResponse,
GetHttpRequestActionsResponse, GetTemplateFunctionConfigResponse,
GetTemplateFunctionSummaryResponse, InternalEvent, InternalEventPayload, JsonPrimitive,
PluginWindowContext, RenderPurpose, ShowToastRequest,
};
use yaak_plugins::manager::PluginManager; use yaak_plugins::manager::PluginManager;
use yaak_plugins::plugin_meta::PluginMetadata; use yaak_plugins::plugin_meta::PluginMetadata;
use yaak_plugins::template_callback::PluginTemplateCallback; use yaak_plugins::template_callback::PluginTemplateCallback;
use yaak_sse::sse::ServerSentEvent; use yaak_sse::sse::ServerSentEvent;
use yaak_templates::format::format_json; use yaak_templates::format::format_json;
use yaak_templates::{RenderErrorBehavior, RenderOptions, Tokens, transform_args};
use yaak_templates::format_xml::format_xml; use yaak_templates::format_xml::format_xml;
use yaak_templates::{RenderErrorBehavior, RenderOptions, Tokens, transform_args};
mod commands; mod commands;
mod dns;
mod encoding; mod encoding;
mod error; mod error;
mod grpc; mod grpc;
@@ -61,7 +69,6 @@ mod updates;
mod uri_scheme; mod uri_scheme;
mod window; mod window;
mod window_menu; mod window_menu;
mod dns;
#[derive(serde::Serialize)] #[derive(serde::Serialize)]
#[serde(default, rename_all = "camelCase")] #[serde(default, rename_all = "camelCase")]
@@ -852,12 +859,16 @@ async fn cmd_template_function_config<R: Runtime>(
AnyModel::Folder(m) => (m.workspace_id, m.folder_id), AnyModel::Folder(m) => (m.workspace_id, m.folder_id),
AnyModel::Workspace(m) => (m.id, None), AnyModel::Workspace(m) => (m.id, None),
m => { m => {
return Err(GenericError(format!("Unsupported model to call template functions {m:?}"))); return Err(GenericError(format!(
"Unsupported model to call template functions {m:?}"
)));
} }
}; };
let environment_chain = let environment_chain =
window.db().resolve_environments(&workspace_id, folder_id.as_deref(), environment_id)?; window.db().resolve_environments(&workspace_id, folder_id.as_deref(), environment_id)?;
Ok(plugin_manager.get_template_function_config(&window, function_name, environment_chain, values, model.id()).await?) Ok(plugin_manager
.get_template_function_config(&window, function_name, environment_chain, values, model.id())
.await?)
} }
#[tauri::command] #[tauri::command]

View File

@@ -3,7 +3,7 @@ use std::time::SystemTime;
use crate::error::Result; use crate::error::Result;
use crate::history::get_or_upsert_launch_info; use crate::history::get_or_upsert_launch_info;
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use log::debug; use log::{debug, info};
use reqwest::Method; use reqwest::Method;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use tauri::{AppHandle, Emitter, Manager, Runtime, WebviewWindow}; use tauri::{AppHandle, Emitter, Manager, Runtime, WebviewWindow};
@@ -77,6 +77,13 @@ impl YaakNotifier {
self.last_check = SystemTime::now(); self.last_check = SystemTime::now();
if !app_handle.db().get_settings().check_notifications {
info!("Notifications are disabled. Skipping check.");
return Ok(());
}
debug!("Checking for notifications");
#[cfg(feature = "license")] #[cfg(feature = "license")]
let license_check = { let license_check = {
use yaak_license::{check_license, LicenseCheckStatus}; use yaak_license::{check_license, LicenseCheckStatus};

View File

@@ -62,7 +62,7 @@ export type ProxySetting = { "type": "enabled", http: string, https: string, aut
export type ProxySettingAuth = { user: string, password: string, }; export type ProxySettingAuth = { user: string, password: string, };
export type Settings = { model: "settings", id: string, createdAt: string, updatedAt: string, appearance: string, coloredMethods: boolean, editorFont: string | null, editorFontSize: number, editorKeymap: EditorKeymap, editorSoftWrap: boolean, hideWindowControls: boolean, interfaceFont: string | null, interfaceFontSize: number, interfaceScale: number, openWorkspaceNewWindow: boolean | null, proxy: ProxySetting | null, themeDark: string, themeLight: string, updateChannel: string, hideLicenseBadge: boolean, autoupdate: boolean, autoDownloadUpdates: boolean, }; export type Settings = { model: "settings", id: string, createdAt: string, updatedAt: string, appearance: string, coloredMethods: boolean, editorFont: string | null, editorFontSize: number, editorKeymap: EditorKeymap, editorSoftWrap: boolean, hideWindowControls: boolean, interfaceFont: string | null, interfaceFontSize: number, interfaceScale: number, openWorkspaceNewWindow: boolean | null, proxy: ProxySetting | null, themeDark: string, themeLight: string, updateChannel: string, hideLicenseBadge: boolean, autoupdate: boolean, autoDownloadUpdates: boolean, checkNotifications: boolean, };
export type SyncState = { model: "sync_state", id: string, workspaceId: string, createdAt: string, updatedAt: string, flushedAt: string, modelId: string, checksum: string, relPath: string, syncDir: string, }; export type SyncState = { model: "sync_state", id: string, workspaceId: string, createdAt: string, updatedAt: string, flushedAt: string, modelId: string, checksum: string, relPath: string, syncDir: string, };

View File

@@ -0,0 +1 @@
ALTER TABLE settings ADD COLUMN check_notifications BOOLEAN DEFAULT true NOT NULL;

View File

@@ -123,6 +123,7 @@ pub struct Settings {
pub hide_license_badge: bool, pub hide_license_badge: bool,
pub autoupdate: bool, pub autoupdate: bool,
pub auto_download_updates: bool, pub auto_download_updates: bool,
pub check_notifications: bool,
} }
impl UpsertModelInfo for Settings { impl UpsertModelInfo for Settings {
@@ -175,6 +176,7 @@ impl UpsertModelInfo for Settings {
(Autoupdate, self.autoupdate.into()), (Autoupdate, self.autoupdate.into()),
(AutoDownloadUpdates, self.auto_download_updates.into()), (AutoDownloadUpdates, self.auto_download_updates.into()),
(ColoredMethods, self.colored_methods.into()), (ColoredMethods, self.colored_methods.into()),
(CheckNotifications, self.check_notifications.into()),
(Proxy, proxy.into()), (Proxy, proxy.into()),
]) ])
} }
@@ -200,6 +202,7 @@ impl UpsertModelInfo for Settings {
SettingsIden::Autoupdate, SettingsIden::Autoupdate,
SettingsIden::AutoDownloadUpdates, SettingsIden::AutoDownloadUpdates,
SettingsIden::ColoredMethods, SettingsIden::ColoredMethods,
SettingsIden::CheckNotifications,
] ]
} }
@@ -232,6 +235,7 @@ impl UpsertModelInfo for Settings {
auto_download_updates: row.get("auto_download_updates")?, auto_download_updates: row.get("auto_download_updates")?,
hide_license_badge: row.get("hide_license_badge")?, hide_license_badge: row.get("hide_license_badge")?,
colored_methods: row.get("colored_methods")?, colored_methods: row.get("colored_methods")?,
check_notifications: row.get("check_notifications")?,
}) })
} }
} }

View File

@@ -35,6 +35,7 @@ impl<'a> DbContext<'a> {
colored_methods: false, colored_methods: false,
hide_license_badge: false, hide_license_badge: false,
auto_download_updates: true, auto_download_updates: true,
check_notifications: true,
}; };
self.upsert(&settings, &UpdateSource::Background).expect("Failed to upsert settings") self.upsert(&settings, &UpdateSource::Background).expect("Failed to upsert settings")
} }

View File

@@ -71,37 +71,25 @@ export function SettingsGeneral() {
disabled={!settings.autoupdate} disabled={!settings.autoupdate}
help="Automatically download Yaak updates (!50MB) in the background, so they will be immediately ready to install." help="Automatically download Yaak updates (!50MB) in the background, so they will be immediately ready to install."
title="Automatically download updates" title="Automatically download updates"
onChange={(autoDownloadUpdates) => onChange={(autoDownloadUpdates) => patchModel(settings, { autoDownloadUpdates })}
patchModel(settings, { autoDownloadUpdates })
}
/> />
<Separator className="my-4" />
</CargoFeature>
<Select <Checkbox
name="switchWorkspaceBehavior" className="pl-2 mt-1 ml-[14rem]"
label="Workspace Window Behavior" checked={settings.checkNotifications}
labelPosition="left" title="Check for notifications"
labelClassName="w-[14rem]" help="Periodically ping Yaak servers to check for relevant notifications."
size="sm" onChange={(checkNotifications) => patchModel(settings, { checkNotifications })}
value={ />
settings.openWorkspaceNewWindow === true <Checkbox
? 'new' disabled
: settings.openWorkspaceNewWindow === false className="pl-2 mt-1 ml-[14rem]"
? 'current' checked={false}
: 'ask' title="Send anonymous usage statistics"
} help="Yaak is local-first and does not collect analytics or usage data 🔐"
onChange={async (v) => { onChange={(checkNotifications) => patchModel(settings, { checkNotifications })}
if (v === 'current') await patchModel(settings, { openWorkspaceNewWindow: false }); />
else if (v === 'new') await patchModel(settings, { openWorkspaceNewWindow: true }); </CargoFeature>
else await patchModel(settings, { openWorkspaceNewWindow: null });
}}
options={[
{ label: 'Always ask', value: 'ask' },
{ label: 'Open in current window', value: 'current' },
{ label: 'Open in new window', value: 'new' },
]}
/>
<Separator className="my-4" /> <Separator className="my-4" />
@@ -129,7 +117,7 @@ export function SettingsGeneral() {
<Checkbox <Checkbox
checked={workspace.settingValidateCertificates} checked={workspace.settingValidateCertificates}
help="When disabled, skip validation of server certificates, useful when interacting with self-signed certs." help="When disabled, skip validation of server certificates, useful when interacting with self-signed certs."
title="Validate TLS Certificates" title="Validate TLS certificates"
onChange={(settingValidateCertificates) => onChange={(settingValidateCertificates) =>
patchModel(workspace, { settingValidateCertificates }) patchModel(workspace, { settingValidateCertificates })
} }
@@ -137,7 +125,7 @@ export function SettingsGeneral() {
<Checkbox <Checkbox
checked={workspace.settingFollowRedirects} checked={workspace.settingFollowRedirects}
title="Follow Redirects" title="Follow redirects"
onChange={(settingFollowRedirects) => onChange={(settingFollowRedirects) =>
patchModel(workspace, { patchModel(workspace, {
settingFollowRedirects, settingFollowRedirects,

View File

@@ -39,15 +39,38 @@ export function SettingsInterface() {
return ( return (
<VStack space={3} className="mb-4"> <VStack space={3} className="mb-4">
<Select
name="switchWorkspaceBehavior"
label="Open workspace behavior"
size="sm"
help="When opening a workspace, should it open in the current window or a new window?"
value={
settings.openWorkspaceNewWindow === true
? 'new'
: settings.openWorkspaceNewWindow === false
? 'current'
: 'ask'
}
onChange={async (v) => {
if (v === 'current') await patchModel(settings, { openWorkspaceNewWindow: false });
else if (v === 'new') await patchModel(settings, { openWorkspaceNewWindow: true });
else await patchModel(settings, { openWorkspaceNewWindow: null });
}}
options={[
{ label: 'Always ask', value: 'ask' },
{ label: 'Open in current window', value: 'current' },
{ label: 'Open in new window', value: 'new' },
]}
/>
<HStack space={2} alignItems="end"> <HStack space={2} alignItems="end">
{fonts.data && ( {fonts.data && (
<Select <Select
size="sm" size="sm"
name="uiFont" name="uiFont"
label="Interface Font" label="Interface font"
value={settings.interfaceFont ?? NULL_FONT_VALUE} value={settings.interfaceFont ?? NULL_FONT_VALUE}
options={[ options={[
{ label: 'System Default', value: NULL_FONT_VALUE }, { label: 'System default', value: NULL_FONT_VALUE },
...(fonts.data.uiFonts.map((f) => ({ ...(fonts.data.uiFonts.map((f) => ({
label: f, label: f,
value: f, value: f,
@@ -80,10 +103,10 @@ export function SettingsInterface() {
<Select <Select
size="sm" size="sm"
name="editorFont" name="editorFont"
label="Editor Font" label="Editor font"
value={settings.editorFont ?? NULL_FONT_VALUE} value={settings.editorFont ?? NULL_FONT_VALUE}
options={[ options={[
{ label: 'System Default', value: NULL_FONT_VALUE }, { label: 'System default', value: NULL_FONT_VALUE },
...(fonts.data.editorFonts.map((f) => ({ ...(fonts.data.editorFonts.map((f) => ({
label: f, label: f,
value: f, value: f,
@@ -112,19 +135,19 @@ export function SettingsInterface() {
leftSlot={<Icon icon="keyboard" color="secondary" />} leftSlot={<Icon icon="keyboard" color="secondary" />}
size="sm" size="sm"
name="editorKeymap" name="editorKeymap"
label="Editor Keymap" label="Editor keymap"
value={`${settings.editorKeymap}`} value={`${settings.editorKeymap}`}
options={keymaps} options={keymaps}
onChange={(v) => patchModel(settings, { editorKeymap: v })} onChange={(v) => patchModel(settings, { editorKeymap: v })}
/> />
<Checkbox <Checkbox
checked={settings.editorSoftWrap} checked={settings.editorSoftWrap}
title="Wrap Editor Lines" title="Wrap editor lines"
onChange={(editorSoftWrap) => patchModel(settings, { editorSoftWrap })} onChange={(editorSoftWrap) => patchModel(settings, { editorSoftWrap })}
/> />
<Checkbox <Checkbox
checked={settings.coloredMethods} checked={settings.coloredMethods}
title="Colorize Request Methods" title="Colorize request methods"
onChange={(coloredMethods) => patchModel(settings, { coloredMethods })} onChange={(coloredMethods) => patchModel(settings, { coloredMethods })}
/> />
<CargoFeature feature="license"> <CargoFeature feature="license">
@@ -134,7 +157,7 @@ export function SettingsInterface() {
{type() !== 'macos' && ( {type() !== 'macos' && (
<Checkbox <Checkbox
checked={settings.hideWindowControls} checked={settings.hideWindowControls}
title="Hide Window Controls" title="Hide window controls"
help="Hide the close/maximize/minimize controls on Windows or Linux" help="Hide the close/maximize/minimize controls on Windows or Linux"
onChange={(hideWindowControls) => patchModel(settings, { hideWindowControls })} onChange={(hideWindowControls) => patchModel(settings, { hideWindowControls })}
/> />

View File

@@ -56,8 +56,8 @@ function SettingsLicenseCmp() {
<h2 className="text-lg font-bold">Hey, I&apos;m Greg 👋🏼</h2> <h2 className="text-lg font-bold">Hey, I&apos;m Greg 👋🏼</h2>
<p> <p>
Yaak is free for personal projects and learning.{' '} Yaak is free for personal projects and learning.{' '}
{check.data?.type === 'trialing' ? 'After your trial, a ' : 'A '} {check.data?.type === 'trialing' ? 'Once your trial ends, a ' : 'A '}
license is required for work or commercial use. license will be required for work or commercial use.
</p> </p>
<p> <p>
<Link <Link

View File

@@ -38,9 +38,9 @@ export function SettingsProxy() {
} }
}} }}
options={[ options={[
{ label: 'Automatic Proxy Detection', value: 'automatic' }, { label: 'Automatic proxy detection', value: 'automatic' },
{ label: 'Custom Proxy Configuration', value: 'enabled' }, { label: 'Custom proxy configuration', value: 'enabled' },
{ label: 'No Proxy', value: 'disabled' }, { label: 'No proxy', value: 'disabled' },
]} ]}
/> />
{settings.proxy?.type === 'enabled' && ( {settings.proxy?.type === 'enabled' && (

View File

@@ -44,19 +44,19 @@ export function Confirm({
autoFocus autoFocus
onChange={setConfirm} onChange={setConfirm}
placeholder={requireTyping} placeholder={requireTyping}
labelRightSlot={
<CopyIconButton
text={requireTyping}
title="Copy name"
className="text-text-subtlest"
iconSize="sm"
size="2xs"
/>
}
label={ label={
<div className="flex items-center justify-between"> <>
<p> Type <strong>{requireTyping}</strong> to confirm
Type <strong>{requireTyping}</strong> to confirm </>
</p>
<CopyIconButton
text={requireTyping}
title="Copy name"
className="text-text-subtlest ml-auto"
iconSize="sm"
size="2xs"
/>
</div>
} }
/> />
)} )}

View File

@@ -9,6 +9,7 @@ export function Label({
visuallyHidden, visuallyHidden,
tags = [], tags = [],
required, required,
rightSlot,
help, help,
...props ...props
}: HTMLAttributes<HTMLLabelElement> & { }: HTMLAttributes<HTMLLabelElement> & {
@@ -16,6 +17,7 @@ export function Label({
required?: boolean; required?: boolean;
tags?: string[]; tags?: string[];
visuallyHidden?: boolean; visuallyHidden?: boolean;
rightSlot?: ReactNode;
children: ReactNode; children: ReactNode;
help?: ReactNode; help?: ReactNode;
}) { }) {
@@ -30,7 +32,7 @@ export function Label({
)} )}
{...props} {...props}
> >
<span className="inline-block w-full"> <span>
{children} {children}
{required === true && <span className="text-text-subtlest">*</span>} {required === true && <span className="text-text-subtlest">*</span>}
</span> </span>
@@ -40,6 +42,7 @@ export function Label({
</span> </span>
))} ))}
{help && <IconTooltip tabIndex={-1} content={help} />} {help && <IconTooltip tabIndex={-1} content={help} />}
{rightSlot && <div className="ml-auto">{rightSlot}</div>}
</label> </label>
); );
} }