Skip to content

Commit

Permalink
[Spacship] ye olde lint + clean up
Browse files Browse the repository at this point in the history
  • Loading branch information
xmok committed Feb 4, 2025
1 parent 61ea561 commit ce67660
Show file tree
Hide file tree
Showing 7 changed files with 144 additions and 69 deletions.
2 changes: 1 addition & 1 deletion extensions/spaceship/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@

## [Initial Version] - {PR_MERGE_DATE}

Manage Domains and DNS Records.
View Domains and View DNS Records.
1 change: 0 additions & 1 deletion extensions/spaceship/assets/dns.svg

This file was deleted.

Binary file added extensions/spaceship/metadata/spaceship-1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added extensions/spaceship/metadata/spaceship-2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion extensions/spaceship/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
{
"name": "manage-domains",
"title": "Manage Domains",
"description": "",
"description": "View Domains and DNS Records",
"mode": "view"
}
],
Expand Down
150 changes: 108 additions & 42 deletions extensions/spaceship/src/manage-domains.tsx
Original file line number Diff line number Diff line change
@@ -1,54 +1,120 @@
import { Action, ActionPanel, getPreferenceValues, Icon, List } from "@raycast/api";
import { Action, ActionPanel, Color, getPreferenceValues, Icon, Keyboard, List } from "@raycast/api";
import { getFavicon, useCachedState, useFetch } from "@raycast/utils";
import { DomainInfo, ErrorResult, SuccessResult, ResourceRecord } from "./types";
import { DomainInfo, ErrorResult, SuccessResult, ResourceRecord, DomainClientEPPStatus } from "./types";

function useSpaceship<T>(endpoint: string) {
const { apiKey, apiSecret } = getPreferenceValues<Preferences>();
const { isLoading, data } = useFetch(`https://spaceship.dev/api/v1/${endpoint}?take=20&skip=0`, {
headers: {
"X-Api-Key": apiKey,
"X-Api-Secret": apiSecret
},
async parseResponse(response) {
if (!response.ok) {
const res: ErrorResult = await response.json();
throw new Error(res.detail);
}
const res: SuccessResult<T> = await response.json();
return res.items;
},
initialData: []
});
return { isLoading, data };
const { apiKey, apiSecret } = getPreferenceValues<Preferences>();
const { isLoading, data } = useFetch(`https://spaceship.dev/api/v1/${endpoint}?take=20&skip=0`, {
headers: {
"X-Api-Key": apiKey,
"X-Api-Secret": apiSecret,
},
async parseResponse(response) {
if (!response.ok) {
const res: ErrorResult = await response.json();
throw new Error(res.detail);
}
const res: SuccessResult<T> = await response.json();
return res.items;
},
initialData: [],
});
return { isLoading, data };
}

export default function ManageDomains() {
const [isShowingDetail, setIsShowingDetail] = useCachedState("show-details-domains", false);
const { isLoading, data: domains } = useSpaceship<DomainInfo>("domains");

function formatDate(date: string) {
const options: Intl.DateTimeFormatOptions = { year: "numeric", month: "short", day: "numeric" };
const formattedDate = new Date(date).toLocaleDateString('en-US', options);
return formattedDate;
}

return <List isLoading={isLoading} isShowingDetail={isShowingDetail}>
{domains.map(domain => <List.Item key={domain.name} icon={getFavicon(`https://${domain.name}`, { fallback: Icon.Globe })} title={domain.name} accessories={isShowingDetail ? undefined : [
{ date: new Date(domain.expirationDate), tooltip: `Expires on ${formatDate(domain.expirationDate)}` },
{ tag: domain.privacyProtection.level==="high" ? "Private" : "Public", tooltip: "Privacy" }
]} actions={<ActionPanel>
<Action icon={Icon.AppWindowSidebarLeft} title="Toggle Details" onAction={() => setIsShowingDetail(show => !show)} />
<Action.Push icon="dns.svg" title="Manage DNS Records" target={<ManageDNSRecords domain={domain} />} />
<Action.OpenInBrowser icon={getFavicon(`https://${domain.name}`, { fallback: Icon.Globe })} title={`Go to ${domain.name}`} url={`https://${domain.name}`} />
</ActionPanel>} detail={<List.Item.Detail markdown={`${domain.nameservers.provider} Nameservers \n\n ${domain.nameservers.hosts.join("\n\n")}`} />} />)}
const [isShowingDetail, setIsShowingDetail] = useCachedState("show-details-domains", false);
const { isLoading, data: domains } = useSpaceship<DomainInfo>("domains");

function formatDate(date: string) {
const options: Intl.DateTimeFormatOptions = { year: "numeric", month: "short", day: "numeric" };
const formattedDate = new Date(date).toLocaleDateString("en-US", options);
return formattedDate;
}

function generateAccessories(domain: DomainInfo) {
const accessories: List.Item.Accessory[] = [
{ tag: domain.privacyProtection.level === "high" ? "Private" : "Public", tooltip: "Privacy" },
];
accessories.push({
tag: domain.eppStatuses.includes(DomainClientEPPStatus.clientTransferProhibited)
? { value: "LOCKED", color: Color.Green }
: { value: "UNLOCKED", color: Color.Red },
tooltip: "Transfer lock",
});
accessories.push({
date: new Date(domain.expirationDate),
tooltip: `Expires on ${formatDate(domain.expirationDate)}`,
});
return accessories;
}

return (
<List isLoading={isLoading} isShowingDetail={isShowingDetail}>
{domains.map((domain) => (
<List.Item
key={domain.name}
icon={getFavicon(`https://${domain.name}`, { fallback: Icon.Globe })}
title={domain.name}
accessories={isShowingDetail ? undefined : generateAccessories(domain)}
actions={
<ActionPanel>
<Action
icon={Icon.AppWindowSidebarLeft}
title="Toggle Details"
onAction={() => setIsShowingDetail((show) => !show)}
/>
{/* eslint-disable-next-line @raycast/prefer-title-case */}
<Action.Push icon={Icon.Store} title="Manage DNS Records" target={<ManageDNSRecords domain={domain} />} />
<Action.OpenInBrowser
icon={getFavicon(`https://${domain.name}`, { fallback: Icon.Globe })}
title={`Go to ${domain.name}`}
url={`https://${domain.name}`}
shortcut={Keyboard.Shortcut.Common.Open}
/>
</ActionPanel>
}
detail={
<List.Item.Detail
markdown={`${domain.nameservers.provider} Nameservers \n\n ${domain.nameservers.hosts.join("\n\n")}`}
/>
}
/>
))}
</List>
);
}

