Skip to content

Commit 43a7a6d

Browse files
authored
Merge pull request #48 from jycr/main
Externalize config from Docker image
2 parents 720713f + 0a30fd0 commit 43a7a6d

File tree

11 files changed

+171
-113
lines changed

11 files changed

+171
-113
lines changed

.dockerignore

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
.env
2+
.dockerignore
3+
.git
4+
.gitignore
5+
node_modules/
6+
dist/
7+
.history/
8+
.github/
9+
Dockerfile
10+
*.md

Dockerfile

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,11 @@ COPY . .
77
RUN npm run build
88

99
# Stage 2: Serve the application with Nginx
10-
FROM nginx:1.26 as production-stage
10+
FROM nginx:1.27 as production-stage
11+
1112
COPY --from=build-stage /app/dist /usr/share/nginx/html
13+
COPY --from=build-stage /app/dist/assets/app-config.js /usr/share/nginx/html-template/app-config.template.js
14+
COPY ./docker-entrypoint.d/*.sh /docker-entrypoint.d/
15+
RUN chmod +x /docker-entrypoint.d/*.sh
1216
EXPOSE 80
13-
CMD ["nginx", "-g", "daemon off;"]
17+
CMD ["nginx", "-g", "daemon off;"]

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ docker build -t copilot-metrics-viewer .
137137

138138
### Docker run
139139
```
140-
docker run -p 8080:80 copilot-metrics-viewer
140+
docker run -p 8080:80 --env-file ./.env copilot-metrics-viewer
141141
```
142142
The application will be accessible at http://localhost:8080
143143

docker-entrypoint.d/99-config-app.sh

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
#!/bin/sh
2+
3+
configTemplateFile=/usr/share/nginx/html-template/app-config.template.js
4+
configTargetFile=/usr/share/nginx/html/assets/app-config.js
5+
6+
envsubst <"$configTemplateFile" >"$configTargetFile"

public/assets/app-config.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
window._ENV_ = {
2+
// These values are replaced by the entrypoint of Docker Image
3+
VUE_APP_MOCKED_DATA: "${VUE_APP_MOCKED_DATA}",
4+
VUE_APP_SCOPE: "${VUE_APP_SCOPE}",
5+
VUE_APP_GITHUB_ORG: "${VUE_APP_GITHUB_ORG}",
6+
VUE_APP_GITHUB_ENT: "${VUE_APP_GITHUB_ENT}",
7+
VUE_APP_GITHUB_TOKEN: "${VUE_APP_GITHUB_TOKEN}",
8+
};

public/index.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
<meta name="viewport" content="width=device-width,initial-scale=1.0">
77
<link rel="icon" href="<%= BASE_URL %>favicon.svg">
88
<title><%= htmlWebpackPlugin.options.title %></title>
9+
<script src="<%= BASE_URL %>assets/app-config.js"></script>
910
</head>
1011
<body>
1112
<noscript>

src/api/ExtractSeats.ts

Lines changed: 33 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,83 +1,64 @@
11
// TypeScript
22
import axios from "axios";
33
import { Seat } from "../model/Seat";
4+
import config from '../config';
45

56
import organizationMockedResponse_seats from '../assets/organization_response_sample_seats.json';
67
import enterpriseMockedResponse_seats from '../assets/enterprise_response_sample_seats.json';
78

89
export const getSeatsApi = async (): Promise<Seat[]> => {
910
const perPage = 50;
1011
let page = 1;
11-
let seatUrl = `https://api.github.com/`;
1212
let seatsData: Seat[] = [];
1313

1414
let response;
1515

16-
17-
if (process.env.VUE_APP_MOCKED_DATA === "true") {
18-
console.log("Using mock data. Check VUE_APP_MOCKED_DATA variable.");
19-
if (process.env.VUE_APP_SCOPE === "organization") {
20-
response = organizationMockedResponse_seats;
21-
}
22-
else if (process.env.VUE_APP_SCOPE === "enterprise") {
23-
response = enterpriseMockedResponse_seats;
24-
}
25-
else {
26-
throw new Error(`Invalid VUE_APP_SCOPE value: ${process.env.VUE_APP_SCOPE}. Expected "organization" or "enterprise".`);
27-
}
28-
seatsData = seatsData.concat(response.seats.map((item: any) => new Seat(item)));
29-
return seatsData;
16+
if (config.scope.type !== "organization") {
17+
// when the scope is not organization, return seatsData,by default it will return empty array
18+
return seatsData;
3019
}
3120
else {
32-
// if VUE_APP_GITHUB_TOKEN is not set, throw an error
33-
if (!process.env.VUE_APP_GITHUB_TOKEN) {
34-
throw new Error("VUE_APP_GITHUB_TOKEN environment variable is not set.");
35-
return seatsData;
36-
}
37-
else if (process.env.VUE_APP_SCOPE === "organization") {
38-
seatUrl=seatUrl+`orgs/${process.env.VUE_APP_GITHUB_ORG}/copilot/billing/seats`;
39-
}
40-
else if (process.env.VUE_APP_SCOPE === "enterprise") {
41-
seatUrl=seatUrl+`enterprises/${process.env.VUE_APP_GITHUB_ENT}/copilot/billing/seats`;
21+
if (config.mockedData) {
22+
response = organizationMockedResponse_seats;
23+
seatsData = seatsData.concat(response.seats.map((item: any) => new Seat(item)));
4224
}
4325
else {
44-
throw new Error(`Invalid VUE_APP_SCOPE value: ${process.env.VUE_APP_SCOPE}. Expected "organization" or "enterprise".`);
45-
return seatsData;
46-
}
47-
48-
// Fetch the first page to get the total number of seats
49-
response = await axios.get(seatUrl, {
50-
headers: {
51-
Accept: "application/vnd.github+json",
52-
Authorization: `Bearer ${process.env.VUE_APP_GITHUB_TOKEN}`,
53-
"X-GitHub-Api-Version": "2022-11-28",
54-
},
55-
params: {
56-
per_page: perPage,
57-
page: page
58-
}
59-
});
60-
61-
seatsData = seatsData.concat(response.data.seats.map((item: any) => new Seat(item)));
62-
// Calculate the total pages
63-
const totalSeats = response.data.total_seats;
64-
const totalPages = Math.ceil(totalSeats / perPage);
65-
66-
// Fetch the remaining pages
67-
for (page = 2; page <= totalPages; page++) {
68-
response = await axios.get(seatUrl, {
26+
// Fetch the first page to get the total number of seats
27+
response = await axios.get(`${config.github.apiUrl}/copilot/billing/seats`, {
6928
headers: {
7029
Accept: "application/vnd.github+json",
71-
Authorization: `Bearer ${process.env.VUE_APP_GITHUB_TOKEN}`,
30+
Authorization: `Bearer ${config.github.token}`,
7231
"X-GitHub-Api-Version": "2022-11-28",
7332
},
7433
params: {
7534
per_page: perPage,
7635
page: page
7736
}
7837
});
38+
7939
seatsData = seatsData.concat(response.data.seats.map((item: any) => new Seat(item)));
40+
41+
// Calculate the total pages
42+
const totalSeats = response.data.total_seats;
43+
const totalPages = Math.ceil(totalSeats / perPage);
44+
45+
// Fetch the remaining pages
46+
for (page = 2; page <= totalPages; page++) {
47+
response = await axios.get(`${config.github.apiUrl}/copilot/billing/seats`, {
48+
headers: {
49+
Accept: "application/vnd.github+json",
50+
Authorization: `Bearer ${config.github.token}`,
51+
"X-GitHub-Api-Version": "2022-11-28",
52+
},
53+
params: {
54+
per_page: perPage,
55+
page: page
56+
}
57+
});
58+
59+
seatsData = seatsData.concat(response.data.seats.map((item: any) => new Seat(item)));
60+
}
8061
}
8162
return seatsData;
82-
}
8363
}
64+
}

src/api/GitHubApi.ts

Lines changed: 17 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -9,66 +9,40 @@ import axios from "axios";
99
import { Metrics } from "../model/Metrics";
1010
import organizationMockedResponse from '../assets/organization_response_sample.json';
1111
import enterpriseMockedResponse from '../assets/enterprise_response_sample.json';
12-
12+
import config from '../config';
1313

1414
export const getMetricsApi = async (): Promise<Metrics[]> => {
15-
15+
1616
let response;
1717
let metricsData;
1818

19-
if (process.env.VUE_APP_MOCKED_DATA === "true") {
19+
if (config.mockedData) {
2020
console.log("Using mock data. Check VUE_APP_MOCKED_DATA variable.");
21-
if (process.env.VUE_APP_SCOPE === "organization") {
22-
response = organizationMockedResponse;
23-
} else if (process.env.VUE_APP_SCOPE === "enterprise") {
24-
response = enterpriseMockedResponse;
25-
} else {
26-
throw new Error(`Invalid VUE_APP_SCOPE value: ${process.env.VUE_APP_SCOPE}. Expected "organization" or "enterprise".`);
27-
}
28-
21+
response = config.scope.type === "organization" ? organizationMockedResponse : enterpriseMockedResponse;
2922
metricsData = response.map((item: any) => new Metrics(item));
3023
} else {
31-
// if VUE_APP_GITHUB_TOKEN is not set, throw an error
32-
if (!process.env.VUE_APP_GITHUB_TOKEN) {
33-
throw new Error("VUE_APP_GITHUB_TOKEN environment variable is not set.");
34-
}
35-
if (process.env.VUE_APP_SCOPE === "organization") {
36-
response = await axios.get(
37-
`https://api.github.com/orgs/${process.env.VUE_APP_GITHUB_ORG}/copilot/usage`,
38-
{
39-
headers: {
40-
Accept: "application/vnd.github+json",
41-
Authorization: `Bearer ${process.env.VUE_APP_GITHUB_TOKEN}`,
42-
"X-GitHub-Api-Version": "2022-11-28",
43-
},
44-
}
45-
);
46-
} else if (process.env.VUE_APP_SCOPE === "enterprise") {
24+
response = await axios.get(
25+
`${config.github.apiUrl}/copilot/usage`,
26+
{
27+
headers: {
28+
Accept: "application/vnd.github+json",
29+
Authorization: `Bearer ${config.github.token}`,
30+
"X-GitHub-Api-Version": "2022-11-28",
31+
},
32+
}
33+
);
4734

48-
response = await axios.get(
49-
`https://api.github.com/enterprises/${process.env.VUE_APP_GITHUB_ENT}/copilot/usage`,
50-
{
51-
headers: {
52-
Accept: "application/vnd.github+json",
53-
Authorization: `Bearer ${process.env.VUE_APP_GITHUB_TOKEN}`,
54-
"X-GitHub-Api-Version": "2022-11-28",
55-
},
56-
}
57-
);
58-
} else {
59-
throw new Error(`Invalid VUE_APP_SCOPE value: ${process.env.VUE_APP_SCOPE}. Expected "organization" or "enterprise".`);
60-
}
6135

6236
metricsData = response.data.map((item: any) => new Metrics(item));
6337
}
6438
return metricsData;
6539
};
6640

67-
export const getTeams = async (): Promise<string[]> =>{
68-
const response = await axios.get(`https://api.github.com/orgs/${process.env.VUE_APP_GITHUB_ORG}/teams`, {
41+
export const getTeams = async (): Promise<string[]> => {
42+
const response = await axios.get(`${config.github.apiUrl}/teams`, {
6943
headers: {
7044
Accept: 'application/vnd.github+json',
71-
Authorization: `Bearer ${process.env.VUE_APP_GITHUB_TOKEN}`,
45+
Authorization: `Bearer ${config.github.token}`,
7246
'X-GitHub-Api-Version': '2022-11-28',
7347
},
7448
});

src/components/ApiResponse.vue

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929

3030
<script lang="ts">
3131
import { defineComponent } from 'vue';
32+
import config from '../config';
3233
3334
export default defineComponent({
3435
name: 'ApiResponse',
@@ -44,7 +45,7 @@ export default defineComponent({
4445
},
4546
data() {
4647
return {
47-
vueAppScope: process.env.VUE_APP_SCOPE,
48+
vueAppScope: config.scope.type,
4849
showCopyMessage: false,
4950
showSeatMessage: false,
5051
isError: false,

src/components/MainComponent.vue

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ import BreakdownComponent from './BreakdownComponent.vue'
5555
import CopilotChatViewer from './CopilotChatViewer.vue'
5656
import SeatsAnalysisViewer from './SeatsAnalysisViewer.vue'
5757
import ApiResponse from './ApiResponse.vue'
58+
import config from '../config';
5859
5960
export default defineComponent({
6061
name: 'MainComponent',
@@ -67,27 +68,22 @@ export default defineComponent({
6768
},
6869
computed: {
6970
gitHubOrgName() {
70-
return process.env.VUE_APP_GITHUB_ORG;
71+
return config.github.org;
7172
},
7273
itemName() {
73-
if (process.env.VUE_APP_SCOPE === 'enterprise' || process.env.VUE_APP_SCOPE === 'organization') {
74-
return process.env.VUE_APP_SCOPE;
75-
} else {
76-
console.log("invalid");
77-
return 'invalid';
78-
}
74+
return config.scope.type;
7975
},
8076
capitalizedItemName():string {
8177
return this.itemName.charAt(0).toUpperCase() + this.itemName.slice(1);
8278
},
8379
displayedViewName(): string {
84-
return this.capitalizedItemName === 'Enterprise' ? process.env.VUE_APP_GITHUB_ENT: process.env.VUE_APP_GITHUB_ORG;
80+
return config.scope.name;
8581
},
8682
isScopeOrganization() {
87-
return process.env.VUE_APP_SCOPE === 'organization';
83+
return config.scope.type === 'organization';
8884
},
8985
mockedDataMessage() {
90-
return process.env.VUE_APP_MOCKED_DATA === 'true' ? 'Using mock data - see README if unintended' : '';
86+
return config.mockedData ? 'Using mock data - see README if unintended' : '';
9187
}
9288
},
9389
data () {
@@ -97,8 +93,15 @@ export default defineComponent({
9793
}
9894
},
9995
created() {
100-
if(this.itemName !== 'invalid'){
101-
this.tabItems.unshift(this.itemName);
96+
this.tabItems.unshift(this.itemName);
97+
if (config.scope.type === 'organization') {
98+
// get the last item in the array,which is 'api response'
99+
//and add 'seat analysis' before it
100+
let lastItem = this.tabItems.pop();
101+
this.tabItems.push('seat analysis');
102+
if (lastItem) {
103+
this.tabItems.push(lastItem);
104+
}
102105
}
103106
},
104107
setup() {
@@ -124,7 +127,7 @@ export default defineComponent({
124127
apiError.value = '401 Unauthorized access - check if your token in the .env file is correct.';
125128
break;
126129
case 404:
127-
apiError.value = `404 Not Found - is the organization '${process.env.VUE_APP_GITHUB_ORG}' correct?`;
130+
apiError.value = `404 Not Found - is the ${config.scope.type} '${config.scope.name}' correct?`;
128131
break;
129132
default:
130133
apiError.value = error.message;
@@ -154,7 +157,7 @@ export default defineComponent({
154157
apiError.value = '401 Unauthorized access - check if your token in the .env file is correct.';
155158
break;
156159
case 404:
157-
apiError.value = `404 Not Found - is the organization '${process.env.VUE_APP_GITHUB_ORG}' correct?`;
160+
apiError.value = `404 Not Found - is the ${config.scope.type} '${config.scope.name}' correct?`;
158161
break;
159162
default:
160163
apiError.value = error.message;

0 commit comments

Comments
 (0)