Skip to content

Commit a06831f

Browse files
authored
Merge pull request #1394 from trycompai/main
[comp] Production Deploy
2 parents e2a28bc + 003100c commit a06831f

File tree

7 files changed

+129
-99
lines changed

7 files changed

+129
-99
lines changed
Lines changed: 5 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,50 +1,11 @@
1-
import {
2-
Breadcrumb,
3-
BreadcrumbItem,
4-
BreadcrumbLink,
5-
BreadcrumbList,
6-
BreadcrumbPage,
7-
BreadcrumbSeparator,
8-
} from '@comp/ui/breadcrumb';
9-
import { db } from '@db';
10-
11-
export default async function Layout({
12-
children,
13-
params,
14-
}: {
1+
interface LayoutProps {
152
children: React.ReactNode;
16-
params: Promise<{ employeeId: string; orgId: string }>;
17-
}) {
18-
const { employeeId, orgId } = await params;
19-
const member = await db.member.findUnique({
20-
where: {
21-
id: employeeId,
22-
},
23-
select: {
24-
user: {
25-
select: {
26-
name: true,
27-
},
28-
},
29-
},
30-
});
3+
}
314

5+
export default async function Layout({ children }: LayoutProps) {
326
return (
33-
<div className="m-auto flex max-w-[1200px] flex-col gap-4">
34-
{member?.user?.name && (
35-
<Breadcrumb>
36-
<BreadcrumbList>
37-
<BreadcrumbItem>
38-
<BreadcrumbLink href={`/${orgId}/people`}>{'People'}</BreadcrumbLink>
39-
</BreadcrumbItem>
40-
<BreadcrumbSeparator />
41-
<BreadcrumbItem>
42-
<BreadcrumbPage>{member.user.name}</BreadcrumbPage>
43-
</BreadcrumbItem>
44-
</BreadcrumbList>
45-
</Breadcrumb>
46-
)}
47-
{children}
7+
<div className="m-auto flex max-w-[1200px] flex-col">
8+
<div>{children}</div>
489
</div>
4910
);
5011
}

apps/app/src/app/(app)/[orgId]/people/[employeeId]/page.tsx

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { auth } from '@/utils/auth';
22

3+
import PageWithBreadcrumb from '@/components/pages/PageWithBreadcrumb';
34
import {
45
type TrainingVideo,
56
trainingVideos as trainingVideosData,
@@ -15,9 +16,9 @@ import { Employee } from './components/Employee';
1516
export default async function EmployeeDetailsPage({
1617
params,
1718
}: {
18-
params: Promise<{ employeeId: string }>;
19+
params: Promise<{ employeeId: string; orgId: string }>;
1920
}) {
20-
const { employeeId } = await params;
21+
const { employeeId, orgId } = await params;
2122

2223
const session = await auth.api.getSession({
2324
headers: await headers(),
@@ -50,14 +51,21 @@ export default async function EmployeeDetailsPage({
5051
const { fleetPolicies, device } = await getFleetPolicies(employee);
5152

5253
return (
53-
<Employee
54-
employee={employee}
55-
policies={policies}
56-
trainingVideos={employeeTrainingVideos}
57-
fleetPolicies={fleetPolicies}
58-
host={device}
59-
canEdit={canEditMembers}
60-
/>
54+
<PageWithBreadcrumb
55+
breadcrumbs={[
56+
{ label: 'People', href: `/${orgId}/people/all` },
57+
{ label: employee.user.name, current: true },
58+
]}
59+
>
60+
<Employee
61+
employee={employee}
62+
policies={policies}
63+
trainingVideos={employeeTrainingVideos}
64+
fleetPolicies={fleetPolicies}
65+
host={device}
66+
canEdit={canEditMembers}
67+
/>
68+
</PageWithBreadcrumb>
6169
);
6270
}
6371

apps/app/src/app/(app)/[orgId]/people/layout.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ export default async function Layout({ children }: { children: React.ReactNode }
3333
{
3434
path: `/${orgId}/people/all`,
3535
label: 'People',
36+
activeOverrideIdPrefix: 'mem_',
3637
},
3738
...(employees.length > 0
3839
? [

apps/app/src/app/s3.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,14 +137,15 @@ export function extractS3KeyFromUrl(url: string): string {
137137

138138
export async function getFleetAgent({ os }: { os: 'macos' | 'windows' | 'linux' }) {
139139
const fleetBucketName = process.env.FLEET_AGENT_BUCKET_NAME;
140+
const fleetAgentFileName = 'Comp AI Agent-1.0.0-arm64.dmg';
140141

141142
if (!fleetBucketName) {
142143
throw new Error('FLEET_AGENT_BUCKET_NAME is not defined.');
143144
}
144145

145146
const getFleetAgentCommand = new GetObjectCommand({
146147
Bucket: fleetBucketName,
147-
Key: `${os}/fleet-osquery.pkg`,
148+
Key: `${os}/${fleetAgentFileName}`,
148149
});
149150

150151
const response = await s3Client.send(getFleetAgentCommand);

apps/portal/src/app/(app)/(home)/[orgId]/components/tasks/DeviceAgentAccordionItem.tsx

Lines changed: 57 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@ export function DeviceAgentAccordionItem({
2424
}: DeviceAgentAccordionItemProps) {
2525
const [isDownloading, setIsDownloading] = useState(false);
2626

27+
// Detect OS from user agent
28+
const isMacOS = typeof window !== 'undefined' && navigator.userAgent.includes('Mac');
29+
2730
const hasInstalledAgent = host !== null;
2831
const allPoliciesPass =
2932
fleetPolicies.length === 0 || fleetPolicies.every((policy) => policy.response === 'pass');
@@ -57,7 +60,7 @@ export function DeviceAgentAccordionItem({
5760
// Method 1: Using a temporary link (most reliable)
5861
const a = document.createElement('a');
5962
a.href = downloadUrl;
60-
a.download = 'compai-device-agent.zip';
63+
a.download = isMacOS ? 'Comp AI Agent-1.0.0-arm64.dmg' : 'compai-device-agent.zip';
6164
document.body.appendChild(a);
6265
a.click();
6366
document.body.removeChild(a);
@@ -136,45 +139,64 @@ export function DeviceAgentAccordionItem({
136139
{getButtonContent()}
137140
</Button>
138141
</li>
142+
{!isMacOS && (
143+
<li>
144+
<strong>Run the "Install Me First" file</strong>
145+
<p className="mt-1">
146+
After extracting the downloaded zip file, locate and run the "Install Me
147+
First" file to prepare your system.
148+
</p>
149+
</li>
150+
)}
139151
<li>
140-
<strong>Run the "Install Me First" file</strong>
141-
<p className="mt-1">
142-
After extracting the downloaded zip file, locate and run the "Install Me First"
143-
file to prepare your system.
144-
</p>
145-
</li>
146-
<li>
147-
<strong>Run the Comp AI Device Agent installer</strong>
152+
<strong>
153+
{isMacOS
154+
? 'Install the Comp AI Device Agent'
155+
: 'Run the Comp AI Device Agent installer'}
156+
</strong>
148157
<p className="mt-1">
149-
Follow the installation wizard steps. When you reach the introduction screen (as
150-
shown below), click "Continue" to proceed through the installation.
158+
{isMacOS
159+
? 'Double-click the downloaded DMG file and follow the installation instructions.'
160+
: 'Follow the installation wizard steps. When you reach the introduction screen (as shown below), click "Continue" to proceed through the installation.'}
151161
</p>
152-
<Image
153-
src="/osquery-agent.jpeg"
154-
alt="Fleet osquery installer introduction screen"
155-
width={600}
156-
height={400}
157-
className="mt-2 rounded-xs border"
158-
/>
162+
{!isMacOS && (
163+
<Image
164+
src="/osquery-agent.jpeg"
165+
alt="Fleet osquery installer introduction screen"
166+
width={600}
167+
height={400}
168+
className="mt-2 rounded-xs border"
169+
/>
170+
)}
159171
</li>
160-
<li>
161-
<strong>Enable MDM</strong>
162-
<div className="space-y-2">
163-
<p>
164-
On Mac, on the top of your screen, find the Fleet Desktop app which looks like
165-
an F made of dots. Click on it and click My Device.
166-
</p>
167-
<p>
168-
You should see a banner that asks you to enable MDM. Click the button and
169-
follow the instructions.
172+
{isMacOS ? (
173+
<li>
174+
<strong>Login with your work email</strong>
175+
<p className="mt-1">
176+
After installation, login with your work email, select your organization and
177+
then click "Link Device" and "Install Agent".
170178
</p>
171-
<p>
172-
After you've enabled MDM, if you refresh the page, the banner will disappear.
173-
Now your computer will automatically enable the necessary settings on your
174-
computer in order to be compliant.
175-
</p>
176-
</div>
177-
</li>
179+
</li>
180+
) : (
181+
<li>
182+
<strong>Enable MDM</strong>
183+
<div className="space-y-2">
184+
<p>
185+
Find the Fleet Desktop app in your system tray (bottom right corner). Click
186+
on it and click My Device.
187+
</p>
188+
<p>
189+
You should see a banner that asks you to enable MDM. Click the button and
190+
follow the instructions.
191+
</p>
192+
<p>
193+
After you've enabled MDM, if you refresh the page, the banner will
194+
disappear. Now your computer will automatically enable the necessary
195+
settings on your computer in order to be compliant.
196+
</p>
197+
</div>
198+
</li>
199+
)}
178200
</ol>
179201
</div>
180202
) : (

apps/portal/src/app/api/download-agent/route.ts

Lines changed: 42 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import archiver from 'archiver';
66
import { type NextRequest, NextResponse } from 'next/server';
77
import { PassThrough, Readable } from 'stream';
88
import {
9-
generateMacScript,
109
generateWindowsScript,
1110
getPackageFilename,
1211
getReadmeContent,
@@ -52,12 +51,46 @@ export async function GET(req: NextRequest) {
5251
return new NextResponse('Server configuration error', { status: 500 });
5352
}
5453

55-
// Generate OS-specific script
56-
const fleetDevicePath = os === 'macos' ? fleetDevicePathMac : fleetDevicePathWindows;
57-
const script =
58-
os === 'macos'
59-
? generateMacScript({ orgId, employeeId, fleetDevicePath })
60-
: generateWindowsScript({ orgId, employeeId, fleetDevicePath });
54+
// For macOS, serve the DMG directly. For Windows, create a zip with script and installer.
55+
if (os === 'macos') {
56+
try {
57+
// Direct DMG download for macOS
58+
const macosPackageFilename = 'Comp AI Agent-1.0.0-arm64.dmg';
59+
const packageKey = `macos/${macosPackageFilename}`;
60+
61+
const getObjectCommand = new GetObjectCommand({
62+
Bucket: fleetBucketName,
63+
Key: packageKey,
64+
});
65+
66+
const s3Response = await s3Client.send(getObjectCommand);
67+
68+
if (!s3Response.Body) {
69+
return new NextResponse('DMG file not found', { status: 404 });
70+
}
71+
72+
// Convert S3 stream to Web Stream for NextResponse
73+
const s3Stream = s3Response.Body as Readable;
74+
const webStream = Readable.toWeb(s3Stream) as unknown as ReadableStream;
75+
76+
// Return streaming response with headers that trigger browser download
77+
return new NextResponse(webStream, {
78+
headers: {
79+
'Content-Type': 'application/x-apple-diskimage',
80+
'Content-Disposition': `attachment; filename="${macosPackageFilename}"`,
81+
'Cache-Control': 'no-cache, no-store, must-revalidate',
82+
'X-Accel-Buffering': 'no',
83+
},
84+
});
85+
} catch (error) {
86+
logger('Error downloading macOS DMG', { error });
87+
return new NextResponse('Failed to download macOS agent', { status: 500 });
88+
}
89+
}
90+
91+
// Windows flow: Generate script and create zip
92+
const fleetDevicePath = fleetDevicePathWindows;
93+
const script = generateWindowsScript({ orgId, employeeId, fleetDevicePath });
6194

6295
try {
6396
// Create a passthrough stream for the response
@@ -92,7 +125,8 @@ export async function GET(req: NextRequest) {
92125

93126
// Get package from S3 and stream it
94127
const packageFilename = getPackageFilename(os);
95-
const packageKey = `${os}/fleet-osquery.${os === 'macos' ? 'pkg' : 'msi'}`;
128+
const windowsPackageFilename = 'fleet-osquery.msi';
129+
const packageKey = `windows/${windowsPackageFilename}`;
96130

97131
const getObjectCommand = new GetObjectCommand({
98132
Bucket: fleetBucketName,

apps/portal/src/utils/s3.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,9 +132,12 @@ export async function getFleetAgent({ os }: { os: 'macos' | 'windows' }) {
132132
throw new Error(`Unsupported OS: ${os}`);
133133
}
134134

135+
const macosPackageFilename = 'Comp AI Agent-1.0.0-arm64.dmg';
136+
const windowsPackageFilename = 'fleet-osquery.msi';
137+
135138
const getFleetAgentCommand = new GetObjectCommand({
136139
Bucket: fleetBucketName,
137-
Key: `${os}/fleet-osquery.${extension}`,
140+
Key: `${os}/${os === 'macos' ? macosPackageFilename : windowsPackageFilename}`,
138141
});
139142

140143
const response = await s3Client.send(getFleetAgentCommand);

0 commit comments

Comments
 (0)