Compare commits

...

31 Commits

Author SHA1 Message Date
6de60c82ba 条件エディタ実装 2024-01-31 05:22:09 +09:00
5cd6d02f6e 条件エディタ追加 2024-01-22 10:52:55 +09:00
276e5e9122 condition test 2023-12-25 17:11:11 +09:00
6e75a2a524 condtion tree 2023-12-25 17:07:40 +09:00
ea6e603036 update APIException 2023-11-22 14:35:37 +09:00
edad30e264 exception の変更 2023-11-21 21:50:50 +09:00
58616100f4 Merge branch 'mvp_step2_dev' of https://dev.azure.com/alicorn-dev/KintoneAppBuilder/_git/KintoneAppBuilder into mvp_step2_dev 2023-11-21 21:22:47 +09:00
359558bad3 add dev build mode 2023-11-21 21:22:30 +09:00
6a6554ed1f add APIException 2023-11-21 21:20:02 +09:00
e20625abdb domain切替バグ修正 2023-11-20 01:50:02 +09:00
f83dd693d5 log bugfix 2023-11-19 13:37:55 +09:00
a464297511 add api.log&errorlog->db 2023-11-19 13:24:29 +09:00
991c8e8083 add api.log&errorlog->db 2023-11-19 13:24:08 +09:00
9ea183ff2d 処理中表示追加 2023-11-15 03:13:24 +09:00
34d368b730 ダイアログ表示時snipper追加 2023-11-15 00:18:06 +09:00
17760a6926 FlowChart のレイアウト修正 2023-11-14 09:11:46 +09:00
8b9cfa34c7 Merge branch 'mvp_step2_dev' of https://dev.azure.com/alicorn-dev/KintoneAppBuilder/_git/KintoneAppBuilder into mvp_step2_dev 2023-11-13 22:04:27 +09:00
5fb8fe53bb backend bug fix 2023-11-13 22:03:45 +09:00
5cb9375db3 add domainid->flow 2023-11-13 18:02:19 +09:00
55181f2c57 ユーザー追加API Bug fix 2023-11-10 02:34:50 +09:00
4577df371a Token無効の際再ログイン対応 2023-11-09 13:47:21 +09:00
0f154832a5 前端APIのURL参数化対応およびバックエンドのBugFix 2023-11-08 15:44:42 +09:00
5951fcc108 depoly bug fix 2023-11-04 22:54:21 +09:00
7966217ac2 add js dev model 2023-11-04 22:40:40 +09:00
64851bd51d added deploy DEV mode 2023-11-04 22:35:23 +09:00
10e584d2ac bugfix 2023-11-04 22:22:26 +09:00
b97a728624 kintone ENV bugfix 2023-11-04 22:16:23 +09:00
5a875e6853 domain bug fix 2023-11-04 22:11:32 +09:00
a782e92bd6 自動採番障害対応 2023-11-01 22:59:23 +09:00
cfc416fd14 自動採番アクション追加・ドメイン追加 2023-11-01 10:47:24 +09:00
df593d2ffe modified ER 2023-10-31 03:42:25 +09:00
72 changed files with 4147 additions and 1699 deletions

View File

@@ -1,5 +1,5 @@
FROM python:3.8 FROM python:3.11.4
RUN mkdir /app RUN mkdir /app
WORKDIR /app WORKDIR /app

File diff suppressed because one or more lines are too long

View File

