Compare commits

..

No commits in common. "c76f47aa3e4bc3e95d4c9fb52ec0d19a8cc03591" and "283088648a08c477636f17d3286e76ce1cb27c40" have entirely different histories.

9 changed files with 58 additions and 543 deletions

View file

@ -72,26 +72,20 @@ jobs:
run: |
apt-get update
apt-get install jq
- name: Download env
- name: Download settings
run: |
pwd
curl -H 'accept: application/json' -H 'authorization: Basic cGFraW46YWRtaW4xMjM=' "$(curl -X 'GET' \
${{ secrets.DEPLOY_KEYS_DL }} \
'https://pakin-inspiron-15-3530.tail360bd.ts.net/api/v1/repos/pakin/taobin_recipe_manager/releases/19/assets/73' \
-H 'accept: application/json' \
-H 'authorization: Basic cGFraW46YWRtaW4xMjM=' | jq -r '.browser_download_url' | sed -e 's/http/https/g' | sed -e 's/100.64.210.13\:3001/pakin-inspiron-15-3530.tail360bd.ts.net/g')" | jq -r '.body.env' | base64 -d > ./server/app.env
cat ./server/app.env
- name: Download secret
-H 'authorization: Basic cGFraW46YWRtaW4xMjM=' | jq -r '.browser_download_url')" | jq -r '.body.env' | base64 -d > ./server/app.env
curl -H 'accept: application/json' -H 'authorization: Basic cGFraW46YWRtaW4xMjM=' "$(curl -X 'GET' \
'https://pakin-inspiron-15-3530.tail360bd.ts.net/api/v1/repos/pakin/taobin_recipe_manager/releases/19/assets/73' \
-H 'accept: application/json' \
-H 'authorization: Basic cGFraW46YWRtaW4xMjM=' | jq -r '.browser_download_url')" | jq -r '.body.secret' | base64 -d > ./server/client_secret.json
- name: Run Golang tests
run: |
pwd
curl -H 'accept: application/json' -H 'authorization: Basic cGFraW46YWRtaW4xMjM=' "$(curl -X 'GET' \
${{ secrets.DEPLOY_KEYS_DL }} \
-H 'accept: application/json' \
-H 'authorization: Basic cGFraW46YWRtaW4xMjM=' | jq -r '.browser_download_url' | sed -e 's/http/https/g' | sed -e 's/100.64.210.13\:3001/pakin-inspiron-15-3530.tail360bd.ts.net/g')" | jq -r '.body.secret' | base64 -d > ./server/client_secret.json
cat ./server/client_secret.json
# - name: Run Golang tests
# run: |
# cd server
# go test -v ./...
cd server
go test -v ./...
- name: Build and push
run: |
pwd

View file

