Compare commits
25 Commits
d77be41b99
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| c8fecc52d6 | |||
| e97cbc6ad2 | |||
| 9fbca2677b | |||
| 6daa4c284d | |||
| 1c1eaa7ee7 | |||
| 96501f712c | |||
| ce52b5aff5 | |||
| b9ad5fd922 | |||
| f6b1a7eede | |||
| c1b2a8cbdd | |||
| aa22b48746 | |||
| ea7c54a5dc | |||
| 0a344ed1ce | |||
| 3d6e4e6f34 | |||
| 2cdbebdfa1 | |||
| 5db0026aa7 | |||
| 7b213dd33a | |||
| b69f26027d | |||
| 95b0c5f1ad | |||
| 4dc99b740b | |||
| 248364a686 | |||
| cd57e11404 | |||
| 99ea80c93c | |||
| 46543c83b1 | |||
| 636efa9744 |
82
README.md
@ -1 +1,83 @@
|
|||||||
# Komponiranje
|
# Komponiranje
|
||||||
|
|
||||||
|
## How to run
|
||||||
|
|
||||||
|
Clone repository
|
||||||
|
```
|
||||||
|
git clone https://gitea.ekirin.com/Intis/prezentacija-komponiranje.git
|
||||||
|
cd prezentacija-komponiranje
|
||||||
|
```
|
||||||
|
|
||||||
|
Start docker compose with all containers:
|
||||||
|
```
|
||||||
|
docker compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
Wait until docker images are built and containers starts.
|
||||||
|
|
||||||
|
Browse to [Local frontend application on port 8080](http://localhost:8080)
|
||||||
|
|
||||||
|
List running containers:
|
||||||
|
```
|
||||||
|
docker compose ps
|
||||||
|
```
|
||||||
|
|
||||||
|
Attach to docker compose output:
|
||||||
|
```
|
||||||
|
docker compose logs -f
|
||||||
|
```
|
||||||
|
|
||||||
|
Stop running containers:
|
||||||
|
```
|
||||||
|
docker compose down
|
||||||
|
```
|
||||||
|
|
||||||
|
### Run on host network
|
||||||
|
|
||||||
|
Start docker compose:
|
||||||
|
```
|
||||||
|
docker compose -f docker-compose-local.yml up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
Containers will listen to the following local ports:
|
||||||
|
|
||||||
|
| Service | Port |
|
||||||
|
| ------------- | ----- |
|
||||||
|
| FE / nginx | 80 |
|
||||||
|
| Envoy proxy | 10000 |
|
||||||
|
| Machines app | 4000 |
|
||||||
|
| Products app | 4001 |
|
||||||
|
| Database | 55432 |
|
||||||
|
|
||||||
|
Browse to [Local frontend application on port 80](http://localhost:80)
|
||||||
|
|
||||||
|
|
||||||
|
Stop running containers:
|
||||||
|
```
|
||||||
|
docker compose -f docker-compose-local.yml down
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## Media
|
||||||
|
|
||||||
|
### Life without Docker Compose
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
### Containers Architecture
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
### Networking - docker-compose.yml
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
### Networking - host network - docker-compose-local.yml
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
### DCCT - Docker Compose Cloud Tester
|
||||||
|
|
||||||
|
- [DCCT repository](https://gitlab.televendcloud.com/cloud/dc-cloud-tester)
|
||||||
|
|
||||||
|

|
||||||
|
|||||||
66
docker-compose-local.yml
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
version: "3.8"
|
||||||
|
|
||||||
|
services:
|
||||||
|
db:
|
||||||
|
build:
|
||||||
|
context: ./database
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
network_mode: "host"
|
||||||
|
environment:
|
||||||
|
- POSTGRES_USER=postgres
|
||||||
|
- POSTGRES_PASSWORD=postgres
|
||||||
|
command: -p 55432
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "pg_isready -U postgres -p 55432"]
|
||||||
|
interval: 1s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 10
|
||||||
|
machines-app:
|
||||||
|
build:
|
||||||
|
context: ./machines
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
network_mode: "host"
|
||||||
|
environment:
|
||||||
|
- APPPORT=4000
|
||||||
|
- DBHOST=localhost
|
||||||
|
- DBPORT=55432
|
||||||
|
- DBNAME=komponiranje
|
||||||
|
- DBUSER=pero
|
||||||
|
- DBPASSWORD=pero.000
|
||||||
|
- PRODUCTSAPPURL=http://localhost:10000
|
||||||
|
depends_on:
|
||||||
|
db:
|
||||||
|
condition: service_healthy
|
||||||
|
products-app:
|
||||||
|
build:
|
||||||
|
context: ./products
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
network_mode: "host"
|
||||||
|
environment:
|
||||||
|
- APPPORT=4001
|
||||||
|
- DBHOST=localhost
|
||||||
|
- DBPORT=55432
|
||||||
|
- DBNAME=komponiranje
|
||||||
|
- DBUSER=pero
|
||||||
|
- DBPASSWORD=pero.000
|
||||||
|
depends_on:
|
||||||
|
db:
|
||||||
|
condition: service_healthy
|
||||||
|
proxy:
|
||||||
|
image: envoyproxy/envoy:v1.28-latest
|
||||||
|
network_mode: "host"
|
||||||
|
volumes:
|
||||||
|
- ./proxy/envoy-local.yaml:/etc/envoy/envoy.yaml
|
||||||
|
depends_on:
|
||||||
|
- machines-app
|
||||||
|
- products-app
|
||||||
|
frontend-app:
|
||||||
|
build:
|
||||||
|
context: ./frontend
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
args:
|
||||||
|
- REACT_APP_MACHINES_API_URL=http://localhost:10000
|
||||||
|
- REACT_APP_PRODUCTS_API_URL=http://localhost:10000
|
||||||
|
network_mode: "host"
|
||||||
|
depends_on:
|
||||||
|
- proxy
|
||||||
@ -5,8 +5,8 @@ services:
|
|||||||
build:
|
build:
|
||||||
context: ./database
|
context: ./database
|
||||||
dockerfile: Dockerfile
|
dockerfile: Dockerfile
|
||||||
ports:
|
networks:
|
||||||
- 55432:5432
|
- backend-net
|
||||||
environment:
|
environment:
|
||||||
- POSTGRES_USER=postgres
|
- POSTGRES_USER=postgres
|
||||||
- POSTGRES_PASSWORD=postgres
|
- POSTGRES_PASSWORD=postgres
|
||||||
@ -19,8 +19,10 @@ services:
|
|||||||
build:
|
build:
|
||||||
context: ./machines
|
context: ./machines
|
||||||
dockerfile: Dockerfile
|
dockerfile: Dockerfile
|
||||||
|
networks:
|
||||||
|
- backend-net
|
||||||
environment:
|
environment:
|
||||||
- APPPORT=3000
|
- APPPORT=4000
|
||||||
- DBHOST=db
|
- DBHOST=db
|
||||||
- DBPORT=5432
|
- DBPORT=5432
|
||||||
- DBNAME=komponiranje
|
- DBNAME=komponiranje
|
||||||
@ -37,8 +39,10 @@ services:
|
|||||||
deploy:
|
deploy:
|
||||||
mode: replicated
|
mode: replicated
|
||||||
replicas: 2
|
replicas: 2
|
||||||
|
networks:
|
||||||
|
- backend-net
|
||||||
environment:
|
environment:
|
||||||
- APPPORT=3000
|
- APPPORT=4001
|
||||||
- DBHOST=db
|
- DBHOST=db
|
||||||
- DBPORT=5432
|
- DBPORT=5432
|
||||||
- DBNAME=komponiranje
|
- DBNAME=komponiranje
|
||||||
@ -49,6 +53,9 @@ services:
|
|||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
proxy:
|
proxy:
|
||||||
image: envoyproxy/envoy:v1.28-latest
|
image: envoyproxy/envoy:v1.28-latest
|
||||||
|
networks:
|
||||||
|
- frontend-net
|
||||||
|
- backend-net
|
||||||
ports:
|
ports:
|
||||||
- "10000:10000"
|
- "10000:10000"
|
||||||
volumes:
|
volumes:
|
||||||
@ -56,3 +63,24 @@ services:
|
|||||||
depends_on:
|
depends_on:
|
||||||
- machines-app
|
- machines-app
|
||||||
- products-app
|
- products-app
|
||||||
|
frontend-app:
|
||||||
|
build:
|
||||||
|
context: ./frontend
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
args:
|
||||||
|
- REACT_APP_MACHINES_API_URL=http://localhost:10000
|
||||||
|
- REACT_APP_PRODUCTS_API_URL=http://localhost:10000
|
||||||
|
networks:
|
||||||
|
- frontend-net
|
||||||
|
ports:
|
||||||
|
- "8080:80"
|
||||||
|
depends_on:
|
||||||
|
- proxy
|
||||||
|
|
||||||
|
networks:
|
||||||
|
frontend-net:
|
||||||
|
name: frontend-net
|
||||||
|
internal: false
|
||||||
|
backend-net:
|
||||||
|
name: backend-net
|
||||||
|
internal: true
|
||||||
|
|||||||
3
frontend/.dockerignore
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
**/node_modules
|
||||||
|
**/build
|
||||||
|
|
||||||
25
frontend/Dockerfile
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
# stage 1: build node frontend
|
||||||
|
FROM node:21 as node-builder
|
||||||
|
|
||||||
|
WORKDIR /node-builder
|
||||||
|
|
||||||
|
ARG REACT_APP_MACHINES_API_URL
|
||||||
|
ARG REACT_APP_PRODUCTS_API_URL
|
||||||
|
|
||||||
|
ENV REACT_APP_MACHINES_API_URL $REACT_APP_MACHINES_API_URL
|
||||||
|
ENV REACT_APP_PRODUCTS_API_URL $REACT_APP_PRODUCTS_API_URL
|
||||||
|
|
||||||
|
COPY ./package.json .
|
||||||
|
COPY ./package-lock.json .
|
||||||
|
COPY ./public ./public
|
||||||
|
COPY ./src ./src
|
||||||
|
|
||||||
|
RUN \
|
||||||
|
npm install && \
|
||||||
|
npm run build
|
||||||
|
|
||||||
|
|
||||||
|
# stage 2: build final image
|
||||||
|
FROM nginx:1.25-alpine
|
||||||
|
|
||||||
|
COPY --from=node-builder /node-builder/build/. /usr/share/nginx/html
|
||||||
36
frontend/Makefile
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
CONTAINER_NAME=frontend-app
|
||||||
|
IMAGE_NAME=komponiranje-frontend-app
|
||||||
|
|
||||||
|
|
||||||
|
run:
|
||||||
|
@npm start
|
||||||
|
|
||||||
|
|
||||||
|
.PHONY: build
|
||||||
|
build:
|
||||||
|
@npm run build
|
||||||
|
|
||||||
|
|
||||||
|
upgrade-packages:
|
||||||
|
@go get -u ./...
|
||||||
|
|
||||||
|
|
||||||
|
docker-build: clean
|
||||||
|
@docker build \
|
||||||
|
--progress=plain \
|
||||||
|
--tag $(IMAGE_NAME) \
|
||||||
|
.
|
||||||
|
|
||||||
|
|
||||||
|
docker-run:
|
||||||
|
@docker run \
|
||||||
|
--name $(CONTAINER_NAME) \
|
||||||
|
--publish 8080:80 \
|
||||||
|
--detach \
|
||||||
|
$(IMAGE_NAME)
|
||||||
|
|
||||||
|
|
||||||
|
clean:
|
||||||
|
- @docker stop $(CONTAINER_NAME)
|
||||||
|
- @docker rm $(CONTAINER_NAME)
|
||||||
|
- @docker rmi $(IMAGE_NAME)
|
||||||
@ -1,43 +1,18 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
|
||||||
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
|
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
<meta name="theme-color" content="#000000" />
|
<meta name="theme-color" content="#000000" />
|
||||||
<meta
|
<meta name="description" content="Komponiranje frontend demo" />
|
||||||
name="description"
|
<title>Komponiranje frontend demo</title>
|
||||||
content="Web site created using create-react-app"
|
</head>
|
||||||
/>
|
|
||||||
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
|
|
||||||
<!--
|
|
||||||
manifest.json provides metadata used when your web app is installed on a
|
|
||||||
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
|
|
||||||
-->
|
|
||||||
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
|
|
||||||
<!--
|
|
||||||
Notice the use of %PUBLIC_URL% in the tags above.
|
|
||||||
It will be replaced with the URL of the `public` folder during the build.
|
|
||||||
Only files inside the `public` folder can be referenced from the HTML.
|
|
||||||
|
|
||||||
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
|
<body>
|
||||||
work correctly both with client-side routing and a non-root public URL.
|
|
||||||
Learn how to configure a non-root public URL by running `npm run build`.
|
|
||||||
-->
|
|
||||||
<title>React App</title>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
<!--
|
</body>
|
||||||
This HTML file is a template.
|
|
||||||
If you open it directly in the browser, you will see an empty page.
|
|
||||||
|
|
||||||
You can add webfonts, meta tags, or analytics to this file.
|
</html>
|
||||||
The build step will place the bundled scripts into the <body> tag.
|
|
||||||
|
|
||||||
To begin the development, run `npm start` or `yarn start`.
|
|
||||||
To create a production bundle, use `npm run build` or `yarn build`.
|
|
||||||
-->
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
Before Width: | Height: | Size: 5.2 KiB |
|
Before Width: | Height: | Size: 9.4 KiB |
@ -1,25 +1,8 @@
|
|||||||
{
|
{
|
||||||
"short_name": "React App",
|
"short_name": "React App",
|
||||||
"name": "Create React App Sample",
|
"name": "Create React App Sample",
|
||||||
"icons": [
|
"start_url": "./index.html",
|
||||||
{
|
"display": "standalone",
|
||||||
"src": "favicon.ico",
|
"theme_color": "#000000",
|
||||||
"sizes": "64x64 32x32 24x24 16x16",
|
"background_color": "#ffffff"
|
||||||
"type": "image/x-icon"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"src": "logo192.png",
|
|
||||||
"type": "image/png",
|
|
||||||
"sizes": "192x192"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"src": "logo512.png",
|
|
||||||
"type": "image/png",
|
|
||||||
"sizes": "512x512"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"start_url": ".",
|
|
||||||
"display": "standalone",
|
|
||||||
"theme_color": "#000000",
|
|
||||||
"background_color": "#ffffff"
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import { API_URL } from "../const";
|
|
||||||
|
|
||||||
export class ApiBase {}
|
export class ApiBase {}
|
||||||
|
|
||||||
@ -12,7 +11,6 @@ const commonHeaders = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const axiosUnauthorizedInstance = axios.create({
|
export const axiosUnauthorizedInstance = axios.create({
|
||||||
baseURL: API_URL,
|
|
||||||
timeout: 5000,
|
timeout: 5000,
|
||||||
headers: commonHeaders,
|
headers: commonHeaders,
|
||||||
});
|
});
|
||||||
@ -25,7 +23,6 @@ axiosUnauthorizedInstance.interceptors.response.use(
|
|||||||
);
|
);
|
||||||
|
|
||||||
export const axiosInstance = axios.create({
|
export const axiosInstance = axios.create({
|
||||||
baseURL: API_URL,
|
|
||||||
timeout: 5000,
|
timeout: 5000,
|
||||||
headers: {
|
headers: {
|
||||||
...commonHeaders,
|
...commonHeaders,
|
||||||
|
|||||||
@ -1 +1,2 @@
|
|||||||
export { machinesApi } from "./machines";
|
export { machinesApi } from "./machines";
|
||||||
|
export { productsApi } from "./products";
|
||||||
|
|||||||
@ -1,14 +1,17 @@
|
|||||||
import { axiosInstance, ApiBase } from "./common";
|
import { axiosInstance, ApiBase } from "./common";
|
||||||
|
import { MACHINES_API_URL } from "../const";
|
||||||
|
|
||||||
|
const baseUrl = `${MACHINES_API_URL}/machines`;
|
||||||
|
|
||||||
class MachinesApi extends ApiBase {
|
class MachinesApi extends ApiBase {
|
||||||
list = async () => {
|
list = async () => {
|
||||||
return axiosInstance.get(`/machines`, {});
|
return axiosInstance.get(`${baseUrl}`, {});
|
||||||
};
|
};
|
||||||
get = async (machineId) => {
|
get = async (machineId) => {
|
||||||
return axiosInstance.get(`/machines/${machineId}`, {});
|
return axiosInstance.get(`${baseUrl}/${machineId}`, {});
|
||||||
};
|
};
|
||||||
listProducts = async (machineId) => {
|
listProducts = async (machineId) => {
|
||||||
return axiosInstance.get(`/machines/${machineId}/products`, {});
|
return axiosInstance.get(`${baseUrl}/${machineId}/products`, {});
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
12
frontend/src/api/products.js
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import { axiosInstance, ApiBase } from "./common";
|
||||||
|
import { PRODUCTS_API_URL } from "../const";
|
||||||
|
|
||||||
|
const baseUrl = `${PRODUCTS_API_URL}/products`;
|
||||||
|
|
||||||
|
class ProductsApi extends ApiBase {
|
||||||
|
get = async (productId) => {
|
||||||
|
return axiosInstance.get(`${baseUrl}/${productId}`, {});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export const productsApi = new ProductsApi();
|
||||||
@ -6,20 +6,21 @@ import Typography from "@mui/material/Typography";
|
|||||||
import { CardActionArea } from "@mui/material";
|
import { CardActionArea } from "@mui/material";
|
||||||
import { PRODUCT_IMAGE_DIR } from "../const";
|
import { PRODUCT_IMAGE_DIR } from "../const";
|
||||||
|
|
||||||
function ProductCard({ product }) {
|
function ProductCard({ product, onClick }) {
|
||||||
const productImg = `${PRODUCT_IMAGE_DIR}/${product.image}`;
|
const productImg = `${PRODUCT_IMAGE_DIR}/${product.image}`;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card>
|
<Card sx={{ width: "100%" }}>
|
||||||
<CardActionArea>
|
<CardActionArea
|
||||||
<CardMedia component="img" height="140" image={productImg} alt={product.name} />
|
onClick={() => {
|
||||||
|
onClick(product.id);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<CardMedia component="img" height="200" image={productImg} alt={product.name} />
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<Typography gutterBottom variant="h5" component="div">
|
<Typography gutterBottom variant="h5" component="div" sx={{ marginBottom: 0 }}>
|
||||||
{product.name}
|
{product.name}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography variant="body2" color="text.secondary">
|
|
||||||
{product.description}
|
|
||||||
</Typography>
|
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</CardActionArea>
|
</CardActionArea>
|
||||||
</Card>
|
</Card>
|
||||||
|
|||||||
36
frontend/src/components/ProductModal.js
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
import * as React from "react";
|
||||||
|
import Button from "@mui/material/Button";
|
||||||
|
import Dialog from "@mui/material/Dialog";
|
||||||
|
import DialogActions from "@mui/material/DialogActions";
|
||||||
|
import DialogContent from "@mui/material/DialogContent";
|
||||||
|
import DialogContentText from "@mui/material/DialogContentText";
|
||||||
|
import DialogTitle from "@mui/material/DialogTitle";
|
||||||
|
import CardMedia from "@mui/material/CardMedia";
|
||||||
|
import { PRODUCT_IMAGE_DIR } from "../const";
|
||||||
|
|
||||||
|
function ProductModal({ product, onClose }) {
|
||||||
|
const productImg = `${PRODUCT_IMAGE_DIR}/${product.image}`;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog open={true} onClose={onClose}>
|
||||||
|
<DialogTitle>{product.name}</DialogTitle>
|
||||||
|
<DialogContent>
|
||||||
|
<CardMedia
|
||||||
|
component="img"
|
||||||
|
height="300"
|
||||||
|
image={productImg}
|
||||||
|
alt={product.name}
|
||||||
|
sx={{ marginBottom: "1rem" }}
|
||||||
|
/>
|
||||||
|
<DialogContentText>{product.description}</DialogContentText>
|
||||||
|
</DialogContent>
|
||||||
|
<DialogActions>
|
||||||
|
<Button onClick={onClose} variant="contained" autoFocus>
|
||||||
|
Close
|
||||||
|
</Button>
|
||||||
|
</DialogActions>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export { ProductModal };
|
||||||
@ -2,12 +2,32 @@ import * as React from "react";
|
|||||||
import Grid from "@mui/material/Unstable_Grid2";
|
import Grid from "@mui/material/Unstable_Grid2";
|
||||||
import Typography from "@mui/material/Typography";
|
import Typography from "@mui/material/Typography";
|
||||||
import { ProductCard } from "./ProductCard";
|
import { ProductCard } from "./ProductCard";
|
||||||
|
import { ProductModal } from "./ProductModal";
|
||||||
|
import { productsApi } from "../api";
|
||||||
|
|
||||||
function Products({ machineName, products, onSelect }) {
|
function Products({ machineName, products, onSelect }) {
|
||||||
|
const [productModal, setProductModal] = React.useState({ isOpen: false, product: null });
|
||||||
|
|
||||||
|
const onProductSelect = (productId) => {
|
||||||
|
productsApi.get(productId).then((response) => {
|
||||||
|
setProductModal({
|
||||||
|
isOpen: true,
|
||||||
|
product: response.data,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const onProductModalClose = () => {
|
||||||
|
setProductModal({
|
||||||
|
isOpen: false,
|
||||||
|
productId: null,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const productItems = products.map((product) => {
|
const productItems = products.map((product) => {
|
||||||
return (
|
return (
|
||||||
<Grid md={6} key={product.id}>
|
<Grid md={6} key={product.id} sx={{ display: "flex" }}>
|
||||||
<ProductCard product={product} />
|
<ProductCard product={product} onClick={onProductSelect} />
|
||||||
</Grid>
|
</Grid>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@ -21,6 +41,8 @@ function Products({ machineName, products, onSelect }) {
|
|||||||
<Grid container spacing={2}>
|
<Grid container spacing={2}>
|
||||||
{productItems}
|
{productItems}
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
|
{productModal.isOpen && <ProductModal product={productModal.product} onClose={onProductModalClose} />}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,2 +1,3 @@
|
|||||||
export const API_URL = process.env.REACT_APP_BACKEND_API_URL || "http://localhost:10000";
|
export const MACHINES_API_URL = process.env.REACT_APP_MACHINES_API_URL || "http://localhost:4000";
|
||||||
|
export const PRODUCTS_API_URL = process.env.REACT_APP_PRODUCTS_API_URL || "http://localhost:4001";
|
||||||
export const PRODUCT_IMAGE_DIR = "/static/products/";
|
export const PRODUCT_IMAGE_DIR = "/static/products/";
|
||||||
|
|||||||
@ -18,9 +18,7 @@ function Home() {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const onMachineSelect = (machineName, machineId) => {
|
const onMachineSelect = (machineName, machineId) => {
|
||||||
console.log("selected:", machineName);
|
|
||||||
machinesApi.listProducts(machineId).then((response) => {
|
machinesApi.listProducts(machineId).then((response) => {
|
||||||
console.log(response.data.products);
|
|
||||||
setProductsData({
|
setProductsData({
|
||||||
machineName,
|
machineName,
|
||||||
products: response.data.products,
|
products: response.data.products,
|
||||||
|
|||||||
@ -26,8 +26,7 @@ docker-build: clean
|
|||||||
docker-run:
|
docker-run:
|
||||||
@docker run \
|
@docker run \
|
||||||
--name $(CONTAINER_NAME) \
|
--name $(CONTAINER_NAME) \
|
||||||
--publish 3000:3000 \
|
--publish 4000:4000 \
|
||||||
--env CONTAINER_NAME="Awesome API server" \
|
|
||||||
--env DBPORT=55432 \
|
--env DBPORT=55432 \
|
||||||
--detach \
|
--detach \
|
||||||
$(IMAGE_NAME)
|
$(IMAGE_NAME)
|
||||||
|
|||||||
@ -6,13 +6,13 @@ import (
|
|||||||
|
|
||||||
type configStruct struct {
|
type configStruct struct {
|
||||||
AppHost string `default:"0.0.0.0"`
|
AppHost string `default:"0.0.0.0"`
|
||||||
AppPort int `default:"3000"`
|
AppPort int `default:"4000"`
|
||||||
DbHost string `default:"localhost"`
|
DbHost string `default:"localhost"`
|
||||||
DbPort int `default:"55432"`
|
DbPort int `default:"55432"`
|
||||||
DbName string `default:"komponiranje"`
|
DbName string `default:"komponiranje"`
|
||||||
DbUser string `default:"pero"`
|
DbUser string `default:"pero"`
|
||||||
DbPassword string `default:"pero.000"`
|
DbPassword string `default:"pero.000"`
|
||||||
ProductsAppUrl string `default:"http://localhost:3001"`
|
ProductsAppUrl string `default:"http://localhost:4001"`
|
||||||
}
|
}
|
||||||
|
|
||||||
const ENV_PREFIX = ""
|
const ENV_PREFIX = ""
|
||||||
|
|||||||
BIN
media/containers-architecture.png
Normal file
|
After Width: | Height: | Size: 1.3 MiB |
BIN
media/dcct.png
Normal file
|
After Width: | Height: | Size: 688 KiB |
BIN
media/life-without-docker-compose.png
Normal file
|
After Width: | Height: | Size: 635 KiB |
BIN
media/networking-host-network.png
Normal file
|
After Width: | Height: | Size: 509 KiB |
BIN
media/networking.png
Normal file
|
After Width: | Height: | Size: 726 KiB |
@ -26,8 +26,7 @@ docker-build: clean
|
|||||||
docker-run:
|
docker-run:
|
||||||
@docker run \
|
@docker run \
|
||||||
--name $(CONTAINER_NAME) \
|
--name $(CONTAINER_NAME) \
|
||||||
--publish 3000:3000 \
|
--publish 4001:4001 \
|
||||||
--env CONTAINER_NAME="Awesome API server" \
|
|
||||||
--env DBPORT=55432 \
|
--env DBPORT=55432 \
|
||||||
--detach \
|
--detach \
|
||||||
$(IMAGE_NAME)
|
$(IMAGE_NAME)
|
||||||
|
|||||||
@ -39,6 +39,10 @@ func handleGetProducts(dbConn *gorm.DB) gin.HandlerFunc {
|
|||||||
|
|
||||||
products := db.GetProducts(dbConn, machineId)
|
products := db.GetProducts(dbConn, machineId)
|
||||||
|
|
||||||
|
// for i := 0; i < len(*products); i++ {
|
||||||
|
// (*products)[i].Name = fmt.Sprintf("[local] %s", (*products)[i].Name)
|
||||||
|
// }
|
||||||
|
|
||||||
c.JSON(
|
c.JSON(
|
||||||
http.StatusOK,
|
http.StatusOK,
|
||||||
GetProductsResponse{
|
GetProductsResponse{
|
||||||
|
|||||||
@ -6,7 +6,7 @@ import (
|
|||||||
|
|
||||||
type configStruct struct {
|
type configStruct struct {
|
||||||
AppHost string `default:"0.0.0.0"`
|
AppHost string `default:"0.0.0.0"`
|
||||||
AppPort int `default:"3001"`
|
AppPort int `default:"4001"`
|
||||||
DbHost string `default:"localhost"`
|
DbHost string `default:"localhost"`
|
||||||
DbPort int `default:"55432"`
|
DbPort int `default:"55432"`
|
||||||
DbName string `default:"komponiranje"`
|
DbName string `default:"komponiranje"`
|
||||||
|
|||||||
59
proxy/envoy-local.yaml
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
static_resources:
|
||||||
|
listeners:
|
||||||
|
- address:
|
||||||
|
socket_address:
|
||||||
|
address: 127.0.0.1
|
||||||
|
port_value: 10000
|
||||||
|
filter_chains:
|
||||||
|
- filters:
|
||||||
|
- name: envoy.filters.network.http_connection_manager
|
||||||
|
typed_config:
|
||||||
|
"@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
|
||||||
|
codec_type: auto
|
||||||
|
stat_prefix: ingress_http
|
||||||
|
route_config:
|
||||||
|
name: local_route
|
||||||
|
virtual_hosts:
|
||||||
|
- name: backend
|
||||||
|
domains:
|
||||||
|
- "*"
|
||||||
|
routes:
|
||||||
|
- match:
|
||||||
|
prefix: "/machines"
|
||||||
|
route:
|
||||||
|
cluster: machines-app
|
||||||
|
- match:
|
||||||
|
prefix: "/products"
|
||||||
|
route:
|
||||||
|
cluster: products-app
|
||||||
|
http_filters:
|
||||||
|
- name: envoy.filters.http.router
|
||||||
|
typed_config:
|
||||||
|
"@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
|
||||||
|
clusters:
|
||||||
|
- name: machines-app
|
||||||
|
connect_timeout: 0.25s
|
||||||
|
type: strict_dns
|
||||||
|
lb_policy: round_robin
|
||||||
|
load_assignment:
|
||||||
|
cluster_name: machines-app
|
||||||
|
endpoints:
|
||||||
|
- lb_endpoints:
|
||||||
|
- endpoint:
|
||||||
|
address:
|
||||||
|
socket_address:
|
||||||
|
address: 127.0.0.1
|
||||||
|
port_value: 4000
|
||||||
|
- name: products-app
|
||||||
|
connect_timeout: 0.25s
|
||||||
|
type: strict_dns
|
||||||
|
lb_policy: round_robin
|
||||||
|
load_assignment:
|
||||||
|
cluster_name: products-app
|
||||||
|
endpoints:
|
||||||
|
- lb_endpoints:
|
||||||
|
- endpoint:
|
||||||
|
address:
|
||||||
|
socket_address:
|
||||||
|
address: 127.0.0.1
|
||||||
|
port_value: 4001
|
||||||
@ -43,7 +43,7 @@ static_resources:
|
|||||||
address:
|
address:
|
||||||
socket_address:
|
socket_address:
|
||||||
address: machines-app
|
address: machines-app
|
||||||
port_value: 3000
|
port_value: 4000
|
||||||
- name: products-app
|
- name: products-app
|
||||||
connect_timeout: 0.25s
|
connect_timeout: 0.25s
|
||||||
type: strict_dns
|
type: strict_dns
|
||||||
@ -56,10 +56,4 @@ static_resources:
|
|||||||
address:
|
address:
|
||||||
socket_address:
|
socket_address:
|
||||||
address: products-app
|
address: products-app
|
||||||
port_value: 3000
|
port_value: 4001
|
||||||
admin:
|
|
||||||
access_log_path: "/dev/null"
|
|
||||||
address:
|
|
||||||
socket_address:
|
|
||||||
address: 0.0.0.0
|
|
||||||
port_value: 800
|
|
||||||
|
|||||||