Initial Commit
This commit is contained in:
@@ -0,0 +1,172 @@
|
||||
import type {
|
||||
CalendarDay,
|
||||
CalendarDateDetail,
|
||||
CurvePoint,
|
||||
FilterBreakdownItem,
|
||||
FilterRecommendation,
|
||||
GalleryImage,
|
||||
HorizonPoint,
|
||||
LogEntry,
|
||||
Phd2Log,
|
||||
Stats,
|
||||
Target,
|
||||
TargetNotes,
|
||||
TargetsResponse,
|
||||
Tonight,
|
||||
VisibilitySummary,
|
||||
WeatherData,
|
||||
Workflow,
|
||||
} from './types';
|
||||
|
||||
const base = '/api';
|
||||
|
||||
async function get<T>(path: string): Promise<T> {
|
||||
const resp = await fetch(`${base}${path}`);
|
||||
if (!resp.ok) throw new Error(`HTTP ${resp.status}: ${path}`);
|
||||
return resp.json() as Promise<T>;
|
||||
}
|
||||
|
||||
async function post<T>(path: string, body: unknown): Promise<T> {
|
||||
const resp = await fetch(`${base}${path}`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
if (!resp.ok) throw new Error(`HTTP ${resp.status}: ${path}`);
|
||||
return resp.json() as Promise<T>;
|
||||
}
|
||||
|
||||
async function put<T>(path: string, body: unknown): Promise<T> {
|
||||
const resp = await fetch(`${base}${path}`, {
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
if (!resp.ok) throw new Error(`HTTP ${resp.status}: ${path}`);
|
||||
return resp.json() as Promise<T>;
|
||||
}
|
||||
|
||||
async function del<T>(path: string): Promise<T> {
|
||||
const resp = await fetch(`${base}${path}`, { method: 'DELETE' });
|
||||
if (!resp.ok) throw new Error(`HTTP ${resp.status}: ${path}`);
|
||||
return resp.json() as Promise<T>;
|
||||
}
|
||||
|
||||
// Targets
|
||||
export interface TargetsParams {
|
||||
type?: string;
|
||||
constellation?: string;
|
||||
filter?: string;
|
||||
tonight?: boolean;
|
||||
search?: string;
|
||||
sort?: string;
|
||||
page?: number;
|
||||
limit?: number;
|
||||
min_alt_deg?: number;
|
||||
min_usable_min?: number;
|
||||
mosaic_only?: boolean;
|
||||
not_imaged?: boolean;
|
||||
}
|
||||
|
||||
export const api = {
|
||||
targets: {
|
||||
list: (params: TargetsParams = {}): Promise<TargetsResponse> => {
|
||||
const q = new URLSearchParams();
|
||||
if (params.type) q.set('type', params.type);
|
||||
if (params.constellation) q.set('constellation', params.constellation);
|
||||
if (params.filter) q.set('filter', params.filter);
|
||||
if (params.tonight !== undefined) q.set('tonight', String(params.tonight));
|
||||
if (params.search) q.set('search', params.search);
|
||||
if (params.sort) q.set('sort', params.sort);
|
||||
if (params.page) q.set('page', String(params.page));
|
||||
if (params.limit) q.set('limit', String(params.limit));
|
||||
if (params.min_alt_deg !== undefined) q.set('min_alt_deg', String(params.min_alt_deg));
|
||||
if (params.min_usable_min !== undefined) q.set('min_usable_min', String(params.min_usable_min));
|
||||
if (params.mosaic_only) q.set('mosaic_only', 'true');
|
||||
if (params.not_imaged) q.set('not_imaged', 'true');
|
||||
return get<TargetsResponse>(`/targets?${q}`);
|
||||
},
|
||||
get: (id: string): Promise<Target> => get(`/targets/${id}`),
|
||||
visibility: (id: string): Promise<VisibilitySummary> => get(`/targets/${id}/visibility`),
|
||||
curve: (id: string): Promise<{ catalog_id: string; curve: CurvePoint[] }> => get(`/targets/${id}/curve`),
|
||||
filters: (id: string): Promise<{ recommendations: FilterRecommendation[] }> => get(`/targets/${id}/filters`),
|
||||
workflow: (id: string, filterId: string): Promise<Workflow> => get(`/targets/${id}/workflow/${filterId}`),
|
||||
yearly: (id: string): Promise<{ catalog_id: string; points: { date: string; alt_at_midnight: number; transit_alt: number; usable_min: number; moon_illumination: number }[] }> => get(`/targets/${id}/yearly`),
|
||||
getNotes: (id: string): Promise<TargetNotes> => get(`/targets/${id}/notes`),
|
||||
putNotes: (id: string, notes: string): Promise<{ status: string }> => put(`/targets/${id}/notes`, { notes }),
|
||||
},
|
||||
|
||||
tonight: {
|
||||
get: (): Promise<Tonight> => get('/tonight'),
|
||||
},
|
||||
|
||||
calendar: {
|
||||
get: (months?: number): Promise<{ days: CalendarDay[] }> =>
|
||||
get(`/calendar${months ? `?months=${months}` : ''}`),
|
||||
getDate: (date: string): Promise<CalendarDateDetail> =>
|
||||
get(`/calendar/${date}`),
|
||||
getNewMoonWindows: (): Promise<{ windows: { date: string; illumination: number; top_targets: { id: string; name: string; common_name?: string; max_alt_deg?: number; recommended_filter?: string }[] }[] }> =>
|
||||
get('/calendar/new-moon-windows'),
|
||||
},
|
||||
|
||||
weather: {
|
||||
get: (): Promise<WeatherData> => get('/weather'),
|
||||
forecast: (): Promise<unknown> => get('/weather/forecast'),
|
||||
},
|
||||
|
||||
log: {
|
||||
list: (page?: number): Promise<{ items: LogEntry[]; total: number }> =>
|
||||
get(`/log${page ? `?page=${page}` : ''}`),
|
||||
forTarget: (catalogId: string): Promise<{ items: LogEntry[]; total_integration_min: number; filter_breakdown: FilterBreakdownItem[] }> =>
|
||||
get(`/log/${catalogId}`),
|
||||
create: (entry: Omit<LogEntry, 'id' | 'created_at' | 'target_name' | 'target_common_name' | 'target_obj_type'>): Promise<{ id: number }> =>
|
||||
post('/log', entry),
|
||||
update: (id: number, data: Partial<LogEntry>): Promise<{ id: number }> =>
|
||||
put(`/log/entry/${id}`, data),
|
||||
delete: (id: number): Promise<{ id: number }> =>
|
||||
del(`/log/entry/${id}`),
|
||||
exportCsv: (): void => { window.open('/api/log/export', '_blank'); },
|
||||
},
|
||||
|
||||
phd2: {
|
||||
list: (): Promise<{ items: Phd2Log[] }> => get('/phd2'),
|
||||
get: (id: number): Promise<Phd2Log> => get(`/phd2/${id}`),
|
||||
delete: (id: number): Promise<{ status: string; id: number }> => del(`/phd2/${id}`),
|
||||
upload: (formData: FormData): Promise<{ id: number; duplicate: boolean; duplicate_id?: number; analysis: unknown; message?: string }> => {
|
||||
return fetch(`${base}/phd2/upload`, { method: 'POST', body: formData })
|
||||
.then(r => r.json() as Promise<{ id: number; duplicate: boolean; duplicate_id?: number; analysis: unknown; message?: string }>);
|
||||
},
|
||||
},
|
||||
|
||||
gallery: {
|
||||
listAll: (): Promise<{ items: (GalleryImage & { target_name?: string; target_common_name?: string })[] }> =>
|
||||
get('/gallery'),
|
||||
list: (catalogId: string): Promise<{ items: GalleryImage[] }> =>
|
||||
get(`/gallery/${catalogId}`),
|
||||
delete: (id: number): Promise<{ id: number }> => del(`/gallery/item/${id}`),
|
||||
upload: (catalogId: string, formData: FormData): Promise<{ id: number; url: string }> => {
|
||||
return fetch(`${base}/gallery/${catalogId}`, { method: 'POST', body: formData })
|
||||
.then(r => r.json() as Promise<{ id: number; url: string }>);
|
||||
},
|
||||
},
|
||||
|
||||
horizon: {
|
||||
get: (): Promise<{ points: HorizonPoint[] }> => get('/horizon'),
|
||||
set: (points: HorizonPoint[]): Promise<{ status: string }> => put('/horizon', points),
|
||||
},
|
||||
|
||||
stats: {
|
||||
get: (): Promise<Stats> => get('/stats'),
|
||||
},
|
||||
|
||||
health: {
|
||||
get: (): Promise<{ status: string; catalog_size: number; catalog_last_refreshed?: number; db_size_bytes?: number; version: string }> => get('/health'),
|
||||
},
|
||||
|
||||
admin: {
|
||||
catalogRefresh: (): Promise<{ status: string }> =>
|
||||
fetch('/api/catalog/refresh', { method: 'POST' }).then(r => r.json() as Promise<{ status: string }>),
|
||||
nightlyRecompute: (): Promise<{ status: string }> =>
|
||||
fetch('/api/nightly/recompute', { method: 'POST' }).then(r => r.json() as Promise<{ status: string }>),
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,237 @@
|
||||
export interface Target {
|
||||
id: string;
|
||||
name: string;
|
||||
common_name?: string;
|
||||
obj_type: string;
|
||||
ra_deg: number;
|
||||
dec_deg: number;
|
||||
ra_h: string;
|
||||
dec_dms: string;
|
||||
constellation?: string;
|
||||
size_arcmin_maj?: number;
|
||||
size_arcmin_min?: number;
|
||||
pos_angle_deg?: number;
|
||||
mag_v?: number;
|
||||
surface_brightness?: number;
|
||||
hubble_type?: string;
|
||||
messier_num?: number;
|
||||
is_highlight: boolean;
|
||||
fov_fill_pct?: number;
|
||||
mosaic_flag: boolean;
|
||||
mosaic_panels_w: number;
|
||||
mosaic_panels_h: number;
|
||||
difficulty?: number;
|
||||
guide_star_density?: string;
|
||||
// From nightly_cache
|
||||
max_alt_deg?: number;
|
||||
usable_min?: number;
|
||||
transit_utc?: string;
|
||||
recommended_filter?: string;
|
||||
best_start_utc?: string;
|
||||
best_end_utc?: string;
|
||||
moon_sep_deg?: number;
|
||||
is_visible_tonight?: boolean;
|
||||
total_integration_min?: number;
|
||||
}
|
||||
|
||||
export interface TargetsResponse {
|
||||
items: Target[];
|
||||
total: number;
|
||||
page: number;
|
||||
limit: number;
|
||||
}
|
||||
|
||||
export interface VisibilitySummary {
|
||||
catalog_id: string;
|
||||
night_date?: string;
|
||||
max_alt_deg?: number;
|
||||
transit_utc?: string;
|
||||
rise_utc?: string;
|
||||
set_utc?: string;
|
||||
best_start_utc?: string;
|
||||
best_end_utc?: string;
|
||||
usable_min?: number;
|
||||
meridian_flip_utc?: string;
|
||||
airmass_at_transit?: number;
|
||||
extinction_mag?: number;
|
||||
moon_sep_deg?: number;
|
||||
recommended_filter?: string;
|
||||
is_visible_tonight?: boolean;
|
||||
}
|
||||
|
||||
export interface CurvePoint {
|
||||
utc: string;
|
||||
alt_deg: number;
|
||||
az_deg: number;
|
||||
airmass: number;
|
||||
above_custom_horizon: boolean;
|
||||
moon_alt_deg: number;
|
||||
}
|
||||
|
||||
export interface Tonight {
|
||||
date: string;
|
||||
astro_dusk_utc: string;
|
||||
astro_dawn_utc: string;
|
||||
moon_rise_utc?: string;
|
||||
moon_set_utc?: string;
|
||||
moon_illumination?: number;
|
||||
moon_phase_name?: string;
|
||||
moon_ra_deg?: number;
|
||||
moon_dec_deg?: number;
|
||||
true_dark_start_utc?: string;
|
||||
true_dark_end_utc?: string;
|
||||
true_dark_minutes?: number;
|
||||
computed_at?: number;
|
||||
}
|
||||
|
||||
export interface WeatherData {
|
||||
dew_point_c?: number;
|
||||
temp_c?: number;
|
||||
humidity_pct?: number;
|
||||
go_nogo?: 'go' | 'marginal' | 'nogo';
|
||||
go_nogo_reasons?: string[];
|
||||
fetched_at?: number;
|
||||
dew_alert?: 'warning' | 'critical';
|
||||
cloudcover?: number;
|
||||
seeing?: number;
|
||||
transparency?: number;
|
||||
lifted_index?: number;
|
||||
wind10m?: { speed: number; direction: string };
|
||||
rh2m?: number;
|
||||
}
|
||||
|
||||
export interface FilterRecommendation {
|
||||
filter_id: string;
|
||||
filter_name: string;
|
||||
suitability: 'ideal' | 'good' | 'marginal' | 'unsuitable';
|
||||
reason: string;
|
||||
warning?: string;
|
||||
est_integration_hours?: number;
|
||||
sessions_needed?: number;
|
||||
exposure_sec?: number;
|
||||
frames_needed?: number;
|
||||
}
|
||||
|
||||
export interface LogEntry {
|
||||
id: number;
|
||||
catalog_id: string;
|
||||
session_date: string;
|
||||
filter_id: string;
|
||||
integration_min: number;
|
||||
quality: 'keeper' | 'needs_more' | 'rejected' | 'pending';
|
||||
notes?: string;
|
||||
guiding_rms?: number;
|
||||
mean_temp_c?: number;
|
||||
created_at: number;
|
||||
target_name?: string;
|
||||
target_common_name?: string;
|
||||
target_obj_type?: string;
|
||||
}
|
||||
|
||||
export interface Phd2Log {
|
||||
id: number;
|
||||
session_date: string;
|
||||
filename: string;
|
||||
rms_total?: number;
|
||||
rms_ra?: number;
|
||||
rms_dec?: number;
|
||||
peak_error?: number;
|
||||
star_lost_count?: number;
|
||||
duration_min?: number;
|
||||
guide_star_snr?: number;
|
||||
created_at: number;
|
||||
// Equipment details
|
||||
equipment_profile?: string;
|
||||
camera_name?: string;
|
||||
exposure_ms?: number;
|
||||
mount_name?: string;
|
||||
pixel_scale_arcsec?: number;
|
||||
hfd_px?: number;
|
||||
guide_star_snr_at_start?: number;
|
||||
}
|
||||
|
||||
export interface GalleryImage {
|
||||
id: number;
|
||||
catalog_id: string;
|
||||
filename: string;
|
||||
url: string;
|
||||
caption?: string;
|
||||
created_at: number;
|
||||
}
|
||||
|
||||
export interface HorizonPoint {
|
||||
az_deg: number;
|
||||
alt_deg: number;
|
||||
}
|
||||
|
||||
export interface Stats {
|
||||
total_sessions: number;
|
||||
total_integration_min: number;
|
||||
objects_with_keeper: number;
|
||||
filter_usage: { filter_id: string; count: number; total_min: number }[];
|
||||
monthly: { month: string; sessions: number; total_min: number }[];
|
||||
by_type: { obj_type: string; sessions: number; total_min: number }[];
|
||||
quality: { quality: string; count: number }[];
|
||||
top_targets: { id: string; name: string; common_name?: string; obj_type: string; sessions: number; total_min: number }[];
|
||||
guiding: { date: string; rms_total?: number; rms_ra?: number; rms_dec?: number }[];
|
||||
}
|
||||
|
||||
export interface Workflow {
|
||||
name: string;
|
||||
steps: string[];
|
||||
plugins: [string, string][];
|
||||
notes: string;
|
||||
}
|
||||
|
||||
export interface CalendarDay {
|
||||
date: string;
|
||||
visible_count?: number;
|
||||
max_usable_min?: number;
|
||||
avg_max_alt?: number;
|
||||
moon_illumination?: number;
|
||||
}
|
||||
|
||||
export interface CalendarDateDetail {
|
||||
date: string;
|
||||
moon_illumination: number;
|
||||
top_targets: {
|
||||
id: string;
|
||||
name: string;
|
||||
common_name?: string;
|
||||
obj_type: string;
|
||||
max_alt_deg?: number;
|
||||
usable_min?: number;
|
||||
transit_utc?: string;
|
||||
recommended_filter?: string;
|
||||
}[];
|
||||
tonight?: {
|
||||
astro_dusk_utc?: string;
|
||||
astro_dawn_utc?: string;
|
||||
moon_rise_utc?: string;
|
||||
moon_set_utc?: string;
|
||||
moon_illumination?: number;
|
||||
moon_phase_name?: string;
|
||||
true_dark_start_utc?: string;
|
||||
true_dark_end_utc?: string;
|
||||
true_dark_minutes?: number;
|
||||
};
|
||||
weather?: {
|
||||
go_nogo?: string;
|
||||
temp_c?: number;
|
||||
dew_point_c?: number;
|
||||
cloudcover?: number;
|
||||
seeing?: number;
|
||||
transparency?: number;
|
||||
};
|
||||
}
|
||||
|
||||
export interface TargetNotes {
|
||||
catalog_id: string;
|
||||
notes: string;
|
||||
}
|
||||
|
||||
export interface FilterBreakdownItem {
|
||||
filter_id: string;
|
||||
total_min: number;
|
||||
sessions: number;
|
||||
}
|
||||
Reference in New Issue
Block a user