@ -15,10 +15,10 @@
>
[{{ getCommitAttr(commit, "Created_at") }}] [{{
getCommitAttr(commit, "Editor")
}}] | {{ getCommitAttr(commit, "Msg") }} | {{ getCommitAttr(commit, "Id") }}
}}] | {{ getCommitAttr(commit, "Msg") }}
</option>
</select>
<br />
<!-- third source selector / from machine -->
<label for="anotherKeys">Select another source : </label>
@ -29,13 +29,13 @@
>
<option>--- Commit ---</option>
<option
*ngFor="let commit of getPatchMapKeysIfProductCode()"
*ngFor="let commit of getPatchMapKeys()"
[value]="commit"
id="source3_{{ commit }}"
>
[{{ getCommitAttr(commit, "Created_at") }}] [{{
getCommitAttr(commit, "Editor")
}}] | {{ getCommitAttr(commit, "Msg") }} | {{ getCommitAttr(commit, "Id") }}
}}] | {{ getCommitAttr(commit, "Msg") }}
</option>
<option>--- Machine ---</option>
<option>--- Recipe ---</option>
@ -109,8 +109,6 @@
<details class="collapse collapse-arrow">
<summary class="collapse-title bg-red-200">
{{ selectedCommit }}
{{ anotherSelectedSource}}
{{ anotherSelectedSource.startsWith('coffeethai02_') }}
</summary>
<div class="collapse-content grid grid-cols-3">
<div class="flex-shrink-0 overflow-x-scroll shadow-lg" id="commit-source" (scroll)="addSyncingScrollDiffPane('commit-source')">
@ -125,7 +123,7 @@
(recipeListFormChange)="onRecipeListFormChange($event)"
></app-recipe-list>
</div>
<div *ngIf="reloadMainSource" class="flex-shrink-0 overflow-x-scroll shadow-lg" id="main-source" (scroll)="addSyncingScrollDiffPane('main-source')">
<div class="flex-shrink-0 overflow-x-scroll shadow-lg" id="main-source" (scroll)="addSyncingScrollDiffPane('main-source')">
<app-recipe-list
[productCode]="
getCommitAttr(selectedCommit, 'contents').productCode!
@ -140,50 +138,33 @@
"
></app-recipe-list>
</div>
<div
class="flex-shrink-0 overflow-x-scroll shadow-lg"
id="another-source"
<div
class="flex-shrink-0 overflow-x-scroll shadow-lg"
id="another-source"
(scroll)="addSyncingScrollDiffPane('another-source')"
>
<ng-container *ngIf="hasProductCodeOfCommits() && anotherSelectedSource != '' && anotherSelectedSource.startsWith('coffeethai02_'); else recipeTemplate"
>
<app-recipe-list
[productCode]="
getCommitAttr(selectedCommit, 'contents').productCode!
"
[noFetch]="true"
[recipeList]="
anotherTargetRecipe[
getCommitAttr(selectedCommit, 'contents').productCode
].recipes
"
[displayOnly]="true"
[diffChangeContext]="
buildContext(
'right',
getCommitAttr(selectedCommit, 'contents').productCode
)
"
(recipeListFormChange)="onSourceListFormChange($event)"
>
</app-recipe-list>
</ng-container>
</div>
<ng-template #recipeTemplate>
<app-recipe-list
[productCode]="getCommitAttr(selectedCommit, 'contents').productCode!"
*ngIf="hasProductCodeOfCommits() && anotherSelectedSource != ''"
>
<app-recipe-list
[productCode]="
getCommitAttr(selectedCommit, 'contents').productCode!
"
[noFetch]="true"
[recipeList]="getCommitAttr(anotherSelectedSource, 'contents').recipes"
[recipeList]="
anotherTargetRecipe[
getCommitAttr(selectedCommit, 'contents').productCode
].recipes
"
[displayOnly]="true"
[diffChangeContext]="
buildContext(
'right',
getCommitAttr(selectedCommit, 'contents').productCode
)"
buildContext(
'right',
getCommitAttr(selectedCommit, 'contents').productCode
)
"
(recipeListFormChange)="onSourceListFormChange($event)"
>
</app-recipe-list>
</ng-template>
</div>
</div>
</details>
</div>

View file