@@ -7,9 +7,11 @@ import httpx
import deepdiff import deepdiff
import app.core.config as config import app.core.config as config
import os import os
from pathlib import Path
from app.db.session import SessionLocal from app.db.session import SessionLocal
from app.db.crud import get_flows,get_activedomain from app.db.crud import get_flows_by_app,get_activedomain
from app.core.auth import get_current_active_user,get_current_user from app.core.auth import get_current_active_user,get_current_user
from app.core.apiexception import APIException
kinton_router = r = APIRouter() kinton_router = r = APIRouter()
@@ -134,7 +136,7 @@ def analysefields(excel,kintone):
for key in adds: for key in adds:
addfields[key] = excel[key] addfields[key] = excel[key]
for key in dels: for key in dels:
if kintone[key]["type"] in c.KINTONE_FIELD_TYPE: if kintone[key]["type"] in config.KINTONE_FIELD_TYPE:
delfields.append(key) delfields.append(key)
return {"update":updatefields,"add":addfields,"del":delfields} return {"update":updatefields,"add":addfields,"del":delfields}
@@ -206,7 +208,7 @@ def analysprocess(excel,kintone):
# return True # return True
return diff return diff
def updateprocesstokintone(app:str,process:dict,c:config.KINTONE_ENV=Depends(getkintoneenv)): def updateprocesstokintone(app:str,process:dict,c:config.KINTONE_ENV):
headers={config.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE,"Content-Type": "application/json"} headers={config.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE,"Content-Type": "application/json"}
url = f"{c.BASE_URL}{config.API_V1_STR}/preview/app/status.json" url = f"{c.BASE_URL}{config.API_V1_STR}/preview/app/status.json"
data = {"app":app,"enable":True} data = {"app":app,"enable":True}
@@ -214,20 +216,22 @@ def updateprocesstokintone(app:str,process:dict,c:config.KINTONE_ENV=Depends(get
r = httpx.put(url,headers=headers,data=json.dumps(data)) r = httpx.put(url,headers=headers,data=json.dumps(data))
return r.json() return r.json()
def getkintoneusers(c:config.KINTONE_ENV=Depends(getkintoneenv)): def getkintoneusers(c:config.KINTONE_ENV):
headers={config.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE} headers={config.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE}
url = f"{c.BASE_URL}/v1/users.json" url = f"{c.BASE_URL}/v1/users.json"
r = httpx.get(url,headers=headers) r = httpx.get(url,headers=headers)
return r.json() return r.json()
def getkintoneorgs(c:config.KINTONE_ENV=Depends(getkintoneenv)): def getkintoneorgs(c:config.KINTONE_ENV):
headers={config.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE} headers={config.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE}
params = {"code":c.KINTONE_USER} params = {"code":c.KINTONE_USER}
url = f"{c.BASE_URL}/v1/user/organizations.json" url = f"{c.BASE_URL}/v1/user/organizations.json"
r = httpx.get(url,headers=headers,params=params) r = httpx.get(url,headers=headers,params=params)
return r.json() return r.json()
def uploadkintonefiles(file,c:config.KINTONE_ENV=Depends(getkintoneenv)): def uploadkintonefiles(file,c:config.KINTONE_ENV):
if (file.endswith('alc_runtime.js') and config.DEPLOY_MODE == "DEV"):
return {'fileKey':file}
upload_files = {'file': open(file,'rb')} upload_files = {'file': open(file,'rb')}
headers={config.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE} headers={config.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE}
data ={'name':'file','filename':os.path.basename(file)} data ={'name':'file','filename':os.path.basename(file)}
@@ -235,13 +239,16 @@ def uploadkintonefiles(file,c:config.KINTONE_ENV=Depends(getkintoneenv)):
r = httpx.post(url,headers=headers,data=data,files=upload_files) r = httpx.post(url,headers=headers,data=data,files=upload_files)
return r.json() return r.json()
def updateappjscss(app,uploads,c:config.KINTONE_ENV=Depends(getkintoneenv)): def updateappjscss(app,uploads,c:config.KINTONE_ENV):
dsjs = [] dsjs = []
dscss = [] dscss = []
for upload in uploads: for upload in uploads:
for key in upload: for key in upload:
if key.endswith('.js'): if key.endswith('.js'):
dsjs.append({'type':'FILE','file':{'fileKey':upload[key]}}) if (key.endswith('alc_runtime.js') and config.DEPLOY_MODE == "DEV"):
dsjs.append({'type':'URL','url':config.DEPLOY_JS_URL})
else:
dsjs.append({'type':'FILE','file':{'fileKey':upload[key]}})
elif key.endswith('.css'): elif key.endswith('.css'):
dscss.append({'type':'FILE','file':{'fileKey':upload[key]}}) dscss.append({'type':'FILE','file':{'fileKey':upload[key]}})
ds ={'js':dsjs,'css':dscss} ds ={'js':dsjs,'css':dscss}
@@ -253,21 +260,23 @@ def updateappjscss(app,uploads,c:config.KINTONE_ENV=Depends(getkintoneenv)):
r = httpx.put(url,headers=headers,data=json.dumps(data)) r = httpx.put(url,headers=headers,data=json.dumps(data))
return r.json() return r.json()
def createappjs(app): def createappjs(domainid,app):
db = SessionLocal() db = SessionLocal()
flows = get_flows(db,app) flows = get_flows_by_app(db,domainid,app)
db.close() db.close()
content={} content={}
for flow in flows: for flow in flows:
content[flow.eventid] = {'flowid':flow.flowid,'name':flow.name,'content':flow.content} content[flow.eventid] = {'flowid':flow.flowid,'name':flow.name,'content':flow.content}
js = 'const flow=' + json.dumps(content) js = 'const alcflow=' + json.dumps(content)
fpath = '{}\\alc_setting_{}.js'.format('Temp',app) scriptdir = Path(__file__).resolve().parent
file = open(fpath,'w',encoding="utf-8") rootdir = scriptdir.parent.parent.parent.parent
file.write(js) fpath = os.path.join(rootdir,"Temp",f"alc_setting_{app}.js")
file.close() print(rootdir)
print(fpath)
with open(fpath,'w') as file:
file.write(js)
return fpath return fpath
@r.post("/test",) @r.post("/test",)
async def test(file:UploadFile= File(...),app:str=None): async def test(file:UploadFile= File(...),app:str=None):
if file.filename.endswith('.xlsx'): if file.filename.endswith('.xlsx'):
@@ -285,7 +294,7 @@ async def test(file:UploadFile= File(...),app:str=None):
# kintone = getfieldsfromkintone(app) # kintone = getfieldsfromkintone(app)
# fields = analysefields(excel,kintone["properties"]) # fields = analysefields(excel,kintone["properties"])
except Exception as e: except Exception as e:
raise HTTPException(status_code=400, detail=f"Error occurred while parsing file {file.filename}: {str(e)}") raise HTTPException(status_code=400, detail=f"Error occurred while parsing file {file.filename}")
else: else:
raise HTTPException(status_code=400, detail=f"File {file.filename} is not an Excel file") raise HTTPException(status_code=400, detail=f"File {file.filename} is not an Excel file")
@@ -293,15 +302,18 @@ async def test(file:UploadFile= File(...),app:str=None):
@r.post("/download",) @r.post("/download",)
async def download(key,c:config.KINTONE_ENV=Depends(getkintoneenv)): async def download(request:Request,key,c:config.KINTONE_ENV=Depends(getkintoneenv)):
headers={config.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE} try:
params = {"fileKey":key} headers={config.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE}
url = f"{c.BASE_URL}/k/v1/file.json" params = {"fileKey":key}
r = httpx.get(url,headers=headers,params=params) url = f"{c.BASE_URL}/k/v1/file.json"
return r.json() r = httpx.get(url,headers=headers,params=params)
return r.json()
except Exception as e:
raise APIException('kintone:upload',request.url._url,f"Error occurred while download file.json:",e)
@r.post("/upload") @r.post("/upload")
async def upload(files:t.List[UploadFile] = File(...)): async def upload(request:Request,files:t.List[UploadFile] = File(...)):
dataframes = [] dataframes = []
for file in files: for file in files:
if file.filename.endswith('.xlsx'): if file.filename.endswith('.xlsx'):
@@ -311,14 +323,14 @@ async def upload(files:t.List[UploadFile] = File(...)):
print(df) print(df)
dataframes.append(df) dataframes.append(df)
except Exception as e: except Exception as e:
raise HTTPException(status_code=400, detail=f"Error occurred while parsing file {file.filename}: {str(e)}") raise APIException('kintone:upload',request.url._url,f"Error occurred while uploading file {file.filename}:",e)
else: else:
raise HTTPException(status_code=400, detail=f"File {file.filename} is not an Excel file") raise APIException('kintone:upload',request.url._url, f"File {file.filename} is not an Excel file",e)
return {"files": [file.filename for file in files]} return {"files": [file.filename for file in files]}
@r.post("/updatejscss") @r.post("/updatejscss")
async def jscss(app:str,files:t.List[UploadFile] = File(...),env = Depends(getkintoneenv)): async def jscss(request:Request,app:str,files:t.List[UploadFile] = File(...),env = Depends(getkintoneenv)):
try: try:
jscs=[] jscs=[]
for file in files: for file in files:
@@ -328,57 +340,82 @@ async def jscss(app:str,files:t.List[UploadFile] = File(...),env = Depends(getki
fout = open(fpath,'wb') fout = open(fpath,'wb')
fout.write(fbytes) fout.write(fbytes)
fout.close() fout.close()
upload = uploadkintonefiles(fpath) upload = uploadkintonefiles(fpath,env)
if upload.get('fileKey') != None: if upload.get('fileKey') != None:
jscs.append({ file.filename:upload['fileKey']}) jscs.append({ file.filename:upload['fileKey']})
appjscs = updateappjscss(app,jscs) appjscs = updateappjscss(app,jscs,env)
if appjscs.get("revision") != None: if appjscs.get("revision") != None:
deoployappfromkintone(app,appjscs["revision"],env) deoployappfromkintone(app,appjscs["revision"],env)
return appjscs return appjscs
except Exception as e: except Exception as e:
raise HTTPException(status_code=400, detail=f"Error occurred while update file {file.filename}: {str(e)}") raise APIException('kintone:updatejscss',request.url._url, f"Error occurred while update js/css {file.filename} is not an Excel file",e)
@r.get("/app")
async def app(request:Request,app:str,c:config.KINTONE_ENV=Depends(getkintoneenv)):
try:
headers={config.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE}
url = f"{c.BASE_URL}{config.API_V1_STR}/app.json"
params ={"id":app}
r = httpx.get(url,headers=headers,params=params)
return r.json()
except Exception as e:
raise APIException('kintone:app',request.url._url, f"Error occurred while get app({c.DOMAIN_NAM}->{app}):",e)
@r.get("/allapps") @r.get("/allapps")
async def allapps(c:config.KINTONE_ENV=Depends(getkintoneenv)): async def allapps(request:Request,c:config.KINTONE_ENV=Depends(getkintoneenv)):
headers={config.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE} try:
url = f"{c.BASE_URL}{config.API_V1_STR}/apps.json" headers={config.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE}
r = httpx.get(url,headers=headers) url = f"{c.BASE_URL}{config.API_V1_STR}/apps.json"
return r.json() r = httpx.get(url,headers=headers)
return r.json()
except Exception as e:
raise APIException('kintone:allapps',request.url._url, f"Error occurred while get allapps({c.DOMAIN_NAM}):",e)
@r.get("/appfields") @r.get("/appfields")
async def appfields(app:str,env = Depends(getkintoneenv)): async def appfields(request:Request,app:str,env = Depends(getkintoneenv)):
return getfieldsfromkintone(app,env) try:
return getfieldsfromkintone(app,env)
except Exception as e:
raise APIException('kintone:appfields',request.url._url, f"Error occurred while get app fileds({env.DOMAIN_NAM}->{app}):",e)
@r.get("/appprocess") @r.get("/appprocess")
async def appprocess(app:str,env = Depends(getkintoneenv)): async def appprocess(request:Request,app:str,env = Depends(getkintoneenv)):
return getprocessfromkintone(app,env) try:
return getprocessfromkintone(app,env)
except Exception as e:
raise APIException('kintone:appprocess',request.url._url, f"Error occurred while get app process({env.DOMAIN_NAM}->{app}):",e)
@r.get("/alljscss") @r.get("/alljscss")
async def alljscs(app:str,c:config.KINTONE_ENV=Depends(getkintoneenv)): async def alljscs(request:Request,app:str,c:config.KINTONE_ENV=Depends(getkintoneenv)):
headers={config.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE} try:
url = f"{c.BASE_URL}{config.API_V1_STR}/app/customize.json" headers={config.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE}
params = {"app":app} url = f"{c.BASE_URL}{config.API_V1_STR}/app/customize.json"
r = httpx.get(url,headers=headers,params=params) params = {"app":app}
return r.json() r = httpx.get(url,headers=headers,params=params)
return r.json()
except Exception as e:
raise APIException('kintone:alljscss',request.url._url, f"Error occurred while get app js/css({c.DOMAIN_NAM}->{app}):",e)
@r.post("/createapp",) @r.post("/createapp",)
async def createapp(name:str,c:config.KINTONE_ENV=Depends(getkintoneenv)): async def createapp(request:Request,name:str,c:config.KINTONE_ENV=Depends(getkintoneenv)):
headers={config.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE,"Content-Type": "application/json"} try:
data = {"name":name} headers={config.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE,"Content-Type": "application/json"}
url = f"{c.BASE_URL}{config.API_V1_STR}/preview/app.json" data = {"name":name}
r = httpx.post(url,headers=headers,data=json.dumps(data)) url = f"{c.BASE_URL}{config.API_V1_STR}/preview/app.json"
result = r.json()
if result.get("app") != None:
url = f"{c.BASE_URL}{config.API_V1_STR}/preview/app/deploy.json"
data = {"apps":[result],"revert": False}
r = httpx.post(url,headers=headers,data=json.dumps(data)) r = httpx.post(url,headers=headers,data=json.dumps(data))
return r.json result = r.json()
if result.get("app") != None:
url = f"{c.BASE_URL}{config.API_V1_STR}/preview/app/deploy.json"
data = {"apps":[result],"revert": False}
r = httpx.post(url,headers=headers,data=json.dumps(data))
return r.json
except Exception as e:
raise APIException('kintone:createapp',request.url._url, f"Error occurred while create app({c.DOMAIN_NAM}->{name}):",e)
property=["label","code","type","required","defaultValue","options"] property=["label","code","type","required","defaultValue","options"]
@r.post("/createappfromexcel",) @r.post("/createappfromexcel",)
async def createappfromexcel(files:t.List[UploadFile] = File(...),env = Depends(getkintoneenv)): async def createappfromexcel(request:Request,files:t.List[UploadFile] = File(...),env = Depends(getkintoneenv)):
for file in files: for file in files:
if file.filename.endswith('.xlsx'): if file.filename.endswith('.xlsx'):
try: try:
@@ -389,8 +426,8 @@ async def createappfromexcel(files:t.List[UploadFile] = File(...),env = Depends(
desc = df.iloc[2,2] desc = df.iloc[2,2]
result = {"app":0,"revision":0,"msg":""} result = {"app":0,"revision":0,"msg":""}
fields = getfieldsfromexcel(df) fields = getfieldsfromexcel(df)
users = getkintoneusers() users = getkintoneusers(env)
orgs = getkintoneorgs() orgs = getkintoneorgs(env)
processes = getprocessfromexcel(df,users["users"], orgs["organizationTitles"]) processes = getprocessfromexcel(df,users["users"], orgs["organizationTitles"])
app = createkintoneapp(appname,env) app = createkintoneapp(appname,env)
if app.get("app") != None: if app.get("app") != None:
@@ -398,22 +435,21 @@ async def createappfromexcel(files:t.List[UploadFile] = File(...),env = Depends(
app = updateappsettingstokintone(result["app"],{"description":desc},env) app = updateappsettingstokintone(result["app"],{"description":desc},env)
if app.get("revision") != None: if app.get("revision") != None:
result["revision"] = app["revision"] result["revision"] = app["revision"]
app = addfieldstokintone(result["app"],env,fields) app = addfieldstokintone(result["app"],fields,env)
if len(processes)> 0: if len(processes)> 0:
app = updateprocesstokintone(result["app"],processes) app = updateprocesstokintone(result["app"],processes,env)
if app.get("revision") != None: if app.get("revision") != None:
result["revision"] = app["revision"] result["revision"] = app["revision"]
deoployappfromkintone(result["app"],result["revision"],env) deoployappfromkintone(result["app"],result["revision"],env)
except Exception as e: except Exception as e:
raise HTTPException(status_code=400, detail=f"Error occurred while parsing file {file.filename}: {str(e)}") raise APIException('kintone:createappfromexcel',request.url._url, f"Error occurred while parsing file ({env.DOMAIN_NAM}->{file.filename}):",e)
else: else:
raise HTTPException(status_code=400, detail=f"File {file.filename} is not an Excel file") raise APIException('kintone:createappfromexcel',request.url._url, f"File {file.filename} is not an Excel file",e)
return result return result
@r.post("/updateappfromexcel") @r.post("/updateappfromexcel")
async def updateappfromexcel(app:str,files:t.List[UploadFile] = File(...),env = Depends(getkintoneenv)): async def updateappfromexcel(request:Request,app:str,files:t.List[UploadFile] = File(...),env = Depends(getkintoneenv)):
for file in files: for file in files:
if file.filename.endswith('.xlsx'): if file.filename.endswith('.xlsx'):
try: try:
@@ -424,8 +460,8 @@ async def updateappfromexcel(app:str,files:t.List[UploadFile] = File(...),env =
settings = analysesettings(excel,kintone) settings = analysesettings(excel,kintone)
excel = getfieldsfromexcel(df) excel = getfieldsfromexcel(df)
kintone = getfieldsfromkintone(app,env) kintone = getfieldsfromkintone(app,env)
users = getkintoneusers() users = getkintoneusers(env)
orgs = getkintoneorgs() orgs = getkintoneorgs(env)
exp = getprocessfromexcel(df,users["users"], orgs["organizationTitles"]) exp = getprocessfromexcel(df,users["users"], orgs["organizationTitles"])
#exp = getprocessfromexcel(df) #exp = getprocessfromexcel(df)
kinp = getprocessfromkintone(app,env) kinp = getprocessfromkintone(app,env)
@@ -451,20 +487,19 @@ async def updateappfromexcel(app:str,files:t.List[UploadFile] = File(...),env =
revision = result["revision"] revision = result["revision"]
deploy = True deploy = True
if len(process)>0: if len(process)>0:
result = updateprocesstokintone(app,exp) result = updateprocesstokintone(app,exp,env)
revision = result["revision"] revision = result["revision"]
deploy = True deploy = True
if deploy: if deploy:
result = deoployappfromkintone(app,revision,env) result = deoployappfromkintone(app,revision,env)
except Exception as e: except Exception as e:
raise HTTPException(status_code=400, detail=f"Error occurred while parsing file {file.filename}: {str(e)}") raise APIException('kintone:updateappfromexcel',request.url._url, f"Error occurred while parsing file ({env.DOMAIN_NAM}->{file.filename}):",e)
else: else:
raise HTTPException(status_code=400, detail=f"File {file.filename} is not an Excel file") raise APIException('kintone:updateappfromexcel',request.url._url, f"File {file.filename} is not an Excel file",e)
return result return result
@r.post("/updateprocessfromexcel",) @r.post("/updateprocessfromexcel",)
async def updateprocessfromexcel(app:str,env = Depends(getkintoneenv)): async def updateprocessfromexcel(request:Request,app:str,env = Depends(getkintoneenv)):
try: try:
excel = getprocessfromexcel() excel = getprocessfromexcel()
@@ -489,32 +524,31 @@ async def updateprocessfromexcel(app:str,env = Depends(getkintoneenv)):
# result = updateappsettingstokintone(app,settings) # result = updateappsettingstokintone(app,settings)
# revision = result["revision"] # revision = result["revision"]
# deploy = True # deploy = True
result = updateprocesstokintone(app,excel) result = updateprocesstokintone(app,excel,env)
revision = result["revision"] revision = result["revision"]
deploy = True deploy = True
if deploy: if deploy:
result = deoployappfromkintone(app,revision,env) result = deoployappfromkintone(app,revision,env)
except Exception as e: except Exception as e:
raise HTTPException(status_code=400, detail=f"Error occurred : {str(e)}") raise APIException('kintone:updateprocessfromexcel',request.url._url, f"Error occurred while update process ({env.DOMAIN_NAM}->{app}):",e)
return result return result
@r.post("/createjstokintone",) @r.post("/createjstokintone",)
async def createjstokintone(app:str,env = Depends(getkintoneenv)): async def createjstokintone(request:Request,app:str,env:config.KINTONE_ENV = Depends(getkintoneenv)):
try: try:
jscs=[] jscs=[]
files=[] files=[]
files.append(createappjs(app)) files.append(createappjs(env.DOMAIN_ID, app))
files.append('Temp\\alc_runtime.js') files.append('Temp\\alc_runtime.js')
for file in files: for file in files:
upload = uploadkintonefiles(file) upload = uploadkintonefiles(file,env)
if upload.get('fileKey') != None: if upload.get('fileKey') != None:
jscs.append({ app + '.js':upload['fileKey']}) jscs.append({ file :upload['fileKey']})
appjscs = updateappjscss(app,jscs) appjscs = updateappjscss(app,jscs,env)
if appjscs.get("revision") != None: if appjscs.get("revision") != None:
deoployappfromkintone(app,appjscs["revision"],env) deoployappfromkintone(app,appjscs["revision"],env)
return appjscs return appjscs
except Exception as e: except Exception as e:
raise HTTPException(status_code=400, detail=f"Error occurred : {str(e)}") raise APIException('kintone:createjstokintone',request.url._url, f"Error occurred while create js ({env.DOMAIN_NAM}->{app}):",e)

View File

@@ -5,6 +5,7 @@ from app.db.crud import *
from app.db.schemas import * from app.db.schemas import *
from typing import List from typing import List
from app.core.auth import get_current_active_user,get_current_user from app.core.auth import get_current_active_user,get_current_user
from app.core.apiexception import APIException
platform_router = r = APIRouter() platform_router = r = APIRouter()
@@ -18,9 +19,11 @@ async def appsetting_details(
id: int, id: int,
db=Depends(get_db), db=Depends(get_db),
): ):
app = get_appsetting(db, id) try:
return app app = get_appsetting(db, id)
return app
except Exception as e:
raise APIException('platform:appsettings',request.url._url,f"Error occurred while get app setting:",e)
@r.post("/appsettings", response_model=App, response_model_exclude_none=True) @r.post("/appsettings", response_model=App, response_model_exclude_none=True)
async def appsetting_create( async def appsetting_create(
@@ -28,7 +31,10 @@ async def appsetting_create(
app: AppBase, app: AppBase,
db=Depends(get_db), db=Depends(get_db),
): ):
return create_appsetting(db, app) try:
return create_appsetting(db, app)
except Exception as e:
raise APIException('platform:appsettings',request.url._url,f"Error occurred while get create app setting:",e)
@r.put( @r.put(
@@ -40,7 +46,10 @@ async def appsetting_edit(
app: AppEdit, app: AppEdit,
db=Depends(get_db), db=Depends(get_db),
): ):
return edit_appsetting(db, id, app) try:
return edit_appsetting(db, id, app)
except Exception as e:
raise APIException('platform:appsettings',request.url._url,f"Error occurred while edit app setting:",e)
@r.delete( @r.delete(
@@ -51,8 +60,10 @@ async def appsettings_delete(
id: int, id: int,
db=Depends(get_db), db=Depends(get_db),
): ):
try:
return delete_appsetting(db, id) return delete_appsetting(db, id)
except Exception as e:
raise APIException('platform:appsettings',request.url._url,f"Error occurred while delete app setting:",e)
@r.get( @r.get(
@@ -65,8 +76,11 @@ async def kintone_data(
type: int, type: int,
db=Depends(get_db), db=Depends(get_db),
): ):
kintone = get_kintones(db, type) try:
return kintone kintone = get_kintones(db, type)
return kintone
except Exception as e:
raise APIException('platform:kintone',request.url._url,f"Error occurred while get kintone env:",e)
@r.get( @r.get(
"/actions", "/actions",
@@ -78,8 +92,11 @@ async def action_data(
request: Request, request: Request,
db=Depends(get_db), db=Depends(get_db),
): ):
actions = get_actions(db) try:
return actions actions = get_actions(db)
return actions
except Exception as e:
raise APIException('platform:actions',request.url._url,f"Error occurred while get actions:",e)
@r.get( @r.get(
"/flow/{flowid}", "/flow/{flowid}",
@@ -91,8 +108,11 @@ async def flow_details(
flowid: str, flowid: str,
db=Depends(get_db), db=Depends(get_db),
): ):
app = get_flow(db, flowid) try:
return app app = get_flow(db, flowid)
return app
except Exception as e:
raise APIException('platform:flow',request.url._url,f"Error occurred while get flow by flowid:",e)
@r.get( @r.get(
@@ -103,19 +123,30 @@ async def flow_details(
async def flow_list( async def flow_list(
request: Request, request: Request,
appid: str, appid: str,
user=Depends(get_current_user),
db=Depends(get_db), db=Depends(get_db),
): ):
flows = get_flows_by_app(db, appid) try:
return flows domain = get_activedomain(db, user.id)
print("domain=>",domain)
flows = get_flows_by_app(db, domain.id, appid)
return flows
except Exception as e:
raise APIException('platform:flow',request.url._url,f"Error occurred while get flow by appid:",e)
@r.post("/flow", response_model=Flow, response_model_exclude_none=True) @r.post("/flow", response_model=Flow, response_model_exclude_none=True)
async def flow_create( async def flow_create(
request: Request, request: Request,
flow: FlowBase, flow: FlowBase,
user=Depends(get_current_user),
db=Depends(get_db), db=Depends(get_db),
): ):
return create_flow(db, flow) try:
domain = get_activedomain(db, user.id)
return create_flow(db, domain.id, flow)
except Exception as e:
raise APIException('platform:flow',request.url._url,f"Error occurred while create flow:",e)
@r.put( @r.put(
@@ -126,7 +157,10 @@ async def flow_edit(
flow: FlowBase, flow: FlowBase,
db=Depends(get_db), db=Depends(get_db),
): ):
return edit_flow(db, flow) try:
return edit_flow(db, flow)
except Exception as e:
raise APIException('platform:flow',request.url._url,f"Error occurred while edit flow:",e)
@r.delete( @r.delete(
@@ -137,8 +171,10 @@ async def flow_delete(
flowid: str, flowid: str,
db=Depends(get_db), db=Depends(get_db),
): ):
try:
return delete_flow(db, flowid) return delete_flow(db, flowid)
except Exception as e:
raise APIException('platform:flow',request.url._url,f"Error occurred while delete flow:",e)
@r.get( @r.get(
"/domains/{tenantid}", "/domains/{tenantid}",
@@ -150,8 +186,11 @@ async def domain_details(
tenantid:str, tenantid:str,
db=Depends(get_db), db=Depends(get_db),
): ):
domains = get_domains(db,tenantid) try:
return domains domains = get_domains(db,tenantid)
return domains
except Exception as e:
raise APIException('platform:domains',request.url._url,f"Error occurred while get domains:",e)
@r.post("/domain", response_model=Domain, response_model_exclude_none=True) @r.post("/domain", response_model=Domain, response_model_exclude_none=True)
async def domain_create( async def domain_create(
@@ -159,7 +198,10 @@ async def domain_create(
domain: DomainBase, domain: DomainBase,
db=Depends(get_db), db=Depends(get_db),
): ):
return create_domain(db, domain) try:
return create_domain(db, domain)
except Exception as e:
raise APIException('platform:domain',request.url._url,f"Error occurred while create domain:",e)
@r.put( @r.put(
@@ -170,7 +212,10 @@ async def domain_edit(
domain: DomainBase, domain: DomainBase,
db=Depends(get_db), db=Depends(get_db),
): ):
return edit_domain(db, domain) try:
return edit_domain(db, domain)
except Exception as e:
raise APIException('platform:domain',request.url._url,f"Error occurred while edit domain:",e)
@r.delete( @r.delete(
@@ -181,8 +226,10 @@ async def domain_delete(
id: int, id: int,
db=Depends(get_db), db=Depends(get_db),
): ):
try:
return delete_domain(db,id) return delete_domain(db,id)
except Exception as e:
raise APIException('platform:domain',request.url._url,f"Error occurred while delete domain:",e)
@r.get( @r.get(
"/domain", "/domain",
@@ -194,8 +241,11 @@ async def userdomain_details(
user=Depends(get_current_user), user=Depends(get_current_user),
db=Depends(get_db), db=Depends(get_db),
): ):
domains = get_domain(db, user.id) try:
return domains domains = get_domain(db, user.id)
return domains
except Exception as e:
raise APIException('platform:domain',request.url._url,f"Error occurred while get user({user.id}) domain:",e)
@r.post( @r.post(
"/domain/{userid}", "/domain/{userid}",
@@ -207,8 +257,11 @@ async def create_userdomain(
domainids:list, domainids:list,
db=Depends(get_db), db=Depends(get_db),
): ):
domain = add_userdomain(db, userid,domainids) try:
return domain domain = add_userdomain(db, userid,domainids)
return domain
except Exception as e:
raise APIException('platform:domain',request.url._url,f"Error occurred while add user({userid}) domain:",e)
@r.delete( @r.delete(
"/domain/{domainid}/{userid}", response_model_exclude_none=True "/domain/{domainid}/{userid}", response_model_exclude_none=True
@@ -219,7 +272,10 @@ async def userdomain_delete(
userid: int, userid: int,
db=Depends(get_db), db=Depends(get_db),
): ):
return delete_userdomain(db, userid,domainid) try:
return delete_userdomain(db, userid,domainid)
except Exception as e:
raise APIException('platform:delete',request.url._url,f"Error occurred while delete user({userid}) domain:",e)
@r.get( @r.get(
@@ -232,8 +288,11 @@ async def get_useractivedomain(
user=Depends(get_current_user), user=Depends(get_current_user),
db=Depends(get_db), db=Depends(get_db),
): ):
domain = get_activedomain(db, user.id) try:
return domain domain = get_activedomain(db, user.id)
return domain
except Exception as e:
raise APIException('platform:activedomain',request.url._url,f"Error occurred while get user({user.id}) activedomain:",e)
@r.put( @r.put(
"/activedomain/{domainid}", "/activedomain/{domainid}",
@@ -245,9 +304,11 @@ async def update_activeuserdomain(
user=Depends(get_current_user), user=Depends(get_current_user),
db=Depends(get_db), db=Depends(get_db),
): ):
domain = active_userdomain(db, user.id,domainid) try:
return domain domain = active_userdomain(db, user.id,domainid)
return domain
except Exception as e:
raise APIException('platform:activedomain',request.url._url,f"Error occurred while update user({user.id}) activedomain:",e)
@r.get( @r.get(
"/events", "/events",
@@ -259,8 +320,11 @@ async def event_data(
request: Request, request: Request,
db=Depends(get_db), db=Depends(get_db),
): ):
events = get_events(db) try:
return events events = get_events(db)
return events
except Exception as e:
raise APIException('platform:events',request.url._url,f"Error occurred while get events:",e)
@r.get( @r.get(
@@ -274,5 +338,8 @@ async def eventactions_data(
eventid: str, eventid: str,
db=Depends(get_db), db=Depends(get_db),
): ):
actions = get_eventactions(db,eventid) try:
return actions actions = get_eventactions(db,eventid)
return actions
except Exception as e:
raise APIException('platform:eventactions',request.url._url,f"Error occurred while get eventactions:",e)

View File

@@ -0,0 +1,26 @@
from fastapi import HTTPException, status
from app.db.schemas import ErrorCreate
from app.db.session import SessionLocal
from app.db.crud import create_log
class APIException(Exception):
def __init__(self,location:str,title:str,content:str,e:Exception):
if(str(e) == ''):
content += e.detail
self.detail = e.detail
self.status_code = e.status_code
else:
self.detail = str(e)
content += str(e)
self.status_code = 500
if(len(content) > 5000):
content =content[0:5000]
self.error = ErrorCreate(location=location,title=title,content=content)
def writedblog(exc: APIException):
db = SessionLocal()
try:
create_log(db,exc.error)
finally:
db.close()

View File

@@ -67,8 +67,8 @@ def sign_up_new_user(db, email: str, password: str, firstname: str,lastname: str
schemas.UserCreate( schemas.UserCreate(
email=email, email=email,
password=password, password=password,
firstname = firstname, first_name = firstname,
lastname = lastname, last_name = lastname,
is_active=True, is_active=True,
is_superuser=False, is_superuser=False,
), ),

View File

@@ -3,13 +3,17 @@ import base64
PROJECT_NAME = "KintoneAppBuilder" PROJECT_NAME = "KintoneAppBuilder"
SQLALCHEMY_DATABASE_URI = "postgres://maxz64:m@xz1205@alicornkintone.postgres.database.azure.com/postgres" #SQLALCHEMY_DATABASE_URI = "postgres://maxz64:m@xz1205@alicornkintone.postgres.database.azure.com/postgres"
#SQLALCHEMY_DATABASE_URI = "postgres://kabAdmin:P@ssw0rd!@kintonetooldb.postgres.database.azure.com/postgres" SQLALCHEMY_DATABASE_URI = "postgres://kabAdmin:P@ssw0rd!@kintonetooldb.postgres.database.azure.com/postgres"
API_V1_STR = "/k/v1" API_V1_STR = "/k/v1"
API_V1_AUTH_KEY = "X-Cybozu-Authorization" API_V1_AUTH_KEY = "X-Cybozu-Authorization"
DEPLOY_MODE = "DEV" #DEV,PROD
DEPLOY_JS_URL = "https://ka-addin.azurewebsites.net/alc_runtime.js"
KINTONE_FIELD_TYPE=["GROUP","GROUP_SELECT","CHECK_BOX","SUBTABLE","DROP_DOWN","USER_SELECT","RADIO_BUTTON","RICH_TEXT","LINK","REFERENCE_TABLE","CALC","TIME","NUMBER","ORGANIZATION_SELECT","FILE","DATETIME","DATE","MULTI_SELECT","SINGLE_LINE_TEXT","MULTI_LINE_TEXT"] KINTONE_FIELD_TYPE=["GROUP","GROUP_SELECT","CHECK_BOX","SUBTABLE","DROP_DOWN","USER_SELECT","RADIO_BUTTON","RICH_TEXT","LINK","REFERENCE_TABLE","CALC","TIME","NUMBER","ORGANIZATION_SELECT","FILE","DATETIME","DATE","MULTI_SELECT","SINGLE_LINE_TEXT","MULTI_LINE_TEXT"]
class KINTONE_ENV: class KINTONE_ENV:
@@ -20,7 +24,13 @@ class KINTONE_ENV:
KINTONE_USER = "" KINTONE_USER = ""
DOMAIN_ID = ""
DOMAIN_NAME =""
def __init__(self,domain) -> None: def __init__(self,domain) -> None:
self.DOMAIN_NAME=domain.name
self.DOMAIN_ID=domain.id
self.BASE_URL = domain.url self.BASE_URL = domain.url
self.KINTONE_USER = domain.kintoneuser self.KINTONE_USER = domain.kintoneuser
self.API_V1_AUTH_VALUE = base64.b64encode(bytes(f"{domain.kintoneuser}:{domain.kintonepwd}","utf-8")) self.API_V1_AUTH_VALUE = base64.b64encode(bytes(f"{domain.kintoneuser}:{domain.kintonepwd}","utf-8"))

View File

@@ -125,11 +125,12 @@ def get_actions(db: Session):
return actions return actions
def create_flow(db: Session, flow: schemas.FlowBase): def create_flow(db: Session, domainid: int, flow: schemas.FlowBase):
db_flow = models.Flow( db_flow = models.Flow(
flowid=flow.flowid, flowid=flow.flowid,
appid=flow.appid, appid=flow.appid,
eventid=flow.eventid, eventid=flow.eventid,
domainid=domainid,
name=flow.name, name=flow.name,
content=flow.content content=flow.content
) )
@@ -176,10 +177,10 @@ def get_flow(db: Session, flowid: str):
raise HTTPException(status_code=404, detail="Data not found") raise HTTPException(status_code=404, detail="Data not found")
return flow return flow
def get_flows_by_app(db: Session, appid: str): def get_flows_by_app(db: Session, domainid: int, appid: str):
flows = db.query(models.Flow).filter(models.Flow.appid == appid).all() flows = db.query(models.Flow).filter(and_(models.Flow.domainid == domainid,models.Flow.appid == appid)).all()
if not flows: if not flows:
raise HTTPException(status_code=404, detail="Data not found") raise Exception("Data not found")
return flows return flows
def create_domain(db: Session, domain: schemas.DomainBase): def create_domain(db: Session, domain: schemas.DomainBase):
@@ -282,3 +283,11 @@ def get_eventactions(db: Session,eventid: str):
if not eveactions: if not eveactions:
raise HTTPException(status_code=404, detail="Data not found") raise HTTPException(status_code=404, detail="Data not found")
return eveactions return eveactions
def create_log(db: Session, error:schemas.ErrorCreate):
db_log = models.ErrorLog(title=error.title,location=error.location,content=error.content)
db.add(db_log)
db.commit()
db.refresh(db_log)
return db_log

View File

@@ -47,6 +47,7 @@ class Flow(Base):
flowid = Column(String(100), index=True, nullable=False) flowid = Column(String(100), index=True, nullable=False)
appid = Column(String(100), index=True, nullable=False) appid = Column(String(100), index=True, nullable=False)
eventid = Column(String(100), index=True, nullable=False) eventid = Column(String(100), index=True, nullable=False)
domainid = Column(Integer,ForeignKey("domain.id"))
name = Column(String(200)) name = Column(String(200))
content = Column(String) content = Column(String)
@@ -90,3 +91,11 @@ class EventAction(Base):
eventid = Column(Integer,ForeignKey("event.id")) eventid = Column(Integer,ForeignKey("event.id"))
actionid = Column(Integer,ForeignKey("action.id")) actionid = Column(Integer,ForeignKey("action.id"))
class ErrorLog(Base):
__tablename__ = "errorlog"
id = Column(Integer, primary_key=True, index=True)
title = Column(String(50))
location = Column(String(500))
content = Column(String(5000))

View File

@@ -20,9 +20,12 @@ class UserOut(UserBase):
class UserCreate(UserBase): class UserCreate(UserBase):
email:str
password: str password: str
first_name: str first_name: str
last_name: str last_name: str
is_active:bool
is_superuser:bool
class Config: class Config:
orm_mode = True orm_mode = True
@@ -101,6 +104,7 @@ class Flow(Base):
flowid: str flowid: str
appid: str appid: str
eventid: str eventid: str
domainid: int
name: str = None name: str = None
content: str = None content: str = None
@@ -136,3 +140,8 @@ class Event(Base):
class Config: class Config:
orm_mode = True orm_mode = True
class ErrorCreate(BaseModel):
title:str
location:str
content:str

View File

@@ -1,3 +1,4 @@
import os
from fastapi import FastAPI, Depends from fastapi import FastAPI, Depends
from starlette.requests import Request from starlette.requests import Request
import uvicorn import uvicorn
@@ -11,6 +12,11 @@ from app.core.auth import get_current_active_user
from app.core.celery_app import celery_app from app.core.celery_app import celery_app
from app import tasks from app import tasks
from fastapi.middleware.cors import CORSMiddleware from fastapi.middleware.cors import CORSMiddleware
import logging
from app.core.apiexception import APIException, writedblog
from app.db.crud import create_log
from fastapi.responses import JSONResponse
import asyncio
Base.metadata.create_all(bind=engine) Base.metadata.create_all(bind=engine)
@@ -19,9 +25,7 @@ app = FastAPI(
) )
origins = [ origins = [
"http://localhost:9000", "*"
"http://localhost",
"http://localhost:8080",
] ]
app.add_middleware( app.add_middleware(
@@ -39,6 +43,25 @@ app.add_middleware(
# request.state.db.close() # request.state.db.close()
# return response # return response
@app.on_event("startup")
async def startup_event():
log_dir="log"
if not os.path.exists(log_dir):
os.makedirs(log_dir)
logger = logging.getLogger("uvicorn.access")
handler = logging.handlers.RotatingFileHandler(f"{log_dir}/api.log",mode="a",maxBytes = 100*1024, backupCount = 3)
handler.setFormatter(logging.Formatter("%(asctime)s - %(levelname)s - %(message)s"))
logger.addHandler(handler)
@app.exception_handler(APIException)
async def api_exception_handler(request: Request, exc: APIException):
loop = asyncio.get_event_loop()
loop.run_in_executor(None,writedblog,exc)
return JSONResponse(
status_code=exc.status_code,
content={"detail": f"{exc.detail}"},
)
@app.get("/api/v1") @app.get("/api/v1")
async def root(): async def root():

File diff suppressed because it is too large Load Diff

View File

@@ -1,2 +1,3 @@
KAB_BACKEND_URL="http://127.0.0.1:8000/api/v1/" KAB_BACKEND_URL="https://kab-backend.azurewebsites.net/"
#KAB_BACKEND_URL="http://127.0.0.1:8000/"

View File

@@ -1,2 +1,2 @@
VUE_BACKEND_URL="http://localhost:8000/api/" #KAB_BACKEND_URL="https://kab-backend.azurewebsites.net/"
KAB_BACKEND_URL="http://127.0.0.1:8000/"

View File

@@ -1,8 +1,8 @@
{ {
"name": "kintone-app-builder", "name": "kintone-automate",
"version": "0.2.0", "version": "0.2.0",
"description": "Kintoneアプリの自動生成とデプロイを支援ツールです", "description": "Kintoneアプリの自動生成とデプロイを支援ツールです",
"productName": "Kintone App Builder", "productName": "Kintone Automate",
"author": "maxiaozhe@alicorns.co.jp <maxiaozhe@alicorns.co.jp>", "author": "maxiaozhe@alicorns.co.jp <maxiaozhe@alicorns.co.jp>",
"private": true, "private": true,
"scripts": { "scripts": {
@@ -10,7 +10,10 @@
"format": "prettier --write \"**/*.{js,ts,vue,scss,html,md,json}\" --ignore-path .gitignore", "format": "prettier --write \"**/*.{js,ts,vue,scss,html,md,json}\" --ignore-path .gitignore",
"test": "echo \"No test specified\" && exit 0", "test": "echo \"No test specified\" && exit 0",
"dev": "quasar dev", "dev": "quasar dev",
"build": "quasar build" "dev:local": "set \"LOCAL=true\" && quasar dev",
"build": "set \"SOURCE_MAP=false\" && quasar build",
"build:dev":"set \"SOURCE_MAP=true\" && quasar build"
}, },
"dependencies": { "dependencies": {
"@quasar/extras": "^1.16.4", "@quasar/extras": "^1.16.4",

View File

@@ -0,0 +1,8 @@
<configuration>
<system.webServer>
<staticContent>
<mimeMap fileExtension=".woff" mimeType="application/font-woff" />
<mimeMap fileExtension=".woff2" mimeType="application/font-woff2" />
</staticContent>
</system.webServer>
</configuration>

View File

@@ -10,10 +10,14 @@
const { configure } = require('quasar/wrappers'); const { configure } = require('quasar/wrappers');
const dotenv = require('dotenv').config().parsed; const envPath = process.env.LOCAL==='true'?'.env.development':'.env';
const package = require('./package.json'); const dotenv = require('dotenv').config({path:envPath}).parsed;
console.log('dotenv=>',dotenv);
// const package = require('./package.json');
const { Notify } = require('quasar'); const { Notify } = require('quasar');
const version = package.version; const version = process.env.npm_package_version;
const productName=process.env.npm_package_productName;
// console.log(process.env);
module.exports = configure(function (/* ctx */) { module.exports = configure(function (/* ctx */) {
return { return {
eslint: { eslint: {
@@ -49,7 +53,6 @@ module.exports = configure(function (/* ctx */) {
// 'themify', // 'themify',
// 'line-awesome', // 'line-awesome',
// 'roboto-font-latin-ext', // this or either 'roboto-font', NEVER both! // 'roboto-font-latin-ext', // this or either 'roboto-font', NEVER both!
'roboto-font', // optional, you are not bound to it 'roboto-font', // optional, you are not bound to it
'material-icons', // optional, you are not bound to it 'material-icons', // optional, you are not bound to it
], ],
@@ -60,6 +63,7 @@ module.exports = configure(function (/* ctx */) {
browser: ['es2019', 'edge88', 'firefox78', 'chrome87', 'safari13.1'], browser: ['es2019', 'edge88', 'firefox78', 'chrome87', 'safari13.1'],
node: 'node16' node: 'node16'
}, },
sourcemap:process.env.SOURCE_MAP === 'true',
vueRouterMode: 'hash', // available values: 'hash', 'history' vueRouterMode: 'hash', // available values: 'hash', 'history'
// vueRouterBase, // vueRouterBase,
@@ -70,7 +74,7 @@ module.exports = configure(function (/* ctx */) {
// publicPath: '/', // publicPath: '/',
// analyze: true, // analyze: true,
env: { ...dotenv, version }, env: { ...dotenv, version ,productName},
// rawDefine: {} // rawDefine: {}
// ignorePublicFolder: true, // ignorePublicFolder: true,
// minify: false, // minify: false,

View File

@@ -1,5 +1,6 @@
import { boot } from 'quasar/wrappers'; import { boot } from 'quasar/wrappers';
import axios, { AxiosInstance } from 'axios'; import axios, { AxiosInstance } from 'axios';
import {router} from 'src/router';
declare module '@vue/runtime-core' { declare module '@vue/runtime-core' {
interface ComponentCustomProperties { interface ComponentCustomProperties {
@@ -15,7 +16,28 @@ declare module '@vue/runtime-core' {
// "export default () => {}" function below (which runs individually // "export default () => {}" function below (which runs individually
// for each client) // for each client)
const api:AxiosInstance = axios.create({ baseURL: process.env.KAB_BACKEND_URL }); const api:AxiosInstance = axios.create({ baseURL: process.env.KAB_BACKEND_URL });
const token=localStorage.getItem('token')||'';
if(token!==''){
api.defaults.headers["Authorization"]='Bearer ' + token;
}
api.interceptors.response.use(
(response)=>response,
(error)=>{
const orgReq=error.config;
if(error.response && error.response.status===401){
console.error("401エラー");
localStorage.removeItem('token');
router.replace({
path:"/login",
query:{redirect:router.currentRoute.value.fullPath}
});
// router.push({
// path:"/login",
// query:{redirect:router.currentRoute.value.fullPath}
// });
}
}
)
export default boot(({ app }) => { export default boot(({ app }) => {
// for use inside Vue files (Options API) through this.$axios and this.$api // for use inside Vue files (Options API) through this.$axios and this.$api

View File

@@ -1,6 +1,9 @@
<template> <template>
<div class="q-pa-md"> <div class="q-pa-md">
<q-table row-key="name" :selection="type" v-model:selected="selected" :columns="columns" :rows="rows" <div v-if="!isLoaded" class="spinner flex flex-center">
<q-spinner color="primary" size="3em" />
</div>
<q-table v-else row-key="name" :selection="type" v-model:selected="selected" :columns="columns" :rows="rows"
class="action-table" class="action-table"
flat bordered flat bordered
virtual-scroll virtual-scroll
@@ -20,20 +23,20 @@ export default {
type: String type: String
}, },
setup() { setup() {
const columns = [ const isLoaded=ref(false);
{ name: 'name', required: true,label: 'アクション名',align: 'left',field: 'name',sortable: true}, const columns = [
{ name: 'desc', align: 'left', label: '説明', field: 'desc', sortable: true }, { name: 'name', required: true,label: 'アクション名',align: 'left',field: 'name',sortable: true},
// { name: 'content', label: '内容', field: 'content', sortable: true } { name: 'desc', align: 'left', label: '説明', field: 'desc', sortable: true },
] // { name: 'content', label: '内容', field: 'content', sortable: true }
const rows = reactive([]) ];
const rows = reactive([])
onMounted(async () => { onMounted(async () => {
await api.get('http://127.0.0.1:8000/api/kintone/1').then(res =>{ const res =await api.get('api/actions');
res.data.forEach((item) => res.data.forEach((item) =>
{ {
rows.push({name:item.name,desc:item.desc,content:item.content}); rows.push({name:item.name,desc:item.title,outputPoints:item.outputpoints,property:item.property});
} });
) isLoaded.value=true;
});
}); });
return { return {
columns, columns,
@@ -41,7 +44,8 @@ export default {
selected: ref([]), selected: ref([]),
pagination:ref({ pagination:ref({
rowsPerPage:0 rowsPerPage:0
}) }),
isLoaded,
} }
}, },

View File

@@ -36,7 +36,7 @@
import { AppInfo, AppSeed } from './models'; import { AppInfo, AppSeed } from './models';
import { ref, defineComponent, watch, onMounted , toRefs } from 'vue'; import { ref, defineComponent, watch, onMounted , toRefs } from 'vue';
import { api } from 'boot/axios'; import { api } from 'boot/axios';
import { promises } from 'dns'; import { useAuthStore } from 'src/stores/useAuthStore';
export default defineComponent({ export default defineComponent({
props: { props: {
@@ -44,12 +44,13 @@ export default defineComponent({
}, },
setup(props) { setup(props) {
const { app } = toRefs(props); const { app } = toRefs(props);
const authStore = useAuthStore();
const appinfo = ref<AppInfo>({ const appinfo = ref<AppInfo>({
appId: "", appId: "",
name: "", name: "",
description: "" description: ""
}); });
const link= ref('https://mfu07rkgnb7c.cybozu.com/k/' + app.value); const link= ref(`${authStore.currentDomain.kintoneUrl}/k/${app.value}`);
const getAppInfo = async (appId:string|undefined) => { const getAppInfo = async (appId:string|undefined) => {
if(!appId){ if(!appId){
return; return;
@@ -59,7 +60,7 @@ export default defineComponent({
let retry =0; let retry =0;
while(retry<=3 && result && result.appId!==appId){ while(retry<=3 && result && result.appId!==appId){
await new Promise(resolve => setTimeout(resolve, 1000)); await new Promise(resolve => setTimeout(resolve, 1000));
const response = await api.get('app', { const response = await api.get('api/v1/app', {
params:{ params:{
app: appId app: appId
} }
@@ -73,7 +74,7 @@ export default defineComponent({
watch(app, async (newApp) => { watch(app, async (newApp) => {
appinfo.value = await getAppInfo(newApp); appinfo.value = await getAppInfo(newApp);
link.value = 'https://mfu07rkgnb7c.cybozu.com/k/' + newApp; link.value = `${authStore.currentDomain.kintoneUrl}/k/${newApp}`;
}, { immediate: true }); }, { immediate: true });
const linkClick=(ev : MouseEvent)=>{ const linkClick=(ev : MouseEvent)=>{
@@ -82,7 +83,7 @@ export default defineComponent({
}; };
onMounted(async ()=>{ onMounted(async ()=>{
appinfo.value = await getAppInfo(app.value); appinfo.value = await getAppInfo(app.value);
link.value = 'https://mfu07rkgnb7c.cybozu.com/k/' + app.value; link.value = `${authStore.currentDomain.kintoneUrl}/k/${app.value}`;
}); });
return { return {

View File

@@ -1,11 +1,23 @@
<template> <template>
<div class="q-pa-md"> <div class="q-pa-md" >
<q-table :title="name+'一覧'" :selection="type" v-model:selected="selected" :columns="columns" :rows="rows" /> <div v-if="!isLoaded" class="spinner flex flex-center">
<q-spinner color="primary" size="3em" />
</div>
<q-table v-else :selection="type" v-model:selected="selected" :columns="columns" :rows="rows" >
<template v-slot:body-cell-description="props">
<q-td :props="props">
<q-scroll-area class="description-cell">
<div v-html="props.row.description" ></div>
</q-scroll-area>
</q-td>
</template>
</q-table>
</div> </div>
</template> </template>
<script> <script lang="ts">
import { ref,onMounted,reactive } from 'vue' import { ref,onMounted,reactive } from 'vue'
import { api } from 'boot/axios'; import { api } from 'boot/axios';
import { LeftDataBus } from './flowEditor/left/DataBus';
export default { export default {
name: 'AppSelect', name: 'AppSelect',
@@ -15,27 +27,58 @@ export default {
}, },
setup() { setup() {
const columns = [ const columns = [
{ name: 'id', required: true,label: 'アプリID',align: 'left',field: 'id',sortable: true}, { name: 'id', required: true,label: 'ID',align: 'left',field: 'id',sortable: true},
{ name: 'name', align: 'center', label: 'アプリ名', field: 'name', sortable: true }, { name: 'name', label: 'アプリ名', field: 'name', sortable: true,align:'left' },
{ name: 'creator', label: '作成者', field: 'creator', sortable: true }, { name: 'description', label: '概要', field: 'description',align:'left', sortable: false },
{ name: 'createdate', label: '作成日時', field: 'createdate' } { name: 'createdate', label: '作成日時', field: 'createdate',align:'left'}
] ]
const rows = reactive([]) const isLoaded=ref(false);
const rows :any[]= reactive([]);
onMounted( () => { onMounted( () => {
api.get('allapps').then(res =>{ api.get('api/v1/allapps').then(res =>{
res.data.apps.forEach((item) => res.data.apps.forEach((item:any) =>
{ {
rows.push({id:item.appId,name:item.name,creator:item.creator.name,createdate:item.createdAt}); rows.push({
} id:item.appId,
) name:item.name,
}); description:item.description,
createdate:dateFormat(item.createdAt)});
});
isLoaded.value=true;
});
}); });
const dateFormat=(dateStr:string)=>{
const date = new Date(dateStr);
const pad = (num:number) => num.toString().padStart(2, '0');
const year = date.getFullYear();
const month = pad(date.getMonth() + 1);
const day = pad(date.getDate());
const hours = pad(date.getHours());
const minutes = pad(date.getMinutes());
const seconds = pad(date.getSeconds());
return `${year}/${month}/${day} ${hours}:${minutes}:${seconds}`;
}
return { return {
columns, columns,
rows, rows,
selected: ref([]), selected: ref([]),
isLoaded
} }
}, },
} }
</script> </script>
<style lang="scss">
.description-cell{
height: 60px;
width: 300px;
max-height: 60px;
max-width: 300px;
white-space: break-spaces;
}
.spinner{
min-height: 300px;
min-width: 400px;
}
</style>

View File

@@ -0,0 +1,107 @@
<template>
<show-dialog v-model:visible="showflg" name="条件エディタ" @close="closeDg" width="60vw" height="60vh">
<template v-slot:toolbar>
<q-btn flat round dense icon="more_vert" >
<q-menu auto-close anchor="bottom start">
<q-list>
<q-item clickable @click="copyCondition()">
<q-item-section avatar><q-icon name="content_copy" ></q-icon></q-item-section>
<q-item-section >コピー</q-item-section>
</q-item>
<q-item clickable @click="pasteCondition()">
<q-item-section avatar><q-icon name="content_paste" ></q-icon></q-item-section>
<q-item-section >貼り付け</q-item-section>
</q-item>
</q-list>
</q-menu>
</q-btn>
</template>
<NodeCondition v-model:conditionTree="tree"></NodeCondition>
</show-dialog>
</template>
<script lang="ts">
import { defineComponent, ref ,watchEffect} from 'vue';
import ShowDialog from '../../components/ShowDialog.vue';
import NodeCondition from './NodeCondition.vue';
import { ConditionTree } from '../../types/Conditions';
import { useQuasar } from 'quasar';
export default defineComponent({
name: 'ConditionObject',
components: {
ShowDialog,
NodeCondition,
},
props: {
conditionTree: {
type: ConditionTree,
default: null
},
show:{
type:Boolean,
default:false
}
},
emits:[
"closed",
"update:conditionTree",
"update:show"
],
setup(props,context) {
const appDg = ref();
const $q=useQuasar();
const tree = ref(props.conditionTree);
const closeDg = (val:string) => {
if (val == 'OK') {
if(tree.value.root.children.length===0){
$q.notify({
type: 'negative',
message: `条件式を設定してください。`
});
}
context.emit("update:conditionTree",tree.value);
}
showflg.value=false;
context.emit("update:show",false);
context.emit("closed",val);
};
const showflg =ref(props.show);
//条件式をコピーする
const copyCondition=()=>{
if (navigator.clipboard) {
const jsonData=tree.value.toJson();
navigator.clipboard.writeText(jsonData).then(() => {
console.log('Text successfully copied to clipboard');
},
(err) => {
console.error('Error in copying text: ', err);
});
} else {
console.log('Clipboard API not available');
}
};
//条件式を貼り付ける
const pasteCondition=async ()=>{
try {
const text = await navigator.clipboard.readText();
console.log('Text from clipboard:', text);
tree.value.fromJson(text);
} catch (err) {
console.error('Failed to read text from clipboard: ', err);
throw err;
}
}
watchEffect(() => {
showflg.value=props.show;
});
return {
tree,
appDg,
closeDg,
showflg,
copyCondition,
pasteCondition
};
}
});
</script>

View File

@@ -0,0 +1,79 @@
<template>
<q-field v-model="selectedField" labelColor="primary" class="condition-object"
:clearable="isSelected" stack-label :dense="true" :outlined="true" >
<template v-slot:control >
<q-chip color="primary" text-color="white" v-if="isSelected" :dense="true" class="selected-obj">
{{ selectedField.name }}
</q-chip>
</template>
<template v-slot:append>
<q-icon name="search" class="cursor-pointer" @click="showDg"/>
</template>
</q-field>
<show-dialog v-model:visible="show" name="フィールド一覧" @close="closeDg" widht="400px">
<condition-objects ref="appDg" name="フィールド" type="single" :appId="store.appInfo?.appId"></condition-objects>
</show-dialog>
</template>
<script lang="ts">
import { defineComponent, ref ,watchEffect,computed} from 'vue';
import ShowDialog from '../ShowDialog.vue';
import ConditionObjects from '../ConditionObjects.vue';
import { useFlowEditorStore } from '../../stores/flowEditor';
export default defineComponent({
name: 'ConditionObject',
components: {
ShowDialog,
ConditionObjects,
},
props: {
modelValue: {
type: Object,
default: null
},
},
setup(props, { emit }) {
const appDg = ref();
const show = ref(false);
const selectedField = ref(props.modelValue);
const store = useFlowEditorStore();
const isSelected = computed(()=>{
return selectedField.value!==null && typeof selectedField.value === 'object' && ('name' in selectedField.value)
});
const showDg = () => {
show.value = true;
};
const closeDg = (val:string) => {
if (val == 'OK') {
selectedField.value = appDg.value.selected[0];
}
};
watchEffect(() => {
emit('update:modelValue', selectedField.value);
});
return {
store,
appDg,
show,
showDg,
closeDg,
selectedField,
isSelected
};
}
});
</script>
<style lang="scss">
.condition-object{
min-width: 200px;
max-height: 40px;
padding: 2px;
}
.selected-obj{
margin: 0px;
}
</style>

View File

@@ -0,0 +1,273 @@
<template>
<!-- <q-toolbar class="bg-grey-3" flat dense round icon="menu" aria-label="Menu" @click.stop>
<q-toolbar-title>条件エディタ</q-toolbar-title>
<q-space></q-space>
<q-btn flat round dense icon="info" color="blue" @click="showingCondition=!showingCondition"></q-btn>
</q-toolbar> -->
<div class="q-pa-md">
<q-tree :nodes="[tree.root]" node-key="index" children-key="children"
tick-strategy="strict" v-model:ticked="ticked" :expanded="expanded" default-expand-all dense color="primary" >
<template v-slot:header-root="prop">
<!-- root -->
<div class="row items-center" @click.stop>
<q-select v-model="prop.node.logicalOperator" :options="logicalOperators" filled outlined dense></q-select>
<q-btn flat round dense icon="more_horiz" size="sm" >
<q-menu auto-close anchor="top right">
<q-list>
<q-item clickable @click="addGroup(prop.node, LogicalOperator.AND)">
<q-item-section avatar><q-icon name="playlist_add" ></q-icon></q-item-section>
<q-item-section>グループの追加</q-item-section>
</q-item>
<q-item clickable @click="addCondition(prop.node)">
<q-item-section avatar><q-icon name="add_circle_outline" ></q-icon></q-item-section>
<q-item-section >条件式の追加</q-item-section>
</q-item>
</q-list>
</q-menu>
</q-btn>
</div>
</template>
<template v-slot:header-generic="prop">
<!-- logic group -->
<div v-if="prop.node.type !== NodeType.Condition" class="row items-center" @click.stop>
<q-select v-model="prop.node.logicalOperator" :options="logicalOperators" :outlined="true" :filled="true" :dense="true"></q-select>
<q-btn flat round dense icon="more_horiz" size="sm" >
<q-menu auto-close anchor="top right">
<q-list>
<q-item clickable @click="moveUp(prop.node)">
<q-item-section avatar><q-icon name="arrow_upward" ></q-icon></q-item-section>
<q-item-section >一つ上に移動</q-item-section>
</q-item>
<q-item clickable @click="moveDown(prop.node)">
<q-item-section avatar><q-icon name="arrow_downward" ></q-icon></q-item-section>
<q-item-section >一つ下に移動</q-item-section>
</q-item>
<q-separator inset/>
<q-item clickable @click="addGroup(prop.node, LogicalOperator.AND)">
<q-item-section avatar><q-icon name="playlist_add" ></q-icon></q-item-section>
<q-item-section >グループ追加</q-item-section>
</q-item>
<q-item clickable @click="addCondition(prop.node)">
<q-item-section avatar><q-icon name="add_circle_outline" ></q-icon></q-item-section>
<q-item-section >条件式追加</q-item-section>
</q-item>
<q-separator inset/>
<q-item clickable @click="splitGroup(prop.node)">
<q-item-section avatar><q-icon name="playlist_remove" color="negative"></q-icon></q-item-section>
<q-item-section >グループ化解除</q-item-section>
</q-item>
<q-item clickable @click="removeNode(prop.node)">
<q-item-section avatar><q-icon name="delete" color="negative"></q-icon></q-item-section>
<q-item-section >削除</q-item-section>
</q-item>
</q-list>
</q-menu>
</q-btn>
</div>
<!-- condition -->
<div @click.stop @keypress.stop v-else >
<div class="row no-wrap items-center">
<ConditionObject v-bind="prop.node" v-model="prop.node.object" class="col-4"></ConditionObject>
<q-select v-model="prop.node.operator" :options="operators" class="operator" :outlined="true" :dense="true"></q-select>
<q-input v-if="!prop.node.object || !('options' in prop.node.object)"
v-model="prop.node.value"
class="condition-value" :outlined="true" :dense="true" ></q-input>
<q-select v-if="prop.node.object && ('options' in prop.node.object)"
v-model="prop.node.value"
:options="objectValueOptions(prop.node.object.options)"
clearable
value-key="index"
class="condition-value" :outlined="true" :dense="true" ></q-select>
<q-btn flat round dense icon="more_horiz" size="sm" >
<q-menu auto-close anchor="top right">
<q-list>
<q-item clickable @click="moveUp(prop.node)">
<q-item-section avatar><q-icon name="arrow_upward" ></q-icon></q-item-section>
<q-item-section >一つ上に移動</q-item-section>
</q-item>
<q-item clickable @click="moveDown(prop.node)">
<q-item-section avatar><q-icon name="arrow_downward" ></q-icon></q-item-section>
<q-item-section >一つ下に移動</q-item-section>
</q-item>
<q-separator inset/>
<q-item clickable @click="groupMerge(prop.node)" v-if="canMerge(prop.node)">
<q-item-section avatar><q-icon name="playlist_add"></q-icon></q-item-section>
<q-item-section >グループ化</q-item-section>
</q-item>
<q-separator inset/>
<q-item clickable @click="removeNode(prop.node)">
<q-item-section avatar><q-icon name="delete" color="negative"></q-icon></q-item-section>
<q-item-section>削除</q-item-section>
</q-item>
</q-list>
</q-menu>
</q-btn>
</div>
</div>
</template>
</q-tree>
<!-- <q-btn @click="addCondition(tree.root)" class="q-mt-md" color="primary" icon="mdi-plus">Add Condition</q-btn> -->
<!-- <q-btn @click="getConditionString()" class="q-mt-md" color="primary" icon="mdi-plus">Show Condtion</q-btn>
<q-btn @click="getConditionJson()" class="q-mt-md" color="primary" icon="mdi-plus">Show Condtion data</q-btn>
<q-btn @click="LoadCondition()" class="q-mt-md" color="primary" icon="mdi-plus">Load Condition</q-btn> -->
<q-tooltip anchor="center middle" v-model="showingCondition" no-parent-event>
import { finished } from 'stream';
{{ conditionString }}
</q-tooltip>
</div>
</template>
<script lang="ts">
import { defineComponent,ref,reactive, computed } from 'vue';
import { INode,ConditionTree,GroupNode,ConditionNode, LogicalOperator,Operator,NodeType } from '../../types/Conditions';
import ConditionObject from './ConditionObject.vue';
export default defineComponent( {
name: 'NodeCondition',
components: {
ConditionObject
},
props:{
conditionTree: {
type: ConditionTree,
default: null
},
show:{
type:Boolean,
default:false
}
},
setup(props) {
const ticked= ref([]);
const showingCondition=ref(false);
const logicalOperators = computed(()=>{
const opts=[];
for(const op in LogicalOperator){
opts.push(LogicalOperator[op as keyof typeof LogicalOperator]);
}
return opts;
});
const operators =computed(()=>{
const opts=[];
for(const op in Operator){
opts.push(Operator[op as keyof typeof Operator]);
}
return opts;
});
const tree = reactive(props.conditionTree);
const conditionString = computed(()=>{
return tree.buildConditionString(tree.root);
});
const objectValueOptions=(options:any):any[]=>{
const opts:any[] =[];
Object.keys(options).forEach((key) =>
{
const opt=options[key];
opts.push(opt);
});
return opts;
};
const addGroup = (parent:GroupNode, logicOp:LogicalOperator) => {
if(!parent){
parent=tree.root;
}
tree.addNode(parent,new GroupNode(logicOp,parent));
};
const addCondition = (parent:GroupNode) => {
const newNode = new ConditionNode({},Operator.Equal,'',parent);
tree.addNode(parent,newNode);
};
const removeNode = (node:INode) => {
tree.removeNode(node);
};
const moveUp =(node:INode)=>{
tree.moveNode(node,'up');
}
const moveDown =(node:INode)=>{
tree.moveNode(node,'down');
}
const getConditionJson=()=>{
return tree.toJson();
}
//JsonからConditionTreeのインスタンスを作成
const LoadCondition=()=>{
tree.fromJson(conditionString.value);
}
//グループ化
const groupMerge=(node:INode)=>{
const checkedNodes:INode[]=[];
const checkedIndexs:number[] = ticked.value;
checkedIndexs.forEach(index => {
const node = tree.findByIndex(index);
if(node){
checkedNodes.push(node);
}
});
tree.createGroupNode(node,checkedNodes,LogicalOperator.AND);
ticked.value=[];
}
//グループ化可能かをチェックする
const canMerge =(node:INode)=>{
const checkedIndexs:number[] = ticked.value;
const findNode = checkedIndexs.find(index=>node.index===index);
console.log("findNode=>",findNode!==undefined,findNode);
return findNode!==undefined;
}
//グループ化解散
const splitGroup=(node:INode)=>{
tree.dissolveGroupNode(node as GroupNode);
ticked.value=[];
}
const expanded=computed(()=>tree.getGroups(tree.root));
// addCondition(tree.root);
return {
showingCondition,
conditionString,
tree,
ticked,
logicalOperators,
operators,
addGroup,
addCondition,
removeNode,
moveUp,
moveDown,
LogicalOperator,
Operator,
NodeType,
getConditionJson,
LoadCondition,
objectValueOptions,
expanded,
canMerge,
groupMerge,
splitGroup
};
},
});
</script>
<style lang="scss">
.condition-value{
min-width: 200px;
max-height: 40px;
padding: 2px;
}
.operator{
min-width: 150px;
max-height: 40px;
padding: 2px;
text-align: center;
font-size: 12pt;
}
</style>

View File

@@ -0,0 +1,54 @@
<template>
<div class="q-pa-md">
<div v-if="!isLoaded" class="spinner flex flex-center">
<q-spinner color="primary" size="3em" />
</div>
<q-table v-else row-key="name" :selection="type" v-model:selected="selected" :columns="columns" :rows="rows" />
</div>
</template>
<script>
import { ref,onMounted,reactive } from 'vue'
import { api } from 'boot/axios';
export default {
name: 'ConditionObjects',
props: {
name: String,
type: String,
appId:Number
},
setup(props) {
const isLoaded=ref(false);
const columns = [
{ name: 'name', required: true,label: 'フィールド名',align: 'left',field: row=>row.name,sortable: true},
{ name: 'code', label: 'フィールドコード', align: 'left',field: 'code', sortable: true },
{ name: 'type', label: 'フィールドタイプ', align: 'left',field: 'type', sortable: true }
]
const rows = reactive([])
onMounted( async () => {
const res = await api.get('api/v1/appfields', {
params:{
app: props.appId
}
});
let fields = res.data.properties;
console.log(fields);
Object.keys(fields).forEach((key) =>
{
const fld=fields[key];
// rows.push({name:fields[key].label,code:fields[key].code,type:fields[key].type});
rows.push({name:fld.label,objectType:'field',...fld});
});
isLoaded.value=true;
});
return {
columns,
rows,
selected: ref([]),
isLoaded
}
},
}
</script>

View File

@@ -4,6 +4,7 @@
style="max-width: 400px" style="max-width: 400px"
:url="uploadUrl" :url="uploadUrl"
:label="title" :label="title"
:headers="headers"
accept=".csv,.xlsx" accept=".csv,.xlsx"
v-on:rejected="onRejected" v-on:rejected="onRejected"
v-on:uploaded="onUploadFinished" v-on:uploaded="onUploadFinished"
@@ -15,7 +16,10 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { createUploaderComponent, useQuasar } from 'quasar'; import { createUploaderComponent, useQuasar } from 'quasar';
import { useAuthStore } from 'src/stores/useAuthStore';
import { ref } from 'vue';
const $q=useQuasar(); const $q=useQuasar();
const authStore = useAuthStore();
const emit =defineEmits(['uploaded']); const emit =defineEmits(['uploaded']);
/** /**
* ファイルアップロードを拒否する時の処理 * ファイルアップロードを拒否する時の処理
@@ -67,9 +71,12 @@
title: string; title: string;
uploadUrl:string; uploadUrl:string;
} }
const headers = ref([{name:"Authorization",value:'Bearer ' + authStore.token}]);
const props = withDefaults(defineProps<Props>(), { const props = withDefaults(defineProps<Props>(), {
title:"設計書から導入する(csv or excel)", title:"設計書から導入する(csv or excel)",
uploadUrl: `${process.env.KAB_BACKEND_URL}createappfromexcel` uploadUrl: `${process.env.KAB_BACKEND_URL}api/v1/createappfromexcel`
}); });
</script> </script>
<style lang="scss"> <style lang="scss">

View File

@@ -23,7 +23,7 @@ export default {
] ]
const rows = reactive([]) const rows = reactive([])
onMounted( () => { onMounted( () => {
api.get(`http://127.0.0.1:8000/api/domains/testtenant`).then(res =>{ api.get(`api/domains/1`).then(res =>{
res.data.forEach((item) => res.data.forEach((item) =>
{ {
rows.push({id:item.id,tenantid:item.tenantid,name:item.name,url:item.url,kintoneuser:item.kintoneuser}); rows.push({id:item.id,tenantid:item.tenantid,name:item.name,url:item.url,kintoneuser:item.kintoneuser});

View File

@@ -0,0 +1,43 @@
<template>
<q-btn-dropdown
color="primay"
push
flat
no-caps
icon="share"
size="md"
:label="userStore.currentDomain.domainName"
>
<q-list>
<q-item v-for="domain in domains" :key="domain.domainName"
clickable v-close-popup @click="onItemClick(domain)">
<q-item-section side>
<q-icon name="share" size="sm" color="orange" text-color="white"></q-icon>
</q-item-section>
<q-item-section>
<q-item-label>{{domain.domainName}}</q-item-label>
<q-item-label caption>{{domain.kintoneUrl}}</q-item-label>
</q-item-section>
</q-item>
</q-list>
</q-btn-dropdown>
</template>
<script setup lang="ts" >
import { IDomainInfo } from 'src/types/ActionTypes';
import { useAuthStore,IUserState } from 'stores/useAuthStore';
import { ref } from 'vue';
const userStore = useAuthStore();
const domains = ref<IDomainInfo[]>([]);
(async ()=>{
domains.value = await userStore.getUserDomains();
})();
const onItemClick=(domain:IDomainInfo)=>{
console.log(domain);
userStore.setCurrentDomain(domain);
}
</script>
<style lang="scss">
</style>

View File

@@ -1,6 +1,9 @@
<template> <template>
<div class="q-pa-md"> <div class="q-pa-md">
<q-table :title="name+'一覧'" row-key="name" :selection="type" v-model:selected="selected" :columns="columns" :rows="rows" /> <div v-if="!isLoaded" class="spinner flex flex-center">
<q-spinner color="primary" size="3em" />
</div>
<q-table v-else row-key="name" :selection="type" v-model:selected="selected" :columns="columns" :rows="rows" />
</div> </div>
</template> </template>
<script> <script>
@@ -15,32 +18,36 @@ export default {
appId:Number appId:Number
}, },
setup(props) { setup(props) {
const columns = [ const isLoaded=ref(false);
{ name: 'name', required: true,label: 'フィールド名',align: 'left',field: row=>row.name,sortable: true}, const columns = [
{ name: 'code', label: 'フィールドコード', align: 'left',field: 'code', sortable: true }, { name: 'name', required: true,label: 'フィールド',align: 'left',field: row=>row.name,sortable: true},
{ name: 'type', label: 'フィールドタイプ', align: 'left',field: 'type', sortable: true } { name: 'code', label: 'フィールドコード', align: 'left',field: 'code', sortable: true },
] { name: 'type', label: 'フィールドタイプ', align: 'left',field: 'type', sortable: true }
const rows = reactive([]) ]
onMounted( () => { const rows = reactive([])
api.get('appfields', { onMounted( async () => {
params:{ const res = await api.get('api/v1/appfields', {
app: props.appId params:{
} app: props.appId
}).then(res =>{ }
let fields = res.data.properties; });
console.log(fields); let fields = res.data.properties;
Object.keys(fields).forEach((key) => console.log(fields);
{ Object.keys(fields).forEach((key) =>
rows.push({name:fields[key].label,code:fields[key].code,type:fields[key].type}); {
} const fld=fields[key];
) // rows.push({name:fields[key].label,code:fields[key].code,type:fields[key].type});
}); rows.push({name:fld.label,...fld});
}); });
return { isLoaded.value=true;
columns, });
rows,
selected: ref([]), return {
} columns,
rows,
selected: ref([]),
isLoaded
}
}, },
} }

View File

@@ -1,12 +1,17 @@
<template> <template>
<div class="q-pa-md q-gutter-sm"> <!-- <div class="q-pa-md q-gutter-sm" > -->
<q-dialog :model-value="visible" persistent> <q-dialog :model-value="visible" persistent bordered>
<q-card style="min-width: 350px"> <q-card :style="{minWidth : width}" >
<q-toolbar class="bg-grey-4">
<q-toolbar-title>{{ name }}</q-toolbar-title>
<q-space></q-space>
<slot name="toolbar"></slot>
<q-btn flat round dense icon="close" @click="CloseDialogue('Cancel')" />
</q-toolbar>
<q-card-section> <q-card-section>
<div class="text-h6">{{ name }}選択</div> <!-- <div class="text-h6">{{ name }}</div> -->
</q-card-section> </q-card-section>
<q-card-section class="q-pt-none" :style="{...(height? {minHeight:height}:{}) }">
<q-card-section class="q-pt-none">
<slot></slot> <slot></slot>
</q-card-section> </q-card-section>
<q-card-actions align="right" class="text-primary"> <q-card-actions align="right" class="text-primary">
@@ -15,7 +20,7 @@
</q-card-actions> </q-card-actions>
</q-card> </q-card>
</q-dialog> </q-dialog>
</div> <!-- </div> -->
</template> </template>
<script> <script>
@@ -24,6 +29,8 @@ export default {
props: { props: {
name:String, name:String,
visible: Boolean, visible: Boolean,
width:String,
height:String
}, },
emits: [ emits: [
'close' 'close'

View File

@@ -7,7 +7,7 @@
style="font-size: 2em" style="font-size: 2em"
/> />
<div class="col-7 self-center ellipsis"> <div class="col-7 self-center ellipsis">
<a :href="!store.appInfo?'':`https://mfu07rkgnb7c.cybozu.com/k/${store.appInfo?.appId}`" target="_blank" title="Kiontoneへ"> <a :href="!store.appInfo?'':`${authStore.currentDomain.kintoneUrl}/k/${store.appInfo?.appId}`" target="_blank" title="Kiontoneへ">
{{ store.appInfo?.name }} {{ store.appInfo?.name }}
</a> </a>
</div> </div>
@@ -22,7 +22,7 @@
></q-btn> ></q-btn>
</div> </div>
</div> </div>
<ShowDialog v-model:visible="showSelectApp" name="アプリ" @close="closeDg"> <ShowDialog v-model:visible="showSelectApp" name="アプリ選択" @close="closeDg" width="600px" >
<AppSelect ref="appDg" name="アプリ" type="single"></AppSelect> <AppSelect ref="appDg" name="アプリ" type="single"></AppSelect>
</ShowDialog> </ShowDialog>
</template> </template>
@@ -33,6 +33,7 @@ import {AppInfo} from '../../types/ActionTypes'
import ShowDialog from '../../components/ShowDialog.vue'; import ShowDialog from '../../components/ShowDialog.vue';
import AppSelect from '../../components/AppSelect.vue'; import AppSelect from '../../components/AppSelect.vue';
import { useFlowEditorStore } from 'stores/flowEditor'; import { useFlowEditorStore } from 'stores/flowEditor';
import { useAuthStore } from 'src/stores/useAuthStore';
export default defineComponent({ export default defineComponent({
name: 'AppSelector', name: 'AppSelector',
emits:[ emits:[
@@ -45,6 +46,7 @@ export default defineComponent({
setup(props, context) { setup(props, context) {
const store = useFlowEditorStore(); const store = useFlowEditorStore();
const authStore=useAuthStore();
const appDg = ref(); const appDg = ref();
const showSelectApp=ref(false); const showSelectApp=ref(false);
@@ -67,6 +69,7 @@ export default defineComponent({
} }
return { return {
store, store,
authStore,
showSelectApp, showSelectApp,
showAppDialog, showAppDialog,
closeDg, closeDg,

View File

@@ -28,7 +28,6 @@ import { IKintoneEvent } from '../../types/KintoneEvents';
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';
import { useFlowEditorStore } from 'stores/flowEditor'; import { useFlowEditorStore } from 'stores/flowEditor';
import { ActionFlow, ActionNode, RootAction } from 'src/types/ActionTypes'; import { ActionFlow, ActionNode, RootAction } from 'src/types/ActionTypes';
import { S } from 'app/dist/spa/assets/QTable.50486f7c';
export default defineComponent({ export default defineComponent({
name: 'EventTree', name: 'EventTree',
setup(props, context) { setup(props, context) {

View File

@@ -26,7 +26,11 @@
</q-toolbar> </q-toolbar>
<q-separator /> <q-separator />
<q-card-section> <q-card-section>
<div class="text-h7">{{ node.title }}</div> <div class="row">
<span class="text-h7">{{ node.title }}</span>
<q-space></q-space>
<q-chip color="info" text-color="white" size="0.70rem" v-if="varName(node)" clickable>{{ varName(node) }}</q-chip>
</div>
</q-card-section> </q-card-section>
<template v-if="hasBranch"> <template v-if="hasBranch">
<q-separator /> <q-separator />
@@ -134,7 +138,14 @@ export default defineComponent({
*/ */
const onDeleteAllNode=()=>{ const onDeleteAllNode=()=>{
context.emit('deleteAllNextNodes', props.actionNode); context.emit('deleteAllNextNodes', props.actionNode);
} };
/**
* 変数名取得
*/
const varName =(node:IActionNode)=>{
const prop = node.actionProps.find((prop) => prop.props.name === "verName");
return prop?.props.modelValue;
};
return { return {
node: props.actionNode, node: props.actionNode,
isRoot: props.actionNode.isRoot, isRoot: props.actionNode.isRoot,
@@ -145,7 +156,8 @@ export default defineComponent({
onNodeClick, onNodeClick,
onEditNode, onEditNode,
onDeleteNode, onDeleteNode,
onDeleteAllNode onDeleteAllNode,
varName
} }
} }
}); });

View File

@@ -0,0 +1,95 @@
<template>
<q-field v-model="tree" :label="displayName" labelColor="primary" stack-label >
<template v-slot:control >
<q-card flat class="full-width">
<q-card-actions vertical>
<q-btn color="grey-3" text-color="black" @click="showDg()">クリックで設定{{ isSetted?'設定済み':'未設定' }}</q-btn>
</q-card-actions>
<q-card-section class="text-caption" >
<div v-if="!isSetted">{{ placeholder }}</div>
<div v-else>{{ conditionString }}</div>
</q-card-section>
</q-card>
</template>
</q-field>
<condition-editor v-model:show="show" v-model:conditionTree="tree" @closed="onClosed"></condition-editor>
</template>
<script lang="ts">
import { defineComponent, ref ,watchEffect,computed,reactive} from 'vue';
import { ConditionTree,GroupNode,ConditionNode,LogicalOperator,Operator } from 'app/src/types/Conditions';
import ConditionEditor from '../ConditionEditor/ConditionEditor.vue'
export default defineComponent({
name: 'FieldInput',
components: {
ConditionEditor
},
props: {
displayName:{
type: String,
default: '',
},
name:{
type: String,
default: '',
},
placeholder: {
type: String,
default: '',
},
hint:{
type: String,
default: '',
},
modelValue: {
type: String,
default: null
},
},
setup(props, { emit }) {
const appDg = ref();
const show = ref(false);
const tree = reactive(new ConditionTree());
if(props.modelValue && props.modelValue!==''){
tree.fromJson(props.modelValue);
}else{
const newNode = new ConditionNode({},Operator.Equal,'',tree.root);
tree.addNode(tree.root,newNode);
}
const isSetted=ref(props.modelValue && props.modelValue!=='');
const conditionString = computed(()=>{
return tree.buildConditionString(tree.root);
});
const showDg = () => {
show.value = true;
};
const onClosed = (val:string) => {
if (val == 'OK') {
const conditionJson = tree.toJson();
isSetted.value=true;
emit('update:modelValue', conditionJson);
}
};
watchEffect(() => {
const conditionJson = tree.toJson();
emit('update:modelValue', conditionJson);
});
return {
appDg,
isSetted,
show,
showDg,
onClosed,
tree,
conditionString
};
}
});
</script>

View File

@@ -1,6 +1,6 @@
<template> <template>
<q-input v-model="selectedDate" :label="displayName" :placeholder="placeholder" mask="date" :rules="['date']"> <q-input v-model="selectedDate" :label="displayName" :placeholder="placeholder" label-color="primary" mask="date" :rules="['date']" stack-label>
<template v-slot:append> <template v-slot:append>
<q-icon name="event" class="cursor-pointer"> <q-icon name="event" class="cursor-pointer">
<q-popup-proxy cover transition-show="scale" transition-hide="scale"> <q-popup-proxy cover transition-show="scale" transition-hide="scale">
@@ -25,10 +25,18 @@ export default defineComponent({
type: String, type: String,
default: '', default: '',
}, },
name:{
type: String,
default: '',
},
placeholder: { placeholder: {
type: String, type: String,
default: '', default: '',
}, },
hint:{
type: String,
default: '',
},
modelValue: { modelValue: {
type: String, type: String,
default: '', default: '',

View File

@@ -1,19 +1,37 @@
<template> <template>
<q-input v-model="selectedField" :label="displayName" labelColor="" :placeholder="placeholder" clearable > <q-field v-model="selectedField" :label="displayName" labelColor="primary"
<template v-slot:append> :clearable="isSelected" stack-label :bottom-slots="!isSelected" >
<template v-slot:control >
<q-chip color="primary" text-color="white" v-if="isSelected">
{{ selectedField.name }}
</q-chip>
</template>
<!-- <template v-slot:hint v-if="isSelected">
<div> 項目コード<q-chip size="sm" outline color="secondary" text-color="white">{{selectedField.code}}</q-chip></div>
</template> -->
<template v-slot:hint v-if="!isSelected">
{{ placeholder }}
</template>
<template v-slot:append>
<q-icon name="search" class="cursor-pointer" @click="showDg"/> <q-icon name="search" class="cursor-pointer" @click="showDg"/>
</template> </template>
</q-input> </q-field>
<show-dialog v-model:visible="show" name="フィールド一覧" @close="closeDg"> <show-dialog v-model:visible="show" name="フィールド一覧" @close="closeDg" widht="400px">
<field-select ref="appDg" name="フィールド" type="single" :appId="store.appInfo?.appId"></field-select> <field-select ref="appDg" name="フィールド" type="single" :appId="store.appInfo?.appId"></field-select>
</show-dialog> </show-dialog>
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent, ref ,watchEffect} from 'vue'; import { defineComponent, ref ,watchEffect,computed} from 'vue';
import ShowDialog from '../ShowDialog.vue'; import ShowDialog from '../ShowDialog.vue';
import FieldSelect from '../FieldSelect.vue'; import FieldSelect from '../FieldSelect.vue';
import { useFlowEditorStore } from 'stores/flowEditor'; import { useFlowEditorStore } from 'stores/flowEditor';
interface IField{
name:string,
code:string,
type:string
}
export default defineComponent({ export default defineComponent({
name: 'FieldInput', name: 'FieldInput',
components: { components: {
@@ -25,14 +43,22 @@ export default defineComponent({
type: String, type: String,
default: '', default: '',
}, },
name:{
type: String,
default: '',
},
placeholder: { placeholder: {
type: String, type: String,
default: '', default: '',
}, },
modelValue: { hint:{
type: String, type: String,
default: '', default: '',
}, },
modelValue: {
type: Object,
default: null
},
}, },
setup(props, { emit }) { setup(props, { emit }) {
@@ -40,6 +66,9 @@ export default defineComponent({
const show = ref(false); const show = ref(false);
const selectedField = ref(props.modelValue); const selectedField = ref(props.modelValue);
const store = useFlowEditorStore(); const store = useFlowEditorStore();
const isSelected = computed(()=>{
return selectedField.value!==null && typeof selectedField.value === 'object' && ('name' in selectedField.value)
});
const showDg = () => { const showDg = () => {
show.value = true; show.value = true;
@@ -47,7 +76,7 @@ export default defineComponent({
const closeDg = (val:string) => { const closeDg = (val:string) => {
if (val == 'OK') { if (val == 'OK') {
selectedField.value = appDg.value.selected[0].code; selectedField.value = appDg.value.selected[0];
} }
}; };
@@ -62,6 +91,7 @@ export default defineComponent({
showDg, showDg,
closeDg, closeDg,
selectedField, selectedField,
isSelected
}; };
} }
}); });

View File

@@ -1,5 +1,5 @@
<template> <template>
<q-input :label="displayName" :placeholder="placeholder" v-model="inputValue"/> <q-input :label="displayName" v-model="inputValue" label-color="primary" :placeholder="placeholder" stack-label/>
</template> </template>
<script lang="ts"> <script lang="ts">
@@ -12,17 +12,25 @@ export default defineComponent({
type: String, type: String,
default: '', default: '',
}, },
name:{
type: String,
default: '',
},
placeholder: { placeholder: {
type: String, type: String,
default: '', default: '',
}, },
hint:{
type: String,
default: '',
},
modelValue: { modelValue: {
type: String, type: String,
default: '', default: '',
}, },
}, },
setup(props, { emit }) { setup(props , { emit }) {
const inputValue = ref(props.modelValue); const inputValue = ref(props.modelValue);
watchEffect(() => { watchEffect(() => {

View File

@@ -1,5 +1,5 @@
<template> <template>
<q-input :label="displayName" :placeholder="placeholder" v-model="inputValue" autogrow /> <q-input :label="displayName" label-color="primary" v-model="inputValue" :placeholder="placeholder" autogrow stack-label/>
</template> </template>
<script lang="ts"> <script lang="ts">
@@ -12,10 +12,18 @@ export default defineComponent({
type: String, type: String,
default: '', default: '',
}, },
name:{
type: String,
default: '',
},
placeholder: { placeholder: {
type: String, type: String,
default: '', default: '',
}, },
hint:{
type: String,
default: '',
},
modelValue: { modelValue: {
type: String, type: String,
default: '', default: '',

View File

@@ -1,6 +1,6 @@
<template> <template>
<div> <div>
<div v-for="(item, index) in properties" :key="index"> <div v-for="(item, index) in properties" :key="index" >
<component :is="item.component" v-bind="item.props" v-model="item.props.modelValue"></component> <component :is="item.component" v-bind="item.props" v-model="item.props.modelValue"></component>
</div> </div>
</div> </div>
@@ -16,6 +16,7 @@ import SelectBox from '../right/SelectBox.vue';
import DatePicker from '../right/DatePicker.vue'; import DatePicker from '../right/DatePicker.vue';
import FieldInput from '../right/FieldInput.vue'; import FieldInput from '../right/FieldInput.vue';
import MuiltInputText from '../right/MuiltInputText.vue'; import MuiltInputText from '../right/MuiltInputText.vue';
import ConditionInput from '../right/ConditionInput.vue';
import { IActionNode,IActionProperty } from 'src/types/ActionTypes'; import { IActionNode,IActionProperty } from 'src/types/ActionTypes';
export default defineComponent({ export default defineComponent({
@@ -25,7 +26,8 @@ export default defineComponent({
SelectBox, SelectBox,
DatePicker, DatePicker,
FieldInput, FieldInput,
MuiltInputText MuiltInputText,
ConditionInput
}, },
props: { props: {
nodeProps: { nodeProps: {
@@ -45,3 +47,5 @@ export default defineComponent({
} }
}); });
</script> </script>
<style lang="scss">
</style>

View File

@@ -13,7 +13,7 @@
> >
<q-card class="column full-height" style="width: 300px"> <q-card class="column full-height" style="width: 300px">
<q-card-section> <q-card-section>
<div class="text-h6">プロパティ</div> <div class="text-h6">{{ actionNode.subTitle }}設定</div>
</q-card-section> </q-card-section>
<q-card-section class="col q-pt-none"> <q-card-section class="col q-pt-none">
<property-list :node-props="actionProps" v-if="showPanel" ></property-list> <property-list :node-props="actionProps" v-if="showPanel" ></property-list>

View File

@@ -2,13 +2,14 @@ import { api } from 'boot/axios';
export class Auth export class Auth
{ {
async login(user:string,pwd:string):Promise<boolean> async login(user:string,pwd:string):Promise<boolean>
{ {
const params = new URLSearchParams(); const params = new URLSearchParams();
params.append('username', user); params.append('username', user);
params.append('password', pwd); params.append('password', pwd);
try{ try{
const result = await api.post(`http://127.0.0.1:8000/api/token`,params); const result = await api.post(`api/token`,params);
console.info(result); console.info(result);
localStorage.setItem('Token', result.data.access_token); localStorage.setItem('Token', result.data.access_token);
return true; return true;

View File

@@ -8,7 +8,7 @@ export class FlowCtrl
{ {
const flows:ActionFlow[]=[]; const flows:ActionFlow[]=[];
try{ try{
const result = await api.get(`http://127.0.0.1:8000/api/flows/${appId}`); const result = await api.get(`api/flows/${appId}`);
//console.info(result.data); //console.info(result.data);
if(!result.data || !Array.isArray(result.data)){ if(!result.data || !Array.isArray(result.data)){
return []; return [];
@@ -26,7 +26,7 @@ export class FlowCtrl
async SaveFlow(jsonData:any):Promise<boolean> async SaveFlow(jsonData:any):Promise<boolean>
{ {
const result = await api.post('http://127.0.0.1:8000/api/flow',jsonData); const result = await api.post('api/flow',jsonData);
console.info(result.data) console.info(result.data)
return true; return true;
} }
@@ -37,7 +37,7 @@ export class FlowCtrl
*/ */
async UpdateFlow(jsonData:any):Promise<boolean> async UpdateFlow(jsonData:any):Promise<boolean>
{ {
const result = await api.put('http://127.0.0.1:8000/api/flow/' + jsonData.flowid,jsonData); const result = await api.put('api/flow/' + jsonData.flowid,jsonData);
console.info(result.data) console.info(result.data)
return true; return true;
} }
@@ -48,7 +48,7 @@ export class FlowCtrl
*/ */
async depoly(appid:string):Promise<boolean> async depoly(appid:string):Promise<boolean>
{ {
const result = await api.post(`http://127.0.0.1:8000/api/v1/createjstokintone?app=${appid}`); const result = await api.post(`api/v1/createjstokintone?app=${appid}`);
console.info(result.data); console.info(result.data);
return true; return true;
} }

View File

@@ -11,20 +11,12 @@
@click="toggleLeftDrawer" @click="toggleLeftDrawer"
/> />
<q-toolbar-title> <q-toolbar-title>
Kintone App Builder {{ productName }}
<q-badge align="top" outline>V{{ env.version }}</q-badge> <q-badge align="top" outline>V{{ version }}</q-badge>
</q-toolbar-title> </q-toolbar-title>
<domain-selector></domain-selector>
<q-btn color="blue" size="sm" @click="authStore.userdomain()">
{{ authStore.domain }}
</q-btn>
<q-chip>
{{ authStore.name }}
</q-chip>
<q-btn flat round dense icon="logout" @click="authStore.logout()"/> <q-btn flat round dense icon="logout" @click="authStore.logout()"/>
</q-toolbar> </q-toolbar>
</q-header> </q-header>
<q-drawer <q-drawer
@@ -36,7 +28,7 @@
<q-item-label <q-item-label
header header
> >
Essential Links 関連リンク
</q-item-label> </q-item-label>
<EssentialLink <EssentialLink
@@ -56,6 +48,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref } from 'vue'; import { ref } from 'vue';
import EssentialLink, { EssentialLinkProps } from 'components/EssentialLink.vue'; import EssentialLink, { EssentialLinkProps } from 'components/EssentialLink.vue';
import DomainSelector from 'components/DomainSelector.vue';
import { useAuthStore } from 'stores/useAuthStore'; import { useAuthStore } from 'stores/useAuthStore';
const authStore = useAuthStore(); const authStore = useAuthStore();
@@ -72,14 +65,14 @@ const essentialLinks: EssentialLinkProps[] = [
title: 'フローエディター', title: 'フローエディター',
caption: 'flowChart', caption: 'flowChart',
icon: 'account_tree', icon: 'account_tree',
link: '/#/flowEditor2', link: '/#/FlowChart',
target:'_self' target:'_self'
}, },
{ {
title: 'FlowEditor', title: '条件エディター',
caption: 'FlowEditor', caption: 'condition',
icon: 'account_tree', icon: 'tune',
link: '/#/flowEditor', link: '/#/condition',
target:'_self' target:'_self'
}, },
{ {
@@ -159,8 +152,8 @@ const essentialLinks: EssentialLinkProps[] = [
]; ];
const leftDrawerOpen = ref(false) const leftDrawerOpen = ref(false)
const version = process.env.version;
const env=process.env; const productName = process.env.productName;
function toggleLeftDrawer() { function toggleLeftDrawer() {
leftDrawerOpen.value = !leftDrawerOpen.value leftDrawerOpen.value = !leftDrawerOpen.value

View File

@@ -1,46 +1,54 @@
<template> <template>
<q-page> <q-page>
<div class="q-pa-sm q-gutter-sm event-tree "> <q-layout
<q-drawer container
side="left" class="absolute-full shadow-2 rounded-borders"
overlay >
bordered <div class="q-pa-sm q-gutter-sm ">
v-model="drawerLeft" <q-drawer
:show-if-above="false" side="left"
elevated :overlay="true"
> bordered
<div class="" style="padding:10px"> v-model="drawerLeft"
<div class="flex-center " > :show-if-above="false"
<AppSelector /> elevated
</div> >
<div class="flex-center"> <div class="flex-center fixed-top app-selector" >
<EventTree /> <AppSelector />
</div> </div>
<div class="flex-center fixed-bottom bg-grey-3 q-pa-md row ">
<q-btn color="secondary" glossy label="デプロイ" @click="onDeploy" icon="sync"/> <div class="flex-center absolute-full" style="padding-top:65px;padding-left:15px;padding-right:15px;">
<q-space></q-space> <q-scroll-area class="fit" :horizontal-thumb-style="{ opacity: '0' }">
<q-btn color="primary" label="保存" @click="onSaveFlow" icon="save" /> <EventTree />
</div> </q-scroll-area>
</div> </div>
</q-drawer>
</div> <div class="flex-center fixed-bottom bg-grey-3 q-pa-md row ">
<div class="q-pa-md q-gutter-sm"> <q-btn color="secondary" glossy label="デプロイ" @click="onDeploy" icon="sync" :loading="deployLoading" />
<div class="flowchart" v-if="store.currentFlow"> <q-space></q-space>
<node-item v-for="(node,) in store.currentFlow.actionNodes" :key="node.id" <q-btn color="primary" label="保存" @click="onSaveFlow" icon="save" :loading="saveLoading"/>
:isSelected="node===state.activeNode" :actionNode="node" </div>
@addNode="addNode" </q-drawer>
@nodeSelected="onNodeSelected"
@nodeEdit="onNodeEdit"
@deleteNode="onDeleteNode"
@deleteAllNextNodes="onDeleteAllNextNodes"
></node-item>
</div> </div>
</div> <div class="q-pa-md q-gutter-sm">
<div class="flowchart" v-if="store.currentFlow">
<node-item v-for="(node,) in store.currentFlow.actionNodes" :key="node.id"
:isSelected="node===state.activeNode" :actionNode="node"
@addNode="addNode"
@nodeSelected="onNodeSelected"
@nodeEdit="onNodeEdit"
@deleteNode="onDeleteNode"
@deleteAllNextNodes="onDeleteAllNextNodes"
></node-item>
</div>
</div>
<PropertyPanel :actionNode="state.activeNode" v-model:drawerRight="drawerRight"></PropertyPanel>
</q-layout>
<ShowDialog v-model:visible="showAddAction" name="アクション" @close="closeDg" width="350px">
<action-select ref="appDg" name="model" type="single"></action-select>
</ShowDialog>
</q-page> </q-page>
<PropertyPanel :actionNode="state.activeNode" v-model:drawerRight="drawerRight"></PropertyPanel>
<ShowDialog v-model:visible="showAddAction" name="アクション" @close="closeDg">
<action-select ref="appDg" name="model" type="single"></action-select>
</ShowDialog>
</template> </template>
@@ -57,7 +65,10 @@ import AppSelector from 'components/left/AppSelector.vue';
import EventTree from 'components/left/EventTree.vue'; import EventTree from 'components/left/EventTree.vue';
import {FlowCtrl } from '../control/flowctrl'; import {FlowCtrl } from '../control/flowctrl';
import { useQuasar } from 'quasar'; import { useQuasar } from 'quasar';
const drawerLeft = ref(true); const deployLoading = ref(false);
const saveLoading = ref(false);
const drawerLeft = ref(false);
const $q=useQuasar(); const $q=useQuasar();
const store = useFlowEditorStore(); const store = useFlowEditorStore();
// ref関数を使ってtemplateとバインド // ref関数を使ってtemplateとバインド
@@ -81,6 +92,9 @@ const addActionNode=(action:IActionNode)=>{
} }
const addNode=(node:IActionNode,inputPoint:string)=>{ const addNode=(node:IActionNode,inputPoint:string)=>{
if(drawerRight.value){
drawerRight.value=false;
}
showAddAction.value=true; showAddAction.value=true;
prevNodeIfo.value.prevNode=node; prevNodeIfo.value.prevNode=node;
prevNodeIfo.value.inputPoint=inputPoint; prevNodeIfo.value.inputPoint=inputPoint;
@@ -101,19 +115,28 @@ const onNodeEdit=(node:IActionNode)=>{
const onDeleteNode=(node:IActionNode)=>{ const onDeleteNode=(node:IActionNode)=>{
if(!store.currentFlow) return; if(!store.currentFlow) return;
//右パネルが開いている場合、自動閉じる
if(drawerRight.value && state.activeNode.id===node.id){
drawerRight.value=false;
}
store.currentFlow?.removeNode(node); store.currentFlow?.removeNode(node);
} }
const onDeleteAllNextNodes=(node:IActionNode)=>{ const onDeleteAllNextNodes=(node:IActionNode)=>{
if(!store.currentFlow) return; if(!store.currentFlow) return;
//右パネルが開いている場合、自動閉じる
if(drawerRight.value){
drawerRight.value=false;
}
store.currentFlow?.removeAllNext(node.id); store.currentFlow?.removeAllNext(node.id);
} }
const closeDg=(val :any)=>{ const closeDg=(val :any)=>{
console.log("Dialog closed->",val); console.log("Dialog closed->",val);
if (val == 'OK') { if (val == 'OK') {
const data = appDg.value.selected[0]; const data = appDg.value.selected[0];
const actionProps=JSON.parse(data.content); const actionProps=JSON.parse(data.property);
const action = new ActionNode(data.name,data.desc,"",[],actionProps); const outputPoint =JSON.parse(data.outputPoints);
const action = new ActionNode(data.name,data.desc,"",outputPoint,actionProps);
store.currentFlow?.addNode(action, prevNodeIfo.value.prevNode,prevNodeIfo.value.inputPoint); store.currentFlow?.addNode(action, prevNodeIfo.value.prevNode,prevNodeIfo.value.inputPoint);
} }
} }
@@ -130,13 +153,17 @@ const onDeploy= async ()=>{
return; return;
} }
try{ try{
deployLoading.value=true;
await store.deploy(); await store.deploy();
deployLoading.value=false;
$q.notify({ $q.notify({
type: 'positive', type: 'positive',
caption:"通知", caption:"通知",
message: `デプロイを成功しました。` message: `デプロイを成功しました。`
}); });
}catch(error){ }catch(error){
console.error(error);
deployLoading.value=false;
$q.notify({ $q.notify({
type: 'negative', type: 'negative',
caption:"エラー", caption:"エラー",
@@ -157,13 +184,17 @@ const onSaveFlow = async ()=>{
return; return;
} }
try{ try{
saveLoading.value=true;
await store.saveFlow(targetFlow); await store.saveFlow(targetFlow);
saveLoading.value=false;
$q.notify({ $q.notify({
type: 'positive', type: 'positive',
caption:"通知", caption:"通知",
message: `${targetFlow.getRoot()?.subTitle}のフロー設定を保存しました。` message: `${targetFlow.getRoot()?.subTitle}のフロー設定を保存しました。`
}); });
}catch(error){ }catch(error){
console.error(error);
saveLoading.value=false;
$q.notify({ $q.notify({
type: 'negative', type: 'negative',
caption:"エラー", caption:"エラー",
@@ -174,8 +205,9 @@ const onSaveFlow = async ()=>{
} }
const fetchData = async ()=>{ const fetchData = async ()=>{
const flowCtrl = new FlowCtrl(); drawerLeft.value=true;
if(store.appInfo===undefined) return; if(store.appInfo===undefined) return;
const flowCtrl = new FlowCtrl();
const actionFlows = await flowCtrl.getFlows(store.appInfo?.appId); const actionFlows = await flowCtrl.getFlows(store.appInfo?.appId);
if(actionFlows && actionFlows.length>0){ if(actionFlows && actionFlows.length>0){
store.setFlows(actionFlows); store.setFlows(actionFlows);
@@ -195,16 +227,18 @@ onMounted(() => {
</script> </script>
<style lang="scss"> <style lang="scss">
.app-selector{
padding:15px;
z-index: 999;
}
.flowchart{ .flowchart{
padding-top: 10px; padding-top: 10px;
} }
.flow-toolbar{ .flow-toolbar{
opacity: 50%; opacity: 50%;
} }
.flow-container{
height: 91.5dvb;
overflow: hidden;
}
.event-tree .q-drawer { .event-tree .q-drawer {
top:50px; top:50px;
z-index: 999; z-index: 999;

View File

@@ -13,7 +13,7 @@
</div> </div>
</q-page> </q-page>
<PropertyPanel :actionNode="state.activeNode" v-model:drawerRight="drawerRight"></PropertyPanel> <PropertyPanel :actionNode="state.activeNode" v-model:drawerRight="drawerRight"></PropertyPanel>
<show-dialog v-model:visible="showAddAction" name="アクション" @close="closeDg"> <show-dialog v-model:visible="showAddAction" name="アクション" @close="closeDg" width="350px">
<action-select ref="appDg" name="アクション" type="single"></action-select> <action-select ref="appDg" name="アクション" type="single"></action-select>
</show-dialog> </show-dialog>
</template> </template>

View File

@@ -1,117 +0,0 @@
<template>
<div >
<div class="q-ma-md">
<div class="q-gutter-xs row items-start">
<q-btn
size="md"
@click="drawerLeft = !drawerLeft"
icon="keyboard_double_arrow_right"
round
/>
<q-space />
<q-btn
color="white"
size="sm"
text-color="black"
label="キャンセル"
dense
/>
<q-btn
class="q-px-sm"
color="primary"
size="sm"
label="保存する"
@click="save()"
dense
/>
</div>
</div>
<q-layout
container
class="flow-container shadow-2 rounded-borders"
>
<q-drawer side="left" overlay bordered v-model="drawerLeft">
<div class="q-pa-sm fixed-right">
<q-btn
flat
round
color="primary"
icon="close"
@click="drawerLeft = !drawerLeft"
/>
</div>
<div class="q-mt-lg q-pa-sm">
<q-card-section>
<div class="flex-center">
<ItemSelector />
</div>
</q-card-section>
</div>
<q-separator />
<div class="q-mt-md q-pa-sm">
<q-card-section>
<ControlPanel />
</q-card-section>
</div>
<q-separator />
<q-card-actions align="right">
<div class="q-pa-sm">
<q-btn
flat
color="primary"
size="md"
@click="drawerLeft = !drawerLeft"
label="ジャンプ"
dense
/>
</div>
</q-card-actions>
</q-drawer>
<FlowChartTest />
</q-layout>
</div>
</template>
<script setup lang="ts">
import FlowChartTest from 'pages/FlowChartTest.vue';
import ControlPanel from 'components/flowEditor/left/ControlPanelC.vue';
import ItemSelector from 'components/flowEditor/left/ItemSelector.vue';
import { ref } from 'vue';
import { storeToRefs } from 'pinia';
import { useFlowEditorStore } from 'stores/flowEditor';
import { FlowCtrl } from '../control/flowctrl'
const flowCtrl = new FlowCtrl();
const actName = ref('勤怠管理 - 4');
const drawerLeft = ref(false);
const store = useFlowEditorStore();
const { flowNames1 } = storeToRefs(store);
let isNew = ref(true);
const save = () =>{
if(isNew.value)
{
flowCtrl.SaveFlow({appid:'1',flowid:'flow123',eventid:'event123',name:'test',content:'[]'});
isNew.value = false;
}
else
{
flowCtrl.UpdateFlow({appid:'1',flowid:'flow123',eventid:'event123',name:'test',content:'[{"a":"b"}]'});
}
}
</script>
<style lang="scss">
.flow-toolbar{
opacity: 50%;
}
.flow-container{
height: calc(91.5dvb - 50px);
overflow: hidden;
}
</style>

View File

@@ -4,10 +4,9 @@
<q-page class="window-height window-width row justify-center items-center"> <q-page class="window-height window-width row justify-center items-center">
<div class="column q-pa-lg"> <div class="column q-pa-lg">
<div class="row"> <div class="row">
<q-card square class="shadow-24" style="width:400px;height:540px;"> <q-card :square="false" class="shadow-24" style="width:400px;height:540px;">
<q-card-section class="bg-primary"> <q-card-section class="bg-primary">
<h4 class="text-h5 text-white q-my-md">{{ title}}</h4> <h4 class="text-h5 text-white q-my-md">{{ title}}</h4>
</q-card-section> </q-card-section>
<q-card-section> <q-card-section>
<q-form class="q-px-sm q-pt-xl" ref="loginForm"> <q-form class="q-px-sm q-pt-xl" ref="loginForm">
@@ -31,8 +30,13 @@
</q-card-section> </q-card-section>
<q-card-actions class="q-px-lg"> <q-card-actions class="q-px-lg">
<q-btn unelevated size="lg" color="secondary" @click="submit" class="full-width text-white" <q-btn :loading="loading" unelevated size="lg" color="secondary" @click="submit" class="full-width text-white"
:label="btnLabel" /> label="ログイン" >
<template v-slot:loading>
<q-spinner class="on-left" />
ログイン中...
</template>
</q-btn>
</q-card-actions> </q-card-actions>
</q-card> </q-card>
@@ -52,13 +56,13 @@ import { useAuthStore } from 'stores/useAuthStore';
const authStore = useAuthStore(); const authStore = useAuthStore();
const $q = useQuasar() const $q = useQuasar()
const loginForm = ref(null); const loginForm = ref(null);
const loading = ref(false);
let title = ref('ログイン'); let title = ref('ログイン');
let email = ref(''); let email = ref('');
let password = ref(''); let password = ref('');
let visibility = ref(false); let visibility = ref(false);
let passwordFieldType = ref('password'); let passwordFieldType = ref('password');
let visibilityIcon = ref('visibility'); let visibilityIcon = ref('visibility');
let btnLabel = ref('ログイン');
const required = (val:string) => { const required = (val:string) => {
return (val && val.length > 0 || '必須項目') return (val && val.length > 0 || '必須項目')
} }
@@ -74,26 +78,34 @@ import { useAuthStore } from 'stores/useAuthStore';
passwordFieldType.value = visibility.value ? 'text' : 'password' passwordFieldType.value = visibility.value ? 'text' : 'password'
visibilityIcon.value = visibility.value ? 'visibility_off' : 'visibility' visibilityIcon.value = visibility.value ? 'visibility_off' : 'visibility'
} }
const submit = () =>{ const submit = async () =>{
authStore.login(email.value,password.value).then((result)=>{ loading.value=true;
if(result) try {
{ const result = await authStore.login(email.value,password.value);
$q.notify({ loading.value=false;
icon: 'done', if(result){
color: 'positive', $q.notify({
message: 'ログイン成功' icon: 'done',
}) color: 'positive',
message: 'ログイン成功'
});
} }
else else{
{ $q.notify({
$q.notify({ icon: 'error',
icon: 'error', color: 'negative',
color: 'negative', message: 'ログイン失敗'
message: 'ログイン失敗' });
})
} }
}); }catch (error) {
console.error(error);
loading.value=false;
$q.notify({
icon: 'error',
color: 'negative',
message: 'ログイン失敗'
});
}
} }

View File

@@ -22,7 +22,7 @@
<div class="q-pa-md"> <div class="q-pa-md">
<q-select v-model="model" :options="options" label="Standard"/> <q-select v-model="model" :options="options" label="Standard"/>
<q-btn :label="model+'選択'" color="primary" @click="showDg()" /> <q-btn :label="model+'選択'" color="primary" @click="showDg()" />
<show-dialog v-model:visible="show" :name="model" @close="closeDg"> <show-dialog v-model:visible="show" :name="model" @close="closeDg" width="400px">
<template v-if="model=='アプリ'"> <template v-if="model=='アプリ'">
<app-select ref="appDg" :name="model" type="single"></app-select> <app-select ref="appDg" :name="model" type="single"></app-select>
</template> </template>

View File

@@ -1,15 +1,7 @@
<template> <template>
<div class="q-pa-md"> <div class="q-pa-md">
<q-table <q-table title="Treats" :rows="rows" :columns="columns" row-key="id" selection="single" :filter="filter"
title="Treats" :loading="loading" v-model:selected="selected">
:rows="rows"
:columns="columns"
row-key="id"
selection="single"
:filter="filter"
:loading="loading"
v-model:selected="selected"
>
<template v-slot:top> <template v-slot:top>
<q-btn color="primary" :disable="loading" label="新規" @click="addRow" /> <q-btn color="primary" :disable="loading" label="新規" @click="addRow" />
@@ -54,8 +46,8 @@
</q-form> </q-form>
</q-card-section> </q-card-section>
<q-card-actions align="right" class="text-primary"> <q-card-actions align="right" class="text-primary">
<q-btn label="Save" type="submit" color="primary" @click="onSubmit"/> <q-btn label="Save" type="submit" color="primary" @click="onSubmit" />
<q-btn label="Cancel" type="cancel" color="primary" flat class="q-ml-sm" @click="closeDg()"/> <q-btn label="Cancel" type="cancel" color="primary" flat class="q-ml-sm" @click="closeDg()" />
</q-card-actions> </q-card-actions>
</q-card> </q-card>
@@ -70,7 +62,7 @@
<q-card-actions align="right"> <q-card-actions align="right">
<q-btn flat label="Cancel" color="primary" v-close-popup /> <q-btn flat label="Cancel" color="primary" v-close-popup />
<q-btn flat label="OK" color="primary" v-close-popup @click = "deleteDomain()"/> <q-btn flat label="OK" color="primary" v-close-popup @click="deleteDomain()" />
</q-card-actions> </q-card-actions>
</q-card> </q-card>
</q-dialog> </q-dialog>
@@ -79,12 +71,11 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref,onMounted, reactive} from 'vue'; import { ref, onMounted, reactive } from 'vue';
import { useQuasar } from 'quasar'
import { api } from 'boot/axios'; import { api } from 'boot/axios';
const columns = [ const columns = [
{ name: 'id'}, { name: 'id' },
{ {
name: 'tenantid', name: 'tenantid',
required: true, required: true,
@@ -95,131 +86,121 @@ const columns = [
sortable: true sortable: true
}, },
{ name: 'name', align: 'center', label: 'Name', field: 'name', sortable: true }, { name: 'name', align: 'center', label: 'Name', field: 'name', sortable: true },
{ name: 'url', align: 'left',label: 'URL', field: 'url', sortable: true }, { name: 'url', align: 'left', label: 'URL', field: 'url', sortable: true },
{ name: 'user', label: 'Account', field: 'user' }, { name: 'user', label: 'Account', field: 'user' },
{ name: 'password', label: 'Password', field: 'password' } { name: 'password', label: 'Password', field: 'password' }
] ];
const loading = ref(false) const loading = ref(false);
const filter = ref('') const filter = ref('');
const rows = reactive([]) const rows = ref([]);
const show = ref(false); const show = ref(false);
const confirm = ref(false); const confirm = ref(false);
const selected = ref([]) const selected = ref([]);
const tenantid = ref('') const tenantid = ref('');
const name = ref('') const name = ref('');
const url =ref('') const url = ref('');
const isPwd =ref(true) const isPwd = ref(true);
const kintoneuser =ref('') const kintoneuser = ref('');
const kintonepwd =ref('') const kintonepwd = ref('');
const $q = useQuasar() let editId = ref(0);
let editId = ref(0);
const getDomain = () => {
loading.value = true;
api.get(`http://127.0.0.1:8000/api/domains/testtenant`).then(res => {
rows.length = 0;
res.data.forEach((item) => {
rows.push({ id:item.id,tenantid: item.tenantid,name: item.name, url: item.url, user: item.kintoneuser, password: item.kintonepwd });
}
)
}).finally(()=>{ loading.value = false; });
const getDomain = async () => {
loading.value = true;
const result= await api.get(`api/domains/1`);
rows.value= result.data.map((item)=>{
return { id: item.id, tenantid: item.tenantid, name: item.name, url: item.url, user: item.kintoneuser, password: item.kintonepwd }
});
loading.value = false;
} }
onMounted(() => {
getDomain(); onMounted(async () => {
await getDomain();
}) })
// emulate fetching data from server
const addRow = () => {
editId.value
show.value = true;
}
const removeRow = () => {
//loading.value = true
confirm.value = true;
let row = JSON.parse(JSON.stringify(selected.value[0]));
if (selected.value.length === 0) {
return;
}
editId.value = row.id;
}
// emulate fetching data from server const deleteDomain = () => {
const addRow = () => { api.delete(`api/domain/${editId.value}`).then(() => {
editId.value getDomain();
show.value = true; })
} editId.value = 0;
selected.value = [];
};
const removeRow = () => { const editRow = () => {
//loading.value = true if (selected.value.length === 0) {
confirm.value = true; return;
let row = JSON.parse(JSON.stringify(selected.value[0])); }
if(selected.value.length === 0) let row = JSON.parse(JSON.stringify(selected.value[0]));
{ editId.value = row.id;
return; tenantid.value = row.tenantid;
} name.value = row.name;
editId.value = row.id; url.value = row.url;
} kintoneuser.value = row.user;
kintonepwd.value = row.password;
isPwd.value = true;
show.value = true;
};
const closeDg = () => {
show.value = false;
onReset();
}
const deleteDomain = () => { const onSubmit = () => {
api.delete(`http://127.0.0.1:8000/api/domain/`+ editId.value).then(() =>{ if (editId.value !== 0) {
getDomain(); api.put(`api/domain`, {
}) 'id': editId.value,
editId.value = 0; 'tenantid': tenantid.value,
selected.value=[]; 'name': name.value,
}; 'url': url.value,
'kintoneuser': kintoneuser.value,
const editRow = () => { 'kintonepwd': kintonepwd.value
if(selected.value.length === 0) }).then(() => {
{ getDomain();
return; closeDg();
}
let row = JSON.parse(JSON.stringify(selected.value[0]));
editId.value = row.id;
tenantid.value = row.tenantid;
name.value = row.name;
url.value = row.url;
kintoneuser.value = row.user;
kintonepwd.value = row.password;
isPwd.value = true;
show.value = true;
};
const closeDg = () => {
show.value = false;
onReset(); onReset();
} })
}
else {
api.post(`api/domain`, {
'id': 0,
'tenantid': tenantid.value,
'name': name.value,
'url': url.value,
'kintoneuser': kintoneuser.value,
'kintonepwd': kintonepwd.value
}).then(() => {
getDomain();
closeDg();
onReset();
})
}
selected.value = [];
}
const onSubmit = () => { const onReset = () => {
if(editId.value !== 0) name.value = '';
{ url.value = '';
api.put(`http://127.0.0.1:8000/api/domain`,{ kintoneuser.value = '';
'id': editId.value, kintonepwd.value = '';
'tenantid': tenantid.value, isPwd.value = true;
'name': name.value, editId.value = 0;
'url': url.value, }
'kintoneuser': kintoneuser.value,
'kintonepwd': kintonepwd.value
}).then(() =>{
getDomain();
closeDg();
onReset();
})
}
else
{
api.post(`http://127.0.0.1:8000/api/domain`,{
'id': 0,
'tenantid': tenantid.value,
'name': name.value,
'url': url.value,
'kintoneuser': kintoneuser.value,
'kintonepwd': kintonepwd.value
}).then(() =>{
getDomain();
closeDg();
onReset();
})
}
selected.value=[];
}
const onReset = () => {
name.value = '';
url.value = '';
kintoneuser.value = '';
kintonepwd.value ='';
isPwd.value = true;
editId.value = 0;
}
</script> </script>

View File

@@ -150,7 +150,7 @@ export default {
</template> </template>
</q-table> </q-table>
<show-dialog v-model:visible="show" name="ドメイン" @close="closeDg"> <show-dialog v-model:visible="show" name="ドメイン" @close="closeDg" width="350px">
<domain-select ref="domainDg" name="ドメイン" type="multiple"></domain-select> <domain-select ref="domainDg" name="ドメイン" type="multiple"></domain-select>
</show-dialog> </show-dialog>
@@ -179,6 +179,7 @@ import { useAuthStore } from 'stores/useAuthStore';
const authStore = useAuthStore(); const authStore = useAuthStore();
import { api } from 'boot/axios'; import { api } from 'boot/axios';
import { domain } from 'process';
const $q = useQuasar() const $q = useQuasar()
@@ -193,21 +194,14 @@ let activedomainid = ref(0);
const columns = [ const columns = [
{ name: 'id'}, { name: 'id'},
{ {name: 'name',required: true,label: 'Name',align: 'left',field: 'name',sortable: true},
name: 'name',
required: true,
label: 'Name',
align: 'left',
field: row => row.name,
sortable: true
},
{ name: 'url', align: 'center', label: 'Domain', field: 'url', sortable: true }, { name: 'url', align: 'center', label: 'Domain', field: 'url', sortable: true },
{ name: 'kintoneuser', label: 'User', field: 'kintoneuser', sortable: true }, { name: 'kintoneuser', label: 'User', field: 'kintoneuser', sortable: true },
{ name: 'kintonepwd' }, { name: 'kintonepwd' },
{ name: 'active', field: 'active'} { name: 'active', field: 'active'}
] ]
const rows = reactive([]) const rows = ref([] as any[]);
const isActive = (id:number) =>{ const isActive = (id:number) =>{
if(id == activedomainid.value) if(id == activedomainid.value)
@@ -223,7 +217,7 @@ const newDomain = () => {
const activeDomain = (id:number) => { const activeDomain = (id:number) => {
api.put(`http://127.0.0.1:8000/api/activedomain/`+ id).then(() =>{ api.put(`api/activedomain/`+ id).then(() =>{
getDomain(); getDomain();
}) })
}; };
@@ -234,7 +228,7 @@ const deleteConfirm = (row:object) => {
}; };
const deleteDomain = () => { const deleteDomain = () => {
api.delete(`http://127.0.0.1:8000/api/domain/`+ editId.value+'/1').then(() =>{ api.delete(`api/domain/`+ editId.value+'/1').then(() =>{
getDomain(); getDomain();
}) })
editId.value = 0; editId.value = 0;
@@ -248,25 +242,21 @@ const closeDg = (val:string) => {
{ {
dodmainids.push(domains[key].id); dodmainids.push(domains[key].id);
} }
api.post(`http://127.0.0.1:8000/api/domain`, dodmainids).then(() =>{getDomain();}); api.post(`api/domain`, dodmainids).then(() =>{getDomain();});
} }
}; };
const getDomain = () => { const getDomain = async () => {
api.get(`http://127.0.0.1:8000/api/activedomain`).then(res => { const resp = await api.get(`api/activedomain`);
activedomainid.value = res.data.id; activedomainid.value = resp.data.id;
authStore.changedomain(res.data.name); const domainResult = await api.get(`api/domain`);
}); const domains = domainResult.data as any[];
api.get(`http://127.0.0.1:8000/api/domain`).then(res => { rows.value=domains.map((item)=>{
rows.length = 0; return { id:item.id,name: item.name, url: item.url, kintoneuser: item.kintoneuser, kintonepwd: item.kintonepwd}
res.data.forEach((item) => { });
rows.push({ id:item.id,name: item.name, url: item.url, kintoneuser: item.kintoneuser, kintonepwd: item.kintonepwd});
}
)
});
} }
onMounted(() => { onMounted(async () => {
getDomain(); await getDomain();
}) })
const isDomain = (val) =>{ const isDomain = (val) =>{

View File

@@ -0,0 +1,39 @@
<template>
<q-page>
<div class="flowchart">
<q-btn @click="showCondition()" class="q-mt-md" color="primary" icon="mdi-plus">条件エディタ表示</q-btn>
</div>
<condition-editor v-model:show="show" v-model:conditionTree="tree"></condition-editor>
<q-code>{{conditionString}}</q-code>
</q-page>
</template>
<script setup lang="ts">
import {ref,reactive,computed} from 'vue';
import ConditionEditor from '../components/ConditionEditor/ConditionEditor.vue';
import { useFlowEditorStore } from 'stores/flowEditor';
import { ConditionTree,GroupNode,ConditionNode,LogicalOperator,Operator } from 'app/src/types/Conditions';
const store = useFlowEditorStore();
const tree = reactive(new ConditionTree());
const newNode = new ConditionNode({},Operator.Equal,'',tree.root);
tree.addNode(tree.root,newNode);
const show =ref(false);
const showCondition=()=>{
show.value=true;
}
const conditionString = computed(()=>{
return tree.buildConditionString(tree.root);
});
store.setApp({
appId:'146',
name:'トリトン管理部日報'
});
</script>
<style lang="scss">
.flowchart {
padding-top: 10px;
}
</style>

View File

@@ -4,7 +4,7 @@
<div class="q-pa-md column content-center items-center"> <div class="q-pa-md column content-center items-center">
<div> <div>
<q-btn label="アクション選択" color="primary" @click="showDg()" v-if="addshow" /> <q-btn label="アクション選択" color="primary" @click="showDg()" v-if="addshow" />
<show-dialog v-model:visible="show" name="アクション" @close="closeDg"> <show-dialog v-model:visible="show" name="アクション" @close="closeDg" width="350px">
<action-select ref="appDg" name="アクション" type="single"></action-select> <action-select ref="appDg" name="アクション" type="single"></action-select>
</show-dialog> </show-dialog>
</div> </div>

View File

@@ -17,57 +17,60 @@ import { useAuthStore } from 'stores/useAuthStore';
* with the Router instance. * with the Router instance.
*/ */
// export default route(function (/* { store, ssrContext } */) {
// const createHistory = process.env.SERVER
// ? createMemoryHistory
// : (process.env.VUE_ROUTER_MODE === 'history' ? createWebHistory : createWebHashHistory);
// const Router = createRouter({
// scrollBehavior: () => ({ left: 0, top: 0 }),
// routes,
// // Leave this as is and make changes in quasar.conf.js instead!
// // quasar.conf.js -> build -> vueRouterMode
// // quasar.conf.js -> build -> publicPath
// history: createHistory(process.env.VUE_ROUTER_BASE),
// });
// return Router;
// });
const createHistory = process.env.SERVER const createHistory = process.env.SERVER
? createMemoryHistory ? createMemoryHistory
: (process.env.VUE_ROUTER_MODE === 'history' ? createWebHistory : createWebHashHistory); : (process.env.VUE_ROUTER_MODE === 'history' ? createWebHistory : createWebHashHistory);
export const Router = createRouter({ const routerInstance = createRouter({
scrollBehavior: () => ({ left: 0, top: 0 }), scrollBehavior: () => ({ left: 0, top: 0 }),
routes, routes,
// Leave this as is and make changes in quasar.conf.js instead! // Leave this as is and make changes in quasar.conf.js instead!
// quasar.conf.js -> build -> vueRouterMode // quasar.conf.js -> build -> vueRouterMode
// quasar.conf.js -> build -> publicPath // quasar.conf.js -> build -> publicPath
history: createHistory(process.env.VUE_ROUTER_BASE), history: createHistory(process.env.VUE_ROUTER_BASE),
});
export default route(function (/* { store, ssrContext } */) {
return Router;
});
Router.beforeEach(async (to) => {
// clear alert on route change
//const alertStore = useAlertStore();
//alertStore.clear();
// redirect to login page if not logged in and trying to access a restricted page
const publicPages = ['/login'];
const authRequired = !publicPages.includes(to.path);
const authStore = useAuthStore();
if (authRequired && !authStore.token) {
authStore.returnUrl = to.fullPath;
return '/login';
}
}); });
export default route(function (/* { store, ssrContext } */) {
routerInstance.beforeEach(async (to) => {
// clear alert on route change
//const alertStore = useAlertStore();
//alertStore.clear();
// redirect to login page if not logged in and trying to access a restricted page
const publicPages = ['/login'];
const authRequired = !publicPages.includes(to.path);
const authStore = useAuthStore();
if (authRequired && !authStore.token) {
authStore.returnUrl = to.fullPath;
return '/login';
}
});
return routerInstance;
});
export const router = routerInstance;
// const createHistory = process.env.SERVER
// ? createMemoryHistory
// : (process.env.VUE_ROUTER_MODE === 'history' ? createWebHistory : createWebHashHistory);
// export const Router = createRouter({
// scrollBehavior: () => ({ left: 0, top: 0 }),
// routes,
// // Leave this as is and make changes in quasar.conf.js instead!
// // quasar.conf.js -> build -> vueRouterMode
// // quasar.conf.js -> build -> publicPath
// history: createHistory(process.env.VUE_ROUTER_BASE),
// });
// export default route(function (/* { store, ssrContext } */) {
// return Router;
// });

View File

@@ -14,13 +14,13 @@ const routes: RouteRecordRaw[] = [
{ path: 'ruleEditor', component: () => import('pages/RuleEditor.vue') }, { path: 'ruleEditor', component: () => import('pages/RuleEditor.vue') },
{ path: 'test', component: () => import('pages/testQursar.vue') }, { path: 'test', component: () => import('pages/testQursar.vue') },
{ path: 'flow', component: () => import('pages/testFlow.vue') }, { path: 'flow', component: () => import('pages/testFlow.vue') },
{ path: 'flowchart', component: () => import('pages/FlowChartTest.vue') }, { path: 'FlowChartTest', component: () => import('pages/FlowChartTest.vue') },
{ path: 'flowEditor', component: () => import('pages/FlowEditorPage.vue') }, { path: 'flowEditor', component: () => import('pages/FlowEditorPage.vue') },
{ path: 'flowEditor2', component: () => import('pages/FlowChart.vue') }, { path: 'FlowChart', component: () => import('pages/FlowChart.vue') },
{ path: 'flowChart2', component: () => import('pages/FlowEditorPage2.vue') },
{ path: 'right', component: () => import('pages/testRight.vue') }, { path: 'right', component: () => import('pages/testRight.vue') },
{ path: 'domain', component: () => import('pages/TenantDomain.vue') }, { path: 'domain', component: () => import('pages/TenantDomain.vue') },
{ path: 'userdomain', component: () => import('pages/UserDomain.vue') } { path: 'userdomain', component: () => import('pages/UserDomain.vue')},
{ path: 'condition', component: () => import('pages/conditionPage.vue') }
], ],
}, },
// Always leave this as last one, // Always leave this as last one,

View File

@@ -1,20 +1,26 @@
import { defineStore } from 'pinia'; import { defineStore } from 'pinia';
import { api } from 'boot/axios'; import { api } from 'boot/axios';
import { Router } from '../router'; import {router} from 'src/router';
import {IDomainInfo} from '../types/ActionTypes';
export interface IUserState{
token?:string;
returnUrl:string;
currentDomain:IDomainInfo;
}
export const useAuthStore = defineStore({ export const useAuthStore = defineStore({
id: 'auth', id: 'auth',
state: () =>{ state: ():IUserState =>{
const token=localStorage.getItem('token'); const token=localStorage.getItem('token')||'';
if(token && token!==''){ if(token!==''){
api.defaults.headers["Authorization"]='Bearer ' + token; api.defaults.headers["Authorization"]='Bearer ' + token;
} }
return { return {
token: token, token,
name:localStorage.getItem('name'), returnUrl: '',
domain:localStorage.getItem('domain'), currentDomain: JSON.parse(localStorage.getItem('currentDomain')||"{}")
returnUrl: ''
} }
}, },
actions: { actions: {
@@ -23,18 +29,14 @@ export const useAuthStore = defineStore({
params.append('username', username); params.append('username', username);
params.append('password', password); params.append('password', password);
try{ try{
const result = await api.post(`http://127.0.0.1:8000/api/token`,params); const result = await api.post(`api/token`,params);
console.info(result); console.info(result);
this.token =result.data.access_token; this.token =result.data.access_token;
this.name = result.data.user_name;
localStorage.setItem('token', result.data.access_token); localStorage.setItem('token', result.data.access_token);
localStorage.setItem('name', result.data.user_name);
// const config = {headers:{Authorization : 'Bearer ' + this.token}};
api.defaults.headers["Authorization"]='Bearer ' + this.token; api.defaults.headers["Authorization"]='Bearer ' + this.token;
const activedomain = await api.get(`http://127.0.0.1:8000/api/activedomain`); this.currentDomain=await this.getCurrentDomain();
this.domain = activedomain.data.name; localStorage.setItem('currentDomain',JSON.stringify(this.currentDomain));
localStorage.setItem('domain', activedomain.data.name); this.router.push(this.returnUrl || '/');
Router.push(this.returnUrl || '/');
return true; return true;
}catch(e) }catch(e)
{ {
@@ -42,19 +44,38 @@ export const useAuthStore = defineStore({
return false; return false;
} }
}, },
logout() { async getCurrentDomain():Promise<IDomainInfo>{
this.token = null; const activedomain = await api.get(`api/activedomain`);
localStorage.removeItem('token'); return {
localStorage.removeItem('name'); id:activedomain.data.id,
localStorage.removeItem('domain'); domainName:activedomain.data.name,
Router.push('/login'); kintoneUrl:activedomain.data.url
}
}, },
userdomain() { async getUserDomains():Promise<IDomainInfo[]>{
Router.push('/userdomain'); const resp = await api.get(`api/domain`);
}, const domains =resp.data as any[];
changedomain(domain:string){ return domains.map(data=>{
this.domain = domain; return {
localStorage.setItem('domain', domain); id:data.id,
} domainName:data.name,
kintoneUrl:data.url
}
});
},
logout() {
this.token = undefined;
localStorage.removeItem('token');
localStorage.removeItem('currentDomain');
router.push('/login');
},
async setCurrentDomain(domain:IDomainInfo){
if(domain.id===this.currentDomain.id){
return;
}
await api.put(`api/activedomain/${domain.id}`);
this.currentDomain=domain;
localStorage.setItem('currentDomain',JSON.stringify(this.currentDomain));
}
} }
}); });

View File

@@ -1,4 +1,10 @@
import { v4 as uuidv4 } from 'uuid'; import { v4 as uuidv4 } from 'uuid';
export interface IDomainInfo{
id:number;
domainName:string;
kintoneUrl:string;
}
/** /**
* アプリ情報 * アプリ情報
*/ */
@@ -20,6 +26,8 @@ export interface IActionProperty {
//プロパティ表示名 //プロパティ表示名
displayName: string; displayName: string;
placeholder: string; placeholder: string;
//入力提示・説明
hint:string;
//プロパティ設定値 //プロパティ設定値
modelValue: any; modelValue: any;
}; };
@@ -71,12 +79,13 @@ class ActionProperty implements IActionProperty {
// プロパティ表示名 // プロパティ表示名
displayName: string; displayName: string;
placeholder: string; placeholder: string;
hint:string;
// プロパティ設定値 // プロパティ設定値
modelValue: any; modelValue: any;
}; };
static defaultProperty(): IActionProperty { static defaultProperty(): IActionProperty {
return new ActionProperty('InputText', 'displayName', '表示名', '表示を入力してください', ''); return new ActionProperty('InputText', 'displayName', '表示名', '表示を入力してください', '','');
}; };
constructor( constructor(
@@ -84,6 +93,7 @@ class ActionProperty implements IActionProperty {
name: string, name: string,
displayName: string, displayName: string,
placeholder: string, placeholder: string,
hint:string,
modelValue: any modelValue: any
) { ) {
this.component = component; this.component = component;
@@ -91,6 +101,7 @@ class ActionProperty implements IActionProperty {
name: name, name: name,
displayName: displayName, displayName: displayName,
placeholder: placeholder, placeholder: placeholder,
hint:hint,
modelValue: modelValue modelValue: modelValue
}; };
} }

View File

@@ -0,0 +1,336 @@
//ノード種別
export enum NodeType{
Root = 'root',
LogicGroup ='logicgroup',
Condition ='condition'
}
//ロジックオペレーター
export enum LogicalOperator{
AND = 'AND',
OR = 'OR'
}
//条件オペレーター
export enum Operator{
Equal = '=',
NotEqual='!=',
Greater = '>',
GreaterOrEqual = '>=',
Less = '<',
LessOrEqual = '<=',
Contains = 'contains',
NotContains = 'not contains',
StartWith = 'start With',
EndWith = 'end with',
NotStartWith = 'not start with',
NotEndWith = 'not end with'
}
// INode
export interface INode {
index:number;
type: NodeType;
header:string;
parent: INode | null;
logicalOperator:LogicalOperator
}
// ロジックノード
export class GroupNode implements INode {
index:number;
type: NodeType;
children: INode[];
parent: INode | null;
logicalOperator: LogicalOperator;
get label():string{
return this.logicalOperator;
}
get header():string{
return this.type===NodeType.Root?'root':'generic';
}
get expanded():boolean{
return this.children.length>0;
}
constructor(logicOp:LogicalOperator, parent: INode | null) {
this.index=0;
this.type = parent==null?NodeType.Root: NodeType.LogicGroup;
this.logicalOperator = logicOp;
this.parent=parent;
this.children=[];
}
static fromJSON(json: any, parent: INode | null = null): GroupNode {
const node = new GroupNode(json.logicalOperator, parent);
node.index=json.index;
node.children = json.children.map((childJson: any) => {
return childJson.type === NodeType.LogicGroup
? GroupNode.fromJSON(childJson, node)
: ConditionNode.fromJSON(childJson, node);
});
return node;
}
}
// 条件式ノード
export class ConditionNode implements INode {
index: number;
type: NodeType;
parent:INode;
get logicalOperator(): LogicalOperator{
return this.parent.logicalOperator;
};
object: any; // 比較元
operator: Operator; // 比較子
value: any;
get header():string{
return 'generic';
}
constructor(object: any, operator: Operator, value: any, parent: GroupNode) {
this.index=0;
this.type = NodeType.Condition;
this.object = object;
this.operator = operator;
this.value = value;
this.parent=parent;
}
static fromJSON(json: any, parent: GroupNode): ConditionNode {
const node= new ConditionNode(
json.object,
json.operator,
json.value,
parent
);
node.index=json.index;
return node;
}
}
// 条件式の管理クラス
export class ConditionTree {
root: GroupNode;
maxIndex:number;
constructor() {
this.maxIndex=0;
this.root = new GroupNode(LogicalOperator.AND, null);
}
// ノード追加
addNode(parent: GroupNode, node: INode): void {
this.maxIndex++;
node.index=this.maxIndex;
parent.children.push(node);
}
// ノード削除
removeNode(node: INode): void {
if (node.parent === null) {
throw new Error('ルートノード削除できません');
} else {
const parent = node.parent as GroupNode;
const index = parent.children.indexOf(node);
if (index > -1) {
parent.children.splice(index, 1);
}
}
}
findByIndex(index:number):INode|undefined{
return this.findChildren(this.root,index);
}
findChildren(parent: GroupNode, index: number): INode | undefined {
if (parent.index === index) {
return parent;
}
for (const node of parent.children) {
if (node.index === index) {
return node;
}
if (node.type !== NodeType.Condition) {
const foundNode = this.findChildren(node as GroupNode, index);
if (foundNode) {
return foundNode;
}
}
}
return undefined;
}
getMaxIndex(node:INode):number{
let maxIndex:number=node.index;
if(node.type!==NodeType.Condition){
const groupNode = node as GroupNode;
groupNode.children.forEach((child)=>{
const childMax = this.getMaxIndex(child);
if(childMax>maxIndex){
maxIndex=childMax;
}
});
}
return maxIndex;
}
//条件式を表示する
buildConditionString(node:INode){
if (node.type !== NodeType.Condition) {
let conditionString = '(';
const groupNode = node as GroupNode;
for (let i = 0; i < groupNode.children.length; i++) {
const childConditionString = this.buildConditionString(groupNode.children[i]);
if (childConditionString !== '') {
conditionString += childConditionString;
if (i < groupNode.children.length - 1) {
conditionString += ` ${groupNode.logicalOperator} `;
}
}
}
conditionString += ')';
return conditionString;
} else {
const condNode=node as ConditionNode;
if (condNode.object && condNode.operator ) {
let value=condNode.value;
if(value && typeof value ==='object' && ('label' in value)){
value =condNode.value.label;
}
return `${condNode.object.name} ${condNode.operator} '${value}'`;
} else {
return '';
}
}
}
/**
*
* @param node ノード移動
* @param direction
* @returns
*/
moveNode(node:INode, direction: 'up' | 'down'): void {
if (!node || !node.parent) {
return;
}
const parent = node.parent as GroupNode;
const currentIndex = parent.children.findIndex(child => child === node);
if (currentIndex === -1) {
return;
}
const newIndex = direction === 'up' ? currentIndex - 1 : currentIndex + 1;
// 範囲外のインデックスの処理
if (newIndex >= 0 && newIndex < parent.children.length) {
// ノードの位置を入れ替える
[parent.children[currentIndex], parent.children[newIndex]] = [parent.children[newIndex], parent.children[currentIndex]];
return; // 範囲外なら移動しない
}else if(newIndex<0 && parent.parent){
this.removeNode(node);
const parentIndex = (parent.parent as GroupNode).children.findIndex(child => child === parent);
(parent.parent as GroupNode).children.splice(parentIndex, 0, node);
node.parent = parent.parent;
}
}
getGroups(parent:GroupNode):number[]{
const groups:number[]=[];
groups.push(parent.index);
parent.children.forEach((node)=>{
if(node.type!==NodeType.Condition){
groups.push(...this.getGroups(node as GroupNode));
}
});
return groups;
}
/**
* 条件ノードをグループ化
* @param nodes 結合ノードを選択する
* @param logicOp
* @returns
*/
createGroupNode(firstNode:INode,nodes: INode[], logicOp: LogicalOperator): GroupNode | null {
if (nodes.length === 0) {
return null;
}
// 最初のノードの親ノードを取得
const parent = firstNode.parent as GroupNode;
if (!parent) {
throw new Error('ルートノードをグループ化できません');
}
// 親ノードを取得
const filteredNodes = nodes.filter(node => node.parent === parent);
// 新しいグループノードを作成
const newGroup = new GroupNode(logicOp, parent);
this.maxIndex++;
newGroup.index = this.maxIndex;
// 新しいグループノードの挿入位置を取得
let firstNodeIndex = parent.children.length;
if (filteredNodes.length > 0) {
firstNodeIndex = parent.children.indexOf(filteredNodes[0]);
}
filteredNodes.forEach(node => {
// 元の親ノードから削除する
const nodeIndex = parent.children.indexOf(node);
parent.children.splice(nodeIndex, 1);
// 新しいグループに追加
node.parent = newGroup;
newGroup.children.push(node);
});
// 新しいGroupNodeを挿入する
parent.children.splice(firstNodeIndex, 0, newGroup);
return newGroup;
}
/**
* GroupNodeを解散する
* @param groupNode 対象グループノード
*/
dissolveGroupNode(groupNode: GroupNode): void {
if (groupNode.parent === null || groupNode.type !== NodeType.LogicGroup) {
throw new Error('ルートノードと非グループノードを解散することができません');
}
// 親ノードを取得
const parent = groupNode.parent as GroupNode;
const groupIndex = parent.children.indexOf(groupNode);
// 子ノードをリセットする
groupNode.children.forEach(child => {
child.parent = parent;
parent.children.splice(groupIndex, 0, child);
});
//グループノードを削除する
parent.children.splice(groupIndex + groupNode.children.length, 1);
}
// Jsonから復元
fromJson(jsonString: string): INode {
const json = JSON.parse(jsonString);
this.root = GroupNode.fromJSON(json) as GroupNode;
this.maxIndex=this.getMaxIndex(this.root);
return this.root;
}
toJson():string{
return JSON.stringify(this.root, (key, value) => {
if (key === 'parent') {
return value ? value.type : null;
}
return value;
});
}
}

View File

@@ -12,6 +12,8 @@
}, },
"devDependencies": { "devDependencies": {
"@types/jquery": "^3.5.24", "@types/jquery": "^3.5.24",
"@types/node": "^20.8.9",
"sass": "^1.69.5",
"typescript": "^5.0.2", "typescript": "^5.0.2",
"vite": "^4.4.5" "vite": "^4.4.5"
} }
@@ -377,12 +379,82 @@
"@types/sizzle": "*" "@types/sizzle": "*"
} }
}, },
"node_modules/@types/node": {
"version": "20.11.0",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.0.tgz",
"integrity": "sha512-o9bjXmDNcF7GbM4CNQpmi+TutCgap/K3w1JyKgxAjqx41zp9qlIAVFi0IhCNsJcXolEqLWhbFbEeL0PvYm4pcQ==",
"dev": true,
"dependencies": {
"undici-types": "~5.26.4"
}
},
"node_modules/@types/sizzle": { "node_modules/@types/sizzle": {
"version": "2.3.5", "version": "2.3.5",
"resolved": "https://registry.npmjs.org/@types/sizzle/-/sizzle-2.3.5.tgz", "resolved": "https://registry.npmjs.org/@types/sizzle/-/sizzle-2.3.5.tgz",
"integrity": "sha512-tAe4Q+OLFOA/AMD+0lq8ovp8t3ysxAOeaScnfNdZpUxaGl51ZMDEITxkvFl1STudQ58mz6gzVGl9VhMKhwRnZQ==", "integrity": "sha512-tAe4Q+OLFOA/AMD+0lq8ovp8t3ysxAOeaScnfNdZpUxaGl51ZMDEITxkvFl1STudQ58mz6gzVGl9VhMKhwRnZQ==",
"dev": true "dev": true
}, },
"node_modules/anymatch": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
"integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
"dev": true,
"dependencies": {
"normalize-path": "^3.0.0",
"picomatch": "^2.0.4"
},
"engines": {
"node": ">= 8"
}
},
"node_modules/binary-extensions": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
"integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==",
"dev": true,
"engines": {
"node": ">=8"
}
},
"node_modules/braces": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
"integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
"dev": true,
"dependencies": {
"fill-range": "^7.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/chokidar": {
"version": "3.5.3",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz",
"integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==",
"dev": true,
"funding": [
{
"type": "individual",
"url": "https://paulmillr.com/funding/"
}
],
"dependencies": {
"anymatch": "~3.1.2",
"braces": "~3.0.2",
"glob-parent": "~5.1.2",
"is-binary-path": "~2.1.0",
"is-glob": "~4.0.1",
"normalize-path": "~3.0.0",
"readdirp": "~3.6.0"
},
"engines": {
"node": ">= 8.10.0"
},
"optionalDependencies": {
"fsevents": "~2.3.2"
}
},
"node_modules/esbuild": { "node_modules/esbuild": {
"version": "0.18.20", "version": "0.18.20",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.20.tgz", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.20.tgz",
@@ -420,6 +492,18 @@
"@esbuild/win32-x64": "0.18.20" "@esbuild/win32-x64": "0.18.20"
} }
}, },
"node_modules/fill-range": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
"integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
"dev": true,
"dependencies": {
"to-regex-range": "^5.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/fsevents": { "node_modules/fsevents": {
"version": "2.3.3", "version": "2.3.3",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
@@ -434,6 +518,66 @@
"node": "^8.16.0 || ^10.6.0 || >=11.0.0" "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
} }
}, },
"node_modules/glob-parent": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
"integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
"dev": true,
"dependencies": {
"is-glob": "^4.0.1"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/immutable": {
"version": "4.3.4",
"resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.4.tgz",
"integrity": "sha512-fsXeu4J4i6WNWSikpI88v/PcVflZz+6kMhUfIwc5SY+poQRPnaf5V7qds6SUyUN3cVxEzuCab7QIoLOQ+DQ1wA==",
"dev": true
},
"node_modules/is-binary-path": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
"integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
"dev": true,
"dependencies": {
"binary-extensions": "^2.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/is-extglob": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
"integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
"dev": true,
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/is-glob": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
"integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
"dev": true,
"dependencies": {
"is-extglob": "^2.1.1"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/is-number": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
"dev": true,
"engines": {
"node": ">=0.12.0"
}
},
"node_modules/jquery": { "node_modules/jquery": {
"version": "3.7.1", "version": "3.7.1",
"resolved": "https://registry.npmjs.org/jquery/-/jquery-3.7.1.tgz", "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.7.1.tgz",
@@ -457,12 +601,33 @@
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
} }
}, },
"node_modules/normalize-path": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
"integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
"dev": true,
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/picocolors": { "node_modules/picocolors": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
"integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==",
"dev": true "dev": true
}, },
"node_modules/picomatch": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
"dev": true,
"engines": {
"node": ">=8.6"
},
"funding": {
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/postcss": { "node_modules/postcss": {
"version": "8.4.31", "version": "8.4.31",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz",
@@ -491,6 +656,18 @@
"node": "^10 || ^12 || >=14" "node": "^10 || ^12 || >=14"
} }
}, },
"node_modules/readdirp": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
"integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
"dev": true,
"dependencies": {
"picomatch": "^2.2.1"
},
"engines": {
"node": ">=8.10.0"
}
},
"node_modules/rollup": { "node_modules/rollup": {
"version": "3.29.4", "version": "3.29.4",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-3.29.4.tgz", "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.29.4.tgz",
@@ -507,6 +684,23 @@
"fsevents": "~2.3.2" "fsevents": "~2.3.2"
} }
}, },
"node_modules/sass": {
"version": "1.69.7",
"resolved": "https://registry.npmjs.org/sass/-/sass-1.69.7.tgz",
"integrity": "sha512-rzj2soDeZ8wtE2egyLXgOOHQvaC2iosZrkF6v3EUG+tBwEvhqUCzm0VP3k9gHF9LXbSrRhT5SksoI56Iw8NPnQ==",
"dev": true,
"dependencies": {
"chokidar": ">=3.0.0 <4.0.0",
"immutable": "^4.0.0",
"source-map-js": ">=0.6.2 <2.0.0"
},
"bin": {
"sass": "sass.js"
},
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/source-map-js": { "node_modules/source-map-js": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz",
@@ -516,6 +710,18 @@
"node": ">=0.10.0" "node": ">=0.10.0"
} }
}, },
"node_modules/to-regex-range": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
"dev": true,
"dependencies": {
"is-number": "^7.0.0"
},
"engines": {
"node": ">=8.0"
}
},
"node_modules/typescript": { "node_modules/typescript": {
"version": "5.2.2", "version": "5.2.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz",
@@ -529,6 +735,12 @@
"node": ">=14.17" "node": ">=14.17"
} }
}, },
"node_modules/undici-types": {
"version": "5.26.5",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
"integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==",
"dev": true
},
"node_modules/vite": { "node_modules/vite": {
"version": "4.5.0", "version": "4.5.0",
"resolved": "https://registry.npmjs.org/vite/-/vite-4.5.0.tgz", "resolved": "https://registry.npmjs.org/vite/-/vite-4.5.0.tgz",

View File

@@ -6,10 +6,13 @@
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",
"build": "tsc && vite build && xcopy dist\\*.js ..\\..\\backend\\Temp\\ /E /I /Y", "build": "tsc && vite build && xcopy dist\\*.js ..\\..\\backend\\Temp\\ /E /I /Y",
"build:dev":"tsc && set \"SOURCE_MAP=true\" && vite build && xcopy dist\\*.js ..\\..\\backend\\Temp\\ /E /I /Y",
"preview": "vite preview" "preview": "vite preview"
}, },
"devDependencies": { "devDependencies": {
"@types/jquery": "^3.5.24", "@types/jquery": "^3.5.24",
"@types/node": "^20.8.9",
"sass": "^1.69.5",
"typescript": "^5.0.2", "typescript": "^5.0.2",
"vite": "^4.4.5" "vite": "^4.4.5"
}, },

View File

@@ -0,0 +1,142 @@
import { actionAddins } from ".";
import { IField, IAction,IActionResult, IActionNode, IActionProperty, IContext } from "../types/ActionTypes";
import { Formatter } from "../util/format";
declare global {
interface Window { $format: any; }
}
interface IAutoNumberingProps{
//文書番号を格納する
field:IField;
format:string;
prefix:string;
suffix:string;
verName:string;
}
export class AutoNumbering implements IAction{
name: string;
actionProps: IActionProperty[];
props:IAutoNumberingProps;
constructor(){
this.name="自動採番する";
this.actionProps=[];
this.register();
this.props={
field:{code:''},
format:'',
prefix:'',
suffix:'',
verName:''
}
globalThis.window.$format=this.format;
this.register();
}
async process(actionNode:IActionNode,event:any,context:IContext):Promise<IActionResult> {
let result={
canNext:false,
result:false
};
try{
this.actionProps=actionNode.actionProps;
if (!('field' in actionNode.ActionValue) && !('format' in actionNode.ActionValue)) {
return result
}
this.props = actionNode.ActionValue as IAutoNumberingProps;
const record = event.record;
const docNum = await this.createNumber(this.props);
record[this.props.field.code].value=docNum;
//変数設定
if(this.props.verName){
context.variables[this.props.verName]=docNum;
}
result= {
canNext:true,
result:true
}
return result;
}catch(error){
console.error(error);
event.error="処理中異常が発生しました。";
return {
canNext:false,
result:false
}
}
}
execTemplate(template:string):string{
if(template===undefined) return '';
const regex = /\$\{([^}]+)\}/g;
return template.replace(regex,(match,expr)=>{
return this.execEval(match,expr);
});
}
execEval(match:string,expr:string):string{
console.log(match);
return eval(expr);
}
format(pattern:string):string{
const now=new Date();
return Formatter.dateFormat(now, pattern);
}
async createNumber(props:IAutoNumberingProps){
let number :string='';
let prefix:string='';
let suffix:string='';
let no=1;
try{
no = await this.fetchNo();
}catch(error){
console.log(error);
}
if(props.format!==undefined && props.format!==''){
number=Formatter.numberFormat(no, props.format);
}else{
number=no.toString(10);
}
if(props.prefix!==undefined && props.prefix!==''){
prefix=this.execTemplate(props.prefix);
}
if(props.suffix!==undefined && props.suffix!==''){
suffix=this.execTemplate(props.suffix);
}
return `${prefix}${number}${suffix}`;
}
async fetchNo():Promise<number>{
let recNo=1;
return await new kintone.Promise<number>((resolve,reject)=>{
const appurl = kintone.api.url('/k/v1/records',true);
const params={
app:kintone.app.getId(),
fields:['$id'],
query:'limit 1'
};
return kintone.api(appurl,'GET',params).then((resp)=>{
if(resp.records[0]!==null){
recNo = parseInt(resp.records[0].$id.value,10)+1;
}
resolve(recNo);
}).catch((error)=>{
reject(error);
});
});
}
register(): void {
actionAddins[this.name]=this;
}
}
new AutoNumbering();

View File

@@ -0,0 +1,108 @@
import { actionAddins } from ".";
import { IAction,IActionResult, IActionNode, IActionProperty, IField, IContext } from "../types/ActionTypes";
import { ConditionTree } from '../types/Conditions';
/**
* アクションの属性定義
*/
interface IShownProps{
field:IField;
show:string;
condition:string;
}
/**
* 表示/非表示アクション
*/
export class FieldShownAction implements IAction{
name: string;
actionProps: IActionProperty[];
props:IShownProps;
constructor(){
this.name="表示/非表示";
this.actionProps=[];
this.props={
field:{code:''},
show:'',
condition:''
}
//アクションを登録する
this.register();
}
/**
* アクションの実行を呼び出す
* @param actionNode
* @param event
* @returns
*/
async process(actionNode:IActionNode,event:any,context:IContext):Promise<IActionResult> {
let result={
canNext:true,
result:false
};
try{
//属性設定を取得する
this.actionProps=actionNode.actionProps;
if (!('field' in actionNode.ActionValue) && !('show' in actionNode.ActionValue)) {
return result
}
this.props = actionNode.ActionValue as IShownProps;
//条件式の計算結果を取得
const conditionResult = this.getConditionResult(context);
if(conditionResult){
if(this.props.show==='表示'){
kintone.app.record.setFieldShown(this.props.field.code,true);
}else if (this.props.show==='非表示'){
kintone.app.record.setFieldShown(this.props.field.code,false);
}
}
result= {
canNext:true,
result:true
}
return result;
}catch(error){
event.error=error;
console.error(error);
result.canNext=false;
return result;
}
}
/**
*
* @param context 条件式を実行する
* @returns
*/
getConditionResult(context:any):boolean{
const tree =this.getCondition(this.props.condition);
if(!tree){
//条件を設定されていません
return true;
}
return tree.evaluate(tree.root,context);
}
/**
* @param condition 条件式ツリーを取得する
* @returns
*/
getCondition(condition:string):ConditionTree|null{
try{
const tree = new ConditionTree();
tree.fromJson(condition);
if(tree.getConditions(tree.root).length>0){
return tree;
}else{
return null;
}
}catch(error){
return null;
}
}
register(): void {
actionAddins[this.name]=this;
}
}
new FieldShownAction();

View File

@@ -1,9 +1,9 @@
import { actionAddins } from "."; import { actionAddins } from ".";
import { IAction,IActionResult, IActionNode, IActionProperty } from "../types/ActionTypes"; import { IAction,IActionResult, IActionNode, IActionProperty, IField } from "../types/ActionTypes";
interface IMustInputProps{ interface IMustInputProps{
field:string; field:IField;
message:string; message:string;
} }
@@ -16,13 +16,13 @@ export class MustInputAction implements IAction{
this.actionProps=[]; this.actionProps=[];
this.register(); this.register();
this.props={ this.props={
field:'', field:{code:''},
message:'' message:''
} }
this.register(); this.register();
} }
process(actionNode:IActionNode,event:any):IActionResult { async process(actionNode:IActionNode,event:any):Promise<IActionResult> {
let result={ let result={
canNext:true, canNext:true,
result:false result:false
@@ -33,9 +33,9 @@ export class MustInputAction implements IAction{
} }
this.props = actionNode.ActionValue as IMustInputProps; this.props = actionNode.ActionValue as IMustInputProps;
const record = event.record; const record = event.record;
const value = record[this.props.field]?.value; const value = record[this.props.field.code]?.value;
if(value===undefined || value===''){ if(value===undefined || value===''){
record[this.props.field].error=this.props.message; record[this.props.field.code].error=this.props.message;
return result; return result;
} }
result= { result= {

View File

@@ -15,12 +15,12 @@ declare const alcflow : {
$(function (){ $(function (){
const events=Object.keys(alcflow); const events=Object.keys(alcflow);
kintone.events.on(events,(event:any)=>{ kintone.events.on(events,async (event:any)=>{
const flowinfo = alcflow[event.type]; const flowinfo = alcflow[event.type];
const flow=ActionFlow.fromJSON(flowinfo.content); const flow=ActionFlow.fromJSON(flowinfo.content);
if(flow!==undefined){ if(flow!==undefined){
const process = new ActionProcess(event.type,flow,event); const process = new ActionProcess(event.type,flow,event);
process.exec(); await process.exec();
} }
return event; return event;
}); });

View File

@@ -61,13 +61,36 @@ export interface IActionResult{
result?:any; result?:any;
} }
/**
* コンテキスト
* レコードとフローの変数を持つ
*/
export interface IContext{
record:any,
variables:any
}
/**
* アクションのインターフェース
*/
export interface IAction{ export interface IAction{
//アクションの名前(ユーニック名が必要)
name:string; name:string;
//属性設定情報
actionProps: Array<IActionProperty>; actionProps: Array<IActionProperty>;
process(prop:IActionNode,event:any):IActionResult; //アクションのプロセス実行関数
process(prop:IActionNode,event:any,context:IContext):Promise<IActionResult>;
//アクションの登録関数
register():void; register():void;
} }
export interface IField{
name?:string;
code:string;
type?:string;
}
/** /**
* アクションのプロパティ定義に基づいたクラス * アクションのプロパティ定義に基づいたクラス
*/ */

View File

@@ -0,0 +1,473 @@
import { IContext } from "./ActionTypes";
//ノード種別
export enum NodeType{
Root = 'root',
LogicGroup ='logicgroup',
Condition ='condition'
}
//ロジックオペレーター
export enum LogicalOperator{
AND = 'AND',
OR = 'OR'
}
//条件オペレーター
export enum Operator{
Equal = '=',
NotEqual='!=',
Greater = '>',
GreaterOrEqual = '>=',
Less = '<',
LessOrEqual = '<=',
Contains = 'contains',
NotContains = 'not contains',
StartWith = 'start With',
EndWith = 'end with',
NotStartWith = 'not start with',
NotEndWith = 'not end with'
}
// INode
export interface INode {
index:number;
type: NodeType;
header:string;
parent: INode | null;
logicalOperator:LogicalOperator
}
// ロジックノード
export class GroupNode implements INode {
index:number;
type: NodeType;
children: INode[];
parent: INode | null;
logicalOperator: LogicalOperator;
get label():string{
return this.logicalOperator;
}
get header():string{
return this.type===NodeType.Root?'root':'generic';
}
get expanded():boolean{
return this.children.length>0;
}
constructor(logicOp:LogicalOperator, parent: INode | null) {
this.index=0;
this.type = parent==null?NodeType.Root: NodeType.LogicGroup;
this.logicalOperator = logicOp;
this.parent=parent;
this.children=[];
}
static fromJSON(json: any, parent: INode | null = null): GroupNode {
const node = new GroupNode(json.logicalOperator, parent);
node.index=json.index;
node.children = json.children.map((childJson: any) => {
return childJson.type === NodeType.LogicGroup
? GroupNode.fromJSON(childJson, node)
: ConditionNode.fromJSON(childJson, node);
});
return node;
}
}
// 条件式ノード
export class ConditionNode implements INode {
index: number;
type: NodeType;
parent:INode;
get logicalOperator(): LogicalOperator{
return this.parent.logicalOperator;
};
object: any; // 比較元
operator: Operator; // 比較子
value: any;
get header():string{
return 'generic';
}
constructor(object: any, operator: Operator, value: any, parent: GroupNode) {
this.index=0;
this.type = NodeType.Condition;
this.object = object;
this.operator = operator;
this.value = value;
this.parent=parent;
}
static fromJSON(json: any, parent: GroupNode): ConditionNode {
const node= new ConditionNode(
json.object,
json.operator,
json.value,
parent
);
node.index=json.index;
return node;
}
}
/**
* 条件式の管理クラス
*/
export class ConditionTree {
root: GroupNode;
maxIndex:number;
constructor() {
this.maxIndex=0;
this.root = new GroupNode(LogicalOperator.AND, null);
}
/**
* ノード追加
* @param parent
* @param node
*/
addNode(parent: GroupNode, node: INode): void {
this.maxIndex++;
node.index=this.maxIndex;
parent.children.push(node);
}
/**
* ノード削除
* @param node
*/
removeNode(node: INode): void {
if (node.parent === null) {
throw new Error('ルートノード削除できません');
} else {
const parent = node.parent as GroupNode;
const index = parent.children.indexOf(node);
if (index > -1) {
parent.children.splice(index, 1);
}
}
}
/**
* 条件ツリーからインディクス値で条件ノードを検索する
* @param index
* @returns
*/
findByIndex(index:number):INode|undefined{
return this.findChildren(this.root,index);
}
/**
* 指定のノードグループからインディクス値で条件ノードを検索する
* @param parent
* @param index
* @returns
*/
findChildren(parent: GroupNode, index: number): INode | undefined {
if (parent.index === index) {
return parent;
}
for (const node of parent.children) {
if (node.index === index) {
return node;
}
if (node.type !== NodeType.Condition) {
const foundNode = this.findChildren(node as GroupNode, index);
if (foundNode) {
return foundNode;
}
}
}
return undefined;
}
/**
*
* @param node 最大のインディクス値を取得する
* @returns
*/
getMaxIndex(node:INode):number{
let maxIndex:number=node.index;
if(node.type!==NodeType.Condition){
const groupNode = node as GroupNode;
groupNode.children.forEach((child)=>{
const childMax = this.getMaxIndex(child);
if(childMax>maxIndex){
maxIndex=childMax;
}
});
}
return maxIndex;
}
/**
* 条件式を表示する
* @param node 条件ノード
* @returns
*/
buildConditionString(node:INode){
if (node.type !== NodeType.Condition) {
let conditionString = '(';
const groupNode = node as GroupNode;
for (let i = 0; i < groupNode.children.length; i++) {
const childConditionString = this.buildConditionString(groupNode.children[i]);
if (childConditionString !== '') {
conditionString += childConditionString;
if (i < groupNode.children.length - 1) {
conditionString += ` ${groupNode.logicalOperator} `;
}
}
}
conditionString += ')';
return conditionString;
} else {
const condNode=node as ConditionNode;
if (condNode.object && condNode.operator ) {
let value=condNode.value;
if(value && typeof value ==='object' && ('label' in value)){
value =condNode.value.label;
}
return `${condNode.object.name} ${condNode.operator} '${value}'`;
} else {
return '';
}
}
}
/**
* すべて条件式を取得する
*/
getConditions(parent:GroupNode):ConditionNode[]{
const condiNodes:ConditionNode[]=[];
parent.children.forEach((node)=>{
if(node.type===NodeType.Condition){
condiNodes.push(node as ConditionNode);
}else{
condiNodes.push(...this.getConditions(node as GroupNode));
}
});
return condiNodes;
}
/**
*
* @param parent すべて条件グループを取得する
* @returns
*/
getGroups(parent:GroupNode):number[]{
const groups:number[]=[];
groups.push(parent.index);
parent.children.forEach((node)=>{
if(node.type!==NodeType.Condition){
groups.push(...this.getGroups(node as GroupNode));
}
});
return groups;
}
/**
* Jsonから復元
* @param jsonString
* @returns
*/
fromJson(jsonString: string): INode {
const json = JSON.parse(jsonString);
this.root = GroupNode.fromJSON(json) as GroupNode;
this.maxIndex=this.getMaxIndex(this.root);
return this.root;
}
/**
* JSON文字列に変換する
* @returns
*/
toJson():string{
return JSON.stringify(this.root, (key, value) => {
if (key === 'parent') {
return value ? value.type : null;
}
return value;
});
}
/**
* 条件式を計算する
* @param node
* @param context
* @returns
*/
evaluate(node: INode, context: any): boolean {
if (node.type === NodeType.Condition) {
return this.evaluateCondition(node as ConditionNode, context);
} else if (node.type === NodeType.LogicGroup || node.type === NodeType.Root) {
const groupNode = node as GroupNode;
const results = groupNode.children.map(child => this.evaluate(child, context));
if (groupNode.logicalOperator === LogicalOperator.AND) {
return results.every(result => result);
} else if (groupNode.logicalOperator === LogicalOperator.OR) {
return results.some(result => result);
} else {
throw new Error('Unsupported logical operator');
}
} else {
throw new Error('Unsupported node type');
}
}
/**
* Condition objectの値を取得する
* @param object
* @param context
* @returns
*/
getObjectValue(object:any,context:IContext){
if(!object || typeof object!=="object" || !("objectType" in object)){
return object;
}
if(object.objectType==='field'){
return context.record[object.code].value;
}else if(object.objectType==='var'){
return context.variables[object.varName].value;
}
}
/**
* 比較オブジェクトの値を取得
* @param object
* @returns
*/
getConditionValue(object:any){
if(!object || typeof object!=="object"){
return object;
}
if("label" in object){
return object.label;
}
if("value" in object){
return object.value;
}
if("name" in object){
return object.name;
}
}
/**
* 条件式を計算する
* @param condition
* @param context
* @returns
*/
evaluateCondition(condition: ConditionNode, context: IContext): boolean {
const { object, operator, value } = condition;
const targetValue = this.getObjectValue(object,context);
const conditionValue = this.getConditionValue(value);
switch (operator) {
case Operator.Equal:
case Operator.NotEqual:
case Operator.Greater:
case Operator.GreaterOrEqual:
case Operator.Less:
case Operator.LessOrEqual:
return this.compare(operator,targetValue, conditionValue);
case Operator.Contains:
return this.contains(targetValue,conditionValue);
case Operator.NotContains:
return !this.contains(targetValue,conditionValue);
case Operator.StartWith:
return this.startWith(targetValue,conditionValue);
case Operator.NotStartWith:
return !this.startWith(targetValue,conditionValue);
case Operator.EndWith:
return this.endsWith(targetValue,conditionValue);
case Operator.NotEndWith:
return this.endsWith(targetValue,conditionValue);
default:
throw new Error('Unsupported operator');
}
}
/**
* 比較を実行する
* @param operator
* @param targetValue
* @param value
* @returns
*/
compare(operator: Operator, targetValue: any, value: any): boolean {
// targetValue は日期时value も日期に変換して比較する
if (targetValue instanceof Date) {
const dateValue = new Date(value);
if (!isNaN(dateValue.getTime())) {
value = dateValue;
}
}
//targetValueは数値時value を数値に変換する
else if (typeof targetValue === 'number') {
const numberValue = Number(value);
if (!isNaN(numberValue)) {
value = numberValue;
}
}
else if (typeof targetValue === 'string') {
value = String(value);
}
switch (operator) {
case Operator.Equal:
return targetValue === value;
case Operator.NotEqual:
return targetValue !== value;
case Operator.Greater:
return targetValue > value;
case Operator.GreaterOrEqual:
return targetValue >= value;
case Operator.Less:
return targetValue < value;
case Operator.LessOrEqual:
return targetValue <= value;
default:
throw new Error('Unsupported operator for comparison');
}
}
/**
* 含む計算
* @param targetValue
* @param value
* @returns
*/
contains(targetValue: any, value: any): boolean {
if (typeof targetValue === 'string' && typeof value === 'string') {
return targetValue.includes(value);
}
return false;
}
/**
* StartWith計算
* @param targetValue
* @param value
* @returns
*/
startWith(targetValue: any, value: any): boolean {
if (typeof targetValue === 'string' && typeof value === 'string') {
return targetValue.startsWith(value);
}
return false;
}
/**
* EndsWith計算
* @param targetValue
* @param value
* @returns
*/
endsWith(targetValue: any, value: any): boolean {
if (typeof targetValue === 'string' && typeof value === 'string') {
return targetValue.endsWith(value);
}
return false;
}
}

View File

@@ -1,18 +1,25 @@
import { actionAddins } from "../actions"; import { actionAddins } from "../actions";
import '../actions/must-input'; import '../actions/must-input';
import { ActionFlow,IActionFlow, IActionResult } from "./ActionTypes"; import '../actions/auto-numbering';
import '../actions/field-shown';
import { ActionFlow,IActionFlow, IActionResult,IContext } from "./ActionTypes";
export class ActionProcess{ export class ActionProcess{
eventId:string; eventId:string;
flow:IActionFlow; flow:IActionFlow;
event:any; event:any;
context:IContext;
constructor(eventId:string,flow:ActionFlow,event:any){ constructor(eventId:string,flow:ActionFlow,event:any){
this.eventId=eventId; this.eventId=eventId;
this.flow=flow; this.flow=flow;
this.event=event; this.event=event;
this.context={
record:this.event.record,
variables:{}
};
} }
exec(){ async exec(){
const root = this.flow.getRoot(); const root = this.flow.getRoot();
if(root===undefined || root.nextNodeIds.size===0){ if(root===undefined || root.nextNodeIds.size===0){
return; return;
@@ -26,7 +33,7 @@ export class ActionProcess{
while(nextAction!==undefined && result.canNext){ while(nextAction!==undefined && result.canNext){
const action = actionAddins[nextAction.name]; const action = actionAddins[nextAction.name];
if(action!==undefined){ if(action!==undefined){
result = action.process(nextAction,this.event) result = await action.process(nextAction,this.event,this.context);
} }
const nextInput = nextAction.outputPoints!==undefined?result.result||'':''; const nextInput = nextAction.outputPoints!==undefined?result.result||'':'';
id=nextAction.nextNodeIds.get(nextInput); id=nextAction.nextNodeIds.get(nextInput);

View File

@@ -0,0 +1,71 @@
export class Formatter{
static numberFormat(num:number,format:string):string{
let integerPart = Math.floor(Math.abs(num)).toString();
let fractionPart = Math.abs(num).toString().split('.')[1] || '';
let isNegative = num < 0;
let isPercent = format.includes('%');
// %
if (isPercent) {
num *= 100;
integerPart = Math.floor(Math.abs(num)).toString();
fractionPart = Math.abs(num).toString().split('.')[1] || '';
}
// 小数と整数部分の処理
let [integerFormat, fractionFormat] = format.split('.');
integerPart = integerFormat ? integerPart.padStart(integerFormat.replace(/[^0]/g, '').length, '0') : integerPart;
fractionPart = fractionPart.padEnd(fractionFormat ? fractionFormat.length : 0, '0');
// カマ区切
if (/,/.test(integerFormat)) {
const parts = [];
while (integerPart.length) {
parts.unshift(integerPart.slice(-3));
integerPart = integerPart.slice(0, -3);
}
integerPart = parts.join(',');
}
// すべて組合わせ
let result = fractionFormat ? `${integerPart}.${fractionPart}` : integerPart;
result = isNegative ? `-${result}` : result;
return isPercent ? `${result}%` : result;
}
static dateFormat(date: Date, format: string): string {
const year = date.getFullYear();
const month = date.getMonth() + 1;
const day = date.getDate();
const hour = date.getHours();
const minute = date.getMinutes();
const second = date.getSeconds();
const millisecond = date.getMilliseconds();
const timeZoneOffset = -date.getTimezoneOffset() / 60;
const replacements = {
'yyyy': year.toString(),
'yy': year.toString().slice(-2),
'MM': month.toString().padStart(2, '0'),
'M': month.toString(),
'dd': day.toString().padStart(2, '0'),
'd': day.toString(),
'HH': hour.toString().padStart(2, '0'),
'H': hour.toString(),
'hh': (hour > 12 ? hour - 12 : hour).toString().padStart(2, '0'),
'h': (hour > 12 ? hour - 12 : hour).toString(),
'mm': minute.toString().padStart(2, '0'),
'm': minute.toString(),
'ss': second.toString().padStart(2, '0'),
's': second.toString(),
'fff': millisecond.toString().padStart(3, '0'),
'zzz': (timeZoneOffset >= 0 ? '+' : '-') + Math.abs(timeZoneOffset).toString().padStart(2, '0') + ':00'
};
return format.replace(/yyyy|yy|MM|M|dd|d|HH|H|hh|h|mm|m|ss|s|fff|zzz/g, (match) => {
return replacements[match as keyof typeof replacements] || match;
});
}
}

View File

@@ -1,13 +1,13 @@
{ {
"compilerOptions": { "compilerOptions": {
"target": "ES2020", "target": "ESNext",
"useDefineForClassFields": true, "useDefineForClassFields": true,
"module": "ESNext", "module": "ESNext",
"lib": ["ES2020", "DOM", "DOM.Iterable"], "lib": ["esnext", "dom"],
"skipLibCheck": true, "skipLibCheck": true,
/* Bundler mode */ /* Bundler mode */
"moduleResolution": "bundler", "moduleResolution": "node",
"allowImportingTsExtensions": true, "allowImportingTsExtensions": true,
"resolveJsonModule": true, "resolveJsonModule": true,
"isolatedModules": true, "isolatedModules": true,
@@ -17,7 +17,8 @@
"strict": true, "strict": true,
"noUnusedLocals": true, "noUnusedLocals": true,
"noUnusedParameters": true, "noUnusedParameters": true,
"noFallthroughCasesInSwitch": true "noFallthroughCasesInSwitch": true,
"esModuleInterop": true
}, },
"include": ["src"] "include": ["src"]
} }

View File

@@ -1,14 +1,16 @@
// vite.config.js // vite.config.js
import { defineConfig } from 'vite' import { defineConfig } from 'vite'
const sourcemap = process.env.SOURCE_MAP==='true';
export default defineConfig({ export default defineConfig({
build: { build: {
rollupOptions: { rollupOptions: {
input: 'src/index.ts', // entry file input: 'src/index.ts', // entry file
output:{ output:{
entryFileNames:'alc_runtime.js' entryFileNames:'alc_runtime.js',
// assetFileNames:'alc_kintone_style.css'
} }
}, },
sourcemap:true sourcemap:sourcemap
} }
}) })

View File

@@ -2,131 +2,68 @@
# yarn lockfile v1 # yarn lockfile v1
"@esbuild/android-arm64@0.18.20":
version "0.18.20"
resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz#984b4f9c8d0377443cc2dfcef266d02244593622"
integrity sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==
"@esbuild/android-arm@0.18.20":
version "0.18.20"
resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.18.20.tgz#fedb265bc3a589c84cc11f810804f234947c3682"
integrity sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==
"@esbuild/android-x64@0.18.20":
version "0.18.20"
resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.18.20.tgz#35cf419c4cfc8babe8893d296cd990e9e9f756f2"
integrity sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==
"@esbuild/darwin-arm64@0.18.20":
version "0.18.20"
resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.18.20.tgz#08172cbeccf95fbc383399a7f39cfbddaeb0d7c1"
integrity sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==
"@esbuild/darwin-x64@0.18.20":
version "0.18.20"
resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.18.20.tgz#d70d5790d8bf475556b67d0f8b7c5bdff053d85d"
integrity sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==
"@esbuild/freebsd-arm64@0.18.20":
version "0.18.20"
resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.20.tgz#98755cd12707f93f210e2494d6a4b51b96977f54"
integrity sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==
"@esbuild/freebsd-x64@0.18.20":
version "0.18.20"
resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.18.20.tgz#c1eb2bff03915f87c29cece4c1a7fa1f423b066e"
integrity sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==
"@esbuild/linux-arm64@0.18.20":
version "0.18.20"
resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.18.20.tgz#bad4238bd8f4fc25b5a021280c770ab5fc3a02a0"
integrity sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==
"@esbuild/linux-arm@0.18.20":
version "0.18.20"
resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.18.20.tgz#3e617c61f33508a27150ee417543c8ab5acc73b0"
integrity sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==
"@esbuild/linux-ia32@0.18.20":
version "0.18.20"
resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.18.20.tgz#699391cccba9aee6019b7f9892eb99219f1570a7"
integrity sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==
"@esbuild/linux-loong64@0.18.20":
version "0.18.20"
resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.18.20.tgz#e6fccb7aac178dd2ffb9860465ac89d7f23b977d"
integrity sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==
"@esbuild/linux-mips64el@0.18.20":
version "0.18.20"
resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.18.20.tgz#eeff3a937de9c2310de30622a957ad1bd9183231"
integrity sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==
"@esbuild/linux-ppc64@0.18.20":
version "0.18.20"
resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.18.20.tgz#2f7156bde20b01527993e6881435ad79ba9599fb"
integrity sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==
"@esbuild/linux-riscv64@0.18.20":
version "0.18.20"
resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.18.20.tgz#6628389f210123d8b4743045af8caa7d4ddfc7a6"
integrity sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==
"@esbuild/linux-s390x@0.18.20":
version "0.18.20"
resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.18.20.tgz#255e81fb289b101026131858ab99fba63dcf0071"
integrity sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==
"@esbuild/linux-x64@0.18.20":
version "0.18.20"
resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.18.20.tgz#c7690b3417af318a9b6f96df3031a8865176d338"
integrity sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==
"@esbuild/netbsd-x64@0.18.20":
version "0.18.20"
resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.18.20.tgz#30e8cd8a3dded63975e2df2438ca109601ebe0d1"
integrity sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==
"@esbuild/openbsd-x64@0.18.20":
version "0.18.20"
resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.18.20.tgz#7812af31b205055874c8082ea9cf9ab0da6217ae"
integrity sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==
"@esbuild/sunos-x64@0.18.20":
version "0.18.20"
resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.18.20.tgz#d5c275c3b4e73c9b0ecd38d1ca62c020f887ab9d"
integrity sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==
"@esbuild/win32-arm64@0.18.20":
version "0.18.20"
resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.18.20.tgz#73bc7f5a9f8a77805f357fab97f290d0e4820ac9"
integrity sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==
"@esbuild/win32-ia32@0.18.20":
version "0.18.20"
resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.18.20.tgz#ec93cbf0ef1085cc12e71e0d661d20569ff42102"
integrity sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==
"@esbuild/win32-x64@0.18.20": "@esbuild/win32-x64@0.18.20":
version "0.18.20" version "0.18.20"
resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz#786c5f41f043b07afb1af37683d7c33668858f6d" resolved "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz"
integrity sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ== integrity sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==
"@types/jquery@^3.5.24": "@types/jquery@^3.5.24":
version "3.5.25" version "3.5.24"
resolved "https://registry.yarnpkg.com/@types/jquery/-/jquery-3.5.25.tgz#c817c71d414855f7d71f46da39f43e6b9579b0b9" resolved "https://registry.npmjs.org/@types/jquery/-/jquery-3.5.24.tgz"
integrity sha512-gykx2c+OZf5nx2tv/5fDQqmvGgTiXshELy5jf9IgXPtVfSBl57IUYByN4osbwMXwJijWGOEYQABzGaFZE79A0Q== integrity sha512-V/TG69ge5amcr8Ap7vY3SObqKfZlV7ttqcYnNcYnndI77ySIRi05+3GjvfwRtE2qalAC2ySLIL1ker512sI20g==
dependencies: dependencies:
"@types/sizzle" "*" "@types/sizzle" "*"
"@types/node@^20.8.9", "@types/node@>= 14":
version "20.11.0"
resolved "https://registry.npmjs.org/@types/node/-/node-20.11.0.tgz"
integrity sha512-o9bjXmDNcF7GbM4CNQpmi+TutCgap/K3w1JyKgxAjqx41zp9qlIAVFi0IhCNsJcXolEqLWhbFbEeL0PvYm4pcQ==
dependencies:
undici-types "~5.26.4"
"@types/sizzle@*": "@types/sizzle@*":
version "2.3.5" version "2.3.5"
resolved "https://registry.yarnpkg.com/@types/sizzle/-/sizzle-2.3.5.tgz#d93dd29cdcd5801d90be968073b09a6b370780e4" resolved "https://registry.npmjs.org/@types/sizzle/-/sizzle-2.3.5.tgz"
integrity sha512-tAe4Q+OLFOA/AMD+0lq8ovp8t3ysxAOeaScnfNdZpUxaGl51ZMDEITxkvFl1STudQ58mz6gzVGl9VhMKhwRnZQ== integrity sha512-tAe4Q+OLFOA/AMD+0lq8ovp8t3ysxAOeaScnfNdZpUxaGl51ZMDEITxkvFl1STudQ58mz6gzVGl9VhMKhwRnZQ==
anymatch@~3.1.2:
version "3.1.3"
resolved "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz"
integrity sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==
dependencies:
normalize-path "^3.0.0"
picomatch "^2.0.4"
binary-extensions@^2.0.0:
version "2.2.0"
resolved "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz"
integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==
braces@~3.0.2:
version "3.0.2"
resolved "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz"
integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==
dependencies:
fill-range "^7.0.1"
"chokidar@>=3.0.0 <4.0.0":
version "3.5.3"
resolved "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz"
integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==
dependencies:
anymatch "~3.1.2"
braces "~3.0.2"
glob-parent "~5.1.2"
is-binary-path "~2.1.0"
is-glob "~4.0.1"
normalize-path "~3.0.0"
readdirp "~3.6.0"
optionalDependencies:
fsevents "~2.3.2"
esbuild@^0.18.10: esbuild@^0.18.10:
version "0.18.20" version "0.18.20"
resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.18.20.tgz#4709f5a34801b43b799ab7d6d82f7284a9b7a7a6" resolved "https://registry.npmjs.org/esbuild/-/esbuild-0.18.20.tgz"
integrity sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA== integrity sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==
optionalDependencies: optionalDependencies:
"@esbuild/android-arm" "0.18.20" "@esbuild/android-arm" "0.18.20"
@@ -152,55 +89,131 @@ esbuild@^0.18.10:
"@esbuild/win32-ia32" "0.18.20" "@esbuild/win32-ia32" "0.18.20"
"@esbuild/win32-x64" "0.18.20" "@esbuild/win32-x64" "0.18.20"
fsevents@~2.3.2: fill-range@^7.0.1:
version "2.3.3" version "7.0.1"
resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" resolved "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz"
integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==
dependencies:
to-regex-range "^5.0.1"
glob-parent@~5.1.2:
version "5.1.2"
resolved "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz"
integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==
dependencies:
is-glob "^4.0.1"
immutable@^4.0.0:
version "4.3.4"
resolved "https://registry.npmjs.org/immutable/-/immutable-4.3.4.tgz"
integrity sha512-fsXeu4J4i6WNWSikpI88v/PcVflZz+6kMhUfIwc5SY+poQRPnaf5V7qds6SUyUN3cVxEzuCab7QIoLOQ+DQ1wA==
is-binary-path@~2.1.0:
version "2.1.0"
resolved "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz"
integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==
dependencies:
binary-extensions "^2.0.0"
is-extglob@^2.1.1:
version "2.1.1"
resolved "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz"
integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==
is-glob@^4.0.1, is-glob@~4.0.1:
version "4.0.3"
resolved "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz"
integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==
dependencies:
is-extglob "^2.1.1"
is-number@^7.0.0:
version "7.0.0"
resolved "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz"
integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==
jquery@^3.7.1: jquery@^3.7.1:
version "3.7.1" version "3.7.1"
resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.7.1.tgz#083ef98927c9a6a74d05a6af02806566d16274de" resolved "https://registry.npmjs.org/jquery/-/jquery-3.7.1.tgz"
integrity sha512-m4avr8yL8kmFN8psrbFFFmB/If14iN5o9nw/NgnnM+kybDJpRsAynV2BsfpTYrTRysYUdADVD7CkUUizgkpLfg== integrity sha512-m4avr8yL8kmFN8psrbFFFmB/If14iN5o9nw/NgnnM+kybDJpRsAynV2BsfpTYrTRysYUdADVD7CkUUizgkpLfg==
nanoid@^3.3.6: nanoid@^3.3.6:
version "3.3.6" version "3.3.6"
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.6.tgz#443380c856d6e9f9824267d960b4236ad583ea4c" resolved "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz"
integrity sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA== integrity sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==
normalize-path@^3.0.0, normalize-path@~3.0.0:
version "3.0.0"
resolved "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz"
integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==
picocolors@^1.0.0: picocolors@^1.0.0:
version "1.0.0" version "1.0.0"
resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" resolved "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz"
integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==
picomatch@^2.0.4, picomatch@^2.2.1:
version "2.3.1"
resolved "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz"
integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==
postcss@^8.4.27: postcss@^8.4.27:
version "8.4.31" version "8.4.31"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.31.tgz#92b451050a9f914da6755af352bdc0192508656d" resolved "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz"
integrity sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ== integrity sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==
dependencies: dependencies:
nanoid "^3.3.6" nanoid "^3.3.6"
picocolors "^1.0.0" picocolors "^1.0.0"
source-map-js "^1.0.2" source-map-js "^1.0.2"
readdirp@~3.6.0:
version "3.6.0"
resolved "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz"
integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==
dependencies:
picomatch "^2.2.1"
rollup@^3.27.1: rollup@^3.27.1:
version "3.29.4" version "3.29.4"
resolved "https://registry.yarnpkg.com/rollup/-/rollup-3.29.4.tgz#4d70c0f9834146df8705bfb69a9a19c9e1109981" resolved "https://registry.npmjs.org/rollup/-/rollup-3.29.4.tgz"
integrity sha512-oWzmBZwvYrU0iJHtDmhsm662rC15FRXmcjCk1xD771dFDx5jJ02ufAQQTn0etB2emNk4J9EZg/yWKpsn9BWGRw== integrity sha512-oWzmBZwvYrU0iJHtDmhsm662rC15FRXmcjCk1xD771dFDx5jJ02ufAQQTn0etB2emNk4J9EZg/yWKpsn9BWGRw==
optionalDependencies: optionalDependencies:
fsevents "~2.3.2" fsevents "~2.3.2"
source-map-js@^1.0.2: sass@*, sass@^1.69.5:
version "1.69.7"
resolved "https://registry.npmjs.org/sass/-/sass-1.69.7.tgz"
integrity sha512-rzj2soDeZ8wtE2egyLXgOOHQvaC2iosZrkF6v3EUG+tBwEvhqUCzm0VP3k9gHF9LXbSrRhT5SksoI56Iw8NPnQ==
dependencies:
chokidar ">=3.0.0 <4.0.0"
immutable "^4.0.0"
source-map-js ">=0.6.2 <2.0.0"
source-map-js@^1.0.2, "source-map-js@>=0.6.2 <2.0.0":
version "1.0.2" version "1.0.2"
resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c" resolved "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz"
integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw== integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==
to-regex-range@^5.0.1:
version "5.0.1"
resolved "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz"
integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==
dependencies:
is-number "^7.0.0"
typescript@^5.0.2: typescript@^5.0.2:
version "5.2.2" version "5.2.2"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.2.2.tgz#5ebb5e5a5b75f085f22bc3f8460fba308310fa78" resolved "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz"
integrity sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w== integrity sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==
undici-types@~5.26.4:
version "5.26.5"
resolved "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz"
integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==
vite@^4.4.5: vite@^4.4.5:
version "4.5.0" version "4.5.0"
resolved "https://registry.yarnpkg.com/vite/-/vite-4.5.0.tgz#ec406295b4167ac3bc23e26f9c8ff559287cff26" resolved "https://registry.npmjs.org/vite/-/vite-4.5.0.tgz"
integrity sha512-ulr8rNLA6rkyFAlVWw2q5YJ91v098AFQ2R0PRFwPzREXOUJQPtFUG0t+/ZikhaOCDqFoDhN6/v8Sq0o4araFAw== integrity sha512-ulr8rNLA6rkyFAlVWw2q5YJ91v098AFQ2R0PRFwPzREXOUJQPtFUG0t+/ZikhaOCDqFoDhN6/v8Sq0o4araFAw==
dependencies: dependencies:
esbuild "^0.18.10" esbuild "^0.18.10"

View File

@@ -1,20 +1,33 @@
[ [
{ {
"component": "FieldInput", "component": "FieldInput",
"props": { "props": {
"displayName": "フィールド", "displayName": "フィールド",
"modelValue": "", "modelValue": {},
"name": "field", "name": "field",
"placeholder": "必須項目を選択してください" "placeholder": "対象項目を選択してください"
}
},
{
"component": "MuiltInputText",
"props": {
"displayName": "エラーメッセージ",
"modelValue": "",
"name": "message",
"placeholder": "エラーメッセージを入力してください"
}
} }
},
{
"component": "SelectBox",
"props": {
"displayName": "表示/非表示",
"options": [
"表示",
"非表示"
],
"modelValue": "",
"name": "show",
"placeholder": ""
}
},
{
"component": "ConditionInput",
"props": {
"displayName": "条件",
"modelValue": "",
"name": "condition",
"placeholder": "対象項目を選択してください"
}
}
] ]

59
sample2.json Normal file
View File

@@ -0,0 +1,59 @@
{
"index": 0,
"type": "root",
"children": [
{
"index": 1,
"type": "condition",
"parent": "root",
"logicalOperator": "AND",
"object": {
"label": "Field 1",
"value": "field1"
},
"operator": "=",
"value": "1"
},
{
"index": 2,
"type": "condition",
"parent": "root",
"logicalOperator": "AND",
"object": {
"label": "Field 1",
"value": "field1"
},
"operator": "=",
"value": "2"
},
{
"index": 3,
"type": "condition",
"parent": "root",
"logicalOperator": "AND",
"object": {
"label": "Field 1",
"value": "field1"
},
"operator": {
"label": ">",
"value": "Greater"
},
"value": "3"
},
{
"index": 4,
"type": "condition",
"parent": "root",
"logicalOperator": "AND",
"object": {
"label": "Field 1",
"value": "field1"
},
"operator": "=",
"value": "4"
}
],
"parent": null,
"logicalOperator": "AND"
}