function ManageDNSRecords({ domain }: { domain: DomainInfo }) {
const { isLoading, data: records } = useSpaceship<ResourceRecord>(`dns/records/${domain.name}`);
const { isLoading, data: records } = useSpaceship<ResourceRecord>(`dns/records/${domain.name}`);

function generateAccessories(record: ResourceRecord) {
const accessories: List.Item.Accessory[] = [{ tag: record.type, tooltip: "Type" }];

if (record.address) accessories.unshift({ icon: Icon.Text, text: record.address, tooltip: "Address" });
else if (record.value) accessories.unshift({ icon: Icon.Text, text: record.value, tooltip: "Value" });

return <List isLoading={isLoading}>
{domain.nameservers.provider==="custom" && <List.EmptyView icon="dns.svg" title="Managed with Custom DNS" description="To manage your records here, change nameservers back to Spaceship DNS. You can even choose to see your inactive records and prepare them before changing back." />}
{records.map((record, index) => <List.Item key={index} icon="dns.svg" title={record.name} subtitle={record.type} />)}
return accessories;
}

return (
<List isLoading={isLoading}>
{domain.nameservers.provider === "custom" && (
<List.EmptyView
icon={Icon.Store}
title="Managed with Custom DNS"
description="To manage your records here, change nameservers back to Spaceship DNS. You can even choose to see your inactive records and prepare them before changing back."
/>
)}
{records.map((record, index) => (
<List.Item
key={index}
icon={Icon.Store}
title={record.name}
subtitle={`.${domain.name}`}
accessories={generateAccessories(record)}
/>
))}
</List>
}
);
}
58 changes: 34 additions & 24 deletions extensions/spaceship/src/types.ts
Original file line number Diff line number Diff line change
@@ -1,31 +1,41 @@
export type DomainInfo = {
name: string;
unicodeName: string;
isPremium: boolean;
autoRenew: boolean;
registrationDate: string;
expirationDate: string;
privacyProtection: {
level: "public" | "high";
contactForm: boolean;
export enum DomainClientEPPStatus {
clientDeleteProhibited = "clientDeleteProhibited",
clientHold = "clientHold",
clientRenewProhibited = "clientRenewProhibited",
clientTransferProhibited = "clientTransferProhibited",
clientUpdateProhibited = "clientUpdateProhibited",
}
nameservers: {
export type DomainInfo = {
name: string;
unicodeName: string;
isPremium: boolean;
autoRenew: boolean;
registrationDate: string;
expirationDate: string;
privacyProtection: {
level: "public" | "high";
contactForm: boolean;
};
eppStatuses: DomainClientEPPStatus[];
nameservers: {
provider: "basic" | "custom";
hosts: string[];
}
}
};
};

export type ResourceRecord = {
type: string;
name: string;
ttl?: number;
group: {
type: "custom" | "product" | "personalNs";
}
}
type: string;
name: string;
ttl?: number;
group: {
type: "custom" | "product" | "personalNs";
};
value?: string;
address?: string;
};

export type SuccessResult<T> = {
items: T[];
total: number;
}
export type ErrorResult = { detail: string; data?: Array<{ field: string; details: string }> }
items: T[];
total: number;
};
export type ErrorResult = { detail: string; data?: Array<{ field: string; details: string }> };

0 comments on commit ce67660

Please sign in to comment.