@ -83,8 +83,6 @@ export class MergeComponent
// from another source
anotherTargetRecipe: any = {};
reloadMainSource = true;
// -------------------- current selection
// currentTargetOfMaster: any = undefined;
@ -171,14 +169,6 @@ export class MergeComponent
});
}
// reload data of main-source
triggerReload() {
this.reloadMainSource = false;
setTimeout(() => {
this.reloadMainSource = true;
}, 0);
}
async ngOnInit(): Promise<void> {
// fetch related product codes
}
@ -210,15 +200,6 @@ export class MergeComponent
// get patch map keys
getPatchMapKeys = () => Object.keys(this.patchMap);
// get patch map keys, if have the same productCode
getPatchMapKeysIfProductCode() {
const keys = Object.keys(this.patchMap).filter(selectedCommit => {
return this.patchMap[selectedCommit].productCode === this.selectedProductCode;
});
// console.log("Filtered PacthMap : ", keys);
return keys;
}
// check if load patch keys
isPatchMapKeysLoaded = () => this.getPatchMapKeys().length > 0;
testLoadCheck = () =>
@ -279,26 +260,19 @@ export class MergeComponent
selectCommit = (commit: any) => {
// console.log('select commit', commit.target.value);
this.selectedCommit = commit.target.value;
this.selectedProductCode = this.getCommitAttr(this.selectedCommit,"contents").productCode
// reload data of main-source
this.triggerReload();
console.log("selectedCommit target v : ",this.selectedCommit)
console.log("selectedProductCode : ",this.selectedProductCode)
};
selectAnotherSource = (source: any) => {
this.anotherSelectedSource = source.target.value;
console.log("another select target v : ",this.anotherSelectedSource)
};
changeAnotherSource = async (source: any) => {
//base on change
this.anotherSelectedSource = 'coffeethai02_' + source.target.value + '.json';
// const vset = '691'
// this.anotherSelectedSource = `coffeethai02_${source}.json`;
console.log('another source: target version -> ',this.anotherSelectedSource);
console.log("anotherTargetRecipe : ",this.anotherTargetRecipe)
this.anotherSelectedSource =
'coffeethai02_' + source.target.value + '.json';
console.log(
'another source: target version -> ',
this.anotherSelectedSource
);
// activate fetch
for (let pd of this.getProductCodesOfCommits()) {
await this.getAnotherRecipeOfProductCode(pd);
@ -347,8 +321,7 @@ export class MergeComponent
return {
changeContext: undefined,
skipZeroes: true,
// toppingData: this.anotherTargetRecipe[pd!].ToppingSet,
toppingData: this.getCommitAttr(this.selectedCommit, 'contents').ToppingSet,
toppingData: this.anotherTargetRecipe[pd!].ToppingSet,
};
}
}
@ -411,17 +384,15 @@ export class MergeComponent
// test compare
this.getPatchMapKeys().forEach((patchId) => {
// compare with master
if (this.anotherTargetRecipe[productCode!]['productCode'] == this.fullPatches[patchId].contents['productCode']) {
let cmp = compare(
this.anotherTargetRecipe[productCode!],
this.fullPatches[patchId].contents,
['LastChange']
);
// save only what changes
this.changeMap[patchId + '_' + this.anotherSelectedSource] = {
changes: cmp,
};
}
let cmp = compare(
this.anotherTargetRecipe[productCode!],
this.fullPatches[patchId].contents,
['LastChange']
);
// save only what changes
this.changeMap[patchId + '_' + this.anotherSelectedSource] = {
changes: cmp,
};
});
console.log('change map', this.changeMap);
@ -529,7 +500,6 @@ export class MergeComponent
// selection empty?
isSelectionEmpty(side: string): boolean {
console.log("isSelectionEmpty : ",side,this.selectionMap[this.selectedCommit + side])
return this.selectionMap[this.selectedCommit + side] == undefined || this.selectionMap[this.selectedCommit + side].length == 0;
}

View file

@ -61,7 +61,7 @@
>
<p>Volume</p>
<input
type="number"
type="text"
class="bg-transparent w-8"
formControlName="powderGram"
/>
@ -77,7 +77,7 @@
>
<p>Volume</p>
<input
type="number"
type="text"
class="bg-transparent w-8"
formControlName="syrupGram"
/>
@ -122,7 +122,7 @@
<p>Hot</p>
<input
type="number"
type="text"
class="w-8 bg-transparent"
formControlName="waterYield"
/>
@ -139,7 +139,7 @@
>
<p>Cold</p>
<input
type="number"
type="text"
class="w-8 bg-transparent"
formControlName="waterCold"
/>
@ -161,7 +161,7 @@
>
<p>{{ getTooltipForStirTime(getTypeForRecipeListAtIndex(i)) }}</p>
<input
type="number"
type="text"
class="bg-transparent w-8"
formControlName="stirTime"
/>
@ -301,7 +301,7 @@
>
<input type="checkbox" formControlName="isUse" />
<input
type="number"
type="text"
class="input input-primary"
formControlName="materialPathId"
(click)="openMaterialList(isdx)"

View file

@ -1,58 +0,0 @@
services:
nginx:
image: "jc21/nginx-proxy-manager:latest"
restart: unless-stopped
ports:
- "80:80"
- "81:81"
- "443:443"
volumes:
- ./data:/data
- ./letsencrypt:/etc/letsencrypt
- ./www/taobin_recipe_manager:/var/www/html
- ./_hsts_map.conf:/app/templates/_hsts_map.conf
network_mode: "host"
app:
build:
context: ./server
dockerfile: Dockerfile
restart: unless-stopped
env_file:
- ./server/app.env
- ./server/services/logger/serverlog.log
ports:
- "5555:8080"
environment:
- password_env=forthonu
volumes:
- ./server/cofffeemachineConfig:/app/cofffeemachineConfig
- ./server/data/database.db:/app/data/database.db
- ./server/country.settings.json:/app/country.settings.json
depends_on:
- redis
network_mode: "host"
app_test:
image: pakin-inspiron-15-3530.tail360bd.ts.net/pakin/taobin_recipe_manager:latest
restart: unless-stopped
env_file:
- ./server/app.env
- ./server/services/logger/serverlog.log
ports:
- "5555:8080"
environment:
- password_env=forthonu
volumes:
- ./server/cofffeemachineConfig:/app/cofffeemachineConfig
- ./server/data/database.db:/app/data/database.db
- ./server/country.settings.json:/app/country.settings.json
depends_on:
- redis
network_mode: "host"
redis:
image: redis:latest
restart: always
ports:
- "6379:6379"
network_mode: "host"
volumes:
asset_data:

View file

@ -29,7 +29,7 @@ import (
"go.uber.org/zap"
)
const VERSION = "1.0.35"
const VERSION = "1.0.31"
type Server struct {
server *http.Server

View file

@ -1,2 +0,0 @@
#!/bin/sh
pip install -r requirement.txt

View file

@ -1,14 +0,0 @@
aiofiles==23.2.1
apscheduler==3.10.4
docker==7.0.0
fastapi==0.111.0
flask==3.0.0
gitpython==3.1.43
redis==5.0.7
schedule==1.2.2
truststore==0.9.1
uri-template==1.3.0
uvloop==0.19.0
watchfiles==0.21.0
webcolors==1.13
websockets==12.0

View file

@ -1,356 +0,0 @@
# IGNORE
# RUN_keep
# BUILD_AND_RUN
# VERSION_1
# PORTS_36528
from contextlib import asynccontextmanager
import datetime
import sys
from fastapi import FastAPI
import schedule
import uvicorn.logging
import logging
import docker
import requests
import base64
import os
import time
import zipfile
import redis
from apscheduler.schedulers.background import BackgroundScheduler
import json
# url
source = "https://pakin-inspiron-15-3530.tail360bd.ts.net"
def get_headers() -> dict:
return {"accept":"application/json", "authorization": "Basic cGFraW46YWRtaW4xMjM="}
def get_headers_for_blob() -> dict:
return {"authorization": "Basic cGFraW46YWRtaW4xMjM=", "Content-Type": "application/octet-stream"}
def get_all_releases_url() -> str:
return f"{source}/api/v1/repos/pakin/taobin_recipe_manager/releases"
def get_download_url(tag_id: int, asset_id: int) -> str:
return f"{source}/api/v1/repos/pakin/taobin_recipe_manager/releases/{tag_id}/assets/{asset_id}"
# Flags
update_available_now = False
# Logs
def save_to_log_file(msg: str):
with open("./patches/.updater.log", "a") as f:
f.write(f"{datetime.datetime.now(datetime.timezone(datetime.timedelta(hours=7)))} : {msg}\n")
# Redis
redis_client = redis.StrictRedis(host="localhost", port=6379, db=0)
MAIN_CHANNEL = "updater.noti"
pubsub = redis_client.pubsub()
pubsub.subscribe(MAIN_CHANNEL)
# Settings
settings = {}
# ------------------------------------------------------------------------------------------------
def test_path_from_settings():
# from settings
path_list = settings["path"].keys()
for path in path_list:
logger.debug(f"Checking {path}")
if not os.path.exists(settings["path"][path]["path"]):
logger.warning(f"Deps.fallback: {settings['path'][path]["path"]}")
# retry with fallback
if "fallback_paths" in settings['path'][path].keys():
for fallback_path in settings['path'][path]["fallback_paths"]:
# check kind of fallback
path_kind = fallback_path.split(":")[0]
path_real = fallback_path.split(":")[1]
if os.path.exists(path_real):
logger.info(f"Deps.fallback[{path_kind}]: {path_real} = ok")
break
else:
logger.warning(f"Deps.fallback: skipped {path}")
else:
# path ok(
logger.info(f"Deps: {path} = ok")
def find_latest_version(rels: list[dict]) -> str:
major = 0
minor = 0
patch = 0
for rel in rels:
if rel["tag_name"].startswith("v"):
version = rel["tag_name"][1:].split(".")
if len(version) == 3:
if int(version[0]) > major:
major = int(version[0])
minor = int(version[1])
patch = int(version[2])
elif int(version[0]) == major:
if int(version[1]) > minor:
minor = int(version[1])
patch = int(version[2])
elif int(version[1]) == minor:
if int(version[2]) > patch:
patch = int(version[2])
return f"{major}.{minor}.{patch}"
def get_version(rels: list[dict], tag: str) -> dict:
# assert that tag is valid
is_in_correct_format = tag.startswith("v") and len(tag[1:].split(".")) == 3
if is_in_correct_format:
for rel in rels:
if rel["tag_name"] == tag:
return rel
#
def check_version_job() -> dict:
logger.info("Checking version")
result = requests.get(get_all_releases_url(), timeout=10, headers=get_headers())
if result.status_code == 200:
# connect to server ok!
releases = result.json()
if len(releases) > 0:
# find the latest {major}.{minor}.{patch}
expected_version = find_latest_version(releases)
# get data from list
# version data
latest_version_data = get_version(releases, f"v{expected_version}")
latest_id = latest_version_data["id"]
latest_asset_id = latest_version_data["assets"][0]["id"]
# get download url
download_url = get_download_url(latest_id, latest_asset_id)
# download
logger.info(f"Found version {expected_version} from source")
dl_result = requests.get(download_url, timeout=10, headers=get_headers())
if dl_result.status_code == 200:
# download ok!
# logger.debug(f"data: {dl_result.json()}")
dl_content = dl_result.json()
dl_link = dl_content["browser_download_url"]
# download link is not usable without tailscale vpn
# cut uri, start at /attachments
dl_link = f"{source}{dl_link[dl_link.find("/attachments"):]}"
# logger.info(f"Download link: {dl_link}")
is_already_latest = False
old_version = ""
# check dl and latest
if os.path.exists("./patches/latest.version"):
with open("./patches/latest.version", "r") as f:
latest_version = f.read()
if latest_version == None or latest_version == "":
logger.warning("No version found on running machine. Starting overwrite ...")
save_to_log_file("Warning: version.overwrite caused by missing version on latest.version.")
old_version = "<overwrite>"
else:
old_version = latest_version
if latest_version == expected_version:
logger.info(f"Already at latest version {expected_version}");
is_already_latest = True
else:
logger.info(f"Updating to {expected_version}")
# write back to latest
with open("./patches/latest.version", "w") as f:
f.write(expected_version)
else:
logger.info(f"First time running {expected_version}")
with open("./patches/latest.version", "w") as f:
f.write(expected_version)
# extract
if not is_already_latest:
# download
downloaded = requests.get(dl_link, timeout=10, headers=get_headers_for_blob())
if downloaded.status_code == 200:
# download ok!
# create patch folder
if os.path.exists("./patches") == False:
os.mkdir("./patches")
with open(f"./patches/patch_cli_{expected_version}.zip", "wb") as f:
f.write(downloaded.content)
# write down version
with open("./patches/downloaded.version", "w") as f:
f.write(expected_version)
logger.debug("Downloaded")
else:
logger.debug(f"Failed to download {expected_version}")
logger.info(f"Extracting {expected_version}")
zip_file_path = f"./patches/patch_cli_{expected_version}.zip"
# extract zip
# mkdir client
if os.path.exists("./patches/client") == False:
os.mkdir("./patches/client")
with zipfile.ZipFile(zip_file_path, 'r') as zip_ref:
zip_ref.extractall('./patches/client')
# ------------------------------------------------------------------------------------
# run
# logger.info(f"Running {expected_version}")
# this will check image tag and pull if diff
logger.info("Pulling from registry")
registry_source = f"{source[(source.find('//') + 2):]}"
try:
client = docker.from_env()
log_response = client.api.login(username=settings["secret"]["username"], password=settings["secret"]["password"], registry=source)
logger.debug(f"log_response: {log_response}")
# client.images.pull(f"{registry_source}/pakin/taobin_recipe_manager", tag=expected_version)
pull_prog = client.api.pull(f"{registry_source}/pakin/taobin_recipe_manager", tag=f"v{expected_version}")
logger.debug(f"Pulling: {pull_prog}")
except Exception as e:
logger.error(f"Failed to pull {expected_version}")
with open("./patches/error.txt", "w") as f:
f.write(f"{datetime.datetime.now(datetime.timezone(datetime.timedelta(hours=7)))} : fail to update server\n")
# return {"status": "failed to pull"}
save_to_log_file("fail to update server")
global update_available_now
update_available_now = True
# clean up
logger.info(f"Cleaning up {expected_version}")
os.remove(f"./patches/patch_cli_{expected_version}.zip")
os.remove("./patches/downloaded.version")
save_to_log_file(f"update success from {old_version} -> {expected_version}")
else:
logger.error(f"Failed to download {expected_version}")
return {"status": "failed at download"}
if update_available_now:
# publish
redis_client.publish(MAIN_CHANNEL, "new_version")
return {"status": "ok"}
else:
logger.error("No release found")
return {"status": "no release found"}
# assert that tag is valid
else:
logger.error("Failed to connect to server")
return {"status": "failed to connect to server"}
def running_jobs():
global update_available_now
update_available_now = False
print("---------------------------------------------------------------------")
status = check_version_job()
logger.info(f"Status ({status['status']})")
print("---------------------------------------------------------------------")
if update_available_now:
redis_client.publish(MAIN_CHANNEL, "new_version")
def notification_job():
msgs = pubsub.get_message(timeout=5)
if msgs != None and msgs['type'] == 'message':
msg = msgs["data"].decode("utf-8")
logger.info(f"{MAIN_CHANNEL}> {msg}")
else:
logger.info(f"{MAIN_CHANNEL}> idle")
@asynccontextmanager
async def lifespan(app: FastAPI):
# load settings
config = open("./updater.settings.json", "r")
loaded_settings = json.load(config)
settings['path'] = loaded_settings['path']
try:
secret = open("./updater.secrets", "r")
secret_string = secret.read()
spl = secret_string.split(":")
settings['secret'] = {
"username": spl[0],
"password": spl[1]
}
except:
logger.warning("No secrets found")
exit(1)
test_path_from_settings()
update_available_now = False
logger.info("Starting up. Check update first ...")
redis_client.publish(MAIN_CHANNEL, "updater.first_start")
status = check_version_job()
logger.info(f"Status ({status['status']})")
#
scheduler.add_job(notification_job, 'interval', seconds=10, id="notification")
scheduler.add_job(running_jobs, 'interval', minutes=10, id="check_version")
scheduler.start()
yield
# schedule.every().hours.do(check_version_job)
print("Shutting down")
scheduler = BackgroundScheduler(jobs_default={'max_instances':2})
app = FastAPI(title="Updater",lifespan=lifespan)
logger= logging.getLogger(__name__)
logger.setLevel(logging.DEBUG)
stream_handler = logging.StreamHandler(sys.stdout)
log_formatter = logging.Formatter("%(asctime)s [%(levelname)s] : %(message)s")
stream_handler.setFormatter(log_formatter)
logger.addHandler(stream_handler)
@app.get("/")
async def main():
if update_available_now:
return "Update available"
return "Version is up to date."
@app.get("/job_status")
async def job_status():
return f"{scheduler.get_job("check_version")}"
if __name__ == "__main__":
uvicorn.run(app, host="0.0.0.0", port=36528)