Compare commits
236 Commits
mvp-step1
...
feature/da
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f70c27d814 | ||
|
|
432e52d322 | ||
|
|
f3893c2500 | ||
|
|
e726843189 | ||
|
|
183abeba41 | ||
|
|
70d2513cd7 | ||
|
|
0fda3d143c | ||
|
|
a85a3683f2 | ||
|
|
14287b6948 | ||
|
|
0443257f86 | ||
|
|
18b97c249a | ||
|
|
4ac4c9e9f4 | ||
|
|
24a70aed2e | ||
|
|
79e38ba6dd | ||
|
|
303a3ffc23 | ||
|
|
af86edd3e2 | ||
|
|
c87cff4181 | ||
|
|
05db5a0522 | ||
|
|
bac7020c15 | ||
|
|
c1d33e3ff0 | ||
| 832d46d360 | |||
|
|
c8f9cbda9a | ||
|
|
c1c265c73e | ||
| e4800d2937 | |||
| 550e59b4db | |||
|
|
26a685b872 | ||
|
|
8514adf15e | ||
|
|
5f2059fd6a | ||
| 504a76b4ac | |||
|
|
80694ee49c | ||
|
|
493b9ca0e9 | ||
|
|
55bbf50656 | ||
| 4b27504b99 | |||
|
|
1d248bde43 | ||
|
|
5cad10575f | ||
|
|
3b56c78bf1 | ||
|
|
6ab668f86a | ||
|
|
7b1daaab33 | ||
|
|
ef47912c37 | ||
|
|
140c48bcb7 | ||
| ba0b96146e | |||
| 53aa5dff88 | |||
| e52b02ec7f | |||
| 47dbaaf87d | |||
| 478c751ea7 | |||
| 4ee72a8a75 | |||
|
|
e2db112080 | ||
|
|
3c0d572a0e | ||
|
|
c225ddd39d | ||
| 0e9b0ea693 | |||
| 36f225a5b6 | |||
| 9496128e02 | |||
| 612962cc83 | |||
| 52514b7197 | |||
|
|
234e55bc01 | ||
|
|
f4a1bc3e58 | ||
|
|
192174b2ca | ||
|
|
544370688e | ||
|
|
6a6e772e32 | ||
|
|
f4500a09bc | ||
|
|
e7c3d3c8ad | ||
|
|
91bd72f7e0 | ||
|
|
2e69dc4dcf | ||
|
|
535049a188 | ||
|
|
5bde55e5fc | ||
|
|
c1cad3d7a9 | ||
|
|
c378bfe20c | ||
|
|
0e0d028c24 | ||
|
|
3b6eed32ec | ||
|
|
f62a7c3389 | ||
|
|
a161d8e2c8 | ||
|
|
b97888fca9 | ||
|
|
371ec3a133 | ||
|
|
711b9afaea | ||
|
|
00227ca713 | ||
|
|
842edd6f1f | ||
|
|
cd0c3197fa | ||
|
|
7f35a91765 | ||
| c44b42f498 | |||
| cff6ee5478 | |||
|
|
40b21604f9 | ||
|
|
98fcd2eb47 | ||
|
|
c3b560dbc9 | ||
| 53aadfcaaa | |||
|
|
dda9b7adad | ||
|
|
7fb3d08ccb | ||
|
|
cf4209333d | ||
|
|
61ac281134 | ||
|
|
22e9094d4c | ||
|
|
a13721f63e | ||
|
|
05b9a0ce1b | ||
|
|
b25c17ab53 | ||
|
|
64d2cadd82 | ||
|
|
371e2ee073 | ||
|
|
a7078b54c5 | ||
|
|
5663c313ea | ||
|
|
7da9b81319 | ||
| c426bbf793 | |||
| 329debaab8 | |||
|
|
994a0174f5 | ||
| 2846297112 | |||
| 5cf60ddfdc | |||
| 0de33f04bc | |||
| 472353632c | |||
|
|
1a48fb5b20 | ||
| 99d3a01991 | |||
|
|
da24972482 | ||
| ecb90e7120 | |||
|
|
784cb7a473 | ||
| 5349c46225 | |||
| 3be4402239 | |||
| 4c482ea289 | |||
| 44a73478a7 | |||
| bceac2f172 | |||
| 98842db343 | |||
| 03904a4e35 | |||
| 09b3c8df47 | |||
| 26761f6d39 | |||
| 72608a8ffd | |||
| d1ec123c8b | |||
| 4102ff5522 | |||
| 08e857884b | |||
| c0db2d230b | |||
| 2b9b772b39 | |||
| c46e8a7047 | |||
| 6e6350d6ce | |||
| 1e7d553bd6 | |||
| 35ae2539cb | |||
|
|
a614d754f4 | ||
| 46a6ba534e | |||
| 8fecde4c42 | |||
|
|
3e73799532 | ||
| 3159366560 | |||
| 5176cff2bd | |||
| 978aa723ae | |||
| 926c338f73 | |||
| 6de60c82ba | |||
| 6ed17a50e5 | |||
| 5cd6d02f6e | |||
| 276e5e9122 | |||
| 6e75a2a524 | |||
| ea6e603036 | |||
| edad30e264 | |||
| 58616100f4 | |||
| 359558bad3 | |||
| 6a6554ed1f | |||
| e20625abdb | |||
| f83dd693d5 | |||
| a464297511 | |||
| 991c8e8083 | |||
| 9ea183ff2d | |||
| 34d368b730 | |||
| 17760a6926 | |||
| 8b9cfa34c7 | |||
| 5fb8fe53bb | |||
| 5cb9375db3 | |||
| 55181f2c57 | |||
| 4577df371a | |||
| 0f154832a5 | |||
| 5951fcc108 | |||
| 7966217ac2 | |||
| 64851bd51d | |||
| 10e584d2ac | |||
| b97a728624 | |||
| 5a875e6853 | |||
| 57a4823f61 | |||
| 761eb4c13e | |||
| 354fc6868d | |||
| 2538e4526f | |||
| 26890f5b35 | |||
| 086b5e2621 | |||
| 617b060869 | |||
| a782e92bd6 | |||
| f60f97380f | |||
| cfc416fd14 | |||
| df593d2ffe | |||
| 9cd4c8a5ab | |||
|
|
ead6658455 | ||
| f6d677b51f | |||
| 25f05ab018 | |||
| 178cf33949 | |||
| b54c0f8022 | |||
| c5cc3c1a24 | |||
| 0b414fbfbe | |||
| 981d7a5062 | |||
| 33fc0b74ef | |||
| 286acc4584 | |||
| cdfb1d4310 | |||
| 6844652b5d | |||
| 9e72acf84b | |||
| 52f4af759e | |||
| 76457b6667 | |||
| e1f2afa942 | |||
| 8d5dff60f1 | |||
| 461cd26690 | |||
| 4c6b2ea844 | |||
| 418f45f997 | |||
| 51ebe99d1c | |||
| 2f1f8a60fc | |||
| 64795a80c7 | |||
| 94a17073dd | |||
| 7f7d625fdd | |||
| 6902079866 | |||
| 6aa057e590 | |||
| 2721cd60d1 | |||
| 1f8d079d4d | |||
|
|
f34dec1054 | ||
|
|
01b64f1aba | ||
|
|
3367ada343 | ||
|
|
f4ea3eaccb | ||
|
|
4adb8401d6 | ||
| df59bff6ae | |||
| 3ae685a0e2 | |||
|
|
fce56e43c3 | ||
|
|
42618602f4 | ||
| c1e50736e8 | |||
|
|
e02131846b | ||
|
|
2c3b27d9de | ||
|
|
6ccc833f7d | ||
|
|
a0ecc2eee3 | ||
| 59e6d33656 | |||
| b641c729c2 | |||
| 142cdcda38 | |||
| fc2669dabf | |||
| 8e095b51e3 | |||
| ff03490209 | |||
| 40cd9998d0 | |||
| 973ba159b4 | |||
| 063a5af822 | |||
|
|
6a06c71104 | ||
| cccff1d16d | |||
|
|
100d8de54f | ||
|
|
7c667660c0 | ||
|
|
4eb56372a5 | ||
|
|
16edd398be | ||
|
|
4e08159e6d |
4
backend/.gitignore
vendored
4
backend/.gitignore
vendored
@@ -56,6 +56,7 @@ coverage.xml
|
||||
|
||||
# Django stuff:
|
||||
*.log
|
||||
*.log.*
|
||||
local_settings.py
|
||||
db.sqlite3
|
||||
db.sqlite3-journal
|
||||
@@ -125,4 +126,5 @@ cython_debug/
|
||||
# VS Code settings
|
||||
.vscode/
|
||||
|
||||
*.lock
|
||||
*.lock
|
||||
Temp/
|
||||
@@ -1,5 +1,5 @@
|
||||
|
||||
FROM python:3.8
|
||||
FROM python:3.11.4
|
||||
|
||||
RUN mkdir /app
|
||||
WORKDIR /app
|
||||
|
||||
99
backend/Temp/alc_runtime.js
Normal file
99
backend/Temp/alc_runtime.js
Normal file
File diff suppressed because one or more lines are too long
Binary file not shown.
@@ -29,18 +29,19 @@ async def login(
|
||||
else:
|
||||
permissions = "user"
|
||||
access_token = security.create_access_token(
|
||||
data={"sub": user.email, "permissions": permissions},
|
||||
data={"sub": user.id, "permissions": permissions},
|
||||
expires_delta=access_token_expires,
|
||||
)
|
||||
|
||||
return {"access_token": access_token, "token_type": "bearer"}
|
||||
return {"access_token": access_token, "token_type": "bearer","user_name":user.first_name + " " + user.last_name}
|
||||
|
||||
|
||||
@r.post("/signup")
|
||||
async def signup(
|
||||
firstname:str, lastname:str,
|
||||
db=Depends(get_db), form_data: OAuth2PasswordRequestForm = Depends()
|
||||
):
|
||||
user = sign_up_new_user(db, form_data.username, form_data.password)
|
||||
user = sign_up_new_user(db, form_data.username, form_data.password,firstname,lastname)
|
||||
if not user:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_409_CONFLICT,
|
||||
@@ -56,8 +57,8 @@ async def signup(
|
||||
else:
|
||||
permissions = "user"
|
||||
access_token = security.create_access_token(
|
||||
data={"sub": user.email, "permissions": permissions},
|
||||
data={"sub": user.id, "permissions": permissions},
|
||||
expires_delta=access_token_expires,
|
||||
)
|
||||
|
||||
return {"access_token": access_token, "token_type": "bearer"}
|
||||
return {"access_token": access_token, "token_type": "bearer","user_name":user.first_name + " " + user.last_name}
|
||||
|
||||
@@ -5,33 +5,150 @@ import pandas as pd
|
||||
import json
|
||||
import httpx
|
||||
import deepdiff
|
||||
import app.core.config as c
|
||||
|
||||
import app.core.config as config
|
||||
import os
|
||||
from pathlib import Path
|
||||
from app.db.session import SessionLocal
|
||||
from app.db.crud import get_flows_by_app,get_activedomain,get_kintoneformat
|
||||
from app.core.auth import get_current_active_user,get_current_user
|
||||
from app.core.apiexception import APIException
|
||||
|
||||
kinton_router = r = APIRouter()
|
||||
|
||||
def getfieldsfromexcel(df):
|
||||
def getkintoneenv(user = Depends(get_current_user)):
|
||||
db = SessionLocal()
|
||||
domain = get_activedomain(db, user.id)
|
||||
db.close()
|
||||
kintoneevn = config.KINTONE_ENV(domain)
|
||||
return kintoneevn
|
||||
|
||||
|
||||
def getkintoneformat():
|
||||
db = SessionLocal()
|
||||
formats = get_kintoneformat(db)
|
||||
db.close()
|
||||
return formats
|
||||
|
||||
|
||||
def createkintonefields(property,value,trueformat):
|
||||
p = []
|
||||
if(property=="options"):
|
||||
o=[]
|
||||
for v in value.split(','):
|
||||
o.append(f"\"{v.split('|')[0]}\":{{\"label\":\"{v.split('|')[0]}\",\"index\":\"{v.split('|')[1]}\"}}")
|
||||
p.append(f"\"options\":{{{','.join(o)}}}")
|
||||
elif(property =="expression"):
|
||||
p.append(f"\"hideExpression\":true")
|
||||
p.append(f"\"expression\":\"{value.split(':')[1]}\"")
|
||||
elif(property =="required" or property =="unique" or property =="defaultNowValue" or property =="hideExpression" or property =="digit"):
|
||||
if str(value) == trueformat:
|
||||
p.append(f"\"{property}\":true")
|
||||
else:
|
||||
p.append(f"\"{property}\":false")
|
||||
elif(property =="protocol"):
|
||||
if(value == "メールアドレス"):
|
||||
p.append("\"protocol\":\"MAIL\"")
|
||||
elif(value == "Webサイト"):
|
||||
p.append("\"protocol\":\"WEB\"")
|
||||
elif(value == "電話番号"):
|
||||
p.append("\"protocol\":\"CALL\"")
|
||||
else:
|
||||
p.append(f"\"{property}\":\"{value}\"")
|
||||
return p
|
||||
|
||||
def getfieldsfromexcel(df,mapping):
|
||||
startrow = mapping.startrow
|
||||
startcolumn = mapping.startcolumn
|
||||
typecolumn = mapping.typecolumn
|
||||
codecolumn = mapping.codecolumn
|
||||
property = mapping.field.split(",")
|
||||
trueformat = mapping.trueformat
|
||||
appname = df.iloc[0,2]
|
||||
col=[]
|
||||
for row in range(5,len(df)):
|
||||
if pd.isna(df.iloc[row,1]):
|
||||
for row in range(startrow,len(df)):
|
||||
if pd.isna(df.iloc[row,startcolumn]):
|
||||
break
|
||||
if not df.iloc[row,3] in c.KINTONE_FIELD_TYPE:
|
||||
if not df.iloc[row,typecolumn] in config.KINTONE_FIELD_TYPE:
|
||||
continue
|
||||
p=[]
|
||||
for column in range(1,7):
|
||||
for column in range(startcolumn,startcolumn + len(property)):
|
||||
if(not pd.isna(df.iloc[row,column])):
|
||||
if(property[column-1]=="options"):
|
||||
o=[]
|
||||
for v in df.iloc[row,column].split(','):
|
||||
o.append(f"\"{v.split('|')[0]}\":{{\"label\":\"{v.split('|')[0]}\",\"index\":\"{v.split('|')[1]}\"}}")
|
||||
p.append(f"\"{property[column-1]}\":{{{','.join(o)}}}")
|
||||
elif(property[column-1]=="required"):
|
||||
p.append(f"\"{property[column-1]}\":{df.iloc[row,column]}")
|
||||
propertyname =property[column-1]
|
||||
if(propertyname.find("[") == 0):
|
||||
continue
|
||||
elif (propertyname =="remark"):
|
||||
if (df.iloc[row,column].find("|") !=-1):
|
||||
propertyname = "options"
|
||||
p = p + createkintonefields(propertyname, df.iloc[row,column],trueformat)
|
||||
if (df.iloc[row,column] == "メールアドレス" or df.iloc[row,column] == "Webサイト" or df.iloc[row,column] == "電話番号"):
|
||||
propertyname = "protocol"
|
||||
p = p + createkintonefields(propertyname, df.iloc[row,column],trueformat)
|
||||
if (df.iloc[row,column].find("桁区切り") !=-1):
|
||||
propertyname = "digit"
|
||||
p = p + createkintonefields(propertyname, df.iloc[row,column],trueformat)
|
||||
if (df.iloc[row,column].find("前単位") !=-1):
|
||||
propertyname = "unitPosition"
|
||||
p = p + createkintonefields(propertyname, "BEFORE",trueformat)
|
||||
if (df.iloc[row,column].find("後単位") !=-1):
|
||||
propertyname = "unitPosition"
|
||||
p = p + createkintonefields(propertyname, "AFTER",trueformat)
|
||||
if (df.iloc[row,column].find("単位「") !=-1):
|
||||
propertyname = "unit"
|
||||
ids = df.iloc[row,column].index("単位「")
|
||||
ide = df.iloc[row,column].index("」")
|
||||
unit = df.iloc[row,column][ids+3:ide]
|
||||
p = p + createkintonefields(propertyname, unit,trueformat)
|
||||
else:
|
||||
continue
|
||||
elif(propertyname =="mixValue"):
|
||||
if(df.iloc[row,column].find("レコード登録時の日") != -1):
|
||||
propertyname = "defaultNowValue"
|
||||
df.iloc[row,column] = trueformat
|
||||
p = p + createkintonefields(propertyname, df.iloc[row,column],trueformat)
|
||||
elif(df.iloc[row,column].find("計:") != -1):
|
||||
propertyname = "expression"
|
||||
p = p + createkintonefields(propertyname, df.iloc[row,column],trueformat)
|
||||
elif(df.iloc[row,column] !=""):
|
||||
propertyname = "defaultValue"
|
||||
p = p + createkintonefields(propertyname, df.iloc[row,column],trueformat)
|
||||
else:
|
||||
continue
|
||||
elif(propertyname=="max" or propertyname == "min"):
|
||||
if(df.iloc[row,typecolumn] == "NUMBER"):
|
||||
propertyname = property[column-1] + "Value"
|
||||
p = p + createkintonefields(propertyname, df.iloc[row,column],trueformat)
|
||||
else:
|
||||
propertyname = property[column-1] + "Length"
|
||||
p = p + createkintonefields(propertyname, df.iloc[row,column],trueformat)
|
||||
else:
|
||||
p.append(f"\"{property[column-1]}\":\"{df.iloc[row,column]}\"")
|
||||
col.append(f"\"{df.iloc[row,2]}\":{{{','.join(p)}}}")
|
||||
fields = ",".join(col).replace("False","false").replace("True","true")
|
||||
p = p + createkintonefields(propertyname, df.iloc[row,column],trueformat)
|
||||
|
||||
# if(propertyname=="options"):
|
||||
# o=[]
|
||||
# for v in df.iloc[row,column].split(','):
|
||||
# o.append(f"\"{v.split('|')[0]}\":{{\"label\":\"{v.split('|')[0]}\",\"index\":\"{v.split('|')[1]}\"}}")
|
||||
# p.append(f"\"options\":{{{','.join(o)}}}")
|
||||
# elif(propertyname=="expression"):
|
||||
# p.append(f"\"hideExpression\":true")
|
||||
# p.append(f"\"expression\":{df.iloc[row,column].split(':')[1]}")
|
||||
# elif(propertyname=="required" or propertyname =="unique" or propertyname=="defaultNowValue" or propertyname=="hideExpression" or propertyname=="digit"):
|
||||
# if (df.iloc[row,column] == trueformat):
|
||||
# p.append(f"\"{propertyname}\":true")
|
||||
# else:
|
||||
# p.append(f"\"{propertyname}\":false")
|
||||
# elif(propertyname =="protocol"):
|
||||
# if(df.iloc[row,column] == "メールアドレス"):
|
||||
# p.append("\"protocol\":\"MAIL\"")
|
||||
# elif(df.iloc[row,column] == "Webサイト"):
|
||||
# p.append("\"protocol\":\"WEB\"")
|
||||
# elif(df.iloc[row,column] == "電話番号"):
|
||||
# p.append("\"protocol\":\"CALL\"")
|
||||
# else:
|
||||
# p.append(f"\"{propertyname}\":\"{df.iloc[row,column]}\"")
|
||||
|
||||
|
||||
col.append(f"\"{df.iloc[row,codecolumn]}\":{{{','.join(p)}}}")
|
||||
fields = ",".join(col).replace("\\", "\\\\")
|
||||
return json.loads(f"{{{fields}}}")
|
||||
|
||||
def getsettingfromexcel(df):
|
||||
@@ -39,10 +156,10 @@ def getsettingfromexcel(df):
|
||||
des = df.iloc[2,2]
|
||||
return {"name":appname,"description":des}
|
||||
|
||||
def getsettingfromkintone(app:str):
|
||||
headers={c.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE}
|
||||
def getsettingfromkintone(app:str,c:config.KINTONE_ENV):
|
||||
headers={config.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE}
|
||||
params = {"app":app}
|
||||
url = f"{c.BASE_URL}{c.API_V1_STR}/app/settings.json"
|
||||
url = f"{c.BASE_URL}{config.API_V1_STR}/app/settings.json"
|
||||
r = httpx.get(url,headers=headers,params=params)
|
||||
return r.json()
|
||||
|
||||
@@ -54,24 +171,24 @@ def analysesettings(excel,kintone):
|
||||
updatesettings[key] = excel[key]
|
||||
return updatesettings
|
||||
|
||||
def createkintoneapp(name:str):
|
||||
headers={c.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE,"Content-Type": "application/json"}
|
||||
def createkintoneapp(name:str,c:config.KINTONE_ENV):
|
||||
headers={config.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE,"Content-Type": "application/json"}
|
||||
data = {"name":name}
|
||||
url = f"{c.BASE_URL}{c.API_V1_STR}/preview/app.json"
|
||||
url = f"{c.BASE_URL}{config.API_V1_STR}/preview/app.json"
|
||||
r = httpx.post(url,headers=headers,data=json.dumps(data))
|
||||
return r.json()
|
||||
|
||||
def updateappsettingstokintone(app:str,updates:dict):
|
||||
headers={c.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE,"Content-Type": "application/json"}
|
||||
url = f"{c.BASE_URL}{c.API_V1_STR}/preview/app/settings.json"
|
||||
def updateappsettingstokintone(app:str,updates:dict,c:config.KINTONE_ENV):
|
||||
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/settings.json"
|
||||
data = {"app":app}
|
||||
data.update(updates)
|
||||
r = httpx.put(url,headers=headers,data=json.dumps(data))
|
||||
return r.json()
|
||||
|
||||
def addfieldstokintone(app:str,fields:dict,revision:str = None):
|
||||
headers={c.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE,"Content-Type": "application/json"}
|
||||
url = f"{c.BASE_URL}{c.API_V1_STR}/preview/app/form/fields.json"
|
||||
def addfieldstokintone(app:str,fields:dict,c:config.KINTONE_ENV,revision:str = None):
|
||||
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/form/fields.json"
|
||||
if revision != None:
|
||||
data = {"app":app,"revision":revision,"properties":fields}
|
||||
else:
|
||||
@@ -79,35 +196,75 @@ def addfieldstokintone(app:str,fields:dict,revision:str = None):
|
||||
r = httpx.post(url,headers=headers,data=json.dumps(data))
|
||||
return r.json()
|
||||
|
||||
def updatefieldstokintone(app:str,revision:str,fields:dict):
|
||||
headers={c.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE,"Content-Type": "application/json"}
|
||||
url = f"{c.BASE_URL}{c.API_V1_STR}/preview/app/form/fields.json"
|
||||
def updatefieldstokintone(app:str,revision:str,fields:dict,c:config.KINTONE_ENV):
|
||||
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/form/fields.json"
|
||||
data = {"app":app,"properties":fields}
|
||||
r = httpx.put(url,headers=headers,data=json.dumps(data))
|
||||
return r.json()
|
||||
|
||||
def deletefieldsfromkintone(app:str,revision:str,fields:dict):
|
||||
headers={c.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE,"Content-Type": "application/json"}
|
||||
url = f"{c.BASE_URL}{c.API_V1_STR}/preview/app/form/fields.json"
|
||||
def deletefieldsfromkintone(app:str,revision:str,fields:dict,c:config.KINTONE_ENV):
|
||||
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/form/fields.json"
|
||||
params = {"app":app,"revision":revision,"fields":fields}
|
||||
#r = httpx.delete(url,headers=headers,content=json.dumps(params))
|
||||
r = httpx.request(method="DELETE",url=url,headers=headers,content=json.dumps(params))
|
||||
return r.json()
|
||||
|
||||
def deoployappfromkintone(app:str,revision:str):
|
||||
headers={c.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE,"Content-Type": "application/json"}
|
||||
url = f"{c.BASE_URL}{c.API_V1_STR}/preview/app/deploy.json"
|
||||
def deoployappfromkintone(app:str,revision:str,c:config.KINTONE_ENV):
|
||||
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/deploy.json"
|
||||
data = {"apps":[{"app":app,"revision":revision}],"revert": False}
|
||||
r = httpx.post(url,headers=headers,data=json.dumps(data))
|
||||
return r.json
|
||||
|
||||
def getfieldsfromkintone(app):
|
||||
headers={c.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE}
|
||||
|
||||
# 既定項目に含めるアプリのフィールドのみ取得する
|
||||
# スペース、枠線、ラベルを含まない
|
||||
def getfieldsfromkintone(app:str,c:config.KINTONE_ENV):
|
||||
headers={config.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE}
|
||||
params = {"app":app}
|
||||
url = f"{c.BASE_URL}{c.API_V1_STR}/app/form/fields.json"
|
||||
url = f"{c.BASE_URL}{config.API_V1_STR}/app/form/fields.json"
|
||||
r = httpx.get(url,headers=headers,params=params)
|
||||
return r.json()
|
||||
|
||||
# フォームに配置するフィールドのみ取得する
|
||||
# スペース、枠線、ラベルも含める
|
||||
def getformfromkintone(app:str,c:config.KINTONE_ENV):
|
||||
headers={config.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE}
|
||||
params = {"app":app}
|
||||
url = f"{c.BASE_URL}{config.API_V1_STR}/form.json"
|
||||
r = httpx.get(url,headers=headers,params=params)
|
||||
return r.json()
|
||||
|
||||
|
||||
def merge_kintone_fields(fields_response: dict, form_response: dict) -> dict:
|
||||
fields_properties = fields_response.get('properties', {})
|
||||
form_properties = form_response.get('properties', [])
|
||||
|
||||
merged_properties = {k: v for k, v in fields_properties.items()}
|
||||
|
||||
for index, form_field in enumerate(form_properties):
|
||||
code = form_field.get('code')
|
||||
if code:
|
||||
if code and code not in merged_properties:
|
||||
merged_properties[code] = form_field
|
||||
else:
|
||||
element_id = form_field.get('elementId')
|
||||
if element_id:
|
||||
key = element_id
|
||||
form_field['code']=element_id
|
||||
form_field['label']=form_field.get('type')
|
||||
# else:
|
||||
# key = f"{form_field.get('type')}_{index}"
|
||||
merged_properties[key] = form_field
|
||||
|
||||
merged_response = {
|
||||
'revision': fields_response.get('revision', ''),
|
||||
'properties': merged_properties
|
||||
}
|
||||
|
||||
return merged_response
|
||||
|
||||
def analysefields(excel,kintone):
|
||||
updatefields={}
|
||||
addfields={}
|
||||
@@ -116,22 +273,22 @@ def analysefields(excel,kintone):
|
||||
adds = excel.keys() - kintone.keys()
|
||||
dels = kintone.keys() - excel.keys()
|
||||
for key in updates:
|
||||
for p in property:
|
||||
if excel[key].get(p) != None and kintone[key][p] != excel[key][p]:
|
||||
for p in config.KINTONE_FIELD_PROPERTY:
|
||||
if excel[key].get(p) != None and kintone[key].get(p) != None and kintone[key][p] != excel[key][p]:
|
||||
updatefields[key] = excel[key]
|
||||
break
|
||||
for key in adds:
|
||||
addfields[key] = excel[key]
|
||||
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)
|
||||
|
||||
return {"update":updatefields,"add":addfields,"del":delfields}
|
||||
|
||||
def getprocessfromkintone(app:str):
|
||||
headers={c.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE}
|
||||
def getprocessfromkintone(app:str,c:config.KINTONE_ENV):
|
||||
headers={config.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE}
|
||||
params = {"app":app}
|
||||
url = f"{c.BASE_URL}{c.API_V1_STR}/app/status.json"
|
||||
url = f"{c.BASE_URL}{config.API_V1_STR}/app/status.json"
|
||||
r = httpx.get(url,headers=headers,params=params)
|
||||
return r.json()
|
||||
|
||||
@@ -195,27 +352,81 @@ def analysprocess(excel,kintone):
|
||||
# return True
|
||||
return diff
|
||||
|
||||
def updateprocesstokintone(app:str,process:dict):
|
||||
headers={c.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE,"Content-Type": "application/json"}
|
||||
url = f"{c.BASE_URL}{c.API_V1_STR}/preview/app/status.json"
|
||||
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"}
|
||||
url = f"{c.BASE_URL}{config.API_V1_STR}/preview/app/status.json"
|
||||
data = {"app":app,"enable":True}
|
||||
data.update(process)
|
||||
r = httpx.put(url,headers=headers,data=json.dumps(data))
|
||||
return r.json()
|
||||
|
||||
def getkintoneusers():
|
||||
headers={c.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE}
|
||||
def getkintoneusers(c:config.KINTONE_ENV):
|
||||
headers={config.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE}
|
||||
url = f"{c.BASE_URL}/v1/users.json"
|
||||
r = httpx.get(url,headers=headers)
|
||||
return r.json()
|
||||
|
||||
def getkintoneorgs():
|
||||
headers={c.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE}
|
||||
def getkintoneorgs(c:config.KINTONE_ENV):
|
||||
headers={config.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE}
|
||||
params = {"code":c.KINTONE_USER}
|
||||
url = f"{c.BASE_URL}/v1/user/organizations.json"
|
||||
r = httpx.get(url,headers=headers,params=params)
|
||||
return r.json()
|
||||
|
||||
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')}
|
||||
headers={config.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE}
|
||||
data ={'name':'file','filename':os.path.basename(file)}
|
||||
url = f"{c.BASE_URL}/k/v1/file.json"
|
||||
r = httpx.post(url,headers=headers,data=data,files=upload_files)
|
||||
return r.json()
|
||||
|
||||
def updateappjscss(app,uploads,c:config.KINTONE_ENV):
|
||||
dsjs = []
|
||||
dscss = []
|
||||
for upload in uploads:
|
||||
for key in upload:
|
||||
if key.endswith('.js'):
|
||||
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'):
|
||||
dscss.append({'type':'FILE','file':{'fileKey':upload[key]}})
|
||||
ds ={'js':dsjs,'css':dscss}
|
||||
mb ={'js':[],'css':[]}
|
||||
data = {'app':app,'scope':'ALL','desktop':ds,'mobile':mb}
|
||||
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/customize.json"
|
||||
print(data)
|
||||
r = httpx.put(url,headers=headers,data=json.dumps(data))
|
||||
return r.json()
|
||||
|
||||
def getTempPath(filename):
|
||||
scriptdir = Path(__file__).resolve().parent
|
||||
rootdir = scriptdir.parent.parent.parent.parent
|
||||
fpath = os.path.join(rootdir,"Temp",filename)
|
||||
return fpath
|
||||
|
||||
def createappjs(domainid,app):
|
||||
db = SessionLocal()
|
||||
flows = get_flows_by_app(db,domainid,app)
|
||||
db.close()
|
||||
content={}
|
||||
for flow in flows:
|
||||
content[flow.eventid] = {'flowid':flow.flowid,'name':flow.name,'content':flow.content}
|
||||
js = 'const alcflow=' + json.dumps(content)
|
||||
# scriptdir = Path(__file__).resolve().parent
|
||||
# rootdir = scriptdir.parent.parent.parent.parent
|
||||
# fpath = os.path.join(rootdir,"Temp",f"alc_setting_{app}.js")
|
||||
fpath = getTempPath(f"alc_setting_{app}.js")
|
||||
print(fpath)
|
||||
with open(fpath,'w') as file:
|
||||
file.write(js)
|
||||
return fpath
|
||||
|
||||
@r.post("/test",)
|
||||
async def test(file:UploadFile= File(...),app:str=None):
|
||||
if file.filename.endswith('.xlsx'):
|
||||
@@ -233,15 +444,26 @@ async def test(file:UploadFile= File(...),app:str=None):
|
||||
# kintone = getfieldsfromkintone(app)
|
||||
# fields = analysefields(excel,kintone["properties"])
|
||||
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:
|
||||
raise HTTPException(status_code=400, detail=f"File {file.filename} is not an Excel file")
|
||||
|
||||
return test
|
||||
|
||||
|
||||
@r.post("/upload",)
|
||||
async def upload(files:t.List[UploadFile] = File(...)):
|
||||
@r.post("/download",)
|
||||
async def download(request:Request,key,c:config.KINTONE_ENV=Depends(getkintoneenv)):
|
||||
try:
|
||||
headers={config.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE}
|
||||
params = {"fileKey":key}
|
||||
url = f"{c.BASE_URL}/k/v1/file.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")
|
||||
async def upload(request:Request,files:t.List[UploadFile] = File(...)):
|
||||
dataframes = []
|
||||
for file in files:
|
||||
if file.filename.endswith('.xlsx'):
|
||||
@@ -251,61 +473,112 @@ async def upload(files:t.List[UploadFile] = File(...)):
|
||||
print(df)
|
||||
dataframes.append(df)
|
||||
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:
|
||||
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]}
|
||||
|
||||
@r.get("/allapps",)
|
||||
async def allapps():
|
||||
headers={c.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE}
|
||||
url = f"{c.BASE_URL}{c.API_V1_STR}/apps.json"
|
||||
r = httpx.get(url,headers=headers)
|
||||
return r.json()
|
||||
@r.post("/updatejscss")
|
||||
async def jscss(request:Request,app:str,files:t.List[UploadFile] = File(...),env = Depends(getkintoneenv)):
|
||||
try:
|
||||
jscs=[]
|
||||
for file in files:
|
||||
fbytes = file.file.read()
|
||||
fname = file.filename
|
||||
fpath = '{}\\{}'.format('Temp',fname)
|
||||
fout = open(fpath,'wb')
|
||||
fout.write(fbytes)
|
||||
fout.close()
|
||||
upload = uploadkintonefiles(fpath,env)
|
||||
if upload.get('fileKey') != None:
|
||||
jscs.append({ file.filename:upload['fileKey']})
|
||||
appjscs = updateappjscss(app,jscs,env)
|
||||
if appjscs.get("revision") != None:
|
||||
deoployappfromkintone(app,appjscs["revision"],env)
|
||||
return appjscs
|
||||
except Exception as 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(app:str):
|
||||
headers={c.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE}
|
||||
url = f"{c.BASE_URL}{c.API_V1_STR}/app.json"
|
||||
params ={"id":app}
|
||||
r = httpx.get(url,headers=headers,params=params)
|
||||
return r.json()
|
||||
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_NAME}->{app}):",e)
|
||||
|
||||
@r.get("/allapps")
|
||||
async def allapps(request:Request,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}/apps.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_NAME}):",e)
|
||||
|
||||
@r.get("/appfields")
|
||||
async def appfields(app:str):
|
||||
return getfieldsfromkintone(app)
|
||||
async def appfields(request:Request,app:str,env = Depends(getkintoneenv)):
|
||||
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_NAME}->{app}):",e)
|
||||
|
||||
@r.get("/allfields")
|
||||
async def allfields(request:Request,app:str,env = Depends(getkintoneenv)):
|
||||
try:
|
||||
field_resp = getfieldsfromkintone(app,env)
|
||||
form_resp = getformfromkintone(app,env)
|
||||
return merge_kintone_fields(field_resp,form_resp)
|
||||
except Exception as e:
|
||||
raise APIException('kintone:allfields',request.url._url, f"Error occurred while get form fileds({env.DOMAIN_NAME}->{app}):",e)
|
||||
|
||||
@r.get("/appprocess")
|
||||
async def appprocess(app:str):
|
||||
return getprocessfromkintone(app)
|
||||
async def appprocess(request:Request,app:str,env = Depends(getkintoneenv)):
|
||||
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_NAME}->{app}):",e)
|
||||
|
||||
@r.get("/alljscs")
|
||||
async def alljscs(app:str):
|
||||
headers={c.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE}
|
||||
url = f"{c.BASE_URL}{c.API_V1_STR}/app/customize.json"
|
||||
params = {"app":app}
|
||||
r = httpx.get(url,headers=headers,params=params)
|
||||
return r.json()
|
||||
@r.get("/alljscss")
|
||||
async def alljscs(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/customize.json"
|
||||
params = {"app":app}
|
||||
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_NAME}->{app}):",e)
|
||||
|
||||
@r.post("/createapp",)
|
||||
async def createapp(name:str):
|
||||
headers={c.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE,"Content-Type": "application/json"}
|
||||
data = {"name":name}
|
||||
url = f"{c.BASE_URL}{c.API_V1_STR}/preview/app.json"
|
||||
r = httpx.post(url,headers=headers,data=json.dumps(data))
|
||||
result = r.json()
|
||||
if result.get("app") != None:
|
||||
url = f"{c.BASE_URL}{c.API_V1_STR}/preview/app/deploy.json"
|
||||
data = {"apps":[result],"revert": False}
|
||||
async def createapp(request:Request,name:str,c:config.KINTONE_ENV=Depends(getkintoneenv)):
|
||||
try:
|
||||
headers={config.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE,"Content-Type": "application/json"}
|
||||
data = {"name":name}
|
||||
url = f"{c.BASE_URL}{config.API_V1_STR}/preview/app.json"
|
||||
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_NAME}->{name}):",e)
|
||||
|
||||
property=["label","code","type","required","defaultValue","options"]
|
||||
|
||||
@r.post("/createappfromexcel",)
|
||||
async def createappfromexcel(files:t.List[UploadFile] = File(...)):
|
||||
async def createappfromexcel(request:Request,files:t.List[UploadFile] = File(...),format:int = 0,env = Depends(getkintoneenv)):
|
||||
try:
|
||||
mapping = getkintoneformat()[format]
|
||||
except Exception as e:
|
||||
raise APIException('kintone:createappfromexcel',request.url._url, f"Error occurred while get kintone format:",e)
|
||||
|
||||
for file in files:
|
||||
if file.filename.endswith('.xlsx'):
|
||||
try:
|
||||
@@ -315,87 +588,90 @@ async def createappfromexcel(files:t.List[UploadFile] = File(...)):
|
||||
appname = df.iloc[0,2]
|
||||
desc = df.iloc[2,2]
|
||||
result = {"app":0,"revision":0,"msg":""}
|
||||
fields = getfieldsfromexcel(df)
|
||||
users = getkintoneusers()
|
||||
orgs = getkintoneorgs()
|
||||
fields = getfieldsfromexcel(df,mapping)
|
||||
users = getkintoneusers(env)
|
||||
orgs = getkintoneorgs(env)
|
||||
processes = getprocessfromexcel(df,users["users"], orgs["organizationTitles"])
|
||||
app = createkintoneapp(appname)
|
||||
app = createkintoneapp(appname,env)
|
||||
if app.get("app") != None:
|
||||
result["app"] = app["app"]
|
||||
app = updateappsettingstokintone(result["app"],{"description":desc})
|
||||
app = updateappsettingstokintone(result["app"],{"description":desc},env)
|
||||
if app.get("revision") != None:
|
||||
result["revision"] = app["revision"]
|
||||
app = addfieldstokintone(result["app"],fields)
|
||||
app = addfieldstokintone(result["app"],fields,env)
|
||||
if len(processes)> 0:
|
||||
app = updateprocesstokintone(result["app"],processes)
|
||||
app = updateprocesstokintone(result["app"],processes,env)
|
||||
if app.get("revision") != None:
|
||||
result["revision"] = app["revision"]
|
||||
deoployappfromkintone(result["app"],result["revision"])
|
||||
deoployappfromkintone(result["app"],result["revision"],env)
|
||||
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_NAME}->{file.filename}):",e)
|
||||
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
|
||||
|
||||
|
||||
@r.post("/updateappfromexcel",)
|
||||
async def updateappfromexcel(app:str,files:t.List[UploadFile] = File(...)):
|
||||
@r.post("/updateappfromexcel")
|
||||
async def updateappfromexcel(request:Request,app:str,files:t.List[UploadFile] = File(...),format:int = 0,env = Depends(getkintoneenv)):
|
||||
try:
|
||||
mapping = getkintoneformat()[format]
|
||||
except Exception as e:
|
||||
raise APIException('kintone:updateappfromexcel',request.url._url, f"Error occurred while get kintone format:",e)
|
||||
|
||||
for file in files:
|
||||
if file.filename.endswith('.xlsx'):
|
||||
try:
|
||||
content = await file.read()
|
||||
df = pd.read_excel(BytesIO(content))
|
||||
excel = getsettingfromexcel(df)
|
||||
kintone= getsettingfromkintone(app)
|
||||
kintone= getsettingfromkintone(app,env)
|
||||
settings = analysesettings(excel,kintone)
|
||||
excel = getfieldsfromexcel(df)
|
||||
kintone = getfieldsfromkintone(app)
|
||||
users = getkintoneusers()
|
||||
orgs = getkintoneorgs()
|
||||
excel = getfieldsfromexcel(df,mapping)
|
||||
kintone = getfieldsfromkintone(app,env)
|
||||
users = getkintoneusers(env)
|
||||
orgs = getkintoneorgs(env)
|
||||
exp = getprocessfromexcel(df,users["users"], orgs["organizationTitles"])
|
||||
#exp = getprocessfromexcel(df)
|
||||
kinp = getprocessfromkintone(app)
|
||||
kinp = getprocessfromkintone(app,env)
|
||||
process = analysprocess(exp,kinp)
|
||||
revision = kintone["revision"]
|
||||
fields = analysefields(excel,kintone["properties"])
|
||||
result = {"app":app,"revision":revision,"msg":"No Update"}
|
||||
deploy = False
|
||||
if len(fields["update"]) > 0:
|
||||
result = updatefieldstokintone(app,revision,fields["update"])
|
||||
result = updatefieldstokintone(app,revision,fields["update"],env)
|
||||
revision = result["revision"]
|
||||
deploy = True
|
||||
if len(fields["add"]) > 0:
|
||||
result = addfieldstokintone(app,fields["add"],revision)
|
||||
result = addfieldstokintone(app,fields["add"],env,revision)
|
||||
revision = result["revision"]
|
||||
deploy = True
|
||||
if len(fields["del"]) > 0:
|
||||
result = deletefieldsfromkintone(app,revision,fields["del"])
|
||||
result = deletefieldsfromkintone(app,revision,fields["del"],env)
|
||||
revision = result["revision"]
|
||||
deploy = True
|
||||
if len(settings) > 0:
|
||||
result = updateappsettingstokintone(app,settings)
|
||||
result = updateappsettingstokintone(app,settings,env)
|
||||
revision = result["revision"]
|
||||
deploy = True
|
||||
if len(process)>0:
|
||||
result = updateprocesstokintone(app,exp)
|
||||
result = updateprocesstokintone(app,exp,env)
|
||||
revision = result["revision"]
|
||||
deploy = True
|
||||
if deploy:
|
||||
result = deoployappfromkintone(app,revision)
|
||||
result = deoployappfromkintone(app,revision,env)
|
||||
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_NAME}->{file.filename}):",e)
|
||||
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
|
||||
|
||||
@r.post("/updateprocessfromexcel",)
|
||||
async def updateprocessfromexcel(app:str):
|
||||
async def updateprocessfromexcel(request:Request,app:str,env = Depends(getkintoneenv)):
|
||||
|
||||
try:
|
||||
excel = getprocessfromexcel()
|
||||
kintone = getprocessfromkintone(app)
|
||||
kintone = getprocessfromkintone(app,env)
|
||||
revision = kintone["revision"]
|
||||
#fields = analysefields(excel,kintone["properties"])
|
||||
result = {"app":app,"revision":revision,"msg":"No Update"}
|
||||
@@ -416,14 +692,32 @@ async def updateprocessfromexcel(app:str):
|
||||
# result = updateappsettingstokintone(app,settings)
|
||||
# revision = result["revision"]
|
||||
# deploy = True
|
||||
result = updateprocesstokintone(app,excel)
|
||||
result = updateprocesstokintone(app,excel,env)
|
||||
revision = result["revision"]
|
||||
deploy = True
|
||||
if deploy:
|
||||
result = deoployappfromkintone(app,revision)
|
||||
result = deoployappfromkintone(app,revision,env)
|
||||
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_NAME}->{app}):",e)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
@r.post("/createjstokintone",)
|
||||
async def createjstokintone(request:Request,app:str,env:config.KINTONE_ENV = Depends(getkintoneenv)):
|
||||
try:
|
||||
jscs=[]
|
||||
files=[]
|
||||
files.append(createappjs(env.DOMAIN_ID, app))
|
||||
files.append(getTempPath('alc_runtime.js'))
|
||||
files.append(getTempPath('alc_runtime.css'))
|
||||
for file in files:
|
||||
upload = uploadkintonefiles(file,env)
|
||||
if upload.get('fileKey') != None:
|
||||
jscs.append({ file :upload['fileKey']})
|
||||
appjscs = updateappjscss(app,jscs,env)
|
||||
if appjscs.get("revision") != None:
|
||||
deoployappfromkintone(app,appjscs["revision"],env)
|
||||
return appjscs
|
||||
except Exception as e:
|
||||
raise APIException('kintone:createjstokintone',request.url._url, f"Error occurred while create js ({env.DOMAIN_NAME}->{app}):",e)
|
||||
|
||||
@@ -2,7 +2,10 @@ from fastapi import Request,Depends, APIRouter, UploadFile,HTTPException,File
|
||||
from app.db import Base,engine
|
||||
from app.db.session import get_db
|
||||
from app.db.crud import *
|
||||
from app.db.schemas import AppBase, AppEdit, App,Kintone
|
||||
from app.db.schemas import *
|
||||
from typing import List
|
||||
from app.core.auth import get_current_active_user,get_current_user
|
||||
from app.core.apiexception import APIException
|
||||
|
||||
platform_router = r = APIRouter()
|
||||
|
||||
@@ -16,9 +19,11 @@ async def appsetting_details(
|
||||
id: int,
|
||||
db=Depends(get_db),
|
||||
):
|
||||
app = get_appsetting(db, id)
|
||||
return app
|
||||
|
||||
try:
|
||||
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)
|
||||
async def appsetting_create(
|
||||
@@ -26,7 +31,10 @@ async def appsetting_create(
|
||||
app: AppBase,
|
||||
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(
|
||||
@@ -38,7 +46,10 @@ async def appsetting_edit(
|
||||
app: AppEdit,
|
||||
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(
|
||||
@@ -49,8 +60,10 @@ async def appsettings_delete(
|
||||
id: int,
|
||||
db=Depends(get_db),
|
||||
):
|
||||
|
||||
return delete_appsetting(db, id)
|
||||
try:
|
||||
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(
|
||||
@@ -63,5 +76,270 @@ async def kintone_data(
|
||||
type: int,
|
||||
db=Depends(get_db),
|
||||
):
|
||||
kintone = get_kintones(db, type)
|
||||
return kintone
|
||||
try:
|
||||
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(
|
||||
"/actions",
|
||||
response_model=t.List[Action],
|
||||
response_model_exclude={"id"},
|
||||
response_model_exclude_none=True,
|
||||
)
|
||||
async def action_data(
|
||||
request: Request,
|
||||
db=Depends(get_db),
|
||||
):
|
||||
try:
|
||||
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(
|
||||
"/flow/{flowid}",
|
||||
response_model=Flow,
|
||||
response_model_exclude_none=True,
|
||||
)
|
||||
async def flow_details(
|
||||
request: Request,
|
||||
flowid: str,
|
||||
db=Depends(get_db),
|
||||
):
|
||||
try:
|
||||
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(
|
||||
"/flows/{appid}",
|
||||
response_model=List[Flow],
|
||||
response_model_exclude_none=True,
|
||||
)
|
||||
async def flow_list(
|
||||
request: Request,
|
||||
appid: str,
|
||||
user=Depends(get_current_user),
|
||||
db=Depends(get_db),
|
||||
):
|
||||
try:
|
||||
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)
|
||||
async def flow_create(
|
||||
request: Request,
|
||||
flow: FlowBase,
|
||||
user=Depends(get_current_user),
|
||||
db=Depends(get_db),
|
||||
):
|
||||
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(
|
||||
"/flow/{flowid}", response_model=Flow, response_model_exclude_none=True
|
||||
)
|
||||
async def flow_edit(
|
||||
request: Request,
|
||||
flow: FlowBase,
|
||||
db=Depends(get_db),
|
||||
):
|
||||
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(
|
||||
"/flow/{flowid}", response_model=Flow, response_model_exclude_none=True
|
||||
)
|
||||
async def flow_delete(
|
||||
request: Request,
|
||||
flowid: str,
|
||||
db=Depends(get_db),
|
||||
):
|
||||
try:
|
||||
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(
|
||||
"/domains/{tenantid}",
|
||||
response_model=List[Domain],
|
||||
response_model_exclude_none=True,
|
||||
)
|
||||
async def domain_details(
|
||||
request: Request,
|
||||
tenantid:str,
|
||||
db=Depends(get_db),
|
||||
):
|
||||
try:
|
||||
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)
|
||||
async def domain_create(
|
||||
request: Request,
|
||||
domain: DomainBase,
|
||||
db=Depends(get_db),
|
||||
):
|
||||
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(
|
||||
"/domain", response_model=Domain, response_model_exclude_none=True
|
||||
)
|
||||
async def domain_edit(
|
||||
request: Request,
|
||||
domain: DomainBase,
|
||||
db=Depends(get_db),
|
||||
):
|
||||
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(
|
||||
"/domain/{id}", response_model=Domain, response_model_exclude_none=True
|
||||
)
|
||||
async def domain_delete(
|
||||
request: Request,
|
||||
id: int,
|
||||
db=Depends(get_db),
|
||||
):
|
||||
try:
|
||||
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(
|
||||
"/domain",
|
||||
response_model=List[Domain],
|
||||
response_model_exclude_none=True,
|
||||
)
|
||||
async def userdomain_details(
|
||||
request: Request,
|
||||
user=Depends(get_current_user),
|
||||
db=Depends(get_db),
|
||||
):
|
||||
try:
|
||||
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(
|
||||
"/domain/{userid}",
|
||||
response_model_exclude_none=True,
|
||||
)
|
||||
async def create_userdomain(
|
||||
request: Request,
|
||||
userid: int,
|
||||
domainids:list,
|
||||
db=Depends(get_db),
|
||||
):
|
||||
try:
|
||||
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(
|
||||
"/domain/{domainid}/{userid}", response_model_exclude_none=True
|
||||
)
|
||||
async def userdomain_delete(
|
||||
request: Request,
|
||||
domainid:int,
|
||||
userid: int,
|
||||
db=Depends(get_db),
|
||||
):
|
||||
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(
|
||||
"/activedomain",
|
||||
response_model=Domain,
|
||||
response_model_exclude_none=True,
|
||||
)
|
||||
async def get_useractivedomain(
|
||||
request: Request,
|
||||
user=Depends(get_current_user),
|
||||
db=Depends(get_db),
|
||||
):
|
||||
try:
|
||||
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(
|
||||
"/activedomain/{domainid}",
|
||||
response_model_exclude_none=True,
|
||||
)
|
||||
async def update_activeuserdomain(
|
||||
request: Request,
|
||||
domainid:int,
|
||||
user=Depends(get_current_user),
|
||||
db=Depends(get_db),
|
||||
):
|
||||
try:
|
||||
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(
|
||||
"/events",
|
||||
response_model=t.List[Event],
|
||||
response_model_exclude={"id"},
|
||||
response_model_exclude_none=True,
|
||||
)
|
||||
async def event_data(
|
||||
request: Request,
|
||||
db=Depends(get_db),
|
||||
):
|
||||
try:
|
||||
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(
|
||||
"/eventactions/{eventid}",
|
||||
response_model=t.List[Action],
|
||||
response_model_exclude={"id"},
|
||||
response_model_exclude_none=True,
|
||||
)
|
||||
async def eventactions_data(
|
||||
request: Request,
|
||||
eventid: str,
|
||||
db=Depends(get_db),
|
||||
):
|
||||
try:
|
||||
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)
|
||||
26
backend/app/core/apiexception.py
Normal file
26
backend/app/core/apiexception.py
Normal 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()
|
||||
@@ -3,7 +3,7 @@ from fastapi import Depends, HTTPException, status
|
||||
from jwt import PyJWTError
|
||||
|
||||
from app.db import models, schemas, session
|
||||
from app.db.crud import get_user_by_email, create_user
|
||||
from app.db.crud import get_user_by_email, create_user,get_user
|
||||
from app.core import security
|
||||
|
||||
|
||||
@@ -19,14 +19,14 @@ async def get_current_user(
|
||||
payload = jwt.decode(
|
||||
token, security.SECRET_KEY, algorithms=[security.ALGORITHM]
|
||||
)
|
||||
email: str = payload.get("sub")
|
||||
if email is None:
|
||||
id: int = payload.get("sub")
|
||||
if id is None:
|
||||
raise credentials_exception
|
||||
permissions: str = payload.get("permissions")
|
||||
token_data = schemas.TokenData(email=email, permissions=permissions)
|
||||
token_data = schemas.TokenData(id = id, permissions=permissions)
|
||||
except PyJWTError:
|
||||
raise credentials_exception
|
||||
user = get_user_by_email(db, token_data.email)
|
||||
user = get_user(db, token_data.id)
|
||||
if user is None:
|
||||
raise credentials_exception
|
||||
return user
|
||||
@@ -58,7 +58,7 @@ def authenticate_user(db, email: str, password: str):
|
||||
return user
|
||||
|
||||
|
||||
def sign_up_new_user(db, email: str, password: str):
|
||||
def sign_up_new_user(db, email: str, password: str, firstname: str,lastname: str):
|
||||
user = get_user_by_email(db, email)
|
||||
if user:
|
||||
return False # User already exists
|
||||
@@ -67,6 +67,8 @@ def sign_up_new_user(db, email: str, password: str):
|
||||
schemas.UserCreate(
|
||||
email=email,
|
||||
password=password,
|
||||
first_name = firstname,
|
||||
last_name = lastname,
|
||||
is_active=True,
|
||||
is_superuser=False,
|
||||
),
|
||||
|
||||
@@ -1,18 +1,38 @@
|
||||
import os
|
||||
import base64
|
||||
|
||||
PROJECT_NAME = "KintoneAppBuilder"
|
||||
|
||||
SQLALCHEMY_DATABASE_URI = "mssql+pymssql://maxz64@maxzdb:m@xz1205@maxzdb.database.windows.net/alloc"
|
||||
|
||||
BASE_URL = "https://mfu07rkgnb7c.cybozu.com"
|
||||
|
||||
#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/unittest"
|
||||
API_V1_STR = "/k/v1"
|
||||
|
||||
API_V1_AUTH_KEY = "X-Cybozu-Authorization"
|
||||
|
||||
API_V1_AUTH_VALUE = "TVhaOm1heHoxMjA1"
|
||||
DEPLOY_MODE = "PROD" #DEV,PROD
|
||||
|
||||
KINTONE_USER = "MXZ"
|
||||
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_PROPERTY=['label','code','type','required','unique','maxValue','minValue','maxLength','minLength','defaultValue','defaultNowValue','options','expression','hideExpression','digit','protocol','displayScale','unit','unitPosition']
|
||||
|
||||
class KINTONE_ENV:
|
||||
|
||||
BASE_URL = ""
|
||||
|
||||
API_V1_AUTH_VALUE = ""
|
||||
|
||||
KINTONE_USER = ""
|
||||
|
||||
DOMAIN_ID = ""
|
||||
|
||||
DOMAIN_NAME =""
|
||||
|
||||
def __init__(self,domain) -> None:
|
||||
self.DOMAIN_NAME=domain.name
|
||||
self.DOMAIN_ID=domain.id
|
||||
self.BASE_URL = domain.url
|
||||
self.KINTONE_USER = domain.kintoneuser
|
||||
self.API_V1_AUTH_VALUE = base64.b64encode(bytes(f"{domain.kintoneuser}:{domain.kintonepwd}","utf-8"))
|
||||
@@ -9,7 +9,7 @@ pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
|
||||
|
||||
SECRET_KEY = "alicorns"
|
||||
ALGORITHM = "HS256"
|
||||
ACCESS_TOKEN_EXPIRE_MINUTES = 30
|
||||
ACCESS_TOKEN_EXPIRE_MINUTES = 2880
|
||||
|
||||
|
||||
def get_password_hash(password: str) -> str:
|
||||
@@ -25,7 +25,7 @@ def create_access_token(*, data: dict, expires_delta: timedelta = None):
|
||||
if expires_delta:
|
||||
expire = datetime.utcnow() + expires_delta
|
||||
else:
|
||||
expire = datetime.utcnow() + timedelta(minutes=15)
|
||||
expire = datetime.utcnow() + timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
|
||||
to_encode.update({"exp": expire})
|
||||
encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
|
||||
return encoded_jwt
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
from fastapi import HTTPException, status
|
||||
from sqlalchemy.orm import Session
|
||||
from sqlalchemy import and_
|
||||
import typing as t
|
||||
|
||||
from . import models, schemas
|
||||
@@ -115,4 +116,183 @@ def get_kintones(db: Session, type: int):
|
||||
kintones = db.query(models.Kintone).filter(models.Kintone.type == type).all()
|
||||
if not kintones:
|
||||
raise HTTPException(status_code=404, detail="Data not found")
|
||||
return kintones
|
||||
return kintones
|
||||
|
||||
def get_actions(db: Session):
|
||||
actions = db.query(models.Action).all()
|
||||
if not actions:
|
||||
raise HTTPException(status_code=404, detail="Data not found")
|
||||
return actions
|
||||
|
||||
|
||||
def create_flow(db: Session, domainid: int, flow: schemas.FlowBase):
|
||||
db_flow = models.Flow(
|
||||
flowid=flow.flowid,
|
||||
appid=flow.appid,
|
||||
eventid=flow.eventid,
|
||||
domainid=domainid,
|
||||
name=flow.name,
|
||||
content=flow.content
|
||||
)
|
||||
db.add(db_flow)
|
||||
db.commit()
|
||||
db.refresh(db_flow)
|
||||
return db_flow
|
||||
|
||||
def delete_flow(db: Session, flowid: str):
|
||||
flow = get_flow(db, flowid)
|
||||
if not flow:
|
||||
raise HTTPException(status.HTTP_404_NOT_FOUND, detail="Flow not found")
|
||||
db.delete(flow)
|
||||
db.commit()
|
||||
return flow
|
||||
|
||||
|
||||
def edit_flow(
|
||||
db: Session, flow: schemas.FlowBase
|
||||
) -> schemas.Flow:
|
||||
db_flow = get_flow(db, flow.flowid)
|
||||
if not db_flow:
|
||||
raise HTTPException(status.HTTP_404_NOT_FOUND, detail="Flow not found")
|
||||
update_data = flow.dict(exclude_unset=True)
|
||||
|
||||
for key, value in update_data.items():
|
||||
setattr(db_flow, key, value)
|
||||
|
||||
db.add(db_flow)
|
||||
db.commit()
|
||||
db.refresh(db_flow)
|
||||
return db_flow
|
||||
|
||||
|
||||
def get_flows(db: Session, flowid: str):
|
||||
flows = db.query(models.Flow).all()
|
||||
if not flows:
|
||||
raise HTTPException(status_code=404, detail="Data not found")
|
||||
return flows
|
||||
|
||||
def get_flow(db: Session, flowid: str):
|
||||
flow = db.query(models.Flow).filter(models.Flow.flowid == flowid).first()
|
||||
if not flow:
|
||||
raise HTTPException(status_code=404, detail="Data not found")
|
||||
return flow
|
||||
|
||||
def get_flows_by_app(db: Session, domainid: int, appid: str):
|
||||
flows = db.query(models.Flow).filter(and_(models.Flow.domainid == domainid,models.Flow.appid == appid)).all()
|
||||
if not flows:
|
||||
raise Exception("Data not found")
|
||||
return flows
|
||||
|
||||
def create_domain(db: Session, domain: schemas.DomainBase):
|
||||
db_domain = models.Domain(
|
||||
tenantid = domain.tenantid,
|
||||
name=domain.name,
|
||||
url=domain.url,
|
||||
kintoneuser=domain.kintoneuser,
|
||||
kintonepwd=domain.kintonepwd
|
||||
)
|
||||
db.add(db_domain)
|
||||
db.commit()
|
||||
db.refresh(db_domain)
|
||||
return db_domain
|
||||
|
||||
def delete_domain(db: Session,id: int):
|
||||
db_domain = db.query(models.Domain).get(id)
|
||||
if not db_domain:
|
||||
raise HTTPException(status.HTTP_404_NOT_FOUND, detail="Domain not found")
|
||||
db.delete(db_domain)
|
||||
db.commit()
|
||||
return db_domain
|
||||
|
||||
|
||||
def edit_domain(
|
||||
db: Session, domain: schemas.DomainBase
|
||||
) -> schemas.Domain:
|
||||
db_domain = db.query(models.Domain).get(domain.id)
|
||||
if not db_domain:
|
||||
raise HTTPException(status.HTTP_404_NOT_FOUND, detail="Domain not found")
|
||||
update_data = domain.dict(exclude_unset=True)
|
||||
|
||||
for key, value in update_data.items():
|
||||
if(key != "id"):
|
||||
setattr(db_domain, key, value)
|
||||
|
||||
db.add(db_domain)
|
||||
db.commit()
|
||||
db.refresh(db_domain)
|
||||
return db_domain
|
||||
|
||||
def add_userdomain(db: Session, userid:int,domainids:list):
|
||||
for domainid in domainids:
|
||||
db_domain = models.UserDomain(
|
||||
userid = userid,
|
||||
domainid = domainid
|
||||
)
|
||||
db.add(db_domain)
|
||||
db.commit()
|
||||
db.refresh(db_domain)
|
||||
return db_domain
|
||||
|
||||
def delete_userdomain(db: Session, userid: int,domainid: int):
|
||||
db_domain = db.query(models.UserDomain).filter(and_(models.UserDomain.userid == userid,models.UserDomain.domainid == domainid)).first()
|
||||
if not db_domain:
|
||||
raise HTTPException(status.HTTP_404_NOT_FOUND, detail="Domain not found")
|
||||
db.delete(db_domain)
|
||||
db.commit()
|
||||
return db_domain
|
||||
|
||||
def active_userdomain(db: Session, userid: int,domainid: int):
|
||||
db_userdomains = db.query(models.UserDomain).filter(models.UserDomain.userid == userid).all()
|
||||
if not db_userdomains:
|
||||
raise HTTPException(status.HTTP_404_NOT_FOUND, detail="Domain not found")
|
||||
for domain in db_userdomains:
|
||||
if domain.domainid == domainid:
|
||||
domain.active = True
|
||||
else:
|
||||
domain.active = False
|
||||
db.add(domain)
|
||||
db.commit()
|
||||
return db_userdomains
|
||||
|
||||
def get_activedomain(db: Session, userid: int):
|
||||
db_domain = db.query(models.Domain).join(models.UserDomain,models.UserDomain.domainid == models.Domain.id ).filter(and_(models.UserDomain.userid == userid,models.UserDomain.active == True)).first()
|
||||
if not db_domain:
|
||||
raise HTTPException(status.HTTP_404_NOT_FOUND, detail="Domain not found")
|
||||
return db_domain
|
||||
|
||||
def get_domain(db: Session, userid: str):
|
||||
domains = db.query(models.Domain).join(models.UserDomain,models.UserDomain.domainid == models.Domain.id ).filter(models.UserDomain.userid == userid).all()
|
||||
if not domains:
|
||||
raise HTTPException(status_code=404, detail="Data not found")
|
||||
return domains
|
||||
|
||||
def get_domains(db: Session,tenantid:str):
|
||||
domains = db.query(models.Domain).filter(models.Domain.tenantid == tenantid ).all()
|
||||
if not domains:
|
||||
raise HTTPException(status_code=404, detail="Data not found")
|
||||
return domains
|
||||
|
||||
def get_events(db: Session):
|
||||
events = db.query(models.Event).all()
|
||||
if not events:
|
||||
raise HTTPException(status_code=404, detail="Data not found")
|
||||
return events
|
||||
|
||||
def get_eventactions(db: Session,eventid: str):
|
||||
#eveactions = db.query(models.Action).join(models.EventAction,models.EventAction.actionid == models.Action.id ).join(models.Event,models.Event.id == models.EventAction.eventid).filter(models.Event.eventid == eventid).all()
|
||||
eveactions = db.query(models.Action).join(models.EventAction,models.EventAction.actionid != models.Action.id and models.EventAction.eventid == eventid ).join(models.Event,models.Event.id == models.EventAction.eventid).filter(models.Event.eventid == eventid).all()
|
||||
if not eveactions:
|
||||
raise HTTPException(status_code=404, detail="Data not found")
|
||||
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
|
||||
|
||||
def get_kintoneformat(db: Session):
|
||||
formats = db.query(models.KintoneFormat).order_by(models.KintoneFormat.id).all()
|
||||
return formats
|
||||
@@ -1,12 +1,16 @@
|
||||
from sqlalchemy import Boolean, Column, Integer, String
|
||||
|
||||
from .session import Base
|
||||
from sqlalchemy import Boolean, Column, Integer, String, DateTime,ForeignKey
|
||||
from sqlalchemy.ext.declarative import as_declarative
|
||||
from datetime import datetime
|
||||
|
||||
@as_declarative()
|
||||
class Base:
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
create_time = Column(DateTime, default=datetime.now)
|
||||
update_time = Column(DateTime, default=datetime.now, onupdate=datetime.now)
|
||||
|
||||
class User(Base):
|
||||
__tablename__ = "user"
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
email = Column(String(50), unique=True, index=True, nullable=False)
|
||||
first_name = Column(String(100))
|
||||
last_name = Column(String(100))
|
||||
@@ -17,15 +21,93 @@ class User(Base):
|
||||
class AppSetting(Base):
|
||||
__tablename__ = "appsetting"
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
appid = Column(String(100), index=True, nullable=False)
|
||||
setting = Column(String(1000))
|
||||
|
||||
class Kintone(Base):
|
||||
__tablename__ = "kintone"
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
type = Column(Integer, index=True, nullable=False)
|
||||
name = Column(String(100), nullable=False)
|
||||
desc = Column(String)
|
||||
content = Column(String)
|
||||
content = Column(String)
|
||||
|
||||
class Action(Base):
|
||||
__tablename__ = "action"
|
||||
|
||||
name = Column(String(100), index=True, nullable=False)
|
||||
title = Column(String(200))
|
||||
subtitle = Column(String(500))
|
||||
outputpoints = Column(String)
|
||||
property = Column(String)
|
||||
|
||||
class Flow(Base):
|
||||
__tablename__ = "flow"
|
||||
|
||||
flowid = Column(String(100), index=True, nullable=False)
|
||||
appid = 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))
|
||||
content = Column(String)
|
||||
|
||||
class Tenant(Base):
|
||||
__tablename__ = "tenant"
|
||||
|
||||
tenantid = Column(String(100), index=True, nullable=False)
|
||||
name = Column(String(200))
|
||||
licence = Column(String(200))
|
||||
startdate = Column(DateTime)
|
||||
enddate = Column(DateTime)
|
||||
|
||||
class Domain(Base):
|
||||
__tablename__ = "domain"
|
||||
|
||||
tenantid = Column(String(100), index=True, nullable=False)
|
||||
name = Column(String(100), nullable=False)
|
||||
url = Column(String(200), nullable=False)
|
||||
kintoneuser = Column(String(100), nullable=False)
|
||||
kintonepwd = Column(String(100), nullable=False)
|
||||
|
||||
|
||||
class UserDomain(Base):
|
||||
__tablename__ = "userdomain"
|
||||
|
||||
userid = Column(Integer,ForeignKey("user.id"))
|
||||
domainid = Column(Integer,ForeignKey("domain.id"))
|
||||
active = Column(Boolean, default=False)
|
||||
|
||||
class Event(Base):
|
||||
__tablename__ = "event"
|
||||
|
||||
category = Column(String(100), nullable=False)
|
||||
type = Column(String(100), nullable=False)
|
||||
eventid= Column(String(100), nullable=False)
|
||||
function = Column(String(500), nullable=False)
|
||||
mobile = Column(Boolean, default=False)
|
||||
eventgroup = Column(Boolean, default=False)
|
||||
|
||||
class EventAction(Base):
|
||||
__tablename__ = "eventaction"
|
||||
|
||||
eventid = Column(Integer,ForeignKey("event.id"))
|
||||
actionid = Column(Integer,ForeignKey("action.id"))
|
||||
|
||||
|
||||
class ErrorLog(Base):
|
||||
__tablename__ = "errorlog"
|
||||
|
||||
title = Column(String(50))
|
||||
location = Column(String(500))
|
||||
content = Column(String(5000))
|
||||
|
||||
class KintoneFormat(Base):
|
||||
__tablename__ = "kintoneformat"
|
||||
|
||||
name = Column(String(50))
|
||||
startrow =Column(Integer)
|
||||
startcolumn =Column(Integer)
|
||||
typecolumn =Column(Integer)
|
||||
codecolumn =Column(Integer)
|
||||
field = Column(String(5000))
|
||||
trueformat = Column(String(10))
|
||||
@@ -1,7 +1,12 @@
|
||||
from pydantic import BaseModel
|
||||
from datetime import datetime
|
||||
import typing as t
|
||||
|
||||
|
||||
class Base(BaseModel):
|
||||
create_time: datetime
|
||||
update_time: datetime
|
||||
|
||||
class UserBase(BaseModel):
|
||||
email: str
|
||||
is_active: bool = True
|
||||
@@ -15,7 +20,12 @@ class UserOut(UserBase):
|
||||
|
||||
|
||||
class UserCreate(UserBase):
|
||||
email:str
|
||||
password: str
|
||||
first_name: str
|
||||
last_name: str
|
||||
is_active:bool
|
||||
is_superuser:bool
|
||||
|
||||
class Config:
|
||||
orm_mode = True
|
||||
@@ -41,6 +51,7 @@ class Token(BaseModel):
|
||||
|
||||
|
||||
class TokenData(BaseModel):
|
||||
id:int = 0
|
||||
email: str = None
|
||||
permissions: str = "user"
|
||||
|
||||
@@ -68,4 +79,70 @@ class Kintone(BaseModel):
|
||||
content: str = None
|
||||
|
||||
class Config:
|
||||
orm_mode = True
|
||||
orm_mode = True
|
||||
|
||||
class Action(BaseModel):
|
||||
id: int
|
||||
name: str = None
|
||||
title: str = None
|
||||
subtitle: str = None
|
||||
outputpoints: str = None
|
||||
property: str = None
|
||||
|
||||
class Config:
|
||||
orm_mode = True
|
||||
|
||||
class FlowBase(BaseModel):
|
||||
flowid: str
|
||||
appid: str
|
||||
eventid: str
|
||||
name: str = None
|
||||
content: str = None
|
||||
|
||||
class Flow(Base):
|
||||
id: int
|
||||
flowid: str
|
||||
appid: str
|
||||
eventid: str
|
||||
domainid: int
|
||||
name: str = None
|
||||
content: str = None
|
||||
|
||||
class Config:
|
||||
orm_mode = True
|
||||
|
||||
class DomainBase(BaseModel):
|
||||
id: int
|
||||
tenantid: str
|
||||
name: str
|
||||
url: str
|
||||
kintoneuser: str
|
||||
kintonepwd: str
|
||||
|
||||
class Domain(Base):
|
||||
id: int
|
||||
tenantid: str
|
||||
name: str
|
||||
url: str
|
||||
kintoneuser: str
|
||||
kintonepwd: str
|
||||
|
||||
class Config:
|
||||
orm_mode = True
|
||||
|
||||
class Event(Base):
|
||||
id: int
|
||||
category: str
|
||||
type: str
|
||||
eventid: str
|
||||
function: str
|
||||
mobile: bool
|
||||
eventgroup: bool
|
||||
|
||||
class Config:
|
||||
orm_mode = True
|
||||
|
||||
class ErrorCreate(BaseModel):
|
||||
title:str
|
||||
location:str
|
||||
content:str
|
||||
@@ -1,3 +1,4 @@
|
||||
import os
|
||||
from fastapi import FastAPI, Depends
|
||||
from starlette.requests import Request
|
||||
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 import tasks
|
||||
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)
|
||||
|
||||
@@ -19,9 +25,7 @@ app = FastAPI(
|
||||
)
|
||||
|
||||
origins = [
|
||||
"http://localhost:9000",
|
||||
"http://localhost",
|
||||
"http://localhost:8080",
|
||||
"*"
|
||||
]
|
||||
|
||||
app.add_middleware(
|
||||
@@ -39,6 +43,25 @@ app.add_middleware(
|
||||
# request.state.db.close()
|
||||
# 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")
|
||||
async def root():
|
||||
|
||||
@@ -24,4 +24,8 @@ python -m venv env
|
||||
```bash
|
||||
pip install -r requirements.txt
|
||||
|
||||
```
|
||||
```
|
||||
4. backend 起動
|
||||
```bash
|
||||
uvicorn app.main:app --reload
|
||||
```
|
||||
|
||||
Binary file not shown.
845
document/ALCKintone_20231012.drawio
Normal file
845
document/ALCKintone_20231012.drawio
Normal file
@@ -0,0 +1,845 @@
|
||||
<mxfile host="65bd71144e" pages="3">
|
||||
<diagram name="Page-1" id="efa7a0a1-bf9b-a30e-e6df-94a7791c09e9">
|
||||
<mxGraphModel dx="1073" dy="518" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="826" pageHeight="1169" background="none" math="0" shadow="0">
|
||||
<root>
|
||||
<mxCell id="0"/>
|
||||
<mxCell id="1" parent="0"/>
|
||||
<mxCell id="ZIlfFuTIaODnUzWRKW0w-114" value="<p style="margin: 0px; margin-top: 4px; text-align: center; text-decoration: underline;"><strong>User</strong></p><p style="margin: 0px; margin-left: 8px;"><br></p><p style="margin: 0px; margin-left: 8px;"><br></p>" style="verticalAlign=top;align=left;overflow=fill;fontSize=12;fontFamily=Helvetica;html=1;strokeColor=#003366;shadow=1;fillColor=#D4E1F5;fontColor=#003366" parent="1" vertex="1">
|
||||
<mxGeometry x="40" y="21.64" width="160" height="78.36" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="ZIlfFuTIaODnUzWRKW0w-116" value="" style="endArrow=open;endSize=12;startArrow=diamondThin;startSize=14;startFill=0;edgeStyle=orthogonalEdgeStyle;entryX=1;entryY=0.5;entryDx=0;entryDy=0;exitX=0;exitY=0.5;exitDx=0;exitDy=0;" parent="1" target="ZIlfFuTIaODnUzWRKW0w-114" edge="1">
|
||||
<mxGeometry x="389.35999999999996" y="350" as="geometry">
|
||||
<mxPoint x="350" y="60.820000000000164" as="sourcePoint"/>
|
||||
<mxPoint x="671" y="532" as="targetPoint"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="ZIlfFuTIaODnUzWRKW0w-117" value="0..n" style="resizable=0;align=left;verticalAlign=top;labelBackgroundColor=#ffffff;fontSize=10;strokeColor=#003366;shadow=1;fillColor=#D4E1F5;fontColor=#003366" parent="ZIlfFuTIaODnUzWRKW0w-116" connectable="0" vertex="1">
|
||||
<mxGeometry x="-1" relative="1" as="geometry">
|
||||
<mxPoint x="-30" as="offset"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="ZIlfFuTIaODnUzWRKW0w-118" value="1" style="resizable=0;align=right;verticalAlign=top;labelBackgroundColor=#ffffff;fontSize=10;strokeColor=#003366;shadow=1;fillColor=#D4E1F5;fontColor=#003366" parent="ZIlfFuTIaODnUzWRKW0w-116" connectable="0" vertex="1">
|
||||
<mxGeometry x="1" relative="1" as="geometry">
|
||||
<mxPoint x="18.919999999999582" y="-21.960000000000644" as="offset"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="ZIlfFuTIaODnUzWRKW0w-119" value="<p style="margin: 0px; margin-top: 4px; text-align: center; text-decoration: underline;"><strong>UserApp</strong></p><hr><p style="margin: 0px; margin-left: 8px;"><br></p>" style="verticalAlign=top;align=left;overflow=fill;fontSize=12;fontFamily=Helvetica;html=1;strokeColor=#003366;shadow=1;fillColor=#D4E1F5;fontColor=#003366" parent="1" vertex="1">
|
||||
<mxGeometry x="350" y="250" width="160" height="78.36" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="ZIlfFuTIaODnUzWRKW0w-120" value="" style="endArrow=open;endSize=12;startArrow=diamondThin;startSize=14;startFill=0;edgeStyle=orthogonalEdgeStyle;entryX=0.5;entryY=1;entryDx=0;entryDy=0;exitX=0.5;exitY=0;exitDx=0;exitDy=0;" parent="1" source="ZIlfFuTIaODnUzWRKW0w-119" edge="1">
|
||||
<mxGeometry x="399.35999999999996" y="360" as="geometry">
|
||||
<mxPoint x="360" y="71" as="sourcePoint"/>
|
||||
<mxPoint x="430" y="100.00000000000023" as="targetPoint"/>
|
||||
<Array as="points">
|
||||
<mxPoint x="430" y="140"/>
|
||||
<mxPoint x="430" y="140"/>
|
||||
</Array>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="ZIlfFuTIaODnUzWRKW0w-121" value="0..n" style="resizable=0;align=left;verticalAlign=top;labelBackgroundColor=#ffffff;fontSize=10;strokeColor=#003366;shadow=1;fillColor=#D4E1F5;fontColor=#003366" parent="ZIlfFuTIaODnUzWRKW0w-120" connectable="0" vertex="1">
|
||||
<mxGeometry x="-1" relative="1" as="geometry">
|
||||
<mxPoint x="9" y="-20" as="offset"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="ZIlfFuTIaODnUzWRKW0w-122" value="1" style="resizable=0;align=right;verticalAlign=top;labelBackgroundColor=#ffffff;fontSize=10;strokeColor=#003366;shadow=1;fillColor=#D4E1F5;fontColor=#003366" parent="ZIlfFuTIaODnUzWRKW0w-120" connectable="0" vertex="1">
|
||||
<mxGeometry x="1" relative="1" as="geometry">
|
||||
<mxPoint x="18.919999999999582" y="-21.960000000000644" as="offset"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="ZIlfFuTIaODnUzWRKW0w-123" value="<p style="margin: 0px; margin-top: 4px; text-align: center; text-decoration: underline;"><strong>UserDomain</strong></p><hr><p style="margin: 0px; margin-left: 8px;"><br></p>" style="verticalAlign=top;align=left;overflow=fill;fontSize=12;fontFamily=Helvetica;html=1;strokeColor=#003366;shadow=1;fillColor=#D4E1F5;fontColor=#003366" parent="1" vertex="1">
|
||||
<mxGeometry x="350" y="21.64" width="160" height="78.36" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="ZIlfFuTIaODnUzWRKW0w-124" value="<p style="margin: 0px; margin-top: 4px; text-align: center; text-decoration: underline;"><b>Action</b></p><hr><p style="margin: 0px; margin-left: 8px;"><br></p>" style="verticalAlign=top;align=left;overflow=fill;fontSize=12;fontFamily=Helvetica;html=1;strokeColor=#003366;shadow=1;fillColor=#D4E1F5;fontColor=#003366" parent="1" vertex="1">
|
||||
<mxGeometry x="30" y="440" width="160" height="78.36" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="ZIlfFuTIaODnUzWRKW0w-125" value="<p style="margin: 0px; margin-top: 4px; text-align: center; text-decoration: underline;"><strong>AppAction</strong></p><hr><p style="margin: 0px; margin-left: 8px;"><br></p>" style="verticalAlign=top;align=left;overflow=fill;fontSize=12;fontFamily=Helvetica;html=1;strokeColor=#003366;shadow=1;fillColor=#D4E1F5;fontColor=#003366" parent="1" vertex="1">
|
||||
<mxGeometry x="350" y="440" width="160" height="78.36" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="ZIlfFuTIaODnUzWRKW0w-130" value="" style="endArrow=open;endSize=12;startArrow=diamondThin;startSize=14;startFill=0;edgeStyle=orthogonalEdgeStyle;entryX=1;entryY=0.5;entryDx=0;entryDy=0;exitX=0;exitY=0.5;exitDx=0;exitDy=0;" parent="1" source="ZIlfFuTIaODnUzWRKW0w-125" target="ZIlfFuTIaODnUzWRKW0w-124" edge="1">
|
||||
<mxGeometry x="209.35999999999999" y="733" as="geometry">
|
||||
<mxPoint x="240.59" y="584.64" as="sourcePoint"/>
|
||||
<mxPoint x="240.00000000000003" y="473.0000000000002" as="targetPoint"/>
|
||||
<Array as="points">
|
||||
<mxPoint x="241" y="479"/>
|
||||
<mxPoint x="200" y="480"/>
|
||||
</Array>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="ZIlfFuTIaODnUzWRKW0w-131" value="0..n" style="resizable=0;align=left;verticalAlign=top;labelBackgroundColor=#ffffff;fontSize=10;strokeColor=#003366;shadow=1;fillColor=#D4E1F5;fontColor=#003366" parent="ZIlfFuTIaODnUzWRKW0w-130" connectable="0" vertex="1">
|
||||
<mxGeometry x="-1" relative="1" as="geometry">
|
||||
<mxPoint x="-30" y="-29" as="offset"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="ZIlfFuTIaODnUzWRKW0w-132" value="1" style="resizable=0;align=right;verticalAlign=top;labelBackgroundColor=#ffffff;fontSize=10;strokeColor=#003366;shadow=1;fillColor=#D4E1F5;fontColor=#003366" parent="ZIlfFuTIaODnUzWRKW0w-130" connectable="0" vertex="1">
|
||||
<mxGeometry x="1" relative="1" as="geometry">
|
||||
<mxPoint x="18.919999999999582" y="-21.960000000000644" as="offset"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="ZIlfFuTIaODnUzWRKW0w-133" value="<p style="margin: 0px; margin-top: 4px; text-align: center; text-decoration: underline;"><b>Flow</b></p><hr><p style="margin: 0px; margin-left: 8px;"><br></p>" style="verticalAlign=top;align=left;overflow=fill;fontSize=12;fontFamily=Helvetica;html=1;strokeColor=#003366;shadow=1;fillColor=#D4E1F5;fontColor=#003366" parent="1" vertex="1">
|
||||
<mxGeometry x="630" y="250" width="160" height="78.36" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="ZIlfFuTIaODnUzWRKW0w-134" value="" style="endArrow=open;endSize=12;startArrow=diamondThin;startSize=14;startFill=0;edgeStyle=orthogonalEdgeStyle;entryX=1;entryY=0.5;entryDx=0;entryDy=0;exitX=0;exitY=0.5;exitDx=0;exitDy=0;" parent="1" source="ZIlfFuTIaODnUzWRKW0w-133" target="ZIlfFuTIaODnUzWRKW0w-119" edge="1">
|
||||
<mxGeometry x="408.77" y="598.36" as="geometry">
|
||||
<mxPoint x="440" y="450" as="sourcePoint"/>
|
||||
<mxPoint x="439.41" y="338.36000000000024" as="targetPoint"/>
|
||||
<Array as="points">
|
||||
<mxPoint x="570" y="290"/>
|
||||
<mxPoint x="570" y="290"/>
|
||||
</Array>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="ZIlfFuTIaODnUzWRKW0w-135" value="0..n" style="resizable=0;align=left;verticalAlign=top;labelBackgroundColor=#ffffff;fontSize=10;strokeColor=#003366;shadow=1;fillColor=#D4E1F5;fontColor=#003366" parent="ZIlfFuTIaODnUzWRKW0w-134" connectable="0" vertex="1">
|
||||
<mxGeometry x="-1" relative="1" as="geometry">
|
||||
<mxPoint x="-30" y="-20" as="offset"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="ZIlfFuTIaODnUzWRKW0w-136" value="1" style="resizable=0;align=right;verticalAlign=top;labelBackgroundColor=#ffffff;fontSize=10;strokeColor=#003366;shadow=1;fillColor=#D4E1F5;fontColor=#003366" parent="ZIlfFuTIaODnUzWRKW0w-134" connectable="0" vertex="1">
|
||||
<mxGeometry x="1" relative="1" as="geometry">
|
||||
<mxPoint x="18.919999999999582" y="-21.960000000000644" as="offset"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="ZIlfFuTIaODnUzWRKW0w-140" value="1" style="resizable=0;align=right;verticalAlign=top;labelBackgroundColor=#ffffff;fontSize=10;strokeColor=#003366;shadow=1;fillColor=#D4E1F5;fontColor=#003366" parent="1" connectable="0" vertex="1">
|
||||
<mxGeometry x="420.00000020761206" y="99.99999999998707" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="Z9b4y0bF-qkiY6ReSsG3-1" value="<p style="margin: 0px; margin-top: 4px; text-align: center; text-decoration: underline;"><b>Tenant</b></p>" style="verticalAlign=top;align=left;overflow=fill;fontSize=12;fontFamily=Helvetica;html=1;strokeColor=#003366;shadow=1;fillColor=#D4E1F5;fontColor=#003366" parent="1" vertex="1">
|
||||
<mxGeometry x="40" y="250" width="160" height="78.36" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="Z9b4y0bF-qkiY6ReSsG3-2" value="" style="endArrow=open;endSize=12;startArrow=diamondThin;startSize=14;startFill=0;edgeStyle=orthogonalEdgeStyle;exitX=0.5;exitY=1;exitDx=0;exitDy=0;" parent="1" source="ZIlfFuTIaODnUzWRKW0w-114" target="Z9b4y0bF-qkiY6ReSsG3-1" edge="1">
|
||||
<mxGeometry x="209.35999999999999" y="733" as="geometry">
|
||||
<mxPoint x="230" y="220" as="sourcePoint"/>
|
||||
<mxPoint x="70" y="220" as="targetPoint"/>
|
||||
<Array as="points">
|
||||
<mxPoint x="120" y="220"/>
|
||||
<mxPoint x="121" y="220"/>
|
||||
<mxPoint x="110" y="221"/>
|
||||
</Array>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="Z9b4y0bF-qkiY6ReSsG3-3" value="0..n" style="resizable=0;align=left;verticalAlign=top;labelBackgroundColor=#ffffff;fontSize=10;strokeColor=#003366;shadow=1;fillColor=#D4E1F5;fontColor=#003366" parent="Z9b4y0bF-qkiY6ReSsG3-2" connectable="0" vertex="1">
|
||||
<mxGeometry x="-1" relative="1" as="geometry">
|
||||
<mxPoint x="450" y="-100" as="offset"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="Z9b4y0bF-qkiY6ReSsG3-4" value="1" style="resizable=0;align=right;verticalAlign=top;labelBackgroundColor=#ffffff;fontSize=10;strokeColor=#003366;shadow=1;fillColor=#D4E1F5;fontColor=#003366" parent="Z9b4y0bF-qkiY6ReSsG3-2" connectable="0" vertex="1">
|
||||
<mxGeometry x="1" relative="1" as="geometry">
|
||||
<mxPoint x="18.919999999999582" y="-21.960000000000644" as="offset"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
</root>
|
||||
</mxGraphModel>
|
||||
</diagram>
|
||||
<diagram id="lCZzTbn_7m8qK95pbvvS" name="ER図">
|
||||
<mxGraphModel dx="1900" dy="518" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="827" pageHeight="1169" math="0" shadow="0">
|
||||
<root>
|
||||
<mxCell id="0"/>
|
||||
<mxCell id="1" parent="0"/>
|
||||
<mxCell id="inIyfaXWTM6ArMeTnGn6-1" value="User" style="shape=table;startSize=30;container=1;collapsible=1;childLayout=tableLayout;fixedRows=1;rowLines=0;fontStyle=1;align=center;resizeLast=1;html=1;fillColor=#f8cecc;strokeColor=#b85450;" parent="1" vertex="1">
|
||||
<mxGeometry x="420" y="20" width="180" height="180" as="geometry">
|
||||
<mxRectangle x="310" y="60" width="110" height="30" as="alternateBounds"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="inIyfaXWTM6ArMeTnGn6-2" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=1;" parent="inIyfaXWTM6ArMeTnGn6-1" vertex="1">
|
||||
<mxGeometry y="30" width="180" height="30" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="inIyfaXWTM6ArMeTnGn6-3" value="PK" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;fontStyle=1;overflow=hidden;whiteSpace=wrap;html=1;" parent="inIyfaXWTM6ArMeTnGn6-2" vertex="1">
|
||||
<mxGeometry width="30" height="30" as="geometry">
|
||||
<mxRectangle width="30" height="30" as="alternateBounds"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="inIyfaXWTM6ArMeTnGn6-4" value="id" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;fontStyle=5;overflow=hidden;whiteSpace=wrap;html=1;" parent="inIyfaXWTM6ArMeTnGn6-2" vertex="1">
|
||||
<mxGeometry x="30" width="150" height="30" as="geometry">
|
||||
<mxRectangle width="150" height="30" as="alternateBounds"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="inIyfaXWTM6ArMeTnGn6-5" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=0;" parent="inIyfaXWTM6ArMeTnGn6-1" vertex="1">
|
||||
<mxGeometry y="60" width="180" height="30" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="inIyfaXWTM6ArMeTnGn6-6" value="FK" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;editable=1;overflow=hidden;whiteSpace=wrap;html=1;" parent="inIyfaXWTM6ArMeTnGn6-5" vertex="1">
|
||||
<mxGeometry width="30" height="30" as="geometry">
|
||||
<mxRectangle width="30" height="30" as="alternateBounds"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="inIyfaXWTM6ArMeTnGn6-7" value="<b style="border-color: var(--border-color); text-align: center; color: rgb(0, 51, 102);">Tenant_id</b>" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;overflow=hidden;whiteSpace=wrap;html=1;" parent="inIyfaXWTM6ArMeTnGn6-5" vertex="1">
|
||||
<mxGeometry x="30" width="150" height="30" as="geometry">
|
||||
<mxRectangle width="150" height="30" as="alternateBounds"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="inIyfaXWTM6ArMeTnGn6-8" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=0;" parent="inIyfaXWTM6ArMeTnGn6-1" vertex="1">
|
||||
<mxGeometry y="90" width="180" height="30" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="inIyfaXWTM6ArMeTnGn6-9" value="" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;editable=1;overflow=hidden;whiteSpace=wrap;html=1;" parent="inIyfaXWTM6ArMeTnGn6-8" vertex="1">
|
||||
<mxGeometry width="30" height="30" as="geometry">
|
||||
<mxRectangle width="30" height="30" as="alternateBounds"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="inIyfaXWTM6ArMeTnGn6-10" value="ユーザー名" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;overflow=hidden;whiteSpace=wrap;html=1;" parent="inIyfaXWTM6ArMeTnGn6-8" vertex="1">
|
||||
<mxGeometry x="30" width="150" height="30" as="geometry">
|
||||
<mxRectangle width="150" height="30" as="alternateBounds"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="inIyfaXWTM6ArMeTnGn6-11" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=0;" parent="inIyfaXWTM6ArMeTnGn6-1" vertex="1">
|
||||
<mxGeometry y="120" width="180" height="30" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="inIyfaXWTM6ArMeTnGn6-12" value="" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;editable=1;overflow=hidden;whiteSpace=wrap;html=1;" parent="inIyfaXWTM6ArMeTnGn6-11" vertex="1">
|
||||
<mxGeometry width="30" height="30" as="geometry">
|
||||
<mxRectangle width="30" height="30" as="alternateBounds"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="inIyfaXWTM6ArMeTnGn6-13" value="パスワード(暗号化)" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;overflow=hidden;whiteSpace=wrap;html=1;" parent="inIyfaXWTM6ArMeTnGn6-11" vertex="1">
|
||||
<mxGeometry x="30" width="150" height="30" as="geometry">
|
||||
<mxRectangle width="150" height="30" as="alternateBounds"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="NCbWPNvujZOKFJAbQVH6-1" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=0;" parent="inIyfaXWTM6ArMeTnGn6-1" vertex="1">
|
||||
<mxGeometry y="150" width="180" height="30" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="NCbWPNvujZOKFJAbQVH6-2" value="" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;editable=1;overflow=hidden;" parent="NCbWPNvujZOKFJAbQVH6-1" vertex="1">
|
||||
<mxGeometry width="30" height="30" as="geometry">
|
||||
<mxRectangle width="30" height="30" as="alternateBounds"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="NCbWPNvujZOKFJAbQVH6-3" value="その他情報" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;overflow=hidden;" parent="NCbWPNvujZOKFJAbQVH6-1" vertex="1">
|
||||
<mxGeometry x="30" width="150" height="30" as="geometry">
|
||||
<mxRectangle width="150" height="30" as="alternateBounds"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="inIyfaXWTM6ArMeTnGn6-14" value="Domain" style="shape=table;startSize=30;container=1;collapsible=1;childLayout=tableLayout;fixedRows=1;rowLines=0;fontStyle=1;align=center;resizeLast=1;html=1;fillColor=#f8cecc;strokeColor=#b85450;" parent="1" vertex="1">
|
||||
<mxGeometry x="420" y="240" width="180" height="180" as="geometry">
|
||||
<mxRectangle x="590" y="60" width="100" height="30" as="alternateBounds"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="inIyfaXWTM6ArMeTnGn6-15" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=1;" parent="inIyfaXWTM6ArMeTnGn6-14" vertex="1">
|
||||
<mxGeometry y="30" width="180" height="30" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="inIyfaXWTM6ArMeTnGn6-16" value="PK" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;fontStyle=1;overflow=hidden;whiteSpace=wrap;html=1;" parent="inIyfaXWTM6ArMeTnGn6-15" vertex="1">
|
||||
<mxGeometry width="30" height="30" as="geometry">
|
||||
<mxRectangle width="30" height="30" as="alternateBounds"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="inIyfaXWTM6ArMeTnGn6-17" value="id" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;fontStyle=5;overflow=hidden;whiteSpace=wrap;html=1;" parent="inIyfaXWTM6ArMeTnGn6-15" vertex="1">
|
||||
<mxGeometry x="30" width="150" height="30" as="geometry">
|
||||
<mxRectangle width="150" height="30" as="alternateBounds"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="inIyfaXWTM6ArMeTnGn6-24" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=0;" parent="inIyfaXWTM6ArMeTnGn6-14" vertex="1">
|
||||
<mxGeometry y="60" width="180" height="30" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="inIyfaXWTM6ArMeTnGn6-25" value="" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;editable=1;overflow=hidden;whiteSpace=wrap;html=1;" parent="inIyfaXWTM6ArMeTnGn6-24" vertex="1">
|
||||
<mxGeometry width="30" height="30" as="geometry">
|
||||
<mxRectangle width="30" height="30" as="alternateBounds"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="inIyfaXWTM6ArMeTnGn6-26" value="domain_url" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;overflow=hidden;whiteSpace=wrap;html=1;" parent="inIyfaXWTM6ArMeTnGn6-24" vertex="1">
|
||||
<mxGeometry x="30" width="150" height="30" as="geometry">
|
||||
<mxRectangle width="150" height="30" as="alternateBounds"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="GH2g80-cBXHe62XdPV4N-31" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=0;" parent="inIyfaXWTM6ArMeTnGn6-14" vertex="1">
|
||||
<mxGeometry y="90" width="180" height="30" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="GH2g80-cBXHe62XdPV4N-32" value="" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;editable=1;overflow=hidden;" parent="GH2g80-cBXHe62XdPV4N-31" vertex="1">
|
||||
<mxGeometry width="30" height="30" as="geometry">
|
||||
<mxRectangle width="30" height="30" as="alternateBounds"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="GH2g80-cBXHe62XdPV4N-33" value="kintoneユーザーID" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;overflow=hidden;" parent="GH2g80-cBXHe62XdPV4N-31" vertex="1">
|
||||
<mxGeometry x="30" width="150" height="30" as="geometry">
|
||||
<mxRectangle width="150" height="30" as="alternateBounds"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="GH2g80-cBXHe62XdPV4N-34" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=0;" parent="inIyfaXWTM6ArMeTnGn6-14" vertex="1">
|
||||
<mxGeometry y="120" width="180" height="30" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="GH2g80-cBXHe62XdPV4N-35" value="" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;editable=1;overflow=hidden;" parent="GH2g80-cBXHe62XdPV4N-34" vertex="1">
|
||||
<mxGeometry width="30" height="30" as="geometry">
|
||||
<mxRectangle width="30" height="30" as="alternateBounds"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="GH2g80-cBXHe62XdPV4N-36" value="kintoneパスワード" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;overflow=hidden;" parent="GH2g80-cBXHe62XdPV4N-34" vertex="1">
|
||||
<mxGeometry x="30" width="150" height="30" as="geometry">
|
||||
<mxRectangle width="150" height="30" as="alternateBounds"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="NCbWPNvujZOKFJAbQVH6-4" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=0;" parent="inIyfaXWTM6ArMeTnGn6-14" vertex="1">
|
||||
<mxGeometry y="150" width="180" height="30" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="NCbWPNvujZOKFJAbQVH6-5" value="" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;editable=1;overflow=hidden;" parent="NCbWPNvujZOKFJAbQVH6-4" vertex="1">
|
||||
<mxGeometry width="30" height="30" as="geometry">
|
||||
<mxRectangle width="30" height="30" as="alternateBounds"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="NCbWPNvujZOKFJAbQVH6-6" value="API_Key" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;overflow=hidden;" parent="NCbWPNvujZOKFJAbQVH6-4" vertex="1">
|
||||
<mxGeometry x="30" width="150" height="30" as="geometry">
|
||||
<mxRectangle width="150" height="30" as="alternateBounds"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="QUGX19b9cPh8sdE7zjoR-1" value="<b style="border-color: var(--border-color); color: rgb(0, 51, 102);">Tenant</b>" style="shape=table;startSize=30;container=1;collapsible=1;childLayout=tableLayout;fixedRows=1;rowLines=0;fontStyle=1;align=center;resizeLast=1;html=1;fillColor=#f8cecc;strokeColor=#b85450;" parent="1" vertex="1">
|
||||
<mxGeometry x="130" y="20" width="180" height="180" as="geometry">
|
||||
<mxRectangle x="40" y="60" width="100" height="30" as="alternateBounds"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="QUGX19b9cPh8sdE7zjoR-2" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=1;" parent="QUGX19b9cPh8sdE7zjoR-1" vertex="1">
|
||||
<mxGeometry y="30" width="180" height="30" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="QUGX19b9cPh8sdE7zjoR-3" value="PK" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;fontStyle=1;overflow=hidden;whiteSpace=wrap;html=1;" parent="QUGX19b9cPh8sdE7zjoR-2" vertex="1">
|
||||
<mxGeometry width="30" height="30" as="geometry">
|
||||
<mxRectangle width="30" height="30" as="alternateBounds"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="QUGX19b9cPh8sdE7zjoR-4" value="id" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;fontStyle=5;overflow=hidden;whiteSpace=wrap;html=1;" parent="QUGX19b9cPh8sdE7zjoR-2" vertex="1">
|
||||
<mxGeometry x="30" width="150" height="30" as="geometry">
|
||||
<mxRectangle width="150" height="30" as="alternateBounds"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="QUGX19b9cPh8sdE7zjoR-5" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=0;" parent="QUGX19b9cPh8sdE7zjoR-1" vertex="1">
|
||||
<mxGeometry y="60" width="180" height="30" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="QUGX19b9cPh8sdE7zjoR-6" value="" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;editable=1;overflow=hidden;whiteSpace=wrap;html=1;" parent="QUGX19b9cPh8sdE7zjoR-5" vertex="1">
|
||||
<mxGeometry width="30" height="30" as="geometry">
|
||||
<mxRectangle width="30" height="30" as="alternateBounds"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="QUGX19b9cPh8sdE7zjoR-7" value="名前" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;overflow=hidden;whiteSpace=wrap;html=1;" parent="QUGX19b9cPh8sdE7zjoR-5" vertex="1">
|
||||
<mxGeometry x="30" width="150" height="30" as="geometry">
|
||||
<mxRectangle width="150" height="30" as="alternateBounds"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="QUGX19b9cPh8sdE7zjoR-8" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=0;" parent="QUGX19b9cPh8sdE7zjoR-1" vertex="1">
|
||||
<mxGeometry y="90" width="180" height="30" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="QUGX19b9cPh8sdE7zjoR-9" value="" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;editable=1;overflow=hidden;whiteSpace=wrap;html=1;" parent="QUGX19b9cPh8sdE7zjoR-8" vertex="1">
|
||||
<mxGeometry width="30" height="30" as="geometry">
|
||||
<mxRectangle width="30" height="30" as="alternateBounds"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="QUGX19b9cPh8sdE7zjoR-10" value="ライセンスキー" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;overflow=hidden;whiteSpace=wrap;html=1;" parent="QUGX19b9cPh8sdE7zjoR-8" vertex="1">
|
||||
<mxGeometry x="30" width="150" height="30" as="geometry">
|
||||
<mxRectangle width="150" height="30" as="alternateBounds"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="QUGX19b9cPh8sdE7zjoR-11" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=0;" parent="QUGX19b9cPh8sdE7zjoR-1" vertex="1">
|
||||
<mxGeometry y="120" width="180" height="30" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="QUGX19b9cPh8sdE7zjoR-12" value="" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;editable=1;overflow=hidden;whiteSpace=wrap;html=1;" parent="QUGX19b9cPh8sdE7zjoR-11" vertex="1">
|
||||
<mxGeometry width="30" height="30" as="geometry">
|
||||
<mxRectangle width="30" height="30" as="alternateBounds"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="QUGX19b9cPh8sdE7zjoR-13" value="利用期限" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;overflow=hidden;whiteSpace=wrap;html=1;" parent="QUGX19b9cPh8sdE7zjoR-11" vertex="1">
|
||||
<mxGeometry x="30" width="150" height="30" as="geometry">
|
||||
<mxRectangle width="150" height="30" as="alternateBounds"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="xY3sbCmQmCiIU-0kb39k-5" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=0;" parent="QUGX19b9cPh8sdE7zjoR-1" vertex="1">
|
||||
<mxGeometry y="150" width="180" height="30" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="xY3sbCmQmCiIU-0kb39k-6" value="" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;editable=1;overflow=hidden;" parent="xY3sbCmQmCiIU-0kb39k-5" vertex="1">
|
||||
<mxGeometry width="30" height="30" as="geometry">
|
||||
<mxRectangle width="30" height="30" as="alternateBounds"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="xY3sbCmQmCiIU-0kb39k-7" value="その他情報" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;overflow=hidden;" parent="xY3sbCmQmCiIU-0kb39k-5" vertex="1">
|
||||
<mxGeometry x="30" width="150" height="30" as="geometry">
|
||||
<mxRectangle width="150" height="30" as="alternateBounds"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="8Zu1yShcSHxMs68hy39H-1" value="Flow" style="shape=table;startSize=30;container=1;collapsible=1;childLayout=tableLayout;fixedRows=1;rowLines=0;fontStyle=1;align=center;resizeLast=1;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" parent="1" vertex="1">
|
||||
<mxGeometry x="420" y="480" width="180" height="210" as="geometry">
|
||||
<mxRectangle x="320" y="370" width="60" height="30" as="alternateBounds"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="8Zu1yShcSHxMs68hy39H-2" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=1;" parent="8Zu1yShcSHxMs68hy39H-1" vertex="1">
|
||||
<mxGeometry y="30" width="180" height="30" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="8Zu1yShcSHxMs68hy39H-3" value="PK" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;fontStyle=1;overflow=hidden;whiteSpace=wrap;html=1;" parent="8Zu1yShcSHxMs68hy39H-2" vertex="1">
|
||||
<mxGeometry width="30" height="30" as="geometry">
|
||||
<mxRectangle width="30" height="30" as="alternateBounds"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="8Zu1yShcSHxMs68hy39H-4" value="id" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;fontStyle=5;overflow=hidden;whiteSpace=wrap;html=1;" parent="8Zu1yShcSHxMs68hy39H-2" vertex="1">
|
||||
<mxGeometry x="30" width="150" height="30" as="geometry">
|
||||
<mxRectangle width="150" height="30" as="alternateBounds"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="NCbWPNvujZOKFJAbQVH6-56" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=0;" parent="8Zu1yShcSHxMs68hy39H-1" vertex="1">
|
||||
<mxGeometry y="60" width="180" height="30" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="NCbWPNvujZOKFJAbQVH6-57" value="FK" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;fontStyle=0;overflow=hidden;whiteSpace=wrap;html=1;" parent="NCbWPNvujZOKFJAbQVH6-56" vertex="1">
|
||||
<mxGeometry width="30" height="30" as="geometry">
|
||||
<mxRectangle width="30" height="30" as="alternateBounds"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="NCbWPNvujZOKFJAbQVH6-58" value="domain_id" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;fontStyle=0;overflow=hidden;whiteSpace=wrap;html=1;" parent="NCbWPNvujZOKFJAbQVH6-56" vertex="1">
|
||||
<mxGeometry x="30" width="150" height="30" as="geometry">
|
||||
<mxRectangle width="150" height="30" as="alternateBounds"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="8Zu1yShcSHxMs68hy39H-5" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=0;" parent="8Zu1yShcSHxMs68hy39H-1" vertex="1">
|
||||
<mxGeometry y="90" width="180" height="30" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="8Zu1yShcSHxMs68hy39H-6" value="FK" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;editable=1;overflow=hidden;whiteSpace=wrap;html=1;" parent="8Zu1yShcSHxMs68hy39H-5" vertex="1">
|
||||
<mxGeometry width="30" height="30" as="geometry">
|
||||
<mxRectangle width="30" height="30" as="alternateBounds"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="8Zu1yShcSHxMs68hy39H-7" value="app_id" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;overflow=hidden;whiteSpace=wrap;html=1;" parent="8Zu1yShcSHxMs68hy39H-5" vertex="1">
|
||||
<mxGeometry x="30" width="150" height="30" as="geometry">
|
||||
<mxRectangle width="150" height="30" as="alternateBounds"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="8Zu1yShcSHxMs68hy39H-8" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=0;" parent="8Zu1yShcSHxMs68hy39H-1" vertex="1">
|
||||
<mxGeometry y="120" width="180" height="30" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="8Zu1yShcSHxMs68hy39H-9" value="FK" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;editable=1;overflow=hidden;whiteSpace=wrap;html=1;" parent="8Zu1yShcSHxMs68hy39H-8" vertex="1">
|
||||
<mxGeometry width="30" height="30" as="geometry">
|
||||
<mxRectangle width="30" height="30" as="alternateBounds"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="8Zu1yShcSHxMs68hy39H-10" value="spaceid" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;overflow=hidden;whiteSpace=wrap;html=1;" parent="8Zu1yShcSHxMs68hy39H-8" vertex="1">
|
||||
<mxGeometry x="30" width="150" height="30" as="geometry">
|
||||
<mxRectangle width="150" height="30" as="alternateBounds"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="XZEu5LJmFrV2HCpzu9aW-12" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=0;" parent="8Zu1yShcSHxMs68hy39H-1" vertex="1">
|
||||
<mxGeometry y="150" width="180" height="30" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="XZEu5LJmFrV2HCpzu9aW-13" value="" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;editable=1;overflow=hidden;" parent="XZEu5LJmFrV2HCpzu9aW-12" vertex="1">
|
||||
<mxGeometry width="30" height="30" as="geometry">
|
||||
<mxRectangle width="30" height="30" as="alternateBounds"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="XZEu5LJmFrV2HCpzu9aW-14" value="content(json)" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;overflow=hidden;" parent="XZEu5LJmFrV2HCpzu9aW-12" vertex="1">
|
||||
<mxGeometry x="30" width="150" height="30" as="geometry">
|
||||
<mxRectangle width="150" height="30" as="alternateBounds"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="GH2g80-cBXHe62XdPV4N-14" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=0;" parent="8Zu1yShcSHxMs68hy39H-1" vertex="1">
|
||||
<mxGeometry y="180" width="180" height="30" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="GH2g80-cBXHe62XdPV4N-15" value="FK" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;editable=1;overflow=hidden;" parent="GH2g80-cBXHe62XdPV4N-14" vertex="1">
|
||||
<mxGeometry width="30" height="30" as="geometry">
|
||||
<mxRectangle width="30" height="30" as="alternateBounds"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="GH2g80-cBXHe62XdPV4N-16" value="event_id" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;overflow=hidden;" parent="GH2g80-cBXHe62XdPV4N-14" vertex="1">
|
||||
<mxGeometry x="30" width="150" height="30" as="geometry">
|
||||
<mxRectangle width="150" height="30" as="alternateBounds"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="GH2g80-cBXHe62XdPV4N-1" value="Event" style="shape=table;startSize=30;container=1;collapsible=1;childLayout=tableLayout;fixedRows=1;rowLines=0;fontStyle=1;align=center;resizeLast=1;html=1;fillColor=#f8cecc;strokeColor=#b85450;" parent="1" vertex="1">
|
||||
<mxGeometry x="130" y="480" width="180" height="150" as="geometry">
|
||||
<mxRectangle x="40" y="390" width="70" height="30" as="alternateBounds"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="GH2g80-cBXHe62XdPV4N-2" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=1;" parent="GH2g80-cBXHe62XdPV4N-1" vertex="1">
|
||||
<mxGeometry y="30" width="180" height="30" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="GH2g80-cBXHe62XdPV4N-3" value="PK" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;fontStyle=1;overflow=hidden;whiteSpace=wrap;html=1;" parent="GH2g80-cBXHe62XdPV4N-2" vertex="1">
|
||||
<mxGeometry width="30" height="30" as="geometry">
|
||||
<mxRectangle width="30" height="30" as="alternateBounds"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="GH2g80-cBXHe62XdPV4N-4" value="id" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;fontStyle=5;overflow=hidden;whiteSpace=wrap;html=1;" parent="GH2g80-cBXHe62XdPV4N-2" vertex="1">
|
||||
<mxGeometry x="30" width="150" height="30" as="geometry">
|
||||
<mxRectangle width="150" height="30" as="alternateBounds"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="GH2g80-cBXHe62XdPV4N-5" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=0;" parent="GH2g80-cBXHe62XdPV4N-1" vertex="1">
|
||||
<mxGeometry y="60" width="180" height="30" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="GH2g80-cBXHe62XdPV4N-6" value="" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;editable=1;overflow=hidden;whiteSpace=wrap;html=1;" parent="GH2g80-cBXHe62XdPV4N-5" vertex="1">
|
||||
<mxGeometry width="30" height="30" as="geometry">
|
||||
<mxRectangle width="30" height="30" as="alternateBounds"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="GH2g80-cBXHe62XdPV4N-7" value="画面名" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;overflow=hidden;whiteSpace=wrap;html=1;" parent="GH2g80-cBXHe62XdPV4N-5" vertex="1">
|
||||
<mxGeometry x="30" width="150" height="30" as="geometry">
|
||||
<mxRectangle width="150" height="30" as="alternateBounds"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="GH2g80-cBXHe62XdPV4N-8" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=0;" parent="GH2g80-cBXHe62XdPV4N-1" vertex="1">
|
||||
<mxGeometry y="90" width="180" height="30" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="GH2g80-cBXHe62XdPV4N-9" value="" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;editable=1;overflow=hidden;whiteSpace=wrap;html=1;" parent="GH2g80-cBXHe62XdPV4N-8" vertex="1">
|
||||
<mxGeometry width="30" height="30" as="geometry">
|
||||
<mxRectangle width="30" height="30" as="alternateBounds"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="GH2g80-cBXHe62XdPV4N-10" value="イベント名" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;overflow=hidden;whiteSpace=wrap;html=1;" parent="GH2g80-cBXHe62XdPV4N-8" vertex="1">
|
||||
<mxGeometry x="30" width="150" height="30" as="geometry">
|
||||
<mxRectangle width="150" height="30" as="alternateBounds"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="GH2g80-cBXHe62XdPV4N-11" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=0;" parent="GH2g80-cBXHe62XdPV4N-1" vertex="1">
|
||||
<mxGeometry y="120" width="180" height="30" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="GH2g80-cBXHe62XdPV4N-12" value="" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;editable=1;overflow=hidden;whiteSpace=wrap;html=1;" parent="GH2g80-cBXHe62XdPV4N-11" vertex="1">
|
||||
<mxGeometry width="30" height="30" as="geometry">
|
||||
<mxRectangle width="30" height="30" as="alternateBounds"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="GH2g80-cBXHe62XdPV4N-13" value="モバイル使用可能か" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;overflow=hidden;whiteSpace=wrap;html=1;" parent="GH2g80-cBXHe62XdPV4N-11" vertex="1">
|
||||
<mxGeometry x="30" width="150" height="30" as="geometry">
|
||||
<mxRectangle width="150" height="30" as="alternateBounds"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="GH2g80-cBXHe62XdPV4N-17" value="" style="edgeStyle=entityRelationEdgeStyle;fontSize=12;html=1;endArrow=ERoneToMany;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;curved=1;" parent="1" source="GH2g80-cBXHe62XdPV4N-2" target="GH2g80-cBXHe62XdPV4N-14" edge="1">
|
||||
<mxGeometry width="100" height="100" relative="1" as="geometry">
|
||||
<mxPoint x="630" y="845" as="sourcePoint"/>
|
||||
<mxPoint x="530" y="1085" as="targetPoint"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="GH2g80-cBXHe62XdPV4N-18" value="App" style="shape=table;startSize=30;container=1;collapsible=1;childLayout=tableLayout;fixedRows=1;rowLines=0;fontStyle=1;align=center;resizeLast=1;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" parent="1" vertex="1">
|
||||
<mxGeometry x="710" y="280" width="180" height="150" as="geometry">
|
||||
<mxRectangle x="600" y="370" width="60" height="30" as="alternateBounds"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="NCbWPNvujZOKFJAbQVH6-34" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=1;" parent="GH2g80-cBXHe62XdPV4N-18" vertex="1">
|
||||
<mxGeometry y="30" width="180" height="30" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="NCbWPNvujZOKFJAbQVH6-35" value="PK" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;fontStyle=1;overflow=hidden;whiteSpace=wrap;html=1;" parent="NCbWPNvujZOKFJAbQVH6-34" vertex="1">
|
||||
<mxGeometry width="30" height="30" as="geometry">
|
||||
<mxRectangle width="30" height="30" as="alternateBounds"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="NCbWPNvujZOKFJAbQVH6-36" value="id" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;fontStyle=5;overflow=hidden;whiteSpace=wrap;html=1;" parent="NCbWPNvujZOKFJAbQVH6-34" vertex="1">
|
||||
<mxGeometry x="30" width="150" height="30" as="geometry">
|
||||
<mxRectangle width="150" height="30" as="alternateBounds"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="NCbWPNvujZOKFJAbQVH6-51" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=1;" parent="GH2g80-cBXHe62XdPV4N-18" vertex="1">
|
||||
<mxGeometry y="60" width="180" height="30" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="NCbWPNvujZOKFJAbQVH6-52" value="UK" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;fontStyle=1;overflow=hidden;whiteSpace=wrap;html=1;" parent="NCbWPNvujZOKFJAbQVH6-51" vertex="1">
|
||||
<mxGeometry width="30" height="30" as="geometry">
|
||||
<mxRectangle width="30" height="30" as="alternateBounds"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="NCbWPNvujZOKFJAbQVH6-53" value="domain_id" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;fontStyle=5;overflow=hidden;whiteSpace=wrap;html=1;" parent="NCbWPNvujZOKFJAbQVH6-51" vertex="1">
|
||||
<mxGeometry x="30" width="150" height="30" as="geometry">
|
||||
<mxRectangle width="150" height="30" as="alternateBounds"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="GH2g80-cBXHe62XdPV4N-25" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=0;" parent="GH2g80-cBXHe62XdPV4N-18" vertex="1">
|
||||
<mxGeometry y="90" width="180" height="30" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="GH2g80-cBXHe62XdPV4N-26" value="" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;editable=1;overflow=hidden;whiteSpace=wrap;html=1;" parent="GH2g80-cBXHe62XdPV4N-25" vertex="1">
|
||||
<mxGeometry width="30" height="30" as="geometry">
|
||||
<mxRectangle width="30" height="30" as="alternateBounds"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="GH2g80-cBXHe62XdPV4N-27" value="app_name" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;overflow=hidden;whiteSpace=wrap;html=1;" parent="GH2g80-cBXHe62XdPV4N-25" vertex="1">
|
||||
<mxGeometry x="30" width="150" height="30" as="geometry">
|
||||
<mxRectangle width="150" height="30" as="alternateBounds"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="GH2g80-cBXHe62XdPV4N-28" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=0;" parent="GH2g80-cBXHe62XdPV4N-18" vertex="1">
|
||||
<mxGeometry y="120" width="180" height="30" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="GH2g80-cBXHe62XdPV4N-29" value="" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;editable=1;overflow=hidden;whiteSpace=wrap;html=1;" parent="GH2g80-cBXHe62XdPV4N-28" vertex="1">
|
||||
<mxGeometry width="30" height="30" as="geometry">
|
||||
<mxRectangle width="30" height="30" as="alternateBounds"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="GH2g80-cBXHe62XdPV4N-30" value="update_date" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;overflow=hidden;whiteSpace=wrap;html=1;" parent="GH2g80-cBXHe62XdPV4N-28" vertex="1">
|
||||
<mxGeometry x="30" width="150" height="30" as="geometry">
|
||||
<mxRectangle width="150" height="30" as="alternateBounds"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="AJLbZMHdl7kzV18ppMoM-1" value="Flow_History" style="shape=table;startSize=30;container=1;collapsible=1;childLayout=tableLayout;fixedRows=1;rowLines=0;fontStyle=1;align=center;resizeLast=1;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" parent="1" vertex="1">
|
||||
<mxGeometry x="710" y="480" width="180" height="240" as="geometry">
|
||||
<mxRectangle x="320" y="670" width="110" height="30" as="alternateBounds"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="AJLbZMHdl7kzV18ppMoM-23" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=1;" parent="AJLbZMHdl7kzV18ppMoM-1" vertex="1">
|
||||
<mxGeometry y="30" width="180" height="30" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="AJLbZMHdl7kzV18ppMoM-24" value="PK" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;fontStyle=1;overflow=hidden;whiteSpace=wrap;html=1;" parent="AJLbZMHdl7kzV18ppMoM-23" vertex="1">
|
||||
<mxGeometry width="30" height="30" as="geometry">
|
||||
<mxRectangle width="30" height="30" as="alternateBounds"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="AJLbZMHdl7kzV18ppMoM-25" value="id" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;fontStyle=5;overflow=hidden;whiteSpace=wrap;html=1;" parent="AJLbZMHdl7kzV18ppMoM-23" vertex="1">
|
||||
<mxGeometry x="30" width="150" height="30" as="geometry">
|
||||
<mxRectangle width="150" height="30" as="alternateBounds"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="AJLbZMHdl7kzV18ppMoM-2" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=1;" parent="AJLbZMHdl7kzV18ppMoM-1" vertex="1">
|
||||
<mxGeometry y="60" width="180" height="30" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="AJLbZMHdl7kzV18ppMoM-3" value="FK" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;fontStyle=1;overflow=hidden;whiteSpace=wrap;html=1;" parent="AJLbZMHdl7kzV18ppMoM-2" vertex="1">
|
||||
<mxGeometry width="30" height="30" as="geometry">
|
||||
<mxRectangle width="30" height="30" as="alternateBounds"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="AJLbZMHdl7kzV18ppMoM-4" value="flow_id" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;fontStyle=5;overflow=hidden;whiteSpace=wrap;html=1;" parent="AJLbZMHdl7kzV18ppMoM-2" vertex="1">
|
||||
<mxGeometry x="30" width="150" height="30" as="geometry">
|
||||
<mxRectangle width="150" height="30" as="alternateBounds"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="AJLbZMHdl7kzV18ppMoM-5" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=0;" parent="AJLbZMHdl7kzV18ppMoM-1" vertex="1">
|
||||
<mxGeometry y="90" width="180" height="30" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="AJLbZMHdl7kzV18ppMoM-6" value="FK" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;editable=1;overflow=hidden;whiteSpace=wrap;html=1;" parent="AJLbZMHdl7kzV18ppMoM-5" vertex="1">
|
||||
<mxGeometry width="30" height="30" as="geometry">
|
||||
<mxRectangle width="30" height="30" as="alternateBounds"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="AJLbZMHdl7kzV18ppMoM-7" value="domain_id" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;overflow=hidden;whiteSpace=wrap;html=1;" parent="AJLbZMHdl7kzV18ppMoM-5" vertex="1">
|
||||
<mxGeometry x="30" width="150" height="30" as="geometry">
|
||||
<mxRectangle width="150" height="30" as="alternateBounds"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="AJLbZMHdl7kzV18ppMoM-8" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=0;" parent="AJLbZMHdl7kzV18ppMoM-1" vertex="1">
|
||||
<mxGeometry y="120" width="180" height="30" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="AJLbZMHdl7kzV18ppMoM-9" value="FK" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;editable=1;overflow=hidden;whiteSpace=wrap;html=1;" parent="AJLbZMHdl7kzV18ppMoM-8" vertex="1">
|
||||
<mxGeometry width="30" height="30" as="geometry">
|
||||
<mxRectangle width="30" height="30" as="alternateBounds"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="AJLbZMHdl7kzV18ppMoM-10" value="spaceid" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;overflow=hidden;whiteSpace=wrap;html=1;" parent="AJLbZMHdl7kzV18ppMoM-8" vertex="1">
|
||||
<mxGeometry x="30" width="150" height="30" as="geometry">
|
||||
<mxRectangle width="150" height="30" as="alternateBounds"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="AJLbZMHdl7kzV18ppMoM-11" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=0;" parent="AJLbZMHdl7kzV18ppMoM-1" vertex="1">
|
||||
<mxGeometry y="150" width="180" height="30" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="AJLbZMHdl7kzV18ppMoM-12" value="FK" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;editable=1;overflow=hidden;whiteSpace=wrap;html=1;" parent="AJLbZMHdl7kzV18ppMoM-11" vertex="1">
|
||||
<mxGeometry width="30" height="30" as="geometry">
|
||||
<mxRectangle width="30" height="30" as="alternateBounds"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="AJLbZMHdl7kzV18ppMoM-13" value="appid" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;overflow=hidden;whiteSpace=wrap;html=1;" parent="AJLbZMHdl7kzV18ppMoM-11" vertex="1">
|
||||
<mxGeometry x="30" width="150" height="30" as="geometry">
|
||||
<mxRectangle width="150" height="30" as="alternateBounds"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="AJLbZMHdl7kzV18ppMoM-17" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=0;" parent="AJLbZMHdl7kzV18ppMoM-1" vertex="1">
|
||||
<mxGeometry y="180" width="180" height="30" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="AJLbZMHdl7kzV18ppMoM-18" value="FK" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;editable=1;overflow=hidden;" parent="AJLbZMHdl7kzV18ppMoM-17" vertex="1">
|
||||
<mxGeometry width="30" height="30" as="geometry">
|
||||
<mxRectangle width="30" height="30" as="alternateBounds"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="AJLbZMHdl7kzV18ppMoM-19" value="event_id" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;overflow=hidden;" parent="AJLbZMHdl7kzV18ppMoM-17" vertex="1">
|
||||
<mxGeometry x="30" width="150" height="30" as="geometry">
|
||||
<mxRectangle width="150" height="30" as="alternateBounds"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="AJLbZMHdl7kzV18ppMoM-14" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=0;" parent="AJLbZMHdl7kzV18ppMoM-1" vertex="1">
|
||||
<mxGeometry y="210" width="180" height="30" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="AJLbZMHdl7kzV18ppMoM-15" value="" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;editable=1;overflow=hidden;" parent="AJLbZMHdl7kzV18ppMoM-14" vertex="1">
|
||||
<mxGeometry width="30" height="30" as="geometry">
|
||||
<mxRectangle width="30" height="30" as="alternateBounds"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="AJLbZMHdl7kzV18ppMoM-16" value="content" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;overflow=hidden;" parent="AJLbZMHdl7kzV18ppMoM-14" vertex="1">
|
||||
<mxGeometry x="30" width="150" height="30" as="geometry">
|
||||
<mxRectangle width="150" height="30" as="alternateBounds"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="AJLbZMHdl7kzV18ppMoM-40" value="Action" style="shape=table;startSize=30;container=1;collapsible=1;childLayout=tableLayout;fixedRows=1;rowLines=0;fontStyle=1;align=center;resizeLast=1;html=1;fillColor=#f8cecc;strokeColor=#b85450;" parent="1" vertex="1">
|
||||
<mxGeometry x="130" y="720" width="180" height="150" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="AJLbZMHdl7kzV18ppMoM-41" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=1;" parent="AJLbZMHdl7kzV18ppMoM-40" vertex="1">
|
||||
<mxGeometry y="30" width="180" height="30" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="AJLbZMHdl7kzV18ppMoM-42" value="PK" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;fontStyle=1;overflow=hidden;whiteSpace=wrap;html=1;" parent="AJLbZMHdl7kzV18ppMoM-41" vertex="1">
|
||||
<mxGeometry width="30" height="30" as="geometry">
|
||||
<mxRectangle width="30" height="30" as="alternateBounds"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="AJLbZMHdl7kzV18ppMoM-43" value="id" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;fontStyle=5;overflow=hidden;whiteSpace=wrap;html=1;" parent="AJLbZMHdl7kzV18ppMoM-41" vertex="1">
|
||||
<mxGeometry x="30" width="150" height="30" as="geometry">
|
||||
<mxRectangle width="150" height="30" as="alternateBounds"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="AJLbZMHdl7kzV18ppMoM-44" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=0;" parent="AJLbZMHdl7kzV18ppMoM-40" vertex="1">
|
||||
<mxGeometry y="60" width="180" height="30" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="AJLbZMHdl7kzV18ppMoM-45" value="" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;editable=1;overflow=hidden;whiteSpace=wrap;html=1;" parent="AJLbZMHdl7kzV18ppMoM-44" vertex="1">
|
||||
<mxGeometry width="30" height="30" as="geometry">
|
||||
<mxRectangle width="30" height="30" as="alternateBounds"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="AJLbZMHdl7kzV18ppMoM-46" value="アクション名" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;overflow=hidden;whiteSpace=wrap;html=1;" parent="AJLbZMHdl7kzV18ppMoM-44" vertex="1">
|
||||
<mxGeometry x="30" width="150" height="30" as="geometry">
|
||||
<mxRectangle width="150" height="30" as="alternateBounds"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="AJLbZMHdl7kzV18ppMoM-47" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=0;" parent="AJLbZMHdl7kzV18ppMoM-40" vertex="1">
|
||||
<mxGeometry y="90" width="180" height="30" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="AJLbZMHdl7kzV18ppMoM-48" value="" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;editable=1;overflow=hidden;whiteSpace=wrap;html=1;" parent="AJLbZMHdl7kzV18ppMoM-47" vertex="1">
|
||||
<mxGeometry width="30" height="30" as="geometry">
|
||||
<mxRectangle width="30" height="30" as="alternateBounds"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="AJLbZMHdl7kzV18ppMoM-49" value="説明" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;overflow=hidden;whiteSpace=wrap;html=1;" parent="AJLbZMHdl7kzV18ppMoM-47" vertex="1">
|
||||
<mxGeometry x="30" width="150" height="30" as="geometry">
|
||||
<mxRectangle width="150" height="30" as="alternateBounds"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="AJLbZMHdl7kzV18ppMoM-50" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=0;" parent="AJLbZMHdl7kzV18ppMoM-40" vertex="1">
|
||||
<mxGeometry y="120" width="180" height="30" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="AJLbZMHdl7kzV18ppMoM-51" value="" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;editable=1;overflow=hidden;whiteSpace=wrap;html=1;" parent="AJLbZMHdl7kzV18ppMoM-50" vertex="1">
|
||||
<mxGeometry width="30" height="30" as="geometry">
|
||||
<mxRectangle width="30" height="30" as="alternateBounds"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="AJLbZMHdl7kzV18ppMoM-52" value="属性定義(json)" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;overflow=hidden;whiteSpace=wrap;html=1;" parent="AJLbZMHdl7kzV18ppMoM-50" vertex="1">
|
||||
<mxGeometry x="30" width="150" height="30" as="geometry">
|
||||
<mxRectangle width="150" height="30" as="alternateBounds"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="NCbWPNvujZOKFJAbQVH6-7" value="UserDomain" style="shape=table;startSize=30;container=1;collapsible=1;childLayout=tableLayout;fixedRows=1;rowLines=0;fontStyle=1;align=center;resizeLast=1;html=1;fillColor=#f8cecc;strokeColor=#b85450;" parent="1" vertex="1">
|
||||
<mxGeometry x="710" y="100" width="180" height="120" as="geometry">
|
||||
<mxRectangle x="590" y="60" width="100" height="30" as="alternateBounds"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="NCbWPNvujZOKFJAbQVH6-8" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=1;" parent="NCbWPNvujZOKFJAbQVH6-7" vertex="1">
|
||||
<mxGeometry y="30" width="180" height="30" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="NCbWPNvujZOKFJAbQVH6-9" value="PK" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;fontStyle=1;overflow=hidden;whiteSpace=wrap;html=1;" parent="NCbWPNvujZOKFJAbQVH6-8" vertex="1">
|
||||
<mxGeometry width="30" height="30" as="geometry">
|
||||
<mxRectangle width="30" height="30" as="alternateBounds"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="NCbWPNvujZOKFJAbQVH6-10" value="id" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;fontStyle=5;overflow=hidden;whiteSpace=wrap;html=1;" parent="NCbWPNvujZOKFJAbQVH6-8" vertex="1">
|
||||
<mxGeometry x="30" width="150" height="30" as="geometry">
|
||||
<mxRectangle width="150" height="30" as="alternateBounds"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="NCbWPNvujZOKFJAbQVH6-11" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=0;" parent="NCbWPNvujZOKFJAbQVH6-7" vertex="1">
|
||||
<mxGeometry y="60" width="180" height="30" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="NCbWPNvujZOKFJAbQVH6-12" value="FK" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;editable=1;overflow=hidden;whiteSpace=wrap;html=1;" parent="NCbWPNvujZOKFJAbQVH6-11" vertex="1">
|
||||
<mxGeometry width="30" height="30" as="geometry">
|
||||
<mxRectangle width="30" height="30" as="alternateBounds"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="NCbWPNvujZOKFJAbQVH6-13" value="user_id" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;overflow=hidden;whiteSpace=wrap;html=1;" parent="NCbWPNvujZOKFJAbQVH6-11" vertex="1">
|
||||
<mxGeometry x="30" width="150" height="30" as="geometry">
|
||||
<mxRectangle width="150" height="30" as="alternateBounds"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="NCbWPNvujZOKFJAbQVH6-44" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=0;" parent="NCbWPNvujZOKFJAbQVH6-7" vertex="1">
|
||||
<mxGeometry y="90" width="180" height="30" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="NCbWPNvujZOKFJAbQVH6-45" value="FK" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;fontStyle=0;overflow=hidden;whiteSpace=wrap;html=1;" parent="NCbWPNvujZOKFJAbQVH6-44" vertex="1">
|
||||
<mxGeometry width="30" height="30" as="geometry">
|
||||
<mxRectangle width="30" height="30" as="alternateBounds"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="NCbWPNvujZOKFJAbQVH6-46" value="domain_id" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;fontStyle=0;overflow=hidden;whiteSpace=wrap;html=1;" parent="NCbWPNvujZOKFJAbQVH6-44" vertex="1">
|
||||
<mxGeometry x="30" width="150" height="30" as="geometry">
|
||||
<mxRectangle width="150" height="30" as="alternateBounds"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="NCbWPNvujZOKFJAbQVH6-42" value="" style="edgeStyle=entityRelationEdgeStyle;fontSize=12;html=1;endArrow=ERzeroToMany;endFill=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;curved=1;" parent="1" source="QUGX19b9cPh8sdE7zjoR-2" target="inIyfaXWTM6ArMeTnGn6-2" edge="1">
|
||||
<mxGeometry width="100" height="100" relative="1" as="geometry">
|
||||
<mxPoint x="160" y="350" as="sourcePoint"/>
|
||||
<mxPoint x="260" y="250" as="targetPoint"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="NCbWPNvujZOKFJAbQVH6-47" value="" style="edgeStyle=entityRelationEdgeStyle;fontSize=12;html=1;endArrow=ERmandOne;startArrow=ERmandOne;rounded=1;entryX=1;entryY=0.5;entryDx=0;entryDy=0;exitX=0;exitY=0.5;exitDx=0;exitDy=0;" parent="1" source="NCbWPNvujZOKFJAbQVH6-11" target="inIyfaXWTM6ArMeTnGn6-2" edge="1">
|
||||
<mxGeometry width="100" height="100" relative="1" as="geometry">
|
||||
<mxPoint x="230" y="390" as="sourcePoint"/>
|
||||
<mxPoint x="330" y="290" as="targetPoint"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="NCbWPNvujZOKFJAbQVH6-49" value="" style="edgeStyle=entityRelationEdgeStyle;fontSize=12;html=1;endArrow=ERmandOne;startArrow=ERmandOne;rounded=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" parent="1" source="inIyfaXWTM6ArMeTnGn6-15" target="NCbWPNvujZOKFJAbQVH6-44" edge="1">
|
||||
<mxGeometry width="100" height="100" relative="1" as="geometry">
|
||||
<mxPoint x="200" y="400" as="sourcePoint"/>
|
||||
<mxPoint x="300" y="300" as="targetPoint"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="NCbWPNvujZOKFJAbQVH6-50" value="" style="edgeStyle=entityRelationEdgeStyle;fontSize=12;html=1;endArrow=ERzeroToMany;endFill=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;curved=1;" parent="1" source="inIyfaXWTM6ArMeTnGn6-15" target="NCbWPNvujZOKFJAbQVH6-51" edge="1">
|
||||
<mxGeometry width="100" height="100" relative="1" as="geometry">
|
||||
<mxPoint x="140" y="430" as="sourcePoint"/>
|
||||
<mxPoint x="740" y="445" as="targetPoint"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="NCbWPNvujZOKFJAbQVH6-54" value="" style="edgeStyle=entityRelationEdgeStyle;fontSize=12;html=1;endArrow=ERzeroToMany;endFill=1;exitX=0;exitY=0.5;exitDx=0;exitDy=0;curved=1;" parent="1" source="NCbWPNvujZOKFJAbQVH6-51" target="8Zu1yShcSHxMs68hy39H-5" edge="1">
|
||||
<mxGeometry width="100" height="100" relative="1" as="geometry">
|
||||
<mxPoint x="210" y="450" as="sourcePoint"/>
|
||||
<mxPoint x="310" y="350" as="targetPoint"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="NCbWPNvujZOKFJAbQVH6-55" value="" style="edgeStyle=entityRelationEdgeStyle;fontSize=12;html=1;endArrow=ERzeroToMany;endFill=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;curved=1;" parent="1" source="8Zu1yShcSHxMs68hy39H-2" target="AJLbZMHdl7kzV18ppMoM-2" edge="1">
|
||||
<mxGeometry width="100" height="100" relative="1" as="geometry">
|
||||
<mxPoint x="270" y="470" as="sourcePoint"/>
|
||||
<mxPoint x="370" y="370" as="targetPoint"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="dFNVox72oq8u18PzFUgJ-1" value="EventAction" style="shape=table;startSize=30;container=1;collapsible=1;childLayout=tableLayout;fixedRows=1;rowLines=0;fontStyle=1;align=center;resizeLast=1;html=1;fillColor=#f8cecc;strokeColor=#b85450;" parent="1" vertex="1">
|
||||
<mxGeometry x="-110" y="570" width="180" height="120" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="dFNVox72oq8u18PzFUgJ-2" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=1;" parent="dFNVox72oq8u18PzFUgJ-1" vertex="1">
|
||||
<mxGeometry y="30" width="180" height="30" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="dFNVox72oq8u18PzFUgJ-3" value="PK" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;fontStyle=1;overflow=hidden;whiteSpace=wrap;html=1;" parent="dFNVox72oq8u18PzFUgJ-2" vertex="1">
|
||||
<mxGeometry width="30" height="30" as="geometry">
|
||||
<mxRectangle width="30" height="30" as="alternateBounds"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="dFNVox72oq8u18PzFUgJ-4" value="id" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;fontStyle=5;overflow=hidden;whiteSpace=wrap;html=1;" parent="dFNVox72oq8u18PzFUgJ-2" vertex="1">
|
||||
<mxGeometry x="30" width="150" height="30" as="geometry">
|
||||
<mxRectangle width="150" height="30" as="alternateBounds"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="dFNVox72oq8u18PzFUgJ-5" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=0;" parent="dFNVox72oq8u18PzFUgJ-1" vertex="1">
|
||||
<mxGeometry y="60" width="180" height="30" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="dFNVox72oq8u18PzFUgJ-6" value="" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;editable=1;overflow=hidden;whiteSpace=wrap;html=1;" parent="dFNVox72oq8u18PzFUgJ-5" vertex="1">
|
||||
<mxGeometry width="30" height="30" as="geometry">
|
||||
<mxRectangle width="30" height="30" as="alternateBounds"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="dFNVox72oq8u18PzFUgJ-7" value="event_id" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;overflow=hidden;whiteSpace=wrap;html=1;" parent="dFNVox72oq8u18PzFUgJ-5" vertex="1">
|
||||
<mxGeometry x="30" width="150" height="30" as="geometry">
|
||||
<mxRectangle width="150" height="30" as="alternateBounds"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="dFNVox72oq8u18PzFUgJ-8" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=0;" parent="dFNVox72oq8u18PzFUgJ-1" vertex="1">
|
||||
<mxGeometry y="90" width="180" height="30" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="dFNVox72oq8u18PzFUgJ-9" value="" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;editable=1;overflow=hidden;whiteSpace=wrap;html=1;" parent="dFNVox72oq8u18PzFUgJ-8" vertex="1">
|
||||
<mxGeometry width="30" height="30" as="geometry">
|
||||
<mxRectangle width="30" height="30" as="alternateBounds"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="dFNVox72oq8u18PzFUgJ-10" value="action_id" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;overflow=hidden;whiteSpace=wrap;html=1;" parent="dFNVox72oq8u18PzFUgJ-8" vertex="1">
|
||||
<mxGeometry x="30" width="150" height="30" as="geometry">
|
||||
<mxRectangle width="150" height="30" as="alternateBounds"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="dFNVox72oq8u18PzFUgJ-16" value="" style="edgeStyle=entityRelationEdgeStyle;fontSize=12;html=1;endArrow=ERmandOne;startArrow=ERmandOne;entryX=0;entryY=0.5;entryDx=0;entryDy=0;exitX=1;exitY=0.5;exitDx=0;exitDy=0;curved=1;" parent="1" source="dFNVox72oq8u18PzFUgJ-5" target="GH2g80-cBXHe62XdPV4N-2" edge="1">
|
||||
<mxGeometry width="100" height="100" relative="1" as="geometry">
|
||||
<mxPoint x="290" y="800" as="sourcePoint"/>
|
||||
<mxPoint x="390" y="700" as="targetPoint"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="dFNVox72oq8u18PzFUgJ-17" value="" style="edgeStyle=entityRelationEdgeStyle;fontSize=12;html=1;endArrow=ERmandOne;startArrow=ERmandOne;rounded=1;entryX=1;entryY=0.5;entryDx=0;entryDy=0;exitX=0;exitY=0.5;exitDx=0;exitDy=0;" parent="1" source="AJLbZMHdl7kzV18ppMoM-41" target="dFNVox72oq8u18PzFUgJ-8" edge="1">
|
||||
<mxGeometry width="100" height="100" relative="1" as="geometry">
|
||||
<mxPoint x="-100" y="900" as="sourcePoint"/>
|
||||
<mxPoint y="800" as="targetPoint"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
</root>
|
||||
</mxGraphModel>
|
||||
</diagram>
|
||||
<diagram id="yu2qkxLoxjZt0KdZdE3U" name="ページ3">
|
||||
<mxGraphModel dx="1434" dy="884" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="827" pageHeight="1169" math="0" shadow="0">
|
||||
<root>
|
||||
<mxCell id="0"/>
|
||||
<mxCell id="1" parent="0"/>
|
||||
</root>
|
||||
</mxGraphModel>
|
||||
</diagram>
|
||||
</mxfile>
|
||||
@@ -1,10 +0,0 @@
|
||||
<mxfile host="65bd71144e">
|
||||
<diagram id="UMFNb7zEleFpzTH4EAp9" name="ページ1">
|
||||
<mxGraphModel dx="1202" dy="612" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="827" pageHeight="1169" math="0" shadow="0">
|
||||
<root>
|
||||
<mxCell id="0"/>
|
||||
<mxCell id="1" parent="0"/>
|
||||
</root>
|
||||
</mxGraphModel>
|
||||
</diagram>
|
||||
</mxfile>
|
||||
BIN
document/Kintone自動作成ツールのプラグインについて.xlsx
Normal file
BIN
document/Kintone自動作成ツールのプラグインについて.xlsx
Normal file
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
BIN
document/action-property.png
Normal file
BIN
document/action-property.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 56 KiB |
BIN
document/kintone開発自動化ツール UIデザイン案.pptx
Normal file
BIN
document/kintone開発自動化ツール UIデザイン案.pptx
Normal file
Binary file not shown.
147
document/サイトマップ.drawio
Normal file
147
document/サイトマップ.drawio
Normal file
@@ -0,0 +1,147 @@
|
||||
<mxfile host="app.diagrams.net" modified="2024-02-21T05:42:02.026Z" agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36" etag="T2S5cjvthSOlO5DmGw-C" version="23.1.5" type="device">
|
||||
<diagram id="Z6uZM46JtkVaKDzPjE9h" name="サイトマップ">
|
||||
<mxGraphModel dx="1434" dy="820" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="827" pageHeight="1169" math="0" shadow="0">
|
||||
<root>
|
||||
<mxCell id="0" />
|
||||
<mxCell id="1" parent="0" />
|
||||
<mxCell id="Gi77RX5G2m4J9-6cMje4-14" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="1" source="Gi77RX5G2m4J9-6cMje4-1" target="Gi77RX5G2m4J9-6cMje4-13" edge="1">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="Gi77RX5G2m4J9-6cMje4-1" value="テナント登録" style="html=1;whiteSpace=wrap;strokeColor=none;fillColor=#0079D6;labelPosition=center;verticalLabelPosition=middle;verticalAlign=top;align=center;fontSize=12;outlineConnect=0;spacingTop=-6;fontColor=#FFFFFF;sketch=0;shape=mxgraph.sitemap.login;" parent="1" vertex="1">
|
||||
<mxGeometry x="60" y="50" width="120" height="70" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="Gi77RX5G2m4J9-6cMje4-2" value="Admin Login" style="html=1;whiteSpace=wrap;strokeColor=#2D7600;fillColor=#60a917;labelPosition=center;verticalLabelPosition=middle;verticalAlign=top;align=center;fontSize=12;outlineConnect=0;spacingTop=-6;fontColor=#ffffff;sketch=0;shape=mxgraph.sitemap.login;" parent="1" vertex="1">
|
||||
<mxGeometry x="60" y="270" width="120" height="70" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="Gi77RX5G2m4J9-6cMje4-8" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="1" source="Gi77RX5G2m4J9-6cMje4-5" target="Gi77RX5G2m4J9-6cMje4-7" edge="1">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="Gi77RX5G2m4J9-6cMje4-9" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="1" source="Gi77RX5G2m4J9-6cMje4-5" target="Gi77RX5G2m4J9-6cMje4-7" edge="1">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="Gi77RX5G2m4J9-6cMje4-12" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="1" source="Gi77RX5G2m4J9-6cMje4-5" target="Gi77RX5G2m4J9-6cMje4-11" edge="1">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="Gi77RX5G2m4J9-6cMje4-5" value="Home" style="html=1;whiteSpace=wrap;strokeColor=#2D7600;fillColor=#60a917;labelPosition=center;verticalLabelPosition=middle;verticalAlign=top;align=center;fontSize=12;outlineConnect=0;spacingTop=-6;fontColor=#ffffff;sketch=0;shape=mxgraph.sitemap.home;" parent="1" vertex="1">
|
||||
<mxGeometry x="240" y="270" width="120" height="70" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="Gi77RX5G2m4J9-6cMje4-6" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0;entryY=0.5;entryDx=0;entryDy=0;entryPerimeter=0;" parent="1" source="Gi77RX5G2m4J9-6cMje4-2" target="Gi77RX5G2m4J9-6cMje4-5" edge="1">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="Gi77RX5G2m4J9-6cMje4-42" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="1" source="Gi77RX5G2m4J9-6cMje4-7" target="Gi77RX5G2m4J9-6cMje4-41" edge="1">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="Gi77RX5G2m4J9-6cMje4-7" value="ユーザー登録" style="html=1;whiteSpace=wrap;strokeColor=none;fillColor=#0079D6;labelPosition=center;verticalLabelPosition=middle;verticalAlign=top;align=center;fontSize=12;outlineConnect=0;spacingTop=-6;fontColor=#FFFFFF;sketch=0;shape=mxgraph.sitemap.home;" parent="1" vertex="1">
|
||||
<mxGeometry x="440" y="220" width="120" height="70" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="Gi77RX5G2m4J9-6cMje4-11" value="ドメイン登録" style="html=1;whiteSpace=wrap;strokeColor=none;fillColor=#0079D6;labelPosition=center;verticalLabelPosition=middle;verticalAlign=top;align=center;fontSize=12;outlineConnect=0;spacingTop=-6;fontColor=#FFFFFF;sketch=0;shape=mxgraph.sitemap.home;" parent="1" vertex="1">
|
||||
<mxGeometry x="440" y="340" width="120" height="70" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="Gi77RX5G2m4J9-6cMje4-13" value="テナント管理者作成" style="html=1;whiteSpace=wrap;strokeColor=none;fillColor=#0079D6;labelPosition=center;verticalLabelPosition=middle;verticalAlign=top;align=center;fontSize=12;outlineConnect=0;spacingTop=-6;fontColor=#FFFFFF;sketch=0;shape=mxgraph.sitemap.login;" parent="1" vertex="1">
|
||||
<mxGeometry x="240" y="50" width="120" height="70" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="Gi77RX5G2m4J9-6cMje4-15" value="ライセンス情報" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;backgroundOutline=1;size=15;" parent="1" vertex="1">
|
||||
<mxGeometry x="480" y="10" width="90" height="70" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="Gi77RX5G2m4J9-6cMje4-16" value="Adminユーザー" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;backgroundOutline=1;size=15;" parent="1" vertex="1">
|
||||
<mxGeometry x="480" y="90" width="90" height="70" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="Gi77RX5G2m4J9-6cMje4-17" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0;entryY=0.5;entryDx=0;entryDy=0;entryPerimeter=0;" parent="1" source="Gi77RX5G2m4J9-6cMje4-13" target="Gi77RX5G2m4J9-6cMje4-15" edge="1">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="Gi77RX5G2m4J9-6cMje4-18" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0;entryY=0.5;entryDx=0;entryDy=0;entryPerimeter=0;" parent="1" source="Gi77RX5G2m4J9-6cMje4-13" target="Gi77RX5G2m4J9-6cMje4-16" edge="1">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="Gi77RX5G2m4J9-6cMje4-19" value="テナントDB<br>作成" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;backgroundOutline=1;size=15;" parent="1" vertex="1">
|
||||
<mxGeometry x="550" y="50" width="90" height="70" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="Gi77RX5G2m4J9-6cMje4-22" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="1" source="Gi77RX5G2m4J9-6cMje4-20" target="Gi77RX5G2m4J9-6cMje4-21" edge="1">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="Gi77RX5G2m4J9-6cMje4-20" value="Login" style="html=1;whiteSpace=wrap;strokeColor=#005700;fillColor=#008a00;labelPosition=center;verticalLabelPosition=middle;verticalAlign=top;align=center;fontSize=12;outlineConnect=0;spacingTop=-6;sketch=0;shape=mxgraph.sitemap.login;fontColor=#ffffff;" parent="1" vertex="1">
|
||||
<mxGeometry x="50" y="610" width="120" height="70" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="Gi77RX5G2m4J9-6cMje4-24" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="1" source="Gi77RX5G2m4J9-6cMje4-21" target="Gi77RX5G2m4J9-6cMje4-25" edge="1">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<mxPoint x="430" y="645" as="targetPoint" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="Gi77RX5G2m4J9-6cMje4-21" value="Home" style="html=1;whiteSpace=wrap;strokeColor=#005700;fillColor=#008a00;labelPosition=center;verticalLabelPosition=middle;verticalAlign=top;align=center;fontSize=12;outlineConnect=0;spacingTop=-6;fontColor=#ffffff;sketch=0;shape=mxgraph.sitemap.home;" parent="1" vertex="1">
|
||||
<mxGeometry x="230" y="610" width="120" height="70" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="Gi77RX5G2m4J9-6cMje4-27" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="1" source="Gi77RX5G2m4J9-6cMje4-25" target="Gi77RX5G2m4J9-6cMje4-26" edge="1">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="Gi77RX5G2m4J9-6cMje4-25" value="アプリ一覧" style="html=1;whiteSpace=wrap;strokeColor=none;fillColor=#0079D6;labelPosition=center;verticalLabelPosition=middle;verticalAlign=top;align=center;fontSize=12;outlineConnect=0;spacingTop=-6;fontColor=#FFFFFF;sketch=0;shape=mxgraph.sitemap.news;" parent="1" vertex="1">
|
||||
<mxGeometry x="440" y="610" width="120" height="70" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="Gi77RX5G2m4J9-6cMje4-29" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="1" source="Gi77RX5G2m4J9-6cMje4-26" target="Gi77RX5G2m4J9-6cMje4-28" edge="1">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="Gi77RX5G2m4J9-6cMje4-26" value="フロー一覧" style="html=1;whiteSpace=wrap;strokeColor=none;fillColor=#0079D6;labelPosition=center;verticalLabelPosition=middle;verticalAlign=top;align=center;fontSize=12;outlineConnect=0;spacingTop=-6;fontColor=#FFFFFF;sketch=0;shape=mxgraph.sitemap.news;" parent="1" vertex="1">
|
||||
<mxGeometry x="620" y="610" width="120" height="70" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="Gi77RX5G2m4J9-6cMje4-40" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="1" source="Gi77RX5G2m4J9-6cMje4-28" target="Gi77RX5G2m4J9-6cMje4-39" edge="1">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="Gi77RX5G2m4J9-6cMje4-28" value="フローエディタ" style="html=1;whiteSpace=wrap;strokeColor=#005700;fillColor=#008a00;labelPosition=center;verticalLabelPosition=middle;verticalAlign=top;align=center;fontSize=12;outlineConnect=0;spacingTop=-6;fontColor=#ffffff;sketch=0;shape=mxgraph.sitemap.news;" parent="1" vertex="1">
|
||||
<mxGeometry x="800" y="610" width="120" height="70" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="Gi77RX5G2m4J9-6cMje4-33" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="1" source="Gi77RX5G2m4J9-6cMje4-30" target="Gi77RX5G2m4J9-6cMje4-32" edge="1">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="Gi77RX5G2m4J9-6cMje4-30" value="設計書取込" style="html=1;whiteSpace=wrap;strokeColor=#005700;fillColor=#008a00;labelPosition=center;verticalLabelPosition=middle;verticalAlign=top;align=center;fontSize=12;outlineConnect=0;spacingTop=-6;fontColor=#ffffff;sketch=0;shape=mxgraph.sitemap.news;" parent="1" vertex="1">
|
||||
<mxGeometry x="440" y="715" width="120" height="70" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="Gi77RX5G2m4J9-6cMje4-31" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0;entryY=0.5;entryDx=0;entryDy=0;entryPerimeter=0;" parent="1" source="Gi77RX5G2m4J9-6cMje4-21" target="Gi77RX5G2m4J9-6cMje4-30" edge="1">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="Gi77RX5G2m4J9-6cMje4-32" value="取込結果表示" style="html=1;whiteSpace=wrap;strokeColor=#005700;fillColor=#008a00;labelPosition=center;verticalLabelPosition=middle;verticalAlign=top;align=center;fontSize=12;outlineConnect=0;spacingTop=-6;fontColor=#ffffff;sketch=0;shape=mxgraph.sitemap.news;" parent="1" vertex="1">
|
||||
<mxGeometry x="620" y="715" width="120" height="70" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="Gi77RX5G2m4J9-6cMje4-37" value="設計書ダウロード" style="html=1;whiteSpace=wrap;strokeColor=none;fillColor=#0079D6;labelPosition=center;verticalLabelPosition=middle;verticalAlign=top;align=center;fontSize=12;outlineConnect=0;spacingTop=-6;fontColor=#FFFFFF;sketch=0;shape=mxgraph.sitemap.news;" parent="1" vertex="1">
|
||||
<mxGeometry x="440" y="825" width="120" height="70" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="Gi77RX5G2m4J9-6cMje4-38" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0;entryY=0.5;entryDx=0;entryDy=0;entryPerimeter=0;" parent="1" source="Gi77RX5G2m4J9-6cMje4-21" target="Gi77RX5G2m4J9-6cMje4-37" edge="1">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="Gi77RX5G2m4J9-6cMje4-39" value="フロー履歴管理" style="html=1;whiteSpace=wrap;strokeColor=none;fillColor=#0079D6;labelPosition=center;verticalLabelPosition=middle;verticalAlign=top;align=center;fontSize=12;outlineConnect=0;spacingTop=-6;fontColor=#FFFFFF;sketch=0;shape=mxgraph.sitemap.news;" parent="1" vertex="1">
|
||||
<mxGeometry x="980" y="610" width="120" height="70" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="Gi77RX5G2m4J9-6cMje4-41" value="ALC設定" style="html=1;whiteSpace=wrap;strokeColor=none;fillColor=#0079D6;labelPosition=center;verticalLabelPosition=middle;verticalAlign=top;align=center;fontSize=12;outlineConnect=0;spacingTop=-6;fontColor=#FFFFFF;sketch=0;shape=mxgraph.sitemap.home;" parent="1" vertex="1">
|
||||
<mxGeometry x="620" y="220" width="120" height="70" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="Gi77RX5G2m4J9-6cMje4-43" value="管理ドメイン設定" style="html=1;whiteSpace=wrap;strokeColor=none;fillColor=#0079D6;labelPosition=center;verticalLabelPosition=middle;verticalAlign=top;align=center;fontSize=12;outlineConnect=0;spacingTop=-6;fontColor=#FFFFFF;sketch=0;shape=mxgraph.sitemap.home;" parent="1" vertex="1">
|
||||
<mxGeometry x="800" y="220" width="120" height="70" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="Gi77RX5G2m4J9-6cMje4-44" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0;entryY=0.5;entryDx=0;entryDy=0;entryPerimeter=0;" parent="1" source="Gi77RX5G2m4J9-6cMje4-41" target="Gi77RX5G2m4J9-6cMje4-43" edge="1">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="Gi77RX5G2m4J9-6cMje4-45" value="プロファイル" style="html=1;whiteSpace=wrap;strokeColor=none;fillColor=#0079D6;labelPosition=center;verticalLabelPosition=middle;verticalAlign=top;align=center;fontSize=12;outlineConnect=0;spacingTop=-6;fontColor=#FFFFFF;sketch=0;shape=mxgraph.sitemap.home;" parent="1" vertex="1">
|
||||
<mxGeometry x="440" y="935" width="120" height="70" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="Gi77RX5G2m4J9-6cMje4-46" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0;entryY=0.5;entryDx=0;entryDy=0;entryPerimeter=0;" parent="1" source="Gi77RX5G2m4J9-6cMje4-21" target="Gi77RX5G2m4J9-6cMje4-45" edge="1">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="Gi77RX5G2m4J9-6cMje4-50" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="1" source="Gi77RX5G2m4J9-6cMje4-47" target="Gi77RX5G2m4J9-6cMje4-49" edge="1">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="Gi77RX5G2m4J9-6cMje4-47" value="プロファイル" style="html=1;whiteSpace=wrap;strokeColor=none;fillColor=#0079D6;labelPosition=center;verticalLabelPosition=middle;verticalAlign=top;align=center;fontSize=12;outlineConnect=0;spacingTop=-6;fontColor=#FFFFFF;sketch=0;shape=mxgraph.sitemap.home;" parent="1" vertex="1">
|
||||
<mxGeometry x="440" y="450" width="120" height="70" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="Gi77RX5G2m4J9-6cMje4-48" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0;entryY=0.5;entryDx=0;entryDy=0;entryPerimeter=0;" parent="1" source="Gi77RX5G2m4J9-6cMje4-5" target="Gi77RX5G2m4J9-6cMje4-47" edge="1">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="Gi77RX5G2m4J9-6cMje4-49" value="ライセンス情報" style="html=1;whiteSpace=wrap;strokeColor=none;fillColor=#0079D6;labelPosition=center;verticalLabelPosition=middle;verticalAlign=top;align=center;fontSize=12;outlineConnect=0;spacingTop=-6;fontColor=#FFFFFF;sketch=0;shape=mxgraph.sitemap.home;" parent="1" vertex="1">
|
||||
<mxGeometry x="620" y="450" width="120" height="70" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="Gi77RX5G2m4J9-6cMje4-51" value="ライセンス情報" style="html=1;whiteSpace=wrap;strokeColor=none;fillColor=#0079D6;labelPosition=center;verticalLabelPosition=middle;verticalAlign=top;align=center;fontSize=12;outlineConnect=0;spacingTop=-6;fontColor=#FFFFFF;sketch=0;shape=mxgraph.sitemap.home;" parent="1" vertex="1">
|
||||
<mxGeometry x="620" y="935" width="120" height="70" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="Gi77RX5G2m4J9-6cMje4-52" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0;entryY=0.5;entryDx=0;entryDy=0;entryPerimeter=0;" parent="1" source="Gi77RX5G2m4J9-6cMje4-45" target="Gi77RX5G2m4J9-6cMje4-51" edge="1">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
</root>
|
||||
</mxGraphModel>
|
||||
</diagram>
|
||||
</mxfile>
|
||||
194
document/ルックアップ同期仕様.drawio
Normal file
194
document/ルックアップ同期仕様.drawio
Normal file
File diff suppressed because one or more lines are too long
BIN
document/収支明細管理設計書.xlsx
Normal file
BIN
document/収支明細管理設計書.xlsx
Normal file
Binary file not shown.
BIN
document/日報設計書new.xlsx
Normal file
BIN
document/日報設計書new.xlsx
Normal file
Binary file not shown.
@@ -1,2 +1,6 @@
|
||||
KAB_BACKEND_URL="http://127.0.0.1:8000/api/v1/"
|
||||
|
||||
#開発環境
|
||||
KAB_BACKEND_URL="https://kab-backend.azurewebsites.net/"
|
||||
#単体テスト環境
|
||||
#KAB_BACKEND_URL="https://kab-backend-unittest.azurewebsites.net/"
|
||||
#ローカル開発環境
|
||||
#KAB_BACKEND_URL="http://127.0.0.1:8000/"
|
||||
|
||||
@@ -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/"
|
||||
|
||||
3
frontend/.gitignore
vendored
3
frontend/.gitignore
vendored
@@ -35,3 +35,6 @@ yarn-error.log*
|
||||
|
||||
# local .env files
|
||||
.env.local*
|
||||
|
||||
# pnpm
|
||||
pnpm-lock.yaml
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<html lang="ja-jp">
|
||||
<head>
|
||||
<title><%= productName %></title>
|
||||
|
||||
<meta charset="utf-8">
|
||||
<meta name="description" content="<%= productDescription %>">
|
||||
<meta name="format-detection" content="telephone=no">
|
||||
|
||||
72
frontend/package-lock.json
generated
72
frontend/package-lock.json
generated
@@ -10,13 +10,16 @@
|
||||
"dependencies": {
|
||||
"@quasar/extras": "^1.16.4",
|
||||
"axios": "^1.4.0",
|
||||
"pinia": "^2.1.6",
|
||||
"quasar": "^2.6.0",
|
||||
"uuid": "^9.0.0",
|
||||
"vue": "^3.0.0",
|
||||
"vue-router": "^4.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@quasar/app-vite": "^1.3.0",
|
||||
"@types/node": "^12.20.21",
|
||||
"@types/uuid": "^9.0.3",
|
||||
"@typescript-eslint/eslint-plugin": "^5.10.0",
|
||||
"@typescript-eslint/parser": "^5.10.0",
|
||||
"autoprefixer": "^10.4.2",
|
||||
@@ -28,8 +31,9 @@
|
||||
"typescript": "^4.5.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18 || ^16 || ^14.19",
|
||||
"node": "^20 ||^18 || ^16 || ^14.19",
|
||||
"npm": ">= 6.13.4",
|
||||
"pnpm": ">=8.6.0",
|
||||
"yarn": ">= 1.21.1"
|
||||
}
|
||||
},
|
||||
@@ -544,6 +548,12 @@
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/uuid": {
|
||||
"version": "9.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.3.tgz",
|
||||
"integrity": "sha512-taHQQH/3ZyI3zP8M/puluDEIEvtQHVYcC6y3N8ijFtAd28+Ey/G4sg1u2gB01S8MwybLOKAp9/yCMu/uR5l3Ug==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@typescript-eslint/eslint-plugin": {
|
||||
"version": "5.61.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.61.0.tgz",
|
||||
@@ -4070,6 +4080,56 @@
|
||||
"url": "https://github.com/sponsors/jonschlinkert"
|
||||
}
|
||||
},
|
||||
"node_modules/pinia": {
|
||||
"version": "2.1.6",
|
||||
"resolved": "https://registry.npmjs.org/pinia/-/pinia-2.1.6.tgz",
|
||||
"integrity": "sha512-bIU6QuE5qZviMmct5XwCesXelb5VavdOWKWaB17ggk++NUwQWWbP5YnsONTk3b752QkW9sACiR81rorpeOMSvQ==",
|
||||
"dependencies": {
|
||||
"@vue/devtools-api": "^6.5.0",
|
||||
"vue-demi": ">=0.14.5"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/posva"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@vue/composition-api": "^1.4.0",
|
||||
"typescript": ">=4.4.4",
|
||||
"vue": "^2.6.14 || ^3.3.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@vue/composition-api": {
|
||||
"optional": true
|
||||
},
|
||||
"typescript": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/pinia/node_modules/vue-demi": {
|
||||
"version": "0.14.6",
|
||||
"resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.6.tgz",
|
||||
"integrity": "sha512-8QA7wrYSHKaYgUxDA5ZC24w+eHm3sYCbp0EzcDwKqN3p6HqtTCGR/GVsPyZW92unff4UlcSh++lmqDWN3ZIq4w==",
|
||||
"hasInstallScript": true,
|
||||
"bin": {
|
||||
"vue-demi-fix": "bin/vue-demi-fix.js",
|
||||
"vue-demi-switch": "bin/vue-demi-switch.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/antfu"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@vue/composition-api": "^1.0.0-rc.1",
|
||||
"vue": "^3.0.0-0 || ^2.6.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@vue/composition-api": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/postcss": {
|
||||
"version": "8.4.25",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.25.tgz",
|
||||
@@ -4946,7 +5006,7 @@
|
||||
"version": "4.9.5",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz",
|
||||
"integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
"tsserver": "bin/tsserver"
|
||||
@@ -5045,6 +5105,14 @@
|
||||
"node": ">= 0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/uuid": {
|
||||
"version": "9.0.0",
|
||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz",
|
||||
"integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==",
|
||||
"bin": {
|
||||
"uuid": "dist/bin/uuid"
|
||||
}
|
||||
},
|
||||
"node_modules/vary": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"name": "kintone-app-builder",
|
||||
"version": "0.0.1",
|
||||
"name": "kintone-automate",
|
||||
"version": "0.2.0",
|
||||
"description": "Kintoneアプリの自動生成とデプロイを支援ツールです",
|
||||
"productName": "Kintone App Builder",
|
||||
"productName": "kintone Automate",
|
||||
"author": "maxiaozhe@alicorns.co.jp <maxiaozhe@alicorns.co.jp>",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
@@ -10,18 +10,25 @@
|
||||
"format": "prettier --write \"**/*.{js,ts,vue,scss,html,md,json}\" --ignore-path .gitignore",
|
||||
"test": "echo \"No test specified\" && exit 0",
|
||||
"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": {
|
||||
"@quasar/extras": "^1.16.4",
|
||||
"@vueuse/core": "^10.9.0",
|
||||
"axios": "^1.4.0",
|
||||
"pinia": "^2.1.7",
|
||||
"quasar": "^2.6.0",
|
||||
"uuid": "^9.0.0",
|
||||
"vue": "^3.0.0",
|
||||
"vue-router": "^4.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@quasar/app-vite": "^1.3.0",
|
||||
"@types/node": "^12.20.21",
|
||||
"@types/uuid": "^9.0.3",
|
||||
"@typescript-eslint/eslint-plugin": "^5.10.0",
|
||||
"@typescript-eslint/parser": "^5.10.0",
|
||||
"autoprefixer": "^10.4.2",
|
||||
@@ -33,8 +40,9 @@
|
||||
"typescript": "^4.5.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18 || ^16 || ^14.19",
|
||||
"node": "^20 ||^18 || ^16 || ^14.19",
|
||||
"npm": ">= 6.13.4",
|
||||
"yarn": ">= 1.21.1"
|
||||
"yarn": ">= 1.21.1",
|
||||
"pnpm": ">=8.6.0"
|
||||
}
|
||||
}
|
||||
|
||||
8
frontend/public/web.config
Normal file
8
frontend/public/web.config
Normal 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>
|
||||
@@ -10,10 +10,14 @@
|
||||
|
||||
|
||||
const { configure } = require('quasar/wrappers');
|
||||
const dotenv = require('dotenv').config().parsed;
|
||||
const package = require('./package.json');
|
||||
const envPath = process.env.LOCAL==='true'?'.env.development':'.env';
|
||||
const dotenv = require('dotenv').config({path:envPath}).parsed;
|
||||
console.log('dotenv=>',dotenv);
|
||||
// const package = require('./package.json');
|
||||
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 */) {
|
||||
return {
|
||||
eslint: {
|
||||
@@ -32,7 +36,8 @@ module.exports = configure(function (/* ctx */) {
|
||||
// --> boot files are part of "main.js"
|
||||
// https://v2.quasar.dev/quasar-cli-vite/boot-files
|
||||
boot: [
|
||||
'axios'
|
||||
'axios',
|
||||
'error-handler'
|
||||
],
|
||||
|
||||
// https://v2.quasar.dev/quasar-cli-vite/quasar-config-js#css
|
||||
@@ -49,7 +54,6 @@ module.exports = configure(function (/* ctx */) {
|
||||
// 'themify',
|
||||
// 'line-awesome',
|
||||
// 'roboto-font-latin-ext', // this or either 'roboto-font', NEVER both!
|
||||
|
||||
'roboto-font', // optional, you are not bound to it
|
||||
'material-icons', // optional, you are not bound to it
|
||||
],
|
||||
@@ -60,6 +64,7 @@ module.exports = configure(function (/* ctx */) {
|
||||
browser: ['es2019', 'edge88', 'firefox78', 'chrome87', 'safari13.1'],
|
||||
node: 'node16'
|
||||
},
|
||||
sourcemap:process.env.SOURCE_MAP === 'true',
|
||||
|
||||
vueRouterMode: 'hash', // available values: 'hash', 'history'
|
||||
// vueRouterBase,
|
||||
@@ -70,7 +75,7 @@ module.exports = configure(function (/* ctx */) {
|
||||
|
||||
// publicPath: '/',
|
||||
// analyze: true,
|
||||
env: { ...dotenv, version },
|
||||
env: { ...dotenv, version ,productName},
|
||||
// rawDefine: {}
|
||||
// ignorePublicFolder: true,
|
||||
// minify: false,
|
||||
@@ -89,6 +94,7 @@ module.exports = configure(function (/* ctx */) {
|
||||
// Full list of options: https://v2.quasar.dev/quasar-cli-vite/quasar-config-js#devServer
|
||||
devServer: {
|
||||
// https: true
|
||||
port:9001,
|
||||
open: true, // opens browser window automatically
|
||||
env: { ...dotenv },
|
||||
},
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { boot } from 'quasar/wrappers';
|
||||
import axios, { AxiosInstance } from 'axios';
|
||||
import {router} from 'src/router';
|
||||
|
||||
declare module '@vue/runtime-core' {
|
||||
interface ComponentCustomProperties {
|
||||
@@ -15,7 +16,26 @@ declare module '@vue/runtime-core' {
|
||||
// "export default () => {}" function below (which runs individually
|
||||
// for each client)
|
||||
const api:AxiosInstance = axios.create({ baseURL: process.env.KAB_BACKEND_URL });
|
||||
|
||||
const token=localStorage.getItem('token')||'';
|
||||
if(token!==''){
|
||||
api.defaults.headers["Authorization"]='Bearer ' + token;
|
||||
}
|
||||
//axios例外キャプチャー
|
||||
api.interceptors.response.use(
|
||||
(response)=>response,
|
||||
(error)=>{
|
||||
if (error.response && error.response.status === 401) {
|
||||
// 認証エラーの場合再ログインする
|
||||
console.error('(; ゚Д゚)/認証エラー(401):', error);
|
||||
localStorage.removeItem('token');
|
||||
router.replace({
|
||||
path:"/login",
|
||||
query:{redirect:router.currentRoute.value.fullPath}
|
||||
});
|
||||
}
|
||||
return Promise.reject(error);
|
||||
}
|
||||
)
|
||||
export default boot(({ app }) => {
|
||||
// for use inside Vue files (Options API) through this.$axios and this.$api
|
||||
|
||||
|
||||
21
frontend/src/boot/error-handler.ts
Normal file
21
frontend/src/boot/error-handler.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
// src/boot/error-handler.ts
|
||||
import { boot } from 'quasar/wrappers';
|
||||
import { Router } from 'vue-router';
|
||||
import { App } from 'vue';
|
||||
|
||||
export default boot(({ app, router }: { app: App<Element>; router: Router }) => {
|
||||
document.documentElement.lang="ja-JP";
|
||||
app.config.errorHandler = (err: any, instance: any, info: string) => {
|
||||
if (err.response && err.response.status === 401) {
|
||||
// 認証エラーの場合再ログインする
|
||||
console.error('(; ゚Д゚)/認証エラー(401):', err, info);
|
||||
localStorage.removeItem('token');
|
||||
router.replace({
|
||||
path:"/login",
|
||||
query:{redirect:router.currentRoute.value.fullPath}
|
||||
});
|
||||
} else {
|
||||
console.error('(; ゚Д゚)例外:', err, info);
|
||||
}
|
||||
};
|
||||
});
|
||||
@@ -1,38 +0,0 @@
|
||||
<template>
|
||||
<div class="q-pa-md" style="max-width: 350px">
|
||||
<q-expansion-item
|
||||
class="shadow-1 overflow-hidden"
|
||||
style="border-radius: 30px"
|
||||
icon="explore"
|
||||
label="Counter"
|
||||
@show="startCounting"
|
||||
@hide="stopCounting"
|
||||
header-class="bg-primary text-white"
|
||||
expand-icon-class="text-white"
|
||||
>
|
||||
<q-card>
|
||||
<q-card-section>
|
||||
Counting: <q-badge color="secondary">{{ counter }}</q-badge>.
|
||||
Will only count when opened, using the show/hide events to control count timer.
|
||||
</q-card-section>
|
||||
</q-card>
|
||||
</q-expansion-item>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { defineComponent} from "vue"
|
||||
export default defineComponent({
|
||||
props:{
|
||||
icon:String,
|
||||
|
||||
},
|
||||
setup() {
|
||||
return {
|
||||
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
<style lang="">
|
||||
|
||||
</style>
|
||||
@@ -1,30 +0,0 @@
|
||||
<template>
|
||||
<div>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import {defineComponent,reactive} from 'vue';
|
||||
import {KintoneEvent} from "../models/kintone";
|
||||
export default defineComponent({
|
||||
setup(props, ctx) {
|
||||
const events = reactive<KintoneEvent[]>(
|
||||
[
|
||||
{
|
||||
screen:"レコード追加画面",
|
||||
type:"app.record.create.show",
|
||||
name:"レコード追加画面を表示した後"
|
||||
},
|
||||
{
|
||||
screen:"レコード追加画面",
|
||||
type:"app.record.create.show",
|
||||
name:"レコード追加画面を表示した後"
|
||||
}
|
||||
]
|
||||
);
|
||||
},
|
||||
});
|
||||
</script>
|
||||
<style lang="scss">
|
||||
|
||||
</style>
|
||||
@@ -1,6 +1,17 @@
|
||||
<template>
|
||||
<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="index" :selection="type" v-model:selected="selected" :columns="columns" :rows="rows"
|
||||
class="action-table"
|
||||
flat bordered
|
||||
virtual-scroll
|
||||
:pagination="pagination"
|
||||
:rows-per-page-options="[0]"
|
||||
:filter="filter"
|
||||
>
|
||||
</q-table>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
@@ -11,30 +22,41 @@ export default {
|
||||
name: 'actionSelect',
|
||||
props: {
|
||||
name: String,
|
||||
type: String
|
||||
type: String,
|
||||
filter:String
|
||||
},
|
||||
setup() {
|
||||
const columns = [
|
||||
{ name: 'name', required: true,label: 'アクション名',align: 'left',field: 'name',sortable: true},
|
||||
{ name: 'desc', align: 'left', label: '説明', field: 'desc', sortable: true },
|
||||
{ name: 'content', label: '内容', field: 'content', sortable: true }
|
||||
]
|
||||
const rows = reactive([])
|
||||
setup(props) {
|
||||
const isLoaded=ref(false);
|
||||
const columns = [
|
||||
{ name: 'name', required: true,label: 'アクション名',align: 'left',field: 'name',sortable: true},
|
||||
{ name: 'desc', align: 'left', label: '説明', field: 'desc', sortable: true },
|
||||
// { name: 'content', label: '内容', field: 'content', sortable: true }
|
||||
];
|
||||
const rows = reactive([])
|
||||
onMounted(async () => {
|
||||
await api.get('http://127.0.0.1:8000/api/kintone/2').then(res =>{
|
||||
res.data.forEach((item) =>
|
||||
const res =await api.get('api/actions');
|
||||
res.data.forEach((item,index) =>
|
||||
{
|
||||
rows.push({name:item.name,desc:item.desc,content:item.content});
|
||||
}
|
||||
)
|
||||
});
|
||||
rows.push({index,name:item.name,desc:item.title,outputPoints:item.outputpoints,property:item.property});
|
||||
});
|
||||
isLoaded.value=true;
|
||||
});
|
||||
return {
|
||||
columns,
|
||||
rows,
|
||||
selected: ref([]),
|
||||
pagination:ref({
|
||||
rowsPerPage:0
|
||||
}),
|
||||
isLoaded,
|
||||
}
|
||||
},
|
||||
|
||||
}
|
||||
</script>
|
||||
<style lang="scss">
|
||||
.action-table{
|
||||
min-height: 10vh;
|
||||
max-height: 68vh;
|
||||
}
|
||||
</style>
|
||||
|
||||
131
frontend/src/components/AppFieldSelectBox.vue
Normal file
131
frontend/src/components/AppFieldSelectBox.vue
Normal file
@@ -0,0 +1,131 @@
|
||||
<template>
|
||||
|
||||
<div class="q-mx-md q-mb-lg">
|
||||
<div class="q-mb-xs q-ml-md text-primary">アプリ選択</div>
|
||||
|
||||
<div class="q-pa-md row" style="border: 1px solid rgba(0, 0, 0, 0.12); border-radius: 4px;">
|
||||
<div v-if="selField?.app && !showSelectApp">{{ selField?.app?.name }}</div>
|
||||
<q-space />
|
||||
<div>
|
||||
<q-btn outline dense label="選 択" padding="none sm" color="primary" @click="() => {
|
||||
showSelectApp = true;
|
||||
}"></q-btn>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="!showSelectApp && selField?.app?.name">
|
||||
<div>
|
||||
<div class="row q-mb-md">
|
||||
<!-- <div class="col"> -->
|
||||
<div class="q-mb-xs q-ml-md text-primary">フィールド選択</div>
|
||||
<!-- </div> -->
|
||||
<q-space />
|
||||
<!-- <div class="col"> -->
|
||||
<div class="q-mr-md">
|
||||
<q-input dense debounce="300" v-model="fieldFilter" placeholder="フィールド検索" clearable>
|
||||
<template v-slot:before>
|
||||
<q-icon name="search" />
|
||||
</template>
|
||||
</q-input>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<field-select ref="fieldDlg" name="フィールド" :type="selectType" :updateSelectFields="updateSelectFields"
|
||||
:appId="selField?.app?.id" not_page :filter="fieldFilter"
|
||||
:selectedFields="selField.fields" :fieldTypes="fieldTypes"></field-select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="min-width: 45vw;" v-else>
|
||||
</div>
|
||||
|
||||
|
||||
<show-dialog v-model:visible="showSelectApp" name="アプリ選択">
|
||||
<template v-slot:toolbar>
|
||||
<q-input dense debounce="300" v-model="filter" placeholder="検索" clearable>
|
||||
<template v-slot:before>
|
||||
<q-icon name="search" />
|
||||
</template>
|
||||
</q-input>
|
||||
</template>
|
||||
|
||||
<AppSelectBox ref="appDlg" name="アプリ" type="single" :filter="filter"
|
||||
:updateSelectApp="updateSelectApp"></AppSelectBox>
|
||||
</show-dialog>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, ref, watchEffect, computed, reactive } from 'vue';
|
||||
import ShowDialog from './ShowDialog.vue';
|
||||
import FieldSelect from './FieldSelect.vue';
|
||||
import AppSelectBox from './AppSelectBox.vue';
|
||||
interface IApp {
|
||||
id: string,
|
||||
name: string
|
||||
}
|
||||
interface IField {
|
||||
name: string,
|
||||
code: string,
|
||||
type: string
|
||||
}
|
||||
|
||||
interface IAppFields {
|
||||
app?: IApp,
|
||||
fields: IField[]
|
||||
}
|
||||
|
||||
export default defineComponent({
|
||||
inheritAttrs: false,
|
||||
name: 'AppFieldSelectBox',
|
||||
components: {
|
||||
ShowDialog,
|
||||
FieldSelect,
|
||||
AppSelectBox,
|
||||
},
|
||||
props: {
|
||||
selectedField: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
selectType: {
|
||||
type: String,
|
||||
default: 'single'
|
||||
},
|
||||
fieldTypes:{
|
||||
type:Array,
|
||||
default:()=>[]
|
||||
}
|
||||
},
|
||||
setup(props, { emit }) {
|
||||
const showSelectApp = ref(false);
|
||||
const selField = reactive(props.selectedField);
|
||||
|
||||
const isSelected = computed(() => {
|
||||
return selField !== null && typeof selField === 'object' && ('app' in selField)
|
||||
});
|
||||
|
||||
const updateSelectApp = (newAppinfo: IApp) => {
|
||||
selField.app = newAppinfo
|
||||
}
|
||||
|
||||
const updateSelectFields = (newFields: IField[]) => {
|
||||
selField.fields = newFields
|
||||
}
|
||||
|
||||
watchEffect(() => {
|
||||
emit('update:modelValue', selField);
|
||||
});
|
||||
|
||||
return {
|
||||
showSelectApp,
|
||||
isSelected,
|
||||
updateSelectApp,
|
||||
filter: ref(),
|
||||
updateSelectFields,
|
||||
fieldFilter: ref(),
|
||||
selField
|
||||
};
|
||||
}
|
||||
});
|
||||
</script>
|
||||
@@ -36,7 +36,7 @@
|
||||
import { AppInfo, AppSeed } from './models';
|
||||
import { ref, defineComponent, watch, onMounted , toRefs } from 'vue';
|
||||
import { api } from 'boot/axios';
|
||||
import { promises } from 'dns';
|
||||
import { useAuthStore } from 'src/stores/useAuthStore';
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
@@ -44,12 +44,13 @@ export default defineComponent({
|
||||
},
|
||||
setup(props) {
|
||||
const { app } = toRefs(props);
|
||||
const authStore = useAuthStore();
|
||||
const appinfo = ref<AppInfo>({
|
||||
appId: "",
|
||||
name: "",
|
||||
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) => {
|
||||
if(!appId){
|
||||
return;
|
||||
@@ -59,7 +60,7 @@ export default defineComponent({
|
||||
let retry =0;
|
||||
while(retry<=3 && result && result.appId!==appId){
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
const response = await api.get('app', {
|
||||
const response = await api.get('api/v1/app', {
|
||||
params:{
|
||||
app: appId
|
||||
}
|
||||
@@ -73,7 +74,7 @@ export default defineComponent({
|
||||
|
||||
watch(app, async (newApp) => {
|
||||
appinfo.value = await getAppInfo(newApp);
|
||||
link.value = 'https://mfu07rkgnb7c.cybozu.com/k/' + newApp;
|
||||
link.value = `${authStore.currentDomain.kintoneUrl}/k/${newApp}`;
|
||||
}, { immediate: true });
|
||||
|
||||
const linkClick=(ev : MouseEvent)=>{
|
||||
@@ -82,7 +83,7 @@ export default defineComponent({
|
||||
};
|
||||
onMounted(async ()=>{
|
||||
appinfo.value = await getAppInfo(app.value);
|
||||
link.value = 'https://mfu07rkgnb7c.cybozu.com/k/' + app.value;
|
||||
link.value = `${authStore.currentDomain.kintoneUrl}/k/${app.value}`;
|
||||
});
|
||||
|
||||
return {
|
||||
|
||||
100
frontend/src/components/AppSelectBox.vue
Normal file
100
frontend/src/components/AppSelectBox.vue
Normal file
@@ -0,0 +1,100 @@
|
||||
<template>
|
||||
<div class="q-px-xs">
|
||||
<div v-if="!isLoaded" class="spinner flex flex-center">
|
||||
<q-spinner color="primary" size="3em" />
|
||||
</div>
|
||||
<q-table v-else class="app-table" :selection="type" row-key="id" v-model:selected="selected" flat bordered
|
||||
virtual-scroll :columns="columns" :rows="rows" :pagination="pagination" :rows-per-page-options="[0]"
|
||||
:filter="filter" style="max-height: 65vh;">
|
||||
<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>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { ref, onMounted, reactive, watchEffect } from 'vue'
|
||||
import { api } from 'boot/axios';
|
||||
|
||||
export default {
|
||||
name: 'AppSelectBox',
|
||||
props: {
|
||||
name: String,
|
||||
type: String,
|
||||
filter: String,
|
||||
updateSelectApp: {
|
||||
type: Function
|
||||
}
|
||||
},
|
||||
setup(props) {
|
||||
const columns = [
|
||||
{ name: 'id', required: true, label: 'ID', align: 'left', field: 'id', sortable: true },
|
||||
{ name: 'name', label: 'アプリ名', field: 'name', sortable: true, align: 'left' },
|
||||
{ name: 'description', label: '概要', field: 'description', align: 'left', sortable: false },
|
||||
{ name: 'createdate', label: '作成日時', field: 'createdate', align: 'left' }
|
||||
]
|
||||
const isLoaded = ref(false);
|
||||
const rows: any[] = reactive([]);
|
||||
const selected = ref([])
|
||||
|
||||
watchEffect(()=>{
|
||||
if (selected.value && selected.value[0] && props.updateSelectApp) {
|
||||
props.updateSelectApp(selected.value[0])
|
||||
}
|
||||
});
|
||||
onMounted(() => {
|
||||
api.get('api/v1/allapps').then(res => {
|
||||
res.data.apps.forEach((item: any) => {
|
||||
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 {
|
||||
columns,
|
||||
rows,
|
||||
selected,
|
||||
isLoaded,
|
||||
pagination: ref({
|
||||
rowsPerPage: 10
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
}
|
||||
</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>
|
||||
107
frontend/src/components/ConditionEditor/ConditionEditor.vue
Normal file
107
frontend/src/components/ConditionEditor/ConditionEditor.vue
Normal file
@@ -0,0 +1,107 @@
|
||||
<template>
|
||||
<show-dialog v-model:visible="showflg" name="条件エディタ" @close="closeDg" min-width="50vw" min-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>
|
||||
135
frontend/src/components/ConditionEditor/ConditionObject.vue
Normal file
135
frontend/src/components/ConditionEditor/ConditionObject.vue
Normal file
@@ -0,0 +1,135 @@
|
||||
<template>
|
||||
<div class="q-pa-sm">
|
||||
<q-field labelColor="primary" class="condition-object" dense outlined :label="label" :disable="disabled"
|
||||
:clearable="isSelected">
|
||||
<template v-slot:control>
|
||||
<q-chip color="primary" text-color="white" v-if="isSelected && selectedObject.objectType==='field'" :dense="true" class="selected-obj">
|
||||
{{ selectedObject.name }}
|
||||
</q-chip>
|
||||
<q-chip color="info" text-color="white" v-if="isSelected && selectedObject.objectType==='variable'" :dense="true" class="selected-obj">
|
||||
{{ selectedObject.name.name }}
|
||||
</q-chip>
|
||||
<div v-if="isSelected && selectedObject.objectType==='text'">{{ selectedObject?.sharedText }}</div>
|
||||
</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" min-width="400px">
|
||||
<!-- <template v-slot:toolbar>
|
||||
<q-input dense debounce="200" v-model="filter" placeholder="検索" clearable>
|
||||
<template v-slot:before>
|
||||
<q-icon name="search" />
|
||||
</template>
|
||||
</q-input>
|
||||
</template>
|
||||
<condition-objects ref="appDg" name="フィールド" type="single" :filter="filter" :appId="store.appInfo?.appId" :vars="vars"></condition-objects>
|
||||
-->
|
||||
<DynamicItemInput v-model:selectedObject="selectedObject" :canInput="config.canInput"
|
||||
:buttonsConfig="config.buttonsConfig" :appId="store.appInfo?.appId" />
|
||||
|
||||
</show-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, reactive, ref, watchEffect, computed } from 'vue';
|
||||
import ShowDialog from '../ShowDialog.vue';
|
||||
// import ConditionObjects from '../ConditionObjects.vue';
|
||||
import DynamicItemInput from '../DynamicItemInput/DynamicItemInput.vue';
|
||||
import { useFlowEditorStore } from '../../stores/flowEditor';
|
||||
import { IActionFlow, IActionNode, IActionVariable } from '../../types/ActionTypes';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'ConditionObject',
|
||||
components: {
|
||||
ShowDialog,
|
||||
DynamicItemInput,
|
||||
// ConditionObjects
|
||||
},
|
||||
props: {
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
label: {
|
||||
type: String,
|
||||
default: undefined
|
||||
},
|
||||
config: {
|
||||
type: Object,
|
||||
default: () => {
|
||||
return {
|
||||
canInput: false,
|
||||
buttonsConfig: [
|
||||
{ label: 'フィールド', color: 'primary', type: 'FieldAdd' },
|
||||
{ label: '変数', color: 'green', type: 'VariableAdd' },
|
||||
]
|
||||
};
|
||||
}
|
||||
},
|
||||
modelValue: {
|
||||
type: Object,
|
||||
default: null
|
||||
},
|
||||
},
|
||||
setup(props, { emit }) {
|
||||
// const appDg = ref();
|
||||
const show = ref(false);
|
||||
const selectedObject = ref(props.modelValue);
|
||||
const store = useFlowEditorStore();
|
||||
// const sharedText = ref(''); // 共享的文本状态
|
||||
const isSelected = computed(() => {
|
||||
return selectedObject.value?.sharedText !== '';
|
||||
});
|
||||
// const isSelected = computed(()=>{
|
||||
// return selectedObject.value!==null && typeof selectedObject.value === 'object' && ('name' in selectedObject.value)
|
||||
// });
|
||||
let vars: IActionVariable[] = [];
|
||||
if (store.currentFlow !== undefined && store.activeNode !== undefined) {
|
||||
vars = store.currentFlow.getVarNames(store.activeNode);
|
||||
}
|
||||
// const filter=ref('');
|
||||
const showDg = () => {
|
||||
show.value = true;
|
||||
};
|
||||
|
||||
const closeDg = (val: string) => {
|
||||
if (val == 'OK') {
|
||||
// selectedObject.value = appDg.value.selected[0];
|
||||
}
|
||||
};
|
||||
|
||||
watchEffect(() => {
|
||||
emit('update:modelValue', selectedObject.value);
|
||||
});
|
||||
|
||||
return {
|
||||
store,
|
||||
// appDg,
|
||||
show,
|
||||
showDg,
|
||||
closeDg,
|
||||
selectedObject,
|
||||
vars: reactive(vars),
|
||||
isSelected,
|
||||
buttonsConfig: [
|
||||
{ label: 'フィールド', color: 'primary', type: 'FieldAdd' },
|
||||
{ label: '変数', color: 'green', type: 'VariableAdd' },
|
||||
]
|
||||
// filter
|
||||
};
|
||||
}
|
||||
});
|
||||
</script>
|
||||
<style lang="scss">
|
||||
.condition-object {
|
||||
min-width: 200px;
|
||||
max-height: 40px;
|
||||
margin: 0 2px;
|
||||
}
|
||||
|
||||
.selected-obj {
|
||||
margin: 0 2px;
|
||||
}
|
||||
</style>
|
||||
271
frontend/src/components/ConditionEditor/NodeCondition.vue
Normal file
271
frontend/src/components/ConditionEditor/NodeCondition.vue
Normal file
@@ -0,0 +1,271 @@
|
||||
<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 q-my-xs">
|
||||
<ConditionObject v-bind="prop.node" v-model="prop.node.object" :config="leftDynamicItemConfig" class="col-4"/>
|
||||
<q-select v-model="prop.node.operator" :options="operators" class="operator" :outlined="true" :dense="true"></q-select>
|
||||
<ConditionObject v-bind="prop.node" v-model="prop.node.value" :config="rightDynamicItemConfig" class="col-4"/>
|
||||
<!-- <ConditionObject v-bind="prop.node" v-model="prop.node.object" class="col-4"/> -->
|
||||
<!-- <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-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, inject } 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 operator = inject('Operator')
|
||||
const operators =computed(()=>{
|
||||
return operator ? operator : Object.values(Operator);
|
||||
});
|
||||
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 {
|
||||
leftDynamicItemConfig :inject('leftDynamicItemConfig'),
|
||||
rightDynamicItemConfig:inject('rightDynamicItemConfig'),
|
||||
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;
|
||||
margin: 0 2px;
|
||||
}
|
||||
.operator{
|
||||
min-width: 150px;
|
||||
max-height: 40px;
|
||||
margin-left: 12px;
|
||||
|
||||
text-align: center;
|
||||
font-size: 12pt;
|
||||
}
|
||||
</style>
|
||||
63
frontend/src/components/ConditionObjects.vue
Normal file
63
frontend/src/components/ConditionObjects.vue
Normal file
@@ -0,0 +1,63 @@
|
||||
<template>
|
||||
<div class="q-gutter-y-md" style="max-width: 600px;">
|
||||
<q-card >
|
||||
<q-tabs
|
||||
v-model="tab"
|
||||
dense
|
||||
class="text-grey"
|
||||
active-color="white"
|
||||
active-bg-color="primary"
|
||||
indicator-color="primary"
|
||||
align="justify"
|
||||
narrow-indicator
|
||||
>
|
||||
<q-tab name="fields" label="フィールド"></q-tab>
|
||||
<q-tab name="vars" label="変数"></q-tab>
|
||||
</q-tabs>
|
||||
|
||||
<q-separator></q-separator>
|
||||
|
||||
<q-tab-panels v-model="tab" animated>
|
||||
<q-tab-panel name="fields">
|
||||
<field-list v-model="selected" type="single" :filter="filter" :appId="sourceApp ? sourceApp :appId " :fields="sourceFields"></field-list>
|
||||
</q-tab-panel>
|
||||
|
||||
<q-tab-panel name="vars" >
|
||||
<variable-list v-model="selected" type="single" :vars="vars"></variable-list>
|
||||
</q-tab-panel>
|
||||
</q-tab-panels>
|
||||
</q-card>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { ref, onMounted, reactive, inject } from 'vue'
|
||||
import FieldList from './FieldList.vue';
|
||||
import VariableList from './VariableList.vue';
|
||||
|
||||
export default {
|
||||
name: 'ConditionObjects',
|
||||
components:{
|
||||
FieldList,
|
||||
VariableList
|
||||
},
|
||||
props: {
|
||||
name: String,
|
||||
type: String,
|
||||
appId: Number,
|
||||
vars: Array,
|
||||
filter:String
|
||||
},
|
||||
setup(props) {
|
||||
const selected = ref([]);
|
||||
console.log(selected);
|
||||
|
||||
return {
|
||||
sourceFields : inject('sourceFields'),
|
||||
sourceApp : inject('sourceApp'),
|
||||
tab: ref('fields'),
|
||||
selected
|
||||
}
|
||||
},
|
||||
|
||||
}
|
||||
</script>
|
||||
@@ -4,6 +4,7 @@
|
||||
style="max-width: 400px"
|
||||
:url="uploadUrl"
|
||||
:label="title"
|
||||
:headers="headers"
|
||||
accept=".csv,.xlsx"
|
||||
v-on:rejected="onRejected"
|
||||
v-on:uploaded="onUploadFinished"
|
||||
@@ -15,7 +16,10 @@
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { createUploaderComponent, useQuasar } from 'quasar';
|
||||
import { useAuthStore } from 'src/stores/useAuthStore';
|
||||
import { ref } from 'vue';
|
||||
const $q=useQuasar();
|
||||
const authStore = useAuthStore();
|
||||
const emit =defineEmits(['uploaded']);
|
||||
/**
|
||||
* ファイルアップロードを拒否する時の処理
|
||||
@@ -67,9 +71,12 @@
|
||||
title: string;
|
||||
uploadUrl:string;
|
||||
}
|
||||
|
||||
const headers = ref([{name:"Authorization",value:'Bearer ' + authStore.token}]);
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
title:"設計書から導入する(csv or excel)",
|
||||
uploadUrl: `${process.env.KAB_BACKEND_URL}createappfromexcel`
|
||||
uploadUrl: `${process.env.KAB_BACKEND_URL}api/v1/createappfromexcel?format=1`
|
||||
|
||||
});
|
||||
</script>
|
||||
<style lang="scss">
|
||||
|
||||
@@ -8,24 +8,25 @@ import { ref,onMounted,reactive } from 'vue'
|
||||
import { api } from 'boot/axios';
|
||||
|
||||
export default {
|
||||
name: 'appSelect',
|
||||
name: 'DomainSelect',
|
||||
props: {
|
||||
name: String,
|
||||
type: String
|
||||
},
|
||||
setup() {
|
||||
const columns = [
|
||||
{ name: 'id', required: true,label: 'アプリID',align: 'left',field: 'id',sortable: true},
|
||||
{ name: 'name', align: 'center', label: 'アプリ名', field: 'name', sortable: true },
|
||||
{ name: 'creator', label: '作成者', field: 'creator', sortable: true },
|
||||
{ name: 'createdate', label: '作成日時', field: 'createdate' }
|
||||
{ name: 'id'},
|
||||
{ name: 'tenantid', required: true,label: 'テナント',align: 'left',field: 'tenantid',sortable: true},
|
||||
{ name: 'name', align: 'center', label: 'ドメイン', field: 'name', sortable: true },
|
||||
{ name: 'url', label: 'URL', field: 'url', sortable: true },
|
||||
{ name: 'kintoneuser', label: 'アカウント', field: 'kintoneuser' }
|
||||
]
|
||||
const rows = reactive([])
|
||||
onMounted( () => {
|
||||
api.get('allapps').then(res =>{
|
||||
res.data.apps.forEach((item) =>
|
||||
api.get(`api/domains/1`).then(res =>{
|
||||
res.data.forEach((item) =>
|
||||
{
|
||||
rows.push({id:item.appId,name:item.name,creator:item.creator.name,createdate:item.createdAt});
|
||||
rows.push({id:item.id,tenantid:item.tenantid,name:item.name,url:item.url,kintoneuser:item.kintoneuser});
|
||||
}
|
||||
)
|
||||
});
|
||||
43
frontend/src/components/DomainSelector.vue
Normal file
43
frontend/src/components/DomainSelector.vue
Normal 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>
|
||||
141
frontend/src/components/DynamicItemInput/DynamicItemInput.vue
Normal file
141
frontend/src/components/DynamicItemInput/DynamicItemInput.vue
Normal file
@@ -0,0 +1,141 @@
|
||||
<template>
|
||||
<div class="q-mx-md" style="max-width: 600px;">
|
||||
<!-- <q-card> -->
|
||||
<div class="q-mb-md">
|
||||
<q-input ref="inputRef" outlined dense debounce="200" @update:model-value="updateSharedText"
|
||||
v-model="sharedText" :readonly="!canInput" autogrow>
|
||||
<template v-slot:append>
|
||||
<q-btn flat round padding="none" icon="cancel" @click="clearSharedText" color="grey-6" />
|
||||
</template>
|
||||
</q-input>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="row q-gutter-sm">
|
||||
<q-btn v-for="button in buttonsConfig" :key="button.type" :color="button.color" @mousedown.prevent
|
||||
@click="openDialog(button)" size="sm">
|
||||
{{ button.label }}
|
||||
</q-btn>
|
||||
</div>
|
||||
|
||||
<show-dialog v-model:visible="dialogVisible" :name="currentDialogName" @close="closeDialog" min-width="400px">
|
||||
<template v-slot:toolbar>
|
||||
<q-input dense debounce="200" v-model="filter" clearable>
|
||||
<template v-slot:before>
|
||||
<q-icon name="search" />
|
||||
</template>
|
||||
</q-input>
|
||||
</template>
|
||||
<!-- asdf -->
|
||||
<component :is="currentComponent" @select="handleSelect" :filter="filter" :appId="appId" />
|
||||
</show-dialog>
|
||||
<!-- </q-card> -->
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { ref, inject, watchEffect, defineComponent } from 'vue';
|
||||
import FieldAdd from './FieldAdd.vue';
|
||||
import VariableAdd from './VariableAdd.vue';
|
||||
// import FunctionAdd from './FunctionAdd.vue';
|
||||
import ShowDialog from '../ShowDialog.vue';
|
||||
|
||||
type ButtonConfig = {
|
||||
label: string;
|
||||
color: string;
|
||||
type: string;
|
||||
editable: boolean;
|
||||
};
|
||||
|
||||
export default defineComponent({
|
||||
name: 'DynamicItemInput',
|
||||
components: {
|
||||
FieldAdd,
|
||||
VariableAdd,
|
||||
// FunctionAdd,
|
||||
ShowDialog
|
||||
},
|
||||
props: {
|
||||
// canInput: {
|
||||
// type: Boolean,
|
||||
// default: false
|
||||
// },
|
||||
appId: {
|
||||
type: String,
|
||||
},
|
||||
selectedObject: {
|
||||
default: {}
|
||||
},
|
||||
buttonsConfig: {
|
||||
type: Array as () => ButtonConfig[],
|
||||
default: () => [
|
||||
{ label: 'フィールド', color: 'primary', type: 'FieldAdd' }
|
||||
]
|
||||
}
|
||||
},
|
||||
setup(props, { emit }) {
|
||||
const filter = ref('');
|
||||
const dialogVisible = ref(false);
|
||||
const currentDialogName = ref('');
|
||||
const currentComponent = ref('FieldAdd');
|
||||
const sharedText = ref(props.selectedObject?.sharedText ?? '');
|
||||
const inputRef = ref();
|
||||
const canInput = ref(true);
|
||||
const editable = ref(false);
|
||||
|
||||
const openDialog = (button: ButtonConfig) => {
|
||||
currentDialogName.value = button.label;
|
||||
currentComponent.value = button.type;
|
||||
dialogVisible.value = true;
|
||||
editable.value = button.editable ?? true;
|
||||
};
|
||||
|
||||
const closeDialog = () => {
|
||||
dialogVisible.value = false;
|
||||
};
|
||||
|
||||
const handleSelect = (value:any) => {
|
||||
// 获取当前光标位置
|
||||
// const cursorPosition = inputRef.value.getNativeElement().selectionStart;
|
||||
// if (cursorPosition === undefined || cursorPosition === 0) {
|
||||
sharedText.value = `${value._t}`;
|
||||
// } else {
|
||||
// const textBefore = sharedText.value.substring(0, cursorPosition);
|
||||
// const textAfter = sharedText.value.substring(cursorPosition);
|
||||
// sharedText.value = `${textBefore}${value._t}${textAfter}`;
|
||||
// }
|
||||
|
||||
if (value && value._t && (value._t as string).length > 0) {
|
||||
canInput.value = editable.value;
|
||||
}
|
||||
emit('update:selectedObject', { sharedText: sharedText.value, ...value });
|
||||
dialogVisible.value = false;
|
||||
};
|
||||
|
||||
const clearSharedText = () => {
|
||||
sharedText.value = '';
|
||||
canInput.value = true;
|
||||
emit('update:selectedObject', {});
|
||||
}
|
||||
const updateSharedText = (value:string) => {
|
||||
sharedText.value = value;
|
||||
emit('update:selectedObject', { ...props.selectedObject, sharedText: value,objectType:'text' });
|
||||
}
|
||||
|
||||
return {
|
||||
filter,
|
||||
dialogVisible,
|
||||
currentDialogName,
|
||||
currentComponent,
|
||||
canInput,
|
||||
openDialog,
|
||||
closeDialog,
|
||||
handleSelect,
|
||||
clearSharedText,
|
||||
updateSharedText,
|
||||
sharedText,
|
||||
inputRef
|
||||
};
|
||||
}
|
||||
});
|
||||
</script>
|
||||
41
frontend/src/components/DynamicItemInput/FieldAdd.vue
Normal file
41
frontend/src/components/DynamicItemInput/FieldAdd.vue
Normal file
@@ -0,0 +1,41 @@
|
||||
<template>
|
||||
<field-list v-model="selected" type="single" :filter="filter" :appId="sourceApp ? sourceApp : appId"
|
||||
:fields="sourceFields" @update:modelValue="handleSelect" />
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { computed, inject, ref } from 'vue';
|
||||
import FieldList from '../FieldList.vue';
|
||||
export default {
|
||||
name: 'FieldAdd',
|
||||
components: {
|
||||
FieldList,
|
||||
},
|
||||
props: {
|
||||
appId: Number,
|
||||
filter: String
|
||||
},
|
||||
setup(props, { emit }) {
|
||||
const sourceFields = inject<Array<unknown>>('sourceFields')
|
||||
const sourceApp = inject<number>('sourceApp')
|
||||
const appId = computed(() => {
|
||||
if (sourceFields || sourceApp) {
|
||||
return sourceApp.value
|
||||
} else {
|
||||
return props.appId
|
||||
}
|
||||
});
|
||||
return {
|
||||
sourceFields,
|
||||
sourceApp,
|
||||
selected: ref([]),
|
||||
handleSelect: (newSelection: any[]) => {
|
||||
|
||||
if (newSelection.length > 0) {
|
||||
const v = newSelection[0]
|
||||
emit('select', { _t: `field(${appId.value},${v.name})`, ...v }); // 假设您只需要选择的第一个字段的名称
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
42
frontend/src/components/DynamicItemInput/VariableAdd.vue
Normal file
42
frontend/src/components/DynamicItemInput/VariableAdd.vue
Normal file
@@ -0,0 +1,42 @@
|
||||
<template>
|
||||
<variable-list v-model="selected" type="single" :vars="vars" :filter="filter" @update:modelValue="handleSelect" />
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import VariableList from '../VariableList.vue';
|
||||
import { useFlowEditorStore } from 'src/stores/flowEditor';
|
||||
import { IActionVariable } from 'src/types/ActionTypes';
|
||||
export default {
|
||||
name: 'VariableAdd',
|
||||
components: {
|
||||
VariableList,
|
||||
},
|
||||
props: {
|
||||
appId: Number,
|
||||
filter: String
|
||||
},
|
||||
setup(props, { emit }) {
|
||||
const store = useFlowEditorStore();
|
||||
let vars: IActionVariable[] = [];
|
||||
console.log(store.currentFlow !== undefined && store.activeNode !== undefined);
|
||||
|
||||
if (store.currentFlow !== undefined && store.activeNode !== undefined) {
|
||||
vars = store.currentFlow.getVarNames(store.activeNode);
|
||||
}
|
||||
return {
|
||||
vars,
|
||||
selected: ref([]),
|
||||
handleSelect: (newSelection: any[]) => {
|
||||
if (newSelection.length > 0) {
|
||||
const v = newSelection[0];
|
||||
let name = v.name
|
||||
if (typeof name === 'object') {
|
||||
name = name.name
|
||||
}
|
||||
emit('select', { _t: `var(${name})`, ...v }); // 假设您只需要选择的第一个字段的名称
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
58
frontend/src/components/FieldList.vue
Normal file
58
frontend/src/components/FieldList.vue
Normal file
@@ -0,0 +1,58 @@
|
||||
<template>
|
||||
<div class="q-pa-md">
|
||||
<q-table flat bordered :loading="!isLoaded" row-key="name" :selection="type" :selected="modelValue"
|
||||
@update:selected="$emit('update:modelValue', $event)" :filter="filter" :columns="columns" :rows="rows" />
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { useAsyncState } from '@vueuse/core';
|
||||
import { api } from 'boot/axios';
|
||||
import { computed } from 'vue';
|
||||
|
||||
export default {
|
||||
name: 'FieldList',
|
||||
props: {
|
||||
fields: Array,
|
||||
name: String,
|
||||
type: String,
|
||||
appId: Number,
|
||||
modelValue: Array,
|
||||
filter: String
|
||||
},
|
||||
emits: [
|
||||
'update:modelValue'
|
||||
],
|
||||
setup(props) {
|
||||
// const rows = ref([]);
|
||||
// const isLoaded = ref(false);
|
||||
const columns = [
|
||||
{ name: 'name', required: true, label: 'フィールド名', align: 'left', field: 'name', sortable: true },
|
||||
{ name: 'code', label: 'フィールドコード', align: 'left', field: 'code', sortable: true },
|
||||
{ name: 'type', label: 'フィールドタイプ', align: 'left', field: 'type', sortable: true }
|
||||
]
|
||||
|
||||
const { state : rows, isReady: isLoaded, isLoading } = useAsyncState((args) => {
|
||||
if (props.fields && Object.keys(props.fields).length > 0) {
|
||||
return props.fields.map(f => ({ name: f.label, objectType: 'field', ...f }));
|
||||
} else {
|
||||
return api.get('api/v1/appfields', {
|
||||
params: {
|
||||
app: props.appId
|
||||
}
|
||||
}).then(res => {
|
||||
console.log(res);
|
||||
return Object.values(res.data.properties).map(f => ({ name: f.label, objectType: 'field', ...f }));
|
||||
});
|
||||
}
|
||||
}, [{ name: '', objectType: '', type: '', code: '', label: '' }])
|
||||
|
||||
return {
|
||||
columns,
|
||||
rows,
|
||||
// selected: ref([]),
|
||||
isLoaded
|
||||
}
|
||||
},
|
||||
|
||||
}
|
||||
</script>
|
||||
@@ -1,45 +1,92 @@
|
||||
<template>
|
||||
<div class="q-pa-md">
|
||||
<q-table :title="name+'一覧'" row-key="name" :selection="type" v-model:selected="selected" :columns="columns" :rows="rows" />
|
||||
<div class="q-px-md" style=" min-width: 50vw; max-width: 85vw;">
|
||||
<div v-if="!isLoaded" class="spinner flex flex-center">
|
||||
<q-spinner color="primary" size="3em" />
|
||||
</div>
|
||||
<q-table flat bordered v-else row-key="name" :selection="type" v-model:selected="selected" :columns="columns"
|
||||
:rows="rows" :pagination="pageSetting" :filter="filter" style="max-height: 55vh;"/>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { ref,onMounted,reactive } from 'vue'
|
||||
import { ref, onMounted, reactive, watchEffect } from 'vue'
|
||||
import { api } from 'boot/axios';
|
||||
|
||||
|
||||
export default {
|
||||
name: 'fieldSelect',
|
||||
props: {
|
||||
name: String,
|
||||
type: String,
|
||||
appId:Number
|
||||
type: {
|
||||
type: String,
|
||||
default: 'single'
|
||||
},
|
||||
appId: Number,
|
||||
not_page: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
selectedFields:{
|
||||
type:Array,
|
||||
default:()=>[]
|
||||
},
|
||||
fieldTypes:{
|
||||
type:Array,
|
||||
default:()=>[]
|
||||
},
|
||||
filter: String,
|
||||
updateSelectFields: {
|
||||
type: Function
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
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( () => {
|
||||
api.get('appfields', {
|
||||
params:{
|
||||
app: props.appId
|
||||
}
|
||||
}).then(res =>{
|
||||
let fields = res.data.properties;
|
||||
console.log(fields);
|
||||
Object.keys(fields).forEach((key) =>
|
||||
{
|
||||
rows.push({name:fields[key].label,code:fields[key].code,type:fields[key].type});
|
||||
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 pageSetting = ref({
|
||||
sortBy: 'desc',
|
||||
descending: false,
|
||||
page: 1,
|
||||
rowsPerPage: props.not_page ? 0 : 5
|
||||
// rowsNumber: xx if getting data from a server
|
||||
});
|
||||
const rows = reactive([]);
|
||||
const selected = ref(props.selectedFields && props.selectedFields.length>0?props.selectedFields:[]);
|
||||
|
||||
onMounted(async () => {
|
||||
const url = props.fieldTypes.includes('SPACER')?'api/v1/allfields':'api/v1/appfields';
|
||||
const res = await api.get(url, {
|
||||
params: {
|
||||
app: props.appId
|
||||
}
|
||||
)
|
||||
});
|
||||
let fields = res.data.properties;
|
||||
Object.keys(fields).forEach((key) => {
|
||||
const fld = fields[key];
|
||||
if(props.fieldTypes.length===0 || props.fieldTypes.includes(fld.type)){
|
||||
rows.push({ name: fld.label || fld.code, ...fld });
|
||||
}else if(props.fieldTypes.includes("lookup") && ("lookup" in fld)){
|
||||
rows.push({ name: fld.label || fld.code, ...fld });
|
||||
}
|
||||
|
||||
});
|
||||
isLoaded.value = true;
|
||||
});
|
||||
|
||||
watchEffect(()=>{
|
||||
if (selected.value && selected.value[0] && props.updateSelectFields) {
|
||||
props.updateSelectFields(selected)
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
columns,
|
||||
rows,
|
||||
selected: ref([]),
|
||||
selected,
|
||||
isLoaded,
|
||||
pageSetting
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
<template>
|
||||
<div>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
|
||||
}
|
||||
</script>
|
||||
<style lang="">
|
||||
|
||||
</style>
|
||||
@@ -1,9 +0,0 @@
|
||||
export interface Rule{
|
||||
id:number;
|
||||
name:string;
|
||||
condtion:CondtionTree
|
||||
}
|
||||
|
||||
export interface CondtionTree{
|
||||
|
||||
}
|
||||
@@ -1,12 +1,14 @@
|
||||
<template>
|
||||
<div class="q-pa-md q-gutter-sm">
|
||||
<q-dialog :model-value="visible" persistent>
|
||||
<q-card style="min-width: 350px">
|
||||
<q-card-section>
|
||||
<div class="text-h6">{{ name }}選択</div>
|
||||
</q-card-section>
|
||||
|
||||
<q-card-section class="q-pt-none">
|
||||
<!-- <div class="q-pa-md q-gutter-sm" > -->
|
||||
<q-dialog :model-value="visible" persistent bordered >
|
||||
<q-card class="" style="min-width: 40vw; max-width: 80vw; max-height: 95vh;" :style="cardStyle">
|
||||
<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 class="q-mt-md" :style="sectionStyle">
|
||||
<slot></slot>
|
||||
</q-card-section>
|
||||
<q-card-actions align="right" class="text-primary">
|
||||
@@ -15,15 +17,19 @@
|
||||
</q-card-actions>
|
||||
</q-card>
|
||||
</q-dialog>
|
||||
</div>
|
||||
<!-- </div> -->
|
||||
</template>
|
||||
<script>
|
||||
|
||||
import {computed} from 'vue'
|
||||
export default {
|
||||
name: 'showDialog',
|
||||
name: 'ShowDialog',
|
||||
props: {
|
||||
name:String,
|
||||
visible: Boolean,
|
||||
width:String,
|
||||
height:String,
|
||||
minWidth:String,
|
||||
minHeight:String
|
||||
},
|
||||
emits: [
|
||||
'close'
|
||||
@@ -34,8 +40,20 @@ export default {
|
||||
context.emit('close', val);
|
||||
}
|
||||
|
||||
const cardStyle = computed(() => ({
|
||||
minWidth: props.minWidth,
|
||||
width: props.width
|
||||
}));
|
||||
|
||||
const sectionStyle = computed(() => ({
|
||||
height: props.height,
|
||||
minHeight: props.minHeight
|
||||
}));
|
||||
|
||||
return {
|
||||
CloseDialogue
|
||||
CloseDialogue,
|
||||
cardStyle,
|
||||
sectionStyle
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
61
frontend/src/components/VariableList.vue
Normal file
61
frontend/src/components/VariableList.vue
Normal file
@@ -0,0 +1,61 @@
|
||||
<template>
|
||||
<div class="q-pa-md">
|
||||
<q-table flat bordered row-key="id" :selection="type" :selected="modelValue" :filter="filter"
|
||||
@update:selected="$emit('update:modelValue', $event)" :columns="columns" :rows="rows" />
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { PropType, reactive } from 'vue';
|
||||
import { IActionVariable } from '../types/ActionTypes';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
export default {
|
||||
name: 'VariableList',
|
||||
props: {
|
||||
name: String,
|
||||
type: String,
|
||||
vars: {
|
||||
type: Array as PropType<IActionVariable[]>,
|
||||
reqired: true,
|
||||
default: () => []
|
||||
},
|
||||
modelValue: Array,
|
||||
filter: String
|
||||
},
|
||||
emits: [
|
||||
'update:modelValue'
|
||||
],
|
||||
setup(props) {
|
||||
const variableName = (field) => {
|
||||
const name = field.name;
|
||||
return name.name;
|
||||
}
|
||||
const columns = [
|
||||
{ name: 'actionName', label: 'アクション名', align: 'left', field: 'actionName', sortable: true },
|
||||
{ name: 'displayName', label: '変数表示名', align: 'left', field: 'displayName', sortable: true },
|
||||
{ name: 'name', label: '変数名', align: 'left', field: variableName, required: true, sortable: true }
|
||||
];
|
||||
|
||||
const rows = props.vars.flatMap((v) => {
|
||||
if (v.name.vars && v.name.vars.length > 0) {
|
||||
return v.name.vars
|
||||
.filter(o => o.vName && o.logicalOperator && o.field)
|
||||
.map(o => ({
|
||||
id: uuidv4(),
|
||||
objectType: 'variable',
|
||||
name: { name: `${v.name.name}.${o.vName}` },
|
||||
actionName: v.name.actionName,
|
||||
displayName: v.name.displayName
|
||||
}));
|
||||
} else {
|
||||
return [{ objectType: 'variable', ...v }];
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
columns,
|
||||
rows: reactive(rows)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
29
frontend/src/components/flowEditor/left/ControlPanelC.vue
Normal file
29
frontend/src/components/flowEditor/left/ControlPanelC.vue
Normal file
@@ -0,0 +1,29 @@
|
||||
<template>
|
||||
<div class="q-py-md">
|
||||
<q-tree
|
||||
no-connectors
|
||||
selected-color="primary"
|
||||
default-expand-all
|
||||
:nodes="LeftDataBus.root"
|
||||
v-model:selected="flowNames1"
|
||||
node-key="label"
|
||||
>
|
||||
</q-tree>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
LeftDataBus,
|
||||
setControlPanelE,
|
||||
} from 'components/flowEditor/left/DataBus';
|
||||
import { ref } from 'vue';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useFlowEditorStore } from 'stores/flowEditor';
|
||||
|
||||
// 应该在page中用网络请求获取值并初始化组件
|
||||
// 然后在page中执行setControlPane设置databus
|
||||
const store = useFlowEditorStore();
|
||||
const { flowNames1 } = storeToRefs(store);
|
||||
setControlPanelE();
|
||||
</script>
|
||||
72
frontend/src/components/flowEditor/left/DataBus.ts
Normal file
72
frontend/src/components/flowEditor/left/DataBus.ts
Normal file
@@ -0,0 +1,72 @@
|
||||
import { reactive } from 'vue'
|
||||
|
||||
export const LeftDataBus = reactive<LeftData>({})
|
||||
|
||||
const defaultData = {
|
||||
root: [
|
||||
{
|
||||
label: 'レコードを追加画面',
|
||||
children: [
|
||||
{
|
||||
label: '追加画面表示した時',
|
||||
header: 'rg',
|
||||
value: '1-1',
|
||||
group: 'g1',
|
||||
children: []
|
||||
},
|
||||
{
|
||||
label: '保存をクリックした時',
|
||||
header: 'rg',
|
||||
value: '1-2',
|
||||
group: 'g1',
|
||||
children: []
|
||||
},
|
||||
{
|
||||
label: '保存成功した時',
|
||||
header: 'rg',
|
||||
value: '1-3',
|
||||
group: 'g1',
|
||||
children: []
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
label: 'レコード編集画面',
|
||||
},
|
||||
{
|
||||
label: 'レコード詳細画面',
|
||||
},
|
||||
{
|
||||
label: 'レコード一覧画面',
|
||||
},
|
||||
],
|
||||
data: new Map([['g1', '1-1']])
|
||||
}
|
||||
|
||||
export const setControlPanel = (rootData: LeftData) => {
|
||||
const { root: dr, data: dd } = defaultData
|
||||
LeftDataBus.title = rootData.title
|
||||
LeftDataBus.root = rootData.root ?? dr
|
||||
LeftDataBus.data = rootData.data ?? dd
|
||||
}
|
||||
|
||||
export const setControlPanelE = () => {
|
||||
const { root: dr, data: dd } = defaultData
|
||||
// LeftDataBus.title = rootData.title
|
||||
LeftDataBus.root = dr
|
||||
LeftDataBus.data = dd
|
||||
}
|
||||
|
||||
export interface LeftData {
|
||||
title?: string
|
||||
root?: ControlPanelData[]
|
||||
data?: Map<string, string>
|
||||
}
|
||||
|
||||
export interface ControlPanelData {
|
||||
label: string,
|
||||
header?: string,
|
||||
value?: string,
|
||||
group?: string,
|
||||
children?: ControlPanelData[]
|
||||
}
|
||||
42
frontend/src/components/flowEditor/left/ItemSelector.vue
Normal file
42
frontend/src/components/flowEditor/left/ItemSelector.vue
Normal file
@@ -0,0 +1,42 @@
|
||||
<template>
|
||||
<div
|
||||
class="row"
|
||||
style="
|
||||
border-radius: 2px;
|
||||
box-shadow: rgba(255, 255, 255, 0.1) 0px 0px 0px 1px inset,
|
||||
rgba(0, 0, 0, 0.3) 0px 0px 0px 1px;
|
||||
"
|
||||
>
|
||||
<q-icon
|
||||
class="self-center q-ma-sm"
|
||||
name="widgets"
|
||||
color="grey-9"
|
||||
style="font-size: 2em"
|
||||
/>
|
||||
|
||||
<div class="col-7 self-center ellipsis">
|
||||
{{ actName }}
|
||||
</div>
|
||||
|
||||
<div class="self-center">
|
||||
<q-btn
|
||||
outline
|
||||
dense
|
||||
label="変 更"
|
||||
padding="none sm"
|
||||
color="primary"
|
||||
></q-btn>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { computed } from 'vue';
|
||||
|
||||
export default {
|
||||
props: ['actName'],
|
||||
setup(props) {
|
||||
const actName = computed(() => props.actName);
|
||||
},
|
||||
};
|
||||
</script>
|
||||
94
frontend/src/components/left/AppSelector.vue
Normal file
94
frontend/src/components/left/AppSelector.vue
Normal file
@@ -0,0 +1,94 @@
|
||||
<template>
|
||||
<div class="row app-box">
|
||||
<q-icon
|
||||
class="self-center q-ma-sm"
|
||||
name="widgets"
|
||||
color="grey-9"
|
||||
style="font-size: 2em"
|
||||
/>
|
||||
<div class="col-7 self-center ellipsis">
|
||||
<a :href="!store.appInfo?'':`${authStore.currentDomain.kintoneUrl}/k/${store.appInfo?.appId}`" target="_blank" title="Kiontoneへ">
|
||||
{{ store.appInfo?.name }}
|
||||
</a>
|
||||
</div>
|
||||
<div class="self-center">
|
||||
<q-btn
|
||||
outline
|
||||
dense
|
||||
label="変 更"
|
||||
padding="none sm"
|
||||
color="primary"
|
||||
@click="showAppDialog"
|
||||
></q-btn>
|
||||
</div>
|
||||
</div>
|
||||
<ShowDialog v-model:visible="showSelectApp" name="アプリ選択" @close="closeDg" min-width="50vw" min-height="50vh" >
|
||||
<template v-slot:toolbar>
|
||||
<q-input dense debounce="300" v-model="filter" placeholder="検索" clearable>
|
||||
<template v-slot:before>
|
||||
<q-icon name="search" />
|
||||
</template>
|
||||
</q-input>
|
||||
</template>
|
||||
<AppSelectBox ref="appDg" name="アプリ" type="single" :filter="filter"></AppSelectBox>
|
||||
</ShowDialog>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent,ref } from 'vue';
|
||||
import {AppInfo} from '../../types/ActionTypes'
|
||||
import ShowDialog from '../../components/ShowDialog.vue';
|
||||
import AppSelectBox from '../../components/AppSelectBox.vue';
|
||||
import { useFlowEditorStore } from 'stores/flowEditor';
|
||||
import { useAuthStore } from 'src/stores/useAuthStore';
|
||||
export default defineComponent({
|
||||
name: 'AppSelector',
|
||||
emits:[
|
||||
"appSelected"
|
||||
],
|
||||
components:{
|
||||
AppSelectBox,
|
||||
ShowDialog
|
||||
},
|
||||
setup(props, context) {
|
||||
|
||||
const store = useFlowEditorStore();
|
||||
const authStore=useAuthStore();
|
||||
const appDg = ref();
|
||||
const showSelectApp=ref(false);
|
||||
|
||||
const closeDg=(val :any)=>{
|
||||
showSelectApp.value=false;
|
||||
console.log("Dialog closed->",val);
|
||||
if (val == 'OK') {
|
||||
const data = appDg.value.selected[0];
|
||||
console.log(data);
|
||||
const appInfo={
|
||||
appId:data.id ,
|
||||
name:data.name
|
||||
};
|
||||
store.setApp(appInfo);
|
||||
store.loadFlow();
|
||||
}
|
||||
}
|
||||
const showAppDialog=()=>{
|
||||
showSelectApp.value=true;
|
||||
}
|
||||
return {
|
||||
store,
|
||||
authStore,
|
||||
showSelectApp,
|
||||
showAppDialog,
|
||||
closeDg,
|
||||
appDg,
|
||||
filter:ref('')
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
<style lang="scss">
|
||||
.app-box{
|
||||
border-radius: 2px;
|
||||
box-shadow: rgba(255, 255, 255, 0.1) 0px 0px 0px 1px inset,rgba(0, 0, 0, 0.3) 0px 0px 0px 1px;
|
||||
}
|
||||
</style>
|
||||
178
frontend/src/components/left/EventTree.vue
Normal file
178
frontend/src/components/left/EventTree.vue
Normal file
@@ -0,0 +1,178 @@
|
||||
<template>
|
||||
<!-- <div class="q-pa-md q-gutter-sm"> -->
|
||||
<q-tree :nodes="store.eventTree.screens" node-key="eventId" children-key="events" no-connectors
|
||||
v-model:expanded="store.expandedScreen" :dense="true" :ref="tree">
|
||||
<template v-slot:header-EVENT="prop">
|
||||
<div :ref="prop.node.eventId" class="row col items-center no-wrap event-node" @click="onSelected(prop.node)">
|
||||
<q-icon v-if="prop.node.eventId" name="play_circle" :color="prop.node.hasFlow ? 'green' : 'grey'" size="16px"
|
||||
class="q-mr-sm">
|
||||
</q-icon>
|
||||
<div class="no-wrap"
|
||||
:class="selectedEvent && prop.node.eventId === selectedEvent.eventId ? 'selected-node' : ''">{{
|
||||
prop.node.label }}</div>
|
||||
<q-space></q-space>
|
||||
<!-- <q-icon v-if="prop.node.hasFlow" name="delete" color="negative" size="16px" class="q-mr-sm"></q-icon> -->
|
||||
</div>
|
||||
</template>
|
||||
<template v-slot:header-CHANGE="prop">
|
||||
<div class="row col items-start no-wrap event-node">
|
||||
<div class="no-wrap"
|
||||
:class="selectedEvent && prop.node.eventId === selectedEvent.eventId ? 'selected-node' : ''"
|
||||
>{{ prop.node.label }}</div>
|
||||
<q-space></q-space>
|
||||
<q-icon name="add_circle" color="primary" size="16px" class="q-mr-sm"
|
||||
@click="addChangeEvent(prop.node)"></q-icon>
|
||||
</div>
|
||||
</template>
|
||||
<template v-slot:header-DELETABLE="prop">
|
||||
<div class="row col items-start event-node" @click="onSelected(prop.node)">
|
||||
<q-icon v-if="prop.node.eventId" name="play_circle" :color="prop.node.hasFlow ? 'green' : 'grey'" size="16px" class="q-mr-sm" />
|
||||
<div class="no-wrap" :class="selectedEvent && prop.node.eventId === selectedEvent.eventId ? 'selected-node' : ''" >{{ prop.node.label }}</div>
|
||||
<q-space></q-space>
|
||||
<q-icon name="delete_forever" color="negative" size="16px" @click="deleteEvent(prop.node)"></q-icon>
|
||||
</div>
|
||||
</template>
|
||||
</q-tree>
|
||||
<show-dialog v-model:visible="showDialog" name="フィールド選択" @close="closeDg">
|
||||
<field-select ref="appDg" name="フィールド" type="single" :appId="store.appInfo?.appId"></field-select>
|
||||
</show-dialog>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { QTree, useQuasar } from 'quasar';
|
||||
import { ActionFlow, RootAction } from 'src/types/ActionTypes';
|
||||
import { useFlowEditorStore } from 'stores/flowEditor';
|
||||
import { defineComponent, ref } from 'vue';
|
||||
import { IKintoneEvent, IKintoneEventGroup, IKintoneEventNode } from '../../types/KintoneEvents';
|
||||
import FieldSelect from '../FieldSelect.vue';
|
||||
import ShowDialog from '../ShowDialog.vue';
|
||||
export default defineComponent({
|
||||
name: 'EventTree',
|
||||
components: {
|
||||
ShowDialog,
|
||||
FieldSelect,
|
||||
},
|
||||
setup(props, context) {
|
||||
const $q = useQuasar();
|
||||
const appDg = ref();
|
||||
const store = useFlowEditorStore();
|
||||
const showDialog = ref(false);
|
||||
const tree = ref<QTree>();
|
||||
// const eventTree=ref(kintoneEvents);
|
||||
// const selectedFlow = store.currentFlow;
|
||||
|
||||
// const expanded=ref();
|
||||
const selectedEvent = ref<IKintoneEvent | null>(null);
|
||||
const selectedChangeEvent = ref<IKintoneEventGroup | null>(null);
|
||||
const isFieldChange = (node: IKintoneEventNode) => {
|
||||
return node.header == 'EVENT' && node.eventId.indexOf(".change.") > -1;
|
||||
}
|
||||
//フィールド値変更イベント追加
|
||||
const closeDg = (val: string) => {
|
||||
if (val == 'OK') {
|
||||
if (!selectedChangeEvent.value) { return; }
|
||||
const field = appDg.value.selected[0];
|
||||
const eventid = `${selectedChangeEvent.value.eventId}.${field.code}`;
|
||||
if (store.eventTree.findEventById(eventid)) {
|
||||
return;
|
||||
}
|
||||
selectedChangeEvent.value?.events.push({
|
||||
eventId: eventid,
|
||||
label: field.name,
|
||||
parentId: selectedChangeEvent.value.eventId,
|
||||
header: 'DELETABLE'
|
||||
});
|
||||
tree.value?.expanded?.push(selectedChangeEvent.value.eventId);
|
||||
tree.value?.expandAll();
|
||||
}
|
||||
};
|
||||
const addChangeEvent = (node: IKintoneEventGroup) => {
|
||||
if (store.appInfo === undefined) {
|
||||
return;
|
||||
}
|
||||
selectedChangeEvent.value = node;
|
||||
showDialog.value = true;
|
||||
}
|
||||
|
||||
const deleteEvent = (node: IKintoneEvent) => {
|
||||
if (!node.eventId) {
|
||||
return;
|
||||
}
|
||||
store.deleteEvent(node);
|
||||
store.selectFlow(undefined)
|
||||
|
||||
$q.notify({
|
||||
type: 'positive',
|
||||
caption: "通知",
|
||||
message: `イベント ${node.label} 削除`
|
||||
})
|
||||
}
|
||||
|
||||
const onSelected = (node: IKintoneEvent) => {
|
||||
if (!node.eventId) {
|
||||
return;
|
||||
}
|
||||
selectedEvent.value = node;
|
||||
if (store.appInfo === undefined) {
|
||||
return;
|
||||
}
|
||||
const screen = store.eventTree.findEventById(node.parentId);
|
||||
|
||||
let flow = store.findFlowByEventId(node.eventId);
|
||||
let screenName = screen !== null ? screen.label : "";
|
||||
let nodeLabel = node.label;
|
||||
// if(isFieldChange(node)){
|
||||
// screenName=nodeLabel;
|
||||
// nodeLabel=`${node.label}の値を変更したとき`;
|
||||
// }
|
||||
|
||||
if (flow !== undefined && flow !== null) {
|
||||
store.selectFlow(flow);
|
||||
} else {
|
||||
const root = new RootAction(node.eventId, screenName, nodeLabel)
|
||||
const flow = new ActionFlow(root);
|
||||
store.flows?.push(flow);
|
||||
store.selectFlow(flow);
|
||||
selectedEvent.value.flowData = flow;
|
||||
}
|
||||
};
|
||||
return {
|
||||
// eventTree,
|
||||
// expanded,
|
||||
appDg,
|
||||
tree,
|
||||
showDialog,
|
||||
isFieldChange,
|
||||
onSelected,
|
||||
selectedEvent,
|
||||
addChangeEvent,
|
||||
deleteEvent,
|
||||
closeDg,
|
||||
store
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
<style lang="scss">
|
||||
.nowrap {
|
||||
flex-wrap: nowarp;
|
||||
text-wrap: nowarp;
|
||||
}
|
||||
|
||||
.event-node {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.selected-node {
|
||||
color: $primary;
|
||||
font-weight: bolder;
|
||||
}
|
||||
|
||||
.event-node:hover {
|
||||
background-color: $light-blue-1;
|
||||
}
|
||||
|
||||
.delete-btn {
|
||||
margin-right: 5px;
|
||||
}
|
||||
</style>
|
||||
276
frontend/src/components/main/NodeItem.vue
Normal file
276
frontend/src/components/main/NodeItem.vue
Normal file
@@ -0,0 +1,276 @@
|
||||
<template>
|
||||
<div class="row justify-center no-wrap" >
|
||||
<div class="row">
|
||||
<q-card class="action-node" :class="nodeStyle" :square="false" @click="onNodeClick" >
|
||||
<q-toolbar class="col" >
|
||||
<div class="text-subtitle2">{{ node.subTitle }}</div>
|
||||
<q-space></q-space>
|
||||
<q-btn flat round dense icon="more_horiz" size="sm" >
|
||||
<q-menu auto-close anchor="top right">
|
||||
<q-list>
|
||||
<q-item clickable v-if="isRoot" @click="copyFlow">
|
||||
<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 v-if="!isRoot" @click="onEditNode">
|
||||
<q-item-section avatar><q-icon name="edit" ></q-icon></q-item-section>
|
||||
<q-item-section >編集する</q-item-section>
|
||||
</q-item>
|
||||
<q-item clickable v-if="!isRoot" @click="onDeleteNode">
|
||||
<q-item-section avatar><q-icon name="delete" ></q-icon></q-item-section>
|
||||
<q-item-section>削除する</q-item-section>
|
||||
</q-item>
|
||||
<q-item clickable @click="onDeleteAllNode">
|
||||
<q-item-section avatar><q-icon name="delete_sweep" ></q-icon></q-item-section>
|
||||
<q-item-section >以下すべて削除する</q-item-section>
|
||||
</q-item>
|
||||
</q-list>
|
||||
</q-menu>
|
||||
</q-btn>
|
||||
</q-toolbar>
|
||||
<q-separator />
|
||||
<q-card-section class="action-title">
|
||||
<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>
|
||||
<template v-if="hasBranch">
|
||||
<q-separator />
|
||||
<q-card-actions align="around">
|
||||
<q-btn flat v-for="(point, index) in node.outputPoints" :key="index">
|
||||
{{ point }}
|
||||
</q-btn>
|
||||
</q-card-actions>
|
||||
</template>
|
||||
</q-card>
|
||||
</div>
|
||||
</div>
|
||||
<template v-if="hasBranch">
|
||||
<node-line :action-node="node" @addNode="addNode" :left-columns="leftColumns" :right-columns="rightColumns"></node-line>
|
||||
<div class="row justify-center no-wrap" >
|
||||
<div v-for="(point, index) in node.outputPoints" :key="index" class="column" style="min-width: 300px;">
|
||||
<div class="justify-center" >
|
||||
<node-item v-if="nextNode(point)!==undefined" :key="nextNode(point).id" :isSelected="nextNode(point) === store.activeNode"
|
||||
:actionNode="nextNode(point)" @addNode="addNodeFromItem" @nodeSelected="onNodeSelected" @nodeEdit="onNodeEdit"
|
||||
@deleteNode="onDeleteNodeFromItem" @deleteAllNextNodes="onDeleteAllNextNodes" ></node-item>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template v-if="!hasBranch">
|
||||
<div class="row justify-center no-wrap" >
|
||||
<node-line :action-node="node" @addNode="addNode" ></node-line>
|
||||
</div>
|
||||
<div>
|
||||
<node-item v-if="nextNode('')!==undefined" :key="nextNode('').id" :isSelected="nextNode('') === store.activeNode"
|
||||
:actionNode="nextNode('')" @addNode="addNodeFromItem" @nodeSelected="onNodeSelected" @nodeEdit="onNodeEdit"
|
||||
@deleteNode="onDeleteNodeFromItem" @deleteAllNextNodes="onDeleteAllNextNodes" ></node-item>
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, computed, ref } from 'vue';
|
||||
import { IActionNode, IActionProperty } from '../../types/ActionTypes';
|
||||
import NodeLine, { Direction } from '../main/NodeLine.vue';
|
||||
import { useFlowEditorStore } from 'stores/flowEditor';
|
||||
export default defineComponent({
|
||||
name: 'NodeItem',
|
||||
components: {
|
||||
NodeLine
|
||||
},
|
||||
props: {
|
||||
actionNode: {
|
||||
type: Object as () => IActionNode,
|
||||
required: true
|
||||
},
|
||||
isSelected: {
|
||||
type: Boolean
|
||||
}
|
||||
},
|
||||
emits: [
|
||||
'addNode',
|
||||
"nodeSelected",
|
||||
"nodeEdit",
|
||||
"deleteNode",
|
||||
"deleteAllNextNodes",
|
||||
"copyFlow"
|
||||
],
|
||||
setup(props, context) {
|
||||
const store = useFlowEditorStore();
|
||||
const hasBranch = computed(() => props.actionNode.outputPoints.length > 0);
|
||||
const nodeStyle = computed(() => {
|
||||
return {
|
||||
'root-node': props.actionNode.isRoot,
|
||||
'text-white': props.actionNode.isRoot,
|
||||
'selected': props.isSelected && !props.actionNode.isRoot
|
||||
};
|
||||
});
|
||||
|
||||
const nextNode=(point:string)=>{
|
||||
const nextId= props.actionNode.nextNodeIds.get(point);
|
||||
if(!nextId) return undefined;
|
||||
return store.currentFlow?.findNodeById(nextId);
|
||||
}
|
||||
/**
|
||||
* アクションノード追加イベントを
|
||||
* @param point 入力ポイント
|
||||
*/
|
||||
const addNode = (point: string) => {
|
||||
context.emit('addNode', props.actionNode, point);
|
||||
}
|
||||
/**
|
||||
* アクションノード追加イベントを
|
||||
* @param point 入力ポイント
|
||||
*/
|
||||
const addNodeFromItem = (node:IActionNode,point: string) => {
|
||||
context.emit('addNode', node, point);
|
||||
}
|
||||
|
||||
const leftColumns=computed(()=>{
|
||||
if(!props.actionNode.outputPoints || props.actionNode.outputPoints.length<2){
|
||||
return 1;
|
||||
}
|
||||
const leftNode = nextNode(props.actionNode.outputPoints[0]);
|
||||
if(leftNode){
|
||||
return store.currentFlow?.getColumns(leftNode);
|
||||
}else{
|
||||
return 1;
|
||||
}
|
||||
});
|
||||
|
||||
const rightColumns=computed(()=>{
|
||||
if(!props.actionNode.outputPoints || props.actionNode.outputPoints.length<2){
|
||||
return 1;
|
||||
}
|
||||
const rightNode = nextNode(props.actionNode.outputPoints[1]);
|
||||
if(rightNode){
|
||||
return store.currentFlow?.getColumns(rightNode);
|
||||
}else{
|
||||
return 1;
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* ノード選択状態
|
||||
*/
|
||||
const onNodeClick = () => {
|
||||
context.emit('nodeSelected', props.actionNode);
|
||||
}
|
||||
|
||||
|
||||
const onNodeSelected = (node: IActionNode) => {
|
||||
context.emit('nodeSelected', node);
|
||||
}
|
||||
|
||||
const onEditNode=()=>{
|
||||
context.emit('nodeEdit', props.actionNode);
|
||||
}
|
||||
|
||||
const onNodeEdit=(node:IActionNode)=>{
|
||||
context.emit('nodeEdit', node);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* ノードを削除する
|
||||
*/
|
||||
const onDeleteNode=()=>{
|
||||
context.emit('deleteNode', props.actionNode);
|
||||
}
|
||||
|
||||
/**
|
||||
* ノードを削除する
|
||||
*/
|
||||
const onDeleteNodeFromItem=(node:IActionNode)=>{
|
||||
context.emit('deleteNode', node);
|
||||
}
|
||||
/**
|
||||
* ノードの以下すべて削除する
|
||||
*/
|
||||
const onDeleteAllNode=()=>{
|
||||
context.emit('deleteAllNextNodes', props.actionNode);
|
||||
};
|
||||
|
||||
/**
|
||||
* ノードの以下すべて削除する
|
||||
*/
|
||||
const onDeleteAllNextNodes=(node:IActionNode)=>{
|
||||
context.emit('deleteAllNextNodes', node);
|
||||
};
|
||||
/**
|
||||
* 変数名取得
|
||||
*/
|
||||
const varName =(node:IActionNode)=>{
|
||||
const prop = node.actionProps.find((prop) => prop.props.name === "verName");
|
||||
return prop?.props.modelValue.name;
|
||||
};
|
||||
const copyFlow=()=>{
|
||||
context.emit('copyFlow', props.actionNode);
|
||||
}
|
||||
return {
|
||||
store,
|
||||
node: props.actionNode,
|
||||
nextNode,
|
||||
isRoot: props.actionNode.isRoot,
|
||||
hasBranch,
|
||||
nodeStyle,
|
||||
// getMode,
|
||||
addNode,
|
||||
addNodeFromItem,
|
||||
onNodeClick,
|
||||
onNodeSelected,
|
||||
onEditNode,
|
||||
onNodeEdit,
|
||||
onDeleteNode,
|
||||
onDeleteNodeFromItem,
|
||||
onDeleteAllNode,
|
||||
onDeleteAllNextNodes,
|
||||
copyFlow,
|
||||
varName,
|
||||
leftColumns,
|
||||
rightColumns
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
<style lang="scss">
|
||||
.action-node {
|
||||
min-width: 280px !important;
|
||||
}
|
||||
.action-title{
|
||||
max-width: 280px !important;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.line {
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.line:after {
|
||||
content: '';
|
||||
background-color: $blue-7;
|
||||
display: block;
|
||||
width: 3px;
|
||||
}
|
||||
|
||||
.add-icon {
|
||||
font-size: 2em;
|
||||
color: $blue-7;
|
||||
}
|
||||
|
||||
.root-node {
|
||||
background-color: $blue-7;
|
||||
border-radius: 20px;
|
||||
}
|
||||
|
||||
.action-node:not(.root-node):hover{
|
||||
background-color: $light-blue-1;
|
||||
}
|
||||
|
||||
.selected{
|
||||
background-color: $yellow-1;
|
||||
}
|
||||
</style>
|
||||
168
frontend/src/components/main/NodeLine.vue
Normal file
168
frontend/src/components/main/NodeLine.vue
Normal file
@@ -0,0 +1,168 @@
|
||||
<template>
|
||||
<div class="row justify-center">
|
||||
<svg class="node-line" style="width:100%" :viewBox="viewBox()">
|
||||
<template v-if="!node.outputPoints || node.outputPoints.length===0" >
|
||||
<polyline :points="points(getMode('')).linePoints" class="line" ></polyline>
|
||||
<text class="add-icon"
|
||||
@click="addNode(node,'')"
|
||||
:x="points(getMode('')).iconPoint.x"
|
||||
:y="points(getMode('')).iconPoint.y"
|
||||
font-family="Arial" font-size="25"
|
||||
text-anchor="middle" dy=".3em" style="cursor: pointer;" >
|
||||
⊕
|
||||
</text>
|
||||
</template>
|
||||
<template v-for="(point, index) in node.outputPoints" :key="index" >
|
||||
<polyline :points="points(getMode(point)).linePoints" class="line" ></polyline>
|
||||
<text class="add-icon"
|
||||
@click="addNode(node,point)"
|
||||
:x="points(getMode(point)).iconPoint.x"
|
||||
:y="points(getMode(point)).iconPoint.y"
|
||||
font-family="Arial" font-size="25"
|
||||
text-anchor="middle" dy=".3em" style="cursor: pointer;" >
|
||||
⊕
|
||||
</text>
|
||||
</template>
|
||||
</svg>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { ref, defineComponent, computed, PropType } from 'vue';
|
||||
import { IActionNode, ActionNode, ActionFlow, RootAction } from '../../types/ActionTypes';
|
||||
export enum Direction {
|
||||
Default = "None",
|
||||
Left = "LEFT",
|
||||
Right = "RIGHT",
|
||||
LeftNotNext = "LEFTNOTNEXT",
|
||||
RightNotNext = "RIGHTNOTNEXT",
|
||||
}
|
||||
export default defineComponent({
|
||||
name: 'NodeLine',
|
||||
props: {
|
||||
actionNode: {
|
||||
type: Object as PropType<IActionNode>,
|
||||
required: true
|
||||
},
|
||||
leftColumns:{
|
||||
type:Number,
|
||||
required:false
|
||||
},
|
||||
rightColumns:{
|
||||
type:Number,
|
||||
required:false
|
||||
}
|
||||
},
|
||||
emits: ['addNode'],
|
||||
setup(props,context) {
|
||||
const hasBranch = computed(() => props.actionNode.outputPoints.length > 0);
|
||||
const getMode = (point: string):Direction => {
|
||||
if (point === '' || props.actionNode.outputPoints.length === 0) {
|
||||
return Direction.Default;
|
||||
}
|
||||
if (point === props.actionNode.outputPoints[0]) {
|
||||
if (props.actionNode.nextNodeIds.get(point)) {
|
||||
return Direction.Left;
|
||||
} else {
|
||||
return Direction.LeftNotNext;
|
||||
}
|
||||
} else {
|
||||
if (props.actionNode.nextNodeIds.get(point)) {
|
||||
return Direction.Right;
|
||||
} else {
|
||||
return Direction.RightNotNext;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const points = (mode:Direction) => {
|
||||
let startX ,endX;
|
||||
const leftColumn=props.leftColumns?props.leftColumns:1;
|
||||
const rightColumn=props.rightColumns?props.rightColumns:1;
|
||||
|
||||
switch (mode) {
|
||||
case Direction.Left:
|
||||
startX = leftColumn*300/2.0;
|
||||
endX = ((leftColumn+rightColumn)/2.0 - 0.25)*300;
|
||||
return {
|
||||
linePoints: `${startX}, 60, ${startX}, 40, ${endX}, 40, ${endX}, 0`,
|
||||
iconPoint: { x: endX, y: 20 }
|
||||
};
|
||||
case Direction.Right:
|
||||
startX = ((leftColumn+rightColumn)/2.0 + 0.25)*300;
|
||||
endX = (leftColumn+(rightColumn/2.0))*300;
|
||||
return {
|
||||
linePoints: `${startX}, 0, ${startX}, 40, ${endX}, 40, ${endX}, 60`,
|
||||
iconPoint: { x: startX, y: 20 }
|
||||
};
|
||||
case Direction.LeftNotNext:
|
||||
startX = ((leftColumn+rightColumn)/2.0 - 0.25)*300;
|
||||
return {
|
||||
linePoints: `${startX}, 0, ${startX}, 40`,
|
||||
iconPoint: { x: startX, y: 20 }
|
||||
};
|
||||
case Direction.RightNotNext:
|
||||
startX = ((leftColumn+rightColumn)/2.0 + 0.25)*300;
|
||||
return {
|
||||
linePoints: `${startX}, 0, ${startX}, 40`,
|
||||
iconPoint: { x: startX, y: 20 }
|
||||
};
|
||||
default:
|
||||
return {
|
||||
linePoints: '150, 0, 150, 60',
|
||||
iconPoint: { x: 150, y: 30 }
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
const addNode=(prveNode:IActionNode,point:string)=>{
|
||||
context.emit('addNode',point);
|
||||
}
|
||||
|
||||
const viewBox=()=>{
|
||||
let columns=0;
|
||||
if(props.leftColumns!==undefined) columns+=props.leftColumns;
|
||||
if(props.rightColumns!==undefined) columns+=props.rightColumns;
|
||||
if(columns===0) columns=1;
|
||||
const width= columns*300;
|
||||
return `0 0 ${width} 60`;
|
||||
};
|
||||
|
||||
return {
|
||||
node: props.actionNode,
|
||||
getMode,
|
||||
hasBranch,
|
||||
points,
|
||||
addNode,
|
||||
viewBox
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
<style lang="scss">
|
||||
.node-line {
|
||||
height: 60px;
|
||||
width: 240px;
|
||||
}
|
||||
|
||||
.line {
|
||||
stroke: $blue-7;
|
||||
fill: none;
|
||||
stroke-width: 2;
|
||||
}
|
||||
|
||||
.add-icon {
|
||||
stroke: $blue-8;
|
||||
fill: $blue-8;
|
||||
font-family: Arial;
|
||||
pointer-events: all;
|
||||
font-size: 2.0em;
|
||||
}
|
||||
|
||||
.add-icon:hover{
|
||||
stroke: $blue-8;
|
||||
fill:$blue-8;
|
||||
font-weight: bold;
|
||||
font-size: 2.4em;
|
||||
}
|
||||
</style>
|
||||
@@ -32,4 +32,3 @@ export interface AppInfo {
|
||||
creator?:User;
|
||||
modifier?:User;
|
||||
}
|
||||
|
||||
|
||||
75
frontend/src/components/right/ActionProperty.vue
Normal file
75
frontend/src/components/right/ActionProperty.vue
Normal file
@@ -0,0 +1,75 @@
|
||||
<template>
|
||||
<div>
|
||||
<div v-for="(item, index) in componentData" :key="index">
|
||||
<component :is="item.component" v-bind="item.props" :connectProps="connectProps" v-model="item.props.modelValue"></component>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent,computed } from 'vue';
|
||||
import InputText from '../right/InputText.vue';
|
||||
import SelectBox from '../right/SelectBox.vue';
|
||||
import DatePicker from '../right/DatePicker.vue';
|
||||
import FieldInput from '../right/FieldInput.vue';
|
||||
import EventSetter from '../right/EventSetter.vue';
|
||||
import { IActionProperty, IProp } from 'src/types/ActionTypes';
|
||||
export default defineComponent({
|
||||
name: 'ActionProperty',
|
||||
components: {
|
||||
InputText,
|
||||
SelectBox,
|
||||
DatePicker,
|
||||
FieldInput,
|
||||
EventSetter
|
||||
},
|
||||
props: {
|
||||
jsonData: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
jsonValue:{
|
||||
type: Object,
|
||||
required: false,
|
||||
}
|
||||
},
|
||||
setup(props){
|
||||
const componentData=computed<Array<IActionProperty>>(()=>{
|
||||
return props.jsonData.elements.map((element: any) => {
|
||||
if(props.jsonValue != undefined )
|
||||
{
|
||||
if(props.jsonValue.hasOwnProperty(element.props.name))
|
||||
{
|
||||
element.props.modelValue = props.jsonValue[element.props.name];
|
||||
}
|
||||
else
|
||||
{
|
||||
element.props.modelValue = '';
|
||||
}
|
||||
}
|
||||
return {
|
||||
component: element.component,
|
||||
props: element.props,
|
||||
};
|
||||
});
|
||||
});
|
||||
const connectProps=(props:IProp)=>{
|
||||
const connProps:any={};
|
||||
if(props && "connectProps" in props && props.connectProps!=undefined){
|
||||
for(let connProp of props.connectProps){
|
||||
let targetProp = componentData.value.find((prop)=>prop.props.name===connProp.propName);
|
||||
if(targetProp){
|
||||
connProps[connProp.key]=targetProp;
|
||||
}
|
||||
}
|
||||
}
|
||||
return connProps;
|
||||
}
|
||||
|
||||
return{
|
||||
componentData,
|
||||
connectProps
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
157
frontend/src/components/right/AppFieldSelect.vue
Normal file
157
frontend/src/components/right/AppFieldSelect.vue
Normal file
@@ -0,0 +1,157 @@
|
||||
<template>
|
||||
<div class="q-my-md" v-bind="$attrs">
|
||||
<q-card flat>
|
||||
<q-card-section class="q-pa-none q-my-sm q-mr-md">
|
||||
<!-- <div class=" q-my-none ">App Field Select</div> -->
|
||||
<div class="row q-mb-xs">
|
||||
<div class="text-primary q-mb-xs text-caption">{{ $props.displayName }}</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<div class="q-mb-xs">{{ selectedField.app?.name || '未選択' }}</div>
|
||||
</div>
|
||||
<div class="col-1">
|
||||
<q-btn round flat size="sm" color="primary" icon="search" @click="showDg" />
|
||||
</div>
|
||||
</div>
|
||||
</q-card-section>
|
||||
<q-separator />
|
||||
<q-card-section class="q-pa-none q-ma-none">
|
||||
<div style="">
|
||||
<div v-if="selectedField.fields && selectedField.fields.length > 0">
|
||||
<q-list bordered>
|
||||
<q-virtual-scroll style="max-height: 160px;" :items="selectedField.fields" separator
|
||||
v-slot="{ item, index }">
|
||||
<q-item :key="index" dense clickable>
|
||||
<q-item-section>
|
||||
<q-item-label>
|
||||
{{ item.label }}
|
||||
</q-item-label>
|
||||
</q-item-section>
|
||||
<q-item-section side>
|
||||
<q-btn round flat size="sm" icon="clear" @click="removeField(index)" />
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
</q-virtual-scroll>
|
||||
</q-list>
|
||||
</div>
|
||||
<!-- <div v-else class="row q-mt-lg">
|
||||
</div> -->
|
||||
</div>
|
||||
<!-- <q-separator /> -->
|
||||
</q-card-section>
|
||||
<q-card-section class="q-px-none q-py-xs" v-if="selectedField.fields && selectedField.fields.length === 0">
|
||||
<div class="row">
|
||||
<div class="text-grey text-caption"> {{ $props.placeholder }}</div>
|
||||
<!-- <q-btn flat color="grey" label="clear" @click="clear" /> -->
|
||||
</div>
|
||||
</q-card-section>
|
||||
</q-card>
|
||||
</div>
|
||||
<show-dialog v-model:visible="show" name="フィールド一覧" @close="closeAFBox">
|
||||
<AppFieldSelectBox v-model:selectedField="selectedField" :selectType="selectType" ref="afBox" :fieldTypes="fieldTypes"/>
|
||||
</show-dialog>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, ref, watchEffect } from 'vue';
|
||||
import AppFieldSelectBox from '../AppFieldSelectBox.vue';
|
||||
import ShowDialog from '../ShowDialog.vue';
|
||||
import { useFlowEditorStore } from 'stores/flowEditor';
|
||||
|
||||
export interface IApp {
|
||||
id: string,
|
||||
name: string
|
||||
}
|
||||
export interface IField {
|
||||
name: string,
|
||||
code: string,
|
||||
type: string,
|
||||
label?:string
|
||||
}
|
||||
|
||||
export interface IAppFields {
|
||||
app?: IApp,
|
||||
fields: IField[]
|
||||
}
|
||||
|
||||
export default defineComponent({
|
||||
inheritAttrs: false,
|
||||
name: 'AppFieldSelect',
|
||||
components: {
|
||||
ShowDialog,
|
||||
AppFieldSelectBox
|
||||
},
|
||||
props: {
|
||||
displayName: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
name: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
modelValue: {
|
||||
type: Object,
|
||||
default: null
|
||||
},
|
||||
selectType: {
|
||||
type: String,
|
||||
default: 'single'
|
||||
},
|
||||
fieldTypes:{
|
||||
type:Array,
|
||||
default:()=>[]
|
||||
}
|
||||
},
|
||||
setup(props, { emit }) {
|
||||
const show = ref(false);
|
||||
const afBox = ref();
|
||||
const selectedField = ref<IAppFields>({
|
||||
app: undefined,
|
||||
fields: []
|
||||
});
|
||||
if (props.modelValue && 'app' in props.modelValue && 'fields' in props.modelValue) {
|
||||
selectedField.value = props.modelValue as IAppFields;
|
||||
}
|
||||
const store = useFlowEditorStore();
|
||||
|
||||
const clear = () => {
|
||||
selectedField.value = {
|
||||
fields: []
|
||||
};
|
||||
}
|
||||
|
||||
const removeField = (index: number) => {
|
||||
selectedField.value.fields.splice(index, 1);
|
||||
}
|
||||
|
||||
const closeAFBox = (val: string) => {
|
||||
if (val == 'OK') {
|
||||
console.log(afBox.value);
|
||||
|
||||
selectedField.value = afBox.value.selField;
|
||||
}
|
||||
};
|
||||
|
||||
watchEffect(() => {
|
||||
emit('update:modelValue', selectedField.value);
|
||||
});
|
||||
|
||||
return {
|
||||
store,
|
||||
afBox,
|
||||
show,
|
||||
showDg: () => { show.value = true },
|
||||
selectedField,
|
||||
clear,
|
||||
removeField,
|
||||
closeAFBox,
|
||||
};
|
||||
}
|
||||
});
|
||||
</script>
|
||||
93
frontend/src/components/right/AppSelect.vue
Normal file
93
frontend/src/components/right/AppSelect.vue
Normal file
@@ -0,0 +1,93 @@
|
||||
<template>
|
||||
<div>
|
||||
<q-field :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="() => { dgIsShow = true }">アプリ選択</q-btn>
|
||||
</q-card-actions>
|
||||
<q-card-section class="text-caption">
|
||||
<div v-if="selectedField.app.name">
|
||||
{{ selectedField.app.name }}
|
||||
</div>
|
||||
<div v-else>{{ placeholder }}</div>
|
||||
</q-card-section>
|
||||
</q-card>
|
||||
</template>
|
||||
</q-field>
|
||||
</div>
|
||||
|
||||
<ShowDialog v-model:visible="dgIsShow" name="アプリ選択" @close="closeDg" min-width="50vw" min-height="50vh">
|
||||
<template v-slot:toolbar>
|
||||
<q-input dense debounce="300" v-model="filter" placeholder="検索" clearable>
|
||||
<template v-slot:before>
|
||||
<q-icon name="search" />
|
||||
</template>
|
||||
</q-input>
|
||||
</template>
|
||||
<AppSelectBox ref="appDg" name="アプリ" type="single" :filter="filter"></AppSelectBox>
|
||||
</ShowDialog>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent, reactive, ref, watchEffect } from 'vue';
|
||||
import ShowDialog from '../ShowDialog.vue';
|
||||
import AppSelectBox from '../AppSelectBox.vue';
|
||||
|
||||
|
||||
export default defineComponent({
|
||||
inheritAttrs: false,
|
||||
name: 'AppSelect',
|
||||
components: {
|
||||
ShowDialog,
|
||||
AppSelectBox
|
||||
},
|
||||
props: {
|
||||
context: {
|
||||
type: Array<Props>,
|
||||
default: '',
|
||||
},
|
||||
displayName: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
name: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
modelValue: {
|
||||
type: Object,
|
||||
default: null
|
||||
}
|
||||
},
|
||||
setup(props, { emit }) {
|
||||
const appDg = ref()
|
||||
const dgIsShow = ref(false)
|
||||
const selectedField = props.modelValue && props.modelValue.app ? props.modelValue : reactive({app:{}});
|
||||
const closeDg = (state: string) => {
|
||||
dgIsShow.value = false;
|
||||
if (state == 'OK') {
|
||||
selectedField.app = appDg.value.selected[0];
|
||||
}
|
||||
};
|
||||
|
||||
console.log(selectedField);
|
||||
|
||||
watchEffect(() => {
|
||||
emit('update:modelValue', selectedField);
|
||||
});
|
||||
|
||||
return {
|
||||
filter: ref(''),
|
||||
dgIsShow,
|
||||
appDg,
|
||||
closeDg,
|
||||
selectedField
|
||||
};
|
||||
}
|
||||
});
|
||||
</script>
|
||||
74
frontend/src/components/right/ColorPicker.vue
Normal file
74
frontend/src/components/right/ColorPicker.vue
Normal file
@@ -0,0 +1,74 @@
|
||||
<template>
|
||||
<div class="" v-bind="$attrs">
|
||||
<q-field v-model="color" :label="displayName" labelColor="primary" :clearable="isSelected" stack-label :bottom-slots="!isSelected" >
|
||||
<template v-slot:control>
|
||||
<q-chip text-color="black" color="white" v-if="isSelected">
|
||||
<div class="row">
|
||||
<div class="col-4">
|
||||
<q-avatar class="shadow-1" :style="{ background: color }" size="xs"></q-avatar>
|
||||
</div>
|
||||
<div class="col">
|
||||
{{ color }}
|
||||
</div>
|
||||
</div>
|
||||
</q-chip>
|
||||
</template>
|
||||
<template v-slot:append>
|
||||
<q-icon name="colorize" class="cursor-pointer" color="primary" >
|
||||
<q-popup-proxy cover transition-show="scale" transition-hide="scale">
|
||||
<q-color no-header default-view="palette" v-model="color" />
|
||||
</q-popup-proxy>
|
||||
</q-icon>
|
||||
</template>
|
||||
<template v-slot:hint>
|
||||
{{ placeholder }}
|
||||
</template>
|
||||
</q-field>
|
||||
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent, ref,watchEffect } from 'vue';
|
||||
export default defineComponent({
|
||||
inheritAttrs:false,
|
||||
name: 'ColorPicker',
|
||||
components: {
|
||||
},
|
||||
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 color = ref(props.modelValue??"");
|
||||
const isSelected = computed(()=>props.modelValue && props.modelValue!=="");
|
||||
watchEffect(()=>{
|
||||
emit('update:modelValue', color.value);
|
||||
});
|
||||
return {
|
||||
color,
|
||||
isSelected
|
||||
};
|
||||
}
|
||||
});
|
||||
</script>
|
||||
194
frontend/src/components/right/ConditionInput.vue
Normal file
194
frontend/src/components/right/ConditionInput.vue
Normal file
@@ -0,0 +1,194 @@
|
||||
<template>
|
||||
<div v-bind="$attrs">
|
||||
<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" :disable="btnDisable" @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>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
|
||||
<script lang="ts">
|
||||
import { ConditionNode, ConditionTree, Operator, OperatorListItem } from 'app/src/types/Conditions';
|
||||
import { computed, defineComponent, provide, reactive, ref, watchEffect } from 'vue';
|
||||
import ConditionEditor from '../ConditionEditor/ConditionEditor.vue';
|
||||
|
||||
type Props = {
|
||||
props?: {
|
||||
name: string;
|
||||
modelValue?: {
|
||||
app: {
|
||||
id: string;
|
||||
name: string;
|
||||
},
|
||||
fields: {
|
||||
type: string;
|
||||
label: string;
|
||||
code: string;
|
||||
}[]
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
type InputConfg = {
|
||||
canInput: boolean;
|
||||
buttonsConfig: {
|
||||
label: string;
|
||||
color: string;
|
||||
type: string;
|
||||
}[]
|
||||
};
|
||||
|
||||
|
||||
export default defineComponent({
|
||||
name: 'FieldInput',
|
||||
inheritAttrs: false,
|
||||
components: {
|
||||
ConditionEditor
|
||||
},
|
||||
props: {
|
||||
context: {
|
||||
type: Array<Props>,
|
||||
default: '',
|
||||
},
|
||||
displayName: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
name: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
hint: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
modelValue: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
sourceType: {
|
||||
type: String,
|
||||
default: 'field'
|
||||
},
|
||||
onlySourceSelect: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
operatorList: {
|
||||
type: Array,
|
||||
},
|
||||
inputConfig: {
|
||||
type: Object,
|
||||
default: () => ({
|
||||
left: {
|
||||
canInput: false,
|
||||
buttonsConfig: [
|
||||
{ label: 'フィールド', color: 'primary', type: 'FieldAdd' },
|
||||
{ label: '変数', color: 'green', type: 'VariableAdd' },
|
||||
]
|
||||
},
|
||||
right: {
|
||||
canInput: true,
|
||||
buttonsConfig: [
|
||||
{ label: '変数', color: 'green', type: 'VariableAdd' },
|
||||
]
|
||||
},
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
setup(props, { emit }) {
|
||||
const source = props.context.find(element => element?.props?.name === 'sources')
|
||||
|
||||
if (source) {
|
||||
if (props.sourceType === 'field') {
|
||||
provide('sourceFields', computed(() => source.props?.modelValue?.fields ?? []));
|
||||
} else if (props.sourceType === 'app') {
|
||||
provide('sourceApp', computed(() => source.props?.modelValue?.app?.id));
|
||||
}
|
||||
}
|
||||
|
||||
provide('leftDynamicItemConfig', props.inputConfig.left);
|
||||
provide('rightDynamicItemConfig', props.inputConfig.right);
|
||||
provide('Operator', props.operatorList);
|
||||
|
||||
const btnDisable = computed(() => {
|
||||
const onlySourceSelect = props.onlySourceSelect;
|
||||
|
||||
if (!onlySourceSelect) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (props.sourceType === 'field') {
|
||||
return source?.props?.modelValue?.fields?.length ?? 0 > 0;
|
||||
} else if (props.sourceType === 'app') {
|
||||
return source?.props?.modelValue?.app?.id ? false : true
|
||||
}
|
||||
return true;
|
||||
})
|
||||
|
||||
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({}, (props.operatorList && props.operatorList.length > 0) ? props.operatorList[0] as OperatorListItem : 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') {
|
||||
isSetted.value = true;
|
||||
tree.setQuery(tree.buildConditionQueryString(tree.root));
|
||||
const conditionJson = tree.toJson();
|
||||
emit('update:modelValue', conditionJson);
|
||||
}
|
||||
};
|
||||
|
||||
watchEffect(() => {
|
||||
tree.setQuery(tree.buildConditionQueryString(tree.root));
|
||||
const conditionJson = tree.toJson();
|
||||
emit('update:modelValue', conditionJson);
|
||||
});
|
||||
|
||||
return {
|
||||
appDg,
|
||||
isSetted,
|
||||
show,
|
||||
showDg,
|
||||
onClosed,
|
||||
tree,
|
||||
conditionString,
|
||||
btnDisable
|
||||
};
|
||||
}
|
||||
});
|
||||
</script>
|
||||
284
frontend/src/components/right/DataMapping.vue
Normal file
284
frontend/src/components/right/DataMapping.vue
Normal file
@@ -0,0 +1,284 @@
|
||||
<template>
|
||||
<div>
|
||||
<q-field :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" :disable="btnDisable"
|
||||
@click="() => { dgIsShow = true }">クリックで設定</q-btn>
|
||||
</q-card-actions>
|
||||
<q-card-section class="text-caption">
|
||||
<div v-if="mappingObjectsInputDisplay && mappingObjectsInputDisplay.length > 0">
|
||||
<div v-for="(item) in mappingObjectsInputDisplay" :key="item">{{ item }}</div>
|
||||
</div>
|
||||
<div v-else>{{ placeholder }}</div>
|
||||
</q-card-section>
|
||||
</q-card>
|
||||
</template>
|
||||
</q-field>
|
||||
<show-dialog v-model:visible="dgIsShow" name="データマッピング" @close="closeDg" min-width="55vw" min-height="60vh">
|
||||
|
||||
<div class="">
|
||||
<div class="row q-col-gutter-x-xs flex-center">
|
||||
<div class="col-5">
|
||||
<div class="q-mx-xs">ソース</div>
|
||||
</div>
|
||||
<!-- <div class="col-1">
|
||||
</div> -->
|
||||
<div class="col-5">
|
||||
<div class="row justify-between q-mr-md">
|
||||
<div class="">{{ sourceApp?.name }}</div>
|
||||
<q-btn outline color="primary" size="xs" label="最新のフィールドを取得する"
|
||||
@click="() => updateFields(sourceAppId!)" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-1 q-pl-sm">
|
||||
キー
|
||||
</div>
|
||||
</div>
|
||||
<q-virtual-scroll style="max-height: 60vh;" :items="mappingProps" separator v-slot="{ item, index }">
|
||||
<!-- <div class="q-my-sm" v-for="(item, index) in mappingProps" :key="item.id"> -->
|
||||
<div class="row q-pa-sm q-col-gutter-x-md flex-center">
|
||||
<div class="col-5">
|
||||
<ConditionObject :config="config" v-model="item.from" :disabled="item.disabled"
|
||||
:label="item.disabled ? '「Lookup」によってロックされる' : undefined" />
|
||||
</div>
|
||||
<!-- <div class="col-1">
|
||||
</div> -->
|
||||
<div class="col-5">
|
||||
<q-field v-model="item.vName" type="text" outlined dense :disable="item.disabled" >
|
||||
<!-- <template v-slot:append>
|
||||
<q-icon name="search" class="cursor-pointer"
|
||||
@click="() => { mappingProps[index].to.isDialogVisible = true }" />
|
||||
</template> -->
|
||||
<template v-slot:control>
|
||||
<div class="self-center full-width no-outline" tabindex="0"
|
||||
v-if="item.to.app?.name && item.to.fields?.length > 0 && item.to.fields[0].label">
|
||||
{{ `${item.to.fields[0].label}` }}
|
||||
<span class="text-red" v-if="item.to.fields[0].required">*</span>
|
||||
<q-tooltip class="bg-yellow-2 text-black shadow-4" >
|
||||
<div>アプリ : {{ item.to.app.name }}</div>
|
||||
<div>フィールドのコード : {{ item.to.fields[0].code }}</div>
|
||||
<div>フィールドのタイプ : {{ item.to.fields[0].type }}</div>
|
||||
<div v-if="item.to.fields[0].required">必須項目</div>
|
||||
<!-- <div>フィールド : {{ item.to.fields[0] }}</div>
|
||||
<div>フィールド : {{ item.isKey }}</div> -->
|
||||
</q-tooltip>
|
||||
</div>
|
||||
</template>
|
||||
</q-field>
|
||||
</div>
|
||||
<div class="col-1">
|
||||
<q-checkbox size="sm" v-model="item.isKey" :disable="item.disabled" />
|
||||
<!-- <q-btn flat round dense icon="delete" size="sm" @click="() => deleteMappingObject(index)" /> -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<show-dialog v-model:visible="mappingProps[index].to.isDialogVisible" name="フィールド一覧"
|
||||
@close="closeToDg" ref="fieldDlg">
|
||||
<FieldSelect v-if="onlySourceSelect" ref="fieldDlg" name="フィールド" :appId="sourceAppId" not_page
|
||||
:selectedFields="mappingProps[index].to.fields"
|
||||
:updateSelects="(fields) => { mappingProps[index].to.fields = fields; mappingProps[index].to.app = sourceApp }">
|
||||
</FieldSelect>
|
||||
<AppFieldSelectBox v-else v-model:selectedField="mappingProps[index].to" />
|
||||
</show-dialog>
|
||||
<!-- </div> -->
|
||||
</q-virtual-scroll>
|
||||
|
||||
<div class="q-mt-lg q-ml-md row ">
|
||||
<q-checkbox size="sm" v-model="createWithNull" label="キーが存在しない場合は新規に作成され、存在する場合はデータが更新されます。" />
|
||||
</div>
|
||||
</div>
|
||||
</show-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { computed, defineComponent, watch, isRef, reactive, ref, watchEffect } from 'vue';
|
||||
import ConditionObject from '../ConditionEditor/ConditionObject.vue';
|
||||
import ShowDialog from '../ShowDialog.vue';
|
||||
import AppFieldSelectBox from '../AppFieldSelectBox.vue';
|
||||
import FieldSelect from '../FieldSelect.vue';
|
||||
import { IApp, IField } from './AppFieldSelect.vue';
|
||||
import { api } from 'boot/axios';
|
||||
|
||||
type ContextProps = {
|
||||
props?: {
|
||||
name: string;
|
||||
modelValue?: {
|
||||
app: {
|
||||
id: string;
|
||||
name: string;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
type CurrentModelValueType = {
|
||||
data: MappingValueType[];
|
||||
createWithNull: boolean;
|
||||
}
|
||||
|
||||
type MappingValueType = {
|
||||
id: string;
|
||||
from: { sharedText?: string };
|
||||
to: {
|
||||
app?: IApp,
|
||||
fields: IField[],
|
||||
isDialogVisible: boolean;
|
||||
};
|
||||
isKey: boolean;
|
||||
disabled: boolean;
|
||||
}
|
||||
|
||||
const blackListLabelName = ['レコード番号', '作業者', '更新者', '更新日時', '作成日時', '作成者']
|
||||
|
||||
export default defineComponent({
|
||||
name: 'DataMapping',
|
||||
inheritAttrs: false,
|
||||
components: {
|
||||
ShowDialog,
|
||||
ConditionObject,
|
||||
AppFieldSelectBox,
|
||||
FieldSelect
|
||||
},
|
||||
props: {
|
||||
context: {
|
||||
type: Array<ContextProps>,
|
||||
default: '',
|
||||
},
|
||||
displayName: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
name: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
modelValue: {
|
||||
type: Object as () => CurrentModelValueType,
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
onlySourceSelect: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
fieldTypes:{
|
||||
type:Array,
|
||||
default:()=>[]
|
||||
},
|
||||
},
|
||||
|
||||
setup(props, { emit }) {
|
||||
const source = props.context.find(element => element?.props?.name === 'sources')
|
||||
|
||||
const sourceApp = computed(() => source?.props?.modelValue?.app);
|
||||
|
||||
const sourceAppId = computed(() => sourceApp.value?.id);
|
||||
|
||||
const closeDg = () => {
|
||||
emit('update:modelValue', { data: mappingProps.value, createWithNull: createWithNull.value });
|
||||
}
|
||||
|
||||
const closeToDg = () => {
|
||||
emit('update:modelValue', { data: mappingProps.value, createWithNull: createWithNull.value });
|
||||
}
|
||||
|
||||
const mappingProps = ref(props.modelValue?.data ?? []);
|
||||
|
||||
const createWithNull = ref(props.modelValue?.createWithNull ?? false)
|
||||
|
||||
// 外部ソースコンポーネントの appid をリッスンし、変更されたときに現在のコンポーネントを更新します
|
||||
watch(() => sourceAppId.value, async (newId,) => {
|
||||
if (!newId) return;
|
||||
updateFields(newId)
|
||||
})
|
||||
|
||||
const updateFields = async (sourceAppId: string) => {
|
||||
const ktAppFields = await api.get('api/v1/appfields', {
|
||||
params: {
|
||||
app: sourceAppId
|
||||
}
|
||||
}).then(res => {
|
||||
return Object.values(res.data.properties)
|
||||
// kintoneのデフォルトの非表示フィールドフィルタリング
|
||||
.filter(f => !blackListLabelName.find(label => f.label === label))
|
||||
.map(f => ({ name: f.label, objectType: 'field', ...f }))
|
||||
.map(f => {
|
||||
// 更新前の値を求める
|
||||
const beforeData = mappingProps.value.find(m => m.to.fields[0].code === f.code)
|
||||
return {
|
||||
id: uuidv4(),
|
||||
from: beforeData?.from ?? {}, // 以前のデータを入力します
|
||||
to: {
|
||||
app: sourceApp.value,
|
||||
fields: [f],
|
||||
isDialogVisible: false
|
||||
},
|
||||
isKey: beforeData?.isKey ?? false, // 以前のデータを入力します
|
||||
disabled: false
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
// 「ルックアップ」によってロックされているフィールドを検索する
|
||||
const lookupFixedField = ktAppFields
|
||||
.filter(field => field.to.fields[0].lookup !== undefined)
|
||||
.flatMap(field => field.to.fields[0].lookup.fieldMappings.map((m) => m.field))
|
||||
|
||||
// 「ルックアップ」でロックされたビューコンポーネントを非対話型に設定します
|
||||
if (lookupFixedField.length > 0) {
|
||||
ktAppFields.filter(f => lookupFixedField.includes(f.to.fields[0].code)).forEach(f => f.disabled = true)
|
||||
}
|
||||
|
||||
mappingProps.value = ktAppFields
|
||||
}
|
||||
|
||||
const mappingObjectsInputDisplay = computed(() =>
|
||||
(mappingProps.value && Array.isArray(mappingProps.value)) ?
|
||||
mappingProps.value
|
||||
.filter(item => item.from?.sharedText && item.to.fields?.length > 0)
|
||||
.map(item => {
|
||||
return `field(${item.to.app?.id},${item.to.fields[0].label}) = ${item.from.sharedText} `;
|
||||
})
|
||||
: []
|
||||
);
|
||||
|
||||
const btnDisable = computed(() => props.onlySourceSelect ? !(source?.props?.modelValue?.app?.id) : false);
|
||||
|
||||
watchEffect(() => {
|
||||
emit('update:modelValue', { data: mappingProps.value, createWithNull: createWithNull.value });
|
||||
});
|
||||
|
||||
return {
|
||||
uuidv4,
|
||||
dgIsShow: ref(false),
|
||||
closeDg,
|
||||
toDgIsShow: ref(false),
|
||||
closeToDg,
|
||||
mappingProps,
|
||||
createWithNull,
|
||||
updateFields,
|
||||
// addMappingObject: () => mappingProps.push(defaultMappingProp()),
|
||||
// deleteMappingObject,
|
||||
mappingObjectsInputDisplay,
|
||||
sourceApp,
|
||||
sourceAppId,
|
||||
btnDisable,
|
||||
config: {
|
||||
canInput: false,
|
||||
buttonsConfig: [
|
||||
{ label: 'フィールド', color: 'primary', type: 'FieldAdd' },
|
||||
{ label: '変数', color: 'green', type: 'VariableAdd', editable: false },
|
||||
]
|
||||
}
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
</script>
|
||||
<style lang="scss"></style>
|
||||
239
frontend/src/components/right/DataProcessing.vue
Normal file
239
frontend/src/components/right/DataProcessing.vue
Normal file
@@ -0,0 +1,239 @@
|
||||
<template>
|
||||
<div>
|
||||
<q-field :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="() => { dgIsShow = true }">クリックで設定</q-btn>
|
||||
</q-card-actions>
|
||||
<q-card-section class="text-caption">
|
||||
<div v-if="processingObjectsInputDisplay && processingObjectsInputDisplay.length>0">
|
||||
<div v-for="(item) in processingObjectsInputDisplay" :key="item">{{ item }}</div>
|
||||
</div>
|
||||
<div v-else>{{ placeholder }}</div>
|
||||
</q-card-section>
|
||||
</q-card>
|
||||
</template>
|
||||
</q-field>
|
||||
<show-dialog v-model:visible="dgIsShow" name="集計処理" @close="closeDg" min-width="50vw" min-height="60vh">
|
||||
<div class="q-mx-md q-mb-md">
|
||||
<q-input v-model="processingProps.name" type="text" label-color="primary" label="集計結果の変数名"
|
||||
placeholder="集計結果を格納する変数名を入力してください" stack-label />
|
||||
</div>
|
||||
|
||||
<div class="q-mx-md">
|
||||
<div class="row q-col-gutter-x-xs flex-center">
|
||||
<div class="col-5">
|
||||
<div class="q-mx-xs">データソース</div>
|
||||
</div>
|
||||
<div class="col-2">
|
||||
<div class="q-mx-xs">集計計算</div>
|
||||
</div>
|
||||
<div class="col-4">
|
||||
<div class="q-mx-xs">集計結果変数名</div>
|
||||
</div>
|
||||
<div class="col-1"><q-btn flat round dense icon="add" size="sm" @click="addProcessingObject" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="q-my-sm" v-for="(item, index) in processingObjects" :key="item.id">
|
||||
<div class="row q-col-gutter-x-xs flex-center">
|
||||
<div class="col-5">
|
||||
<ConditionObject v-model="item.field" />
|
||||
</div>
|
||||
<div class="col-2 q-pa-sm">
|
||||
<q-select v-model="item.logicalOperator" :options="logicalOperators" outlined dense></q-select>
|
||||
</div>
|
||||
<div class="col-4">
|
||||
<q-input v-model="item.vName" type="text" outlined dense />
|
||||
</div>
|
||||
<div class="col-1">
|
||||
<q-btn flat round dense icon="delete" size="sm" @click="() => deleteProcessingObject(index)" />
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</show-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { computed, defineComponent, provide, reactive, ref, watchEffect } from 'vue';
|
||||
import ConditionObject from '../ConditionEditor/ConditionObject.vue';
|
||||
import ShowDialog from '../ShowDialog.vue';
|
||||
|
||||
type Props = {
|
||||
props?: {
|
||||
name: string;
|
||||
modelValue?: {
|
||||
fields: {
|
||||
type: string;
|
||||
label: string;
|
||||
code: string;
|
||||
}[]
|
||||
} | string
|
||||
}
|
||||
};
|
||||
|
||||
type ProcessingObjectType = {
|
||||
field?: {
|
||||
sharedText: string;
|
||||
objectType: 'field';
|
||||
};
|
||||
logicalOperator?: string;
|
||||
vName?: string;
|
||||
id: string;
|
||||
}
|
||||
|
||||
type ValueType = {
|
||||
name: string;
|
||||
actionName: string,
|
||||
displayName: string,
|
||||
vars: ProcessingObjectType[];
|
||||
}
|
||||
|
||||
export default defineComponent({
|
||||
name: 'DataProcessing',
|
||||
inheritAttrs: false,
|
||||
components: {
|
||||
ShowDialog,
|
||||
ConditionObject,
|
||||
},
|
||||
props: {
|
||||
context: {
|
||||
type: Array<Props>,
|
||||
default: '',
|
||||
},
|
||||
displayName: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
name: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
modelValue: {
|
||||
type: Object as () => ValueType,
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
|
||||
setup(props, { emit }) {
|
||||
const source = props.context.find(element => element?.props?.name === 'sources')
|
||||
|
||||
if (source) {
|
||||
provide('sourceFields', computed(() => {
|
||||
const modelValue = source.props?.modelValue;
|
||||
if (modelValue && typeof modelValue !== 'string') {
|
||||
return modelValue.fields;
|
||||
}
|
||||
return null;
|
||||
}));
|
||||
provide('sourceApp', computed(() => source.props?.modelValue?.app?.id));
|
||||
}
|
||||
|
||||
const actionName = props.context.find(element => element?.props?.name === 'displayName')
|
||||
|
||||
const processingProps: ValueType = props.modelValue && props.modelValue.vars
|
||||
? reactive(props.modelValue)
|
||||
: reactive({
|
||||
name: '',
|
||||
actionName: actionName?.props?.modelValue as string,
|
||||
displayName: '結果(戻り値)',
|
||||
vars: [
|
||||
{
|
||||
id: uuidv4(),
|
||||
field:{
|
||||
objectType:'field',
|
||||
sharedText:''
|
||||
}
|
||||
}]
|
||||
});
|
||||
|
||||
const closeDg = () => {
|
||||
emit('update:modelValue', processingProps);
|
||||
}
|
||||
|
||||
const processingObjects = processingProps.vars;
|
||||
|
||||
const deleteProcessingObject = (index: number) => {
|
||||
if(processingObjects.length >0){
|
||||
processingObjects.splice(index, 1);
|
||||
}
|
||||
if(processingObjects.length===0){
|
||||
addProcessingObject();
|
||||
}
|
||||
}
|
||||
const processingObjectsInputDisplay = computed(() =>
|
||||
processingObjects ?
|
||||
processingObjects
|
||||
.filter(item => item.field && item.logicalOperator && item.vName)
|
||||
.map(item => {
|
||||
return`var(${processingProps.name}.${item.vName}) = ${item.field?.sharedText}`
|
||||
})
|
||||
: []
|
||||
);
|
||||
|
||||
const addProcessingObject=()=>{
|
||||
processingObjects.push({
|
||||
id: uuidv4(),
|
||||
field:{
|
||||
objectType:'field',
|
||||
sharedText:''
|
||||
}
|
||||
});
|
||||
}
|
||||
//集計処理方法
|
||||
const logicalOperators = ref([
|
||||
{
|
||||
"operator": "",
|
||||
"label": "なし"
|
||||
},
|
||||
{
|
||||
"operator": "SUM",
|
||||
"label": "合計"
|
||||
},
|
||||
{
|
||||
"operator": "AVG",
|
||||
"label": "平均"
|
||||
},
|
||||
{
|
||||
"operator": "MAX",
|
||||
"label": "最大値"
|
||||
},
|
||||
{
|
||||
"operator": "MIN",
|
||||
"label": "最小値"
|
||||
},
|
||||
{
|
||||
"operator": "COUNT",
|
||||
"label": "カウント"
|
||||
},
|
||||
{
|
||||
"operator": "FIRST",
|
||||
"label": "最初の値"
|
||||
}
|
||||
]);
|
||||
|
||||
watchEffect(() => {
|
||||
emit('update:modelValue', processingProps);
|
||||
});
|
||||
return {
|
||||
uuidv4,
|
||||
dgIsShow: ref(false),
|
||||
closeDg,
|
||||
processingObjects,
|
||||
processingProps,
|
||||
addProcessingObject,
|
||||
deleteProcessingObject,
|
||||
logicalOperators,
|
||||
processingObjectsInputDisplay,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
<style lang="scss"></style>
|
||||
59
frontend/src/components/right/DatePicker.vue
Normal file
59
frontend/src/components/right/DatePicker.vue
Normal file
@@ -0,0 +1,59 @@
|
||||
<template>
|
||||
<div v-bind="$attrs">
|
||||
<q-input v-model="selectedDate" :label="displayName" :placeholder="placeholder" label-color="primary" mask="date" :rules="['date']" stack-label>
|
||||
<template v-slot:append>
|
||||
<q-icon name="event" class="cursor-pointer">
|
||||
<q-popup-proxy cover transition-show="scale" transition-hide="scale">
|
||||
<q-date v-model="selectedDate">
|
||||
<div class="row items-center justify-end">
|
||||
<q-btn v-close-popup label="Close" color="primary" flat />
|
||||
</div>
|
||||
</q-date>
|
||||
</q-popup-proxy>
|
||||
</q-icon>
|
||||
</template>
|
||||
</q-input>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, ref ,watchEffect} from 'vue';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'DatePicker',
|
||||
inheritAttrs:false,
|
||||
props: {
|
||||
displayName:{
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
name:{
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
hint:{
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
modelValue: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
setup(props, { emit }) {
|
||||
const selectedDate = ref(props.modelValue);
|
||||
|
||||
watchEffect(() => {
|
||||
emit('update:modelValue', selectedDate.value);
|
||||
});
|
||||
|
||||
return {
|
||||
selectedDate
|
||||
};
|
||||
}
|
||||
});
|
||||
</script>
|
||||
83
frontend/src/components/right/EventSetter.vue
Normal file
83
frontend/src/components/right/EventSetter.vue
Normal file
@@ -0,0 +1,83 @@
|
||||
<template>
|
||||
<div v-bind="$attrs">
|
||||
<q-input :label="displayName" v-model="inputValue" label-color="primary" :placeholder="placeholder" stack-label>
|
||||
<template v-slot:append>
|
||||
<q-btn round dense flat icon="add" @click="addButtonEvent()" />
|
||||
</template>
|
||||
</q-input>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent,ref,watchEffect } from 'vue';
|
||||
import { useFlowEditorStore } from '../../stores/flowEditor';
|
||||
import { IKintoneEventGroup,kintoneEvent } from 'src/types/KintoneEvents';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'EventSetter',
|
||||
inheritAttrs:false,
|
||||
props: {
|
||||
displayName:{
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
name:{
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
hint:{
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
modelValue: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
connectProps:{
|
||||
type:Object,
|
||||
default:undefined
|
||||
}
|
||||
},
|
||||
|
||||
setup(props , { emit }) {
|
||||
const inputValue = ref(props.modelValue);
|
||||
const store = useFlowEditorStore();
|
||||
const addButtonEvent=()=>{
|
||||
const eventId =store.currentFlow?.getRoot()?.name;
|
||||
if(eventId===undefined){return;}
|
||||
let displayName = inputValue.value;
|
||||
if(props.connectProps!==undefined && "displayName" in props.connectProps){
|
||||
displayName =props.connectProps["displayName"].props.modelValue;
|
||||
}
|
||||
const customButtonId=`${eventId}.customButtonClick`;
|
||||
const findedEvent = store.eventTree.findEventById(customButtonId);
|
||||
if(findedEvent && "events" in findedEvent){
|
||||
const customEvents = findedEvent as IKintoneEventGroup;
|
||||
const addEventId = customButtonId+"." + inputValue.value;
|
||||
if(store.eventTree.findEventById(addEventId)){
|
||||
return;
|
||||
}
|
||||
customEvents.events.push({
|
||||
eventId: addEventId,
|
||||
label: displayName,
|
||||
parentId: customButtonId,
|
||||
header: 'DELETABLE'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
watchEffect(() => {
|
||||
emit('update:modelValue', inputValue.value);
|
||||
});
|
||||
|
||||
return {
|
||||
inputValue,
|
||||
addButtonEvent
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
106
frontend/src/components/right/FieldInput.vue
Normal file
106
frontend/src/components/right/FieldInput.vue
Normal file
@@ -0,0 +1,106 @@
|
||||
<template>
|
||||
<div v-bind="$attrs">
|
||||
<q-field v-model="selectedField" :label="displayName" labelColor="primary" :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">
|
||||
{{ placeholder }}
|
||||
</template>
|
||||
|
||||
<template v-slot:append>
|
||||
<q-icon name="search" class="cursor-pointer" color="primary" @click="showDg" />
|
||||
</template>
|
||||
</q-field>
|
||||
<show-dialog v-model:visible="show" name="フィールド一覧" @close="closeDg" widht="400px">
|
||||
<field-select ref="appDg" name="フィールド" :type="selectType" :appId="store.appInfo?.appId" :fieldTypes="fieldTypes"></field-select>
|
||||
</show-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, ref, watchEffect, computed } from 'vue';
|
||||
import ShowDialog from '../ShowDialog.vue';
|
||||
import FieldSelect from '../FieldSelect.vue';
|
||||
import { useFlowEditorStore } from 'stores/flowEditor';
|
||||
interface IField {
|
||||
name: string,
|
||||
code: string,
|
||||
type: string
|
||||
}
|
||||
export default defineComponent({
|
||||
name: 'FieldInput',
|
||||
inheritAttrs:false,
|
||||
components: {
|
||||
ShowDialog,
|
||||
FieldSelect,
|
||||
},
|
||||
props: {
|
||||
displayName: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
name: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
selectType:{
|
||||
type:String,
|
||||
default:'single'
|
||||
},
|
||||
fieldTypes:{
|
||||
type:Array,
|
||||
default:()=>[]
|
||||
},
|
||||
hint: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
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>
|
||||
103
frontend/src/components/right/InputText.vue
Normal file
103
frontend/src/components/right/InputText.vue
Normal file
@@ -0,0 +1,103 @@
|
||||
<template>
|
||||
<div v-bind="$attrs">
|
||||
<q-input :label="displayName" v-model="inputValue" label-color="primary" :placeholder="placeholder" stack-label
|
||||
:rules="rulesExp" :maxlength="maxLength">
|
||||
<template v-slot:append v-if="hint !== ''">
|
||||
<q-icon name="help" size="22px" color="blue-8">
|
||||
<q-tooltip class="bg-yellow-2 text-black shadow-4" anchor="bottom right">
|
||||
<div class="hint-text" v-html="hint" />
|
||||
</q-tooltip>
|
||||
</q-icon>
|
||||
</template>
|
||||
</q-input>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { kMaxLength } from 'buffer';
|
||||
import { defineComponent, ref, watchEffect, computed } from 'vue';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'InputText',
|
||||
inheritAttrs: false,
|
||||
props: {
|
||||
displayName: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
name: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
fieldTypes:{
|
||||
type:Array,
|
||||
default:()=>[]
|
||||
},
|
||||
hint: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
maxLength: {
|
||||
type: Number,
|
||||
default: undefined
|
||||
},
|
||||
//例:[val=>!!val ||'入力してください']
|
||||
rules: {
|
||||
type: String,
|
||||
default: undefined
|
||||
},
|
||||
modelValue: {
|
||||
// type: Any,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
|
||||
setup(props, { emit }) {
|
||||
const inputValue = computed({
|
||||
get: () => {
|
||||
if (props.modelValue !== null && typeof props.modelValue === 'object' && 'name' in props.modelValue) {
|
||||
return props.modelValue.name;
|
||||
} else {
|
||||
return props.modelValue;
|
||||
}
|
||||
},
|
||||
set: (val) => {
|
||||
if (props.name === 'verName') {
|
||||
// return props.modelValue.name;
|
||||
emit('update:modelValue', { name: val });
|
||||
} else {
|
||||
emit('update:modelValue', val);
|
||||
}
|
||||
},
|
||||
});
|
||||
// const inputValue = ref(props.modelValue);
|
||||
const rulesExp = props.rules === undefined ? null : eval(props.rules);
|
||||
|
||||
// const finalValue = computed(() => {
|
||||
// return props.name !== 'verName' ? inputValue.value : {
|
||||
// name: inputValue.value,
|
||||
// };
|
||||
// });
|
||||
// watchEffect(() => {
|
||||
// emit('update:modelValue', finalValue);
|
||||
// });
|
||||
|
||||
return {
|
||||
inputValue,
|
||||
showhint: ref(false),
|
||||
rulesExp
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
<style lang="scss">
|
||||
.hint-text {
|
||||
white-space: always;
|
||||
max-width: 450px;
|
||||
font-size: 1.2em;
|
||||
}
|
||||
</style>
|
||||
49
frontend/src/components/right/MuiltInputText.vue
Normal file
49
frontend/src/components/right/MuiltInputText.vue
Normal file
@@ -0,0 +1,49 @@
|
||||
<template>
|
||||
<div v-bind="$attrs">
|
||||
<q-input :label="displayName" label-color="primary" v-model="inputValue" :placeholder="placeholder" autogrow
|
||||
stack-label />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, ref, watchEffect } from 'vue';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'MuiltInputText',
|
||||
inheritAttrs: false,
|
||||
props: {
|
||||
displayName: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
name: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
hint: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
modelValue: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
|
||||
setup(props, { emit }) {
|
||||
const inputValue = ref(props.modelValue);
|
||||
|
||||
watchEffect(() => {
|
||||
emit('update:modelValue', inputValue.value);
|
||||
});
|
||||
|
||||
return {
|
||||
inputValue,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
87
frontend/src/components/right/NumInput.vue
Normal file
87
frontend/src/components/right/NumInput.vue
Normal file
@@ -0,0 +1,87 @@
|
||||
<template>
|
||||
<div class="" v-bind="$attrs">
|
||||
<q-input v-model.number="numValue" type="number" :label="displayName" label-color="primary" stack-label bottom-slots
|
||||
:min="min"
|
||||
:max="max"
|
||||
:rules="rulesExp"
|
||||
>
|
||||
<template v-slot:hint>
|
||||
{{ placeholder }}
|
||||
</template>
|
||||
</q-input>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent, ref, watchEffect } from 'vue';
|
||||
export default defineComponent({
|
||||
name: 'NumInput',
|
||||
inheritAttrs:false,
|
||||
components: {
|
||||
},
|
||||
props: {
|
||||
displayName: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
name: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
hint: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
min:{
|
||||
type:Number,
|
||||
default:undefined
|
||||
},
|
||||
max:{
|
||||
type:Number,
|
||||
default:undefined
|
||||
},
|
||||
//[val=>!!val ||'数値を入力してください',val=>val<=100 && val>=1 || '1-100の範囲内の数値を入力してください']
|
||||
rules:{
|
||||
type:String,
|
||||
default:undefined
|
||||
},
|
||||
modelValue: {
|
||||
type: [Number , String],
|
||||
default: undefined
|
||||
},
|
||||
},
|
||||
|
||||
setup(props, { emit }) {
|
||||
const numValue = ref(props.modelValue);
|
||||
const rulesExp = props.rules===undefined?null : eval(props.rules);
|
||||
const isError = computed(()=>{
|
||||
const val = numValue.value;
|
||||
if (val === undefined) {
|
||||
return false;
|
||||
}
|
||||
const numVal = typeof val === "string" ? parseInt(val) : val;
|
||||
// Ensure parsed value is a valid number
|
||||
if (isNaN(numVal)) {
|
||||
return true;
|
||||
}
|
||||
// Check against min and max boundaries, if defined
|
||||
if ((props.min !== undefined && numVal < props.min) || (props.max !== undefined && numVal > props.max)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
watchEffect(()=>{
|
||||
emit("update:modelValue",numValue.value);
|
||||
});
|
||||
return {
|
||||
numValue,
|
||||
rulesExp
|
||||
};
|
||||
}
|
||||
});
|
||||
</script>
|
||||
78
frontend/src/components/right/PropertyList.vue
Normal file
78
frontend/src/components/right/PropertyList.vue
Normal file
@@ -0,0 +1,78 @@
|
||||
<template>
|
||||
<div>
|
||||
<div v-for="(item, index) in properties" :key="index" >
|
||||
<component :is="item.component" v-bind="item.props" :context="properties" :connectProps="connectProps(item.props)" v-model="item.props.modelValue"></component>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
/**
|
||||
* プロパティ属性設定生成する
|
||||
*/
|
||||
import { PropType, defineComponent,ref } from 'vue';
|
||||
import InputText from '../right/InputText.vue';
|
||||
import SelectBox from '../right/SelectBox.vue';
|
||||
import DatePicker from '../right/DatePicker.vue';
|
||||
import FieldInput from '../right/FieldInput.vue';
|
||||
import AppFieldSelect from './AppFieldSelect.vue';
|
||||
import MuiltInputText from '../right/MuiltInputText.vue';
|
||||
import ConditionInput from '../right/ConditionInput.vue';
|
||||
import EventSetter from '../right/EventSetter.vue';
|
||||
import ColorPicker from './ColorPicker.vue';
|
||||
import NumInput from './NumInput.vue';
|
||||
import DataProcessing from './DataProcessing.vue';
|
||||
import DataMapping from './DataMapping.vue';
|
||||
import AppSelect from './AppSelect.vue';
|
||||
import { IActionNode,IActionProperty,IProp } from 'src/types/ActionTypes';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'PropertyList',
|
||||
components: {
|
||||
InputText,
|
||||
SelectBox,
|
||||
DatePicker,
|
||||
FieldInput,
|
||||
AppFieldSelect,
|
||||
MuiltInputText,
|
||||
ConditionInput,
|
||||
EventSetter,
|
||||
ColorPicker,
|
||||
NumInput,
|
||||
DataProcessing,
|
||||
DataMapping,
|
||||
AppSelect
|
||||
},
|
||||
props: {
|
||||
nodeProps: {
|
||||
type: Object as PropType<Array<IActionProperty>>,
|
||||
required: true,
|
||||
},
|
||||
jsonValue:{
|
||||
type: Object,
|
||||
required: false,
|
||||
}
|
||||
},
|
||||
setup(props, context) {
|
||||
const properties=ref(props.nodeProps);
|
||||
const connectProps=(props:IProp)=>{
|
||||
const connProps:any={context:properties};
|
||||
if(props && "connectProps" in props && props.connectProps!=undefined){
|
||||
for(let connProp of props.connectProps){
|
||||
let targetProp = properties.value.find((prop)=>prop.props.name===connProp.propName);
|
||||
if(targetProp){
|
||||
connProps[connProp.key]=targetProp;
|
||||
}
|
||||
}
|
||||
}
|
||||
return connProps;
|
||||
}
|
||||
return {
|
||||
properties,
|
||||
connectProps
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
<style lang="scss">
|
||||
</style>
|
||||
80
frontend/src/components/right/PropertyPanel.vue
Normal file
80
frontend/src/components/right/PropertyPanel.vue
Normal file
@@ -0,0 +1,80 @@
|
||||
<template>
|
||||
<div class="q-pa-md q-gutter-sm">
|
||||
<q-drawer
|
||||
side="right"
|
||||
:show-if-above="false"
|
||||
bordered
|
||||
:width="301"
|
||||
:breakpoint="500"
|
||||
class="bg-grey-3"
|
||||
:model-value="showPanel"
|
||||
elevated
|
||||
overlay
|
||||
>
|
||||
<q-card class="column" style="max-width: 300px;min-height: 100%">
|
||||
<q-card-section>
|
||||
<div class="text-h6">{{ actionNode?.subTitle }}:設定</div>
|
||||
</q-card-section>
|
||||
<q-card-section class="col q-pt-none">
|
||||
<property-list :node-props="actionProps" v-if="showPanel" ></property-list>
|
||||
</q-card-section>
|
||||
|
||||
<q-card-actions align="right" class="bg-white text-teal">
|
||||
<q-btn flat label="キャンセル" @click="cancel" outline dense padding="none sm" color="primary"/>
|
||||
<q-btn flat label="更新" @click="save" outline dense padding="none sm" color="primary" />
|
||||
</q-card-actions>
|
||||
</q-card>
|
||||
</q-drawer>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { ref,defineComponent, PropType ,watchEffect} from 'vue'
|
||||
import PropertyList from 'components/right/PropertyList.vue';
|
||||
import { IActionNode } from 'src/types/ActionTypes';
|
||||
export default defineComponent({
|
||||
name: 'PropertyPanel',
|
||||
components: {
|
||||
PropertyList
|
||||
},
|
||||
props: {
|
||||
actionNode:{
|
||||
type:Object as PropType<IActionNode>,
|
||||
required:true
|
||||
},
|
||||
drawerRight:{
|
||||
type:Boolean,
|
||||
required:true
|
||||
}
|
||||
},
|
||||
emits: [
|
||||
'update:drawerRight'
|
||||
],
|
||||
setup(props,{emit}) {
|
||||
const showPanel =ref(props.drawerRight);
|
||||
const actionProps =ref(props.actionNode?.actionProps);
|
||||
watchEffect(() => {
|
||||
showPanel.value = props.drawerRight;
|
||||
actionProps.value= props.actionNode?.actionProps;
|
||||
});
|
||||
|
||||
const cancel = async() =>{
|
||||
showPanel.value = false;
|
||||
emit('update:drawerRight',false )
|
||||
}
|
||||
|
||||
const save = async () =>{
|
||||
showPanel.value=false;
|
||||
emit('update:drawerRight',false )
|
||||
}
|
||||
|
||||
return {
|
||||
cancel,
|
||||
save,
|
||||
actionProps,
|
||||
showPanel
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
<style lang="scss">
|
||||
</style>
|
||||
51
frontend/src/components/right/SelectBox.vue
Normal file
51
frontend/src/components/right/SelectBox.vue
Normal file
@@ -0,0 +1,51 @@
|
||||
<template>
|
||||
<div v-bind="$attrs">
|
||||
<q-select v-model="selectedValue" :use-chips="multiple" :label="displayName" label-color="primary" :options="options" stack-label
|
||||
:multiple="multiple"/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent,ref,watchEffect,computed } from 'vue';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'SelectBox',
|
||||
inheritAttrs:false,
|
||||
props: {
|
||||
displayName:{
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
options: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
selectType:{
|
||||
type:String,
|
||||
default:'',
|
||||
},
|
||||
modelValue: {
|
||||
type: [Array,String],
|
||||
default: null,
|
||||
},
|
||||
},
|
||||
setup(props, { emit }) {
|
||||
const selectedValue = ref(props.modelValue);
|
||||
const multiple = computed(()=>{
|
||||
return props.selectType==='multiple'
|
||||
});
|
||||
watchEffect(() => {
|
||||
emit('update:modelValue', selectedValue.value);
|
||||
});
|
||||
|
||||
return {
|
||||
selectedValue,
|
||||
multiple
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
22
frontend/src/control/auth.ts
Normal file
22
frontend/src/control/auth.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { api } from 'boot/axios';
|
||||
|
||||
export class Auth
|
||||
{
|
||||
|
||||
async login(user:string,pwd:string):Promise<boolean>
|
||||
{
|
||||
const params = new URLSearchParams();
|
||||
params.append('username', user);
|
||||
params.append('password', pwd);
|
||||
try{
|
||||
const result = await api.post(`api/token`,params);
|
||||
console.info(result);
|
||||
localStorage.setItem('Token', result.data.access_token);
|
||||
return true;
|
||||
}catch(e)
|
||||
{
|
||||
console.info(e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
59
frontend/src/control/flowctrl.ts
Normal file
59
frontend/src/control/flowctrl.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
import { api } from 'boot/axios';
|
||||
import { ActionFlow } from 'src/types/ActionTypes';
|
||||
|
||||
export class FlowCtrl {
|
||||
async getFlows(appId: string): Promise<ActionFlow[]> {
|
||||
const flows: ActionFlow[] = [];
|
||||
try {
|
||||
const result = await api.get(`api/flows/${appId}`);
|
||||
//console.info(result.data);
|
||||
if (!result.data || !Array.isArray(result.data)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
for (const flow of result.data) {
|
||||
flows.push(ActionFlow.fromJSON(flow.content));
|
||||
}
|
||||
return flows;
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
return flows;
|
||||
}
|
||||
}
|
||||
|
||||
async SaveFlow(jsonData: any): Promise<boolean> {
|
||||
const result = await api.post('api/flow', jsonData);
|
||||
console.info(result.data);
|
||||
return true;
|
||||
}
|
||||
/**
|
||||
* フローを更新する
|
||||
* @param jsonData
|
||||
* @returns
|
||||
*/
|
||||
async UpdateFlow(jsonData: any): Promise<boolean> {
|
||||
const result = await api.put('api/flow/' + jsonData.flowid, jsonData);
|
||||
console.info(result.data);
|
||||
return true;
|
||||
}
|
||||
/**
|
||||
* フローを消去する
|
||||
* @param flowId
|
||||
* @returns
|
||||
*/
|
||||
async DeleteFlow(flowId: string): Promise<boolean> {
|
||||
const result = await api.delete('api/flow/' + flowId);
|
||||
console.info(result.data);
|
||||
return true;
|
||||
}
|
||||
/**
|
||||
* デプロイ
|
||||
* @param appid
|
||||
* @returns
|
||||
*/
|
||||
async depoly(appid: string): Promise<boolean> {
|
||||
const result = await api.post(`api/v1/createjstokintone?app=${appid}`);
|
||||
console.info(result.data);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -1 +1,25 @@
|
||||
// app global css in SCSS form
|
||||
::-webkit-scrollbar {
|
||||
height: 12px;
|
||||
width: 14px;
|
||||
background: transparent;
|
||||
z-index: 12;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
width: 10px;
|
||||
background-color: #c1c1c1;
|
||||
border-radius: 10px;
|
||||
z-index: 12;
|
||||
border: 4px solid rgba(0, 0, 0, 0);
|
||||
background-clip: padding-box;
|
||||
transition: background-color .32s ease-in-out;
|
||||
margin: 4px;
|
||||
min-height: 32px;
|
||||
min-width: 32px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: #c1c1c1;
|
||||
}
|
||||
|
||||
@@ -11,14 +11,16 @@
|
||||
@click="toggleLeftDrawer"
|
||||
/>
|
||||
<q-toolbar-title>
|
||||
Kintone App Builder
|
||||
<q-badge align="top" outline>V{{ env.version }}</q-badge>
|
||||
{{ productName }}
|
||||
<q-badge align="top" outline>V{{ version }}</q-badge>
|
||||
</q-toolbar-title>
|
||||
<domain-selector></domain-selector>
|
||||
<q-btn flat round dense icon="logout" @click="authStore.logout()"/>
|
||||
</q-toolbar>
|
||||
</q-header>
|
||||
|
||||
<q-drawer
|
||||
v-model="leftDrawerOpen"
|
||||
:model-value="authStore.toggleLeftDrawer"
|
||||
:show-if-above="false"
|
||||
bordered
|
||||
>
|
||||
@@ -26,7 +28,7 @@
|
||||
<q-item-label
|
||||
header
|
||||
>
|
||||
Essential Links
|
||||
関連リンク
|
||||
</q-item-label>
|
||||
|
||||
<EssentialLink
|
||||
@@ -46,6 +48,10 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import EssentialLink, { EssentialLinkProps } from 'components/EssentialLink.vue';
|
||||
import DomainSelector from 'components/DomainSelector.vue';
|
||||
import { useAuthStore } from 'stores/useAuthStore';
|
||||
|
||||
const authStore = useAuthStore();
|
||||
|
||||
const essentialLinks: EssentialLinkProps[] = [
|
||||
{
|
||||
@@ -56,10 +62,17 @@ const essentialLinks: EssentialLinkProps[] = [
|
||||
target:'_self'
|
||||
},
|
||||
{
|
||||
title: 'ルールエディター',
|
||||
caption: 'rule',
|
||||
icon: 'rule',
|
||||
link: '/#/ruleEditor',
|
||||
title: 'フローエディター',
|
||||
caption: 'flowChart',
|
||||
icon: 'account_tree',
|
||||
link: '/#/FlowChart',
|
||||
target:'_self'
|
||||
},
|
||||
{
|
||||
title: '条件エディター',
|
||||
caption: 'condition',
|
||||
icon: 'tune',
|
||||
link: '/#/condition',
|
||||
target:'_self'
|
||||
},
|
||||
{
|
||||
@@ -138,11 +151,10 @@ const essentialLinks: EssentialLinkProps[] = [
|
||||
}
|
||||
];
|
||||
|
||||
const leftDrawerOpen = ref(false)
|
||||
|
||||
const env=process.env;
|
||||
const version = process.env.version;
|
||||
const productName = process.env.productName;
|
||||
|
||||
function toggleLeftDrawer() {
|
||||
leftDrawerOpen.value = !leftDrawerOpen.value
|
||||
authStore.toggleLeftMenu();
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
|
||||
|
||||
export interface KintoneEvent{
|
||||
screen:string,
|
||||
type:string,
|
||||
name:string
|
||||
}
|
||||
|
||||
274
frontend/src/pages/FlowChart.vue
Normal file
274
frontend/src/pages/FlowChart.vue
Normal file
@@ -0,0 +1,274 @@
|
||||
<template>
|
||||
<q-page>
|
||||
<q-layout container class="absolute-full shadow-2 rounded-borders">
|
||||
<div class="q-pa-sm q-gutter-sm ">
|
||||
<q-drawer side="left" :overlay="true" bordered v-model="drawerLeft" :show-if-above="false" elevated>
|
||||
<div class="flex-center fixed-top app-selector">
|
||||
<AppSelector />
|
||||
</div>
|
||||
|
||||
<div class="flex-center absolute-full" style="padding-top:65px;padding-left:15px;padding-right:15px;">
|
||||
<q-scroll-area class="fit" :horizontal-thumb-style="{ opacity: '0' }">
|
||||
<EventTree />
|
||||
</q-scroll-area>
|
||||
</div>
|
||||
|
||||
<div class="flex-center fixed-bottom bg-grey-3 q-pa-md row ">
|
||||
<q-btn color="secondary" glossy label="デプロイ" @click="onDeploy" icon="sync" :loading="deployLoading" />
|
||||
<q-space></q-space>
|
||||
<q-btn color="primary" label="保存" @click="onSaveFlow" icon="save" :loading="saveLoading" />
|
||||
</div>
|
||||
</q-drawer>
|
||||
</div>
|
||||
<q-btn flat dense round
|
||||
:icon="drawerLeft?'keyboard_double_arrow_left':'keyboard_double_arrow_right'"
|
||||
:style="[drawerLeft?{'left':'300px'}:{'left':'0px'}]"
|
||||
@click="drawerLeft=!drawerLeft" class="expand" />
|
||||
<div class="q-pa-md q-gutter-sm" :style="{minWidth: minPanelWidth}">
|
||||
<div class="flowchart" v-if="store.currentFlow" :style="[drawerLeft?{paddingLeft:'300px'}:{}]">
|
||||
<node-item v-if="rootNode!==undefined" :key="rootNode.id" :isSelected="rootNode === store.activeNode"
|
||||
:actionNode="rootNode" @addNode="addNode" @nodeSelected="onNodeSelected" @nodeEdit="onNodeEdit"
|
||||
@deleteNode="onDeleteNode" @deleteAllNextNodes="onDeleteAllNextNodes" @copyFlow="onCopyFlow"></node-item>
|
||||
</div>
|
||||
</div>
|
||||
<PropertyPanel :actionNode="store.activeNode" v-model:drawerRight="drawerRight"></PropertyPanel>
|
||||
</q-layout>
|
||||
<ShowDialog v-model:visible="showAddAction" name="アクション" @close="closeDg" min-width="500px" min-height="500px">
|
||||
<template v-slot:toolbar>
|
||||
<q-input dense debounce="200" v-model="filter" placeholder="検索" clearable>
|
||||
<template v-slot:before>
|
||||
<q-icon name="search" />
|
||||
</template>
|
||||
</q-input>
|
||||
</template>
|
||||
<action-select ref="appDg" name="model" :filter="filter" type="single"></action-select>
|
||||
</ShowDialog>
|
||||
|
||||
</q-page>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, computed, onMounted } from 'vue';
|
||||
import { IActionNode, ActionNode, IActionFlow, ActionFlow, RootAction, IActionProperty } from 'src/types/ActionTypes';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useFlowEditorStore } from 'stores/flowEditor';
|
||||
import { useAuthStore } from 'stores/useAuthStore';
|
||||
|
||||
import NodeItem from 'src/components/main/NodeItem.vue';
|
||||
import ShowDialog from 'components/ShowDialog.vue';
|
||||
import ActionSelect from 'components/ActionSelect.vue';
|
||||
import PropertyPanel from 'components/right/PropertyPanel.vue';
|
||||
import AppSelector from 'components/left/AppSelector.vue';
|
||||
import EventTree from 'components/left/EventTree.vue';
|
||||
import { FlowCtrl } from '../control/flowctrl';
|
||||
import { useQuasar } from 'quasar';
|
||||
const deployLoading = ref(false);
|
||||
const saveLoading = ref(false);
|
||||
const drawerLeft = ref(false);
|
||||
const $q = useQuasar();
|
||||
const store = useFlowEditorStore();
|
||||
const authStore = useAuthStore();
|
||||
|
||||
const appDg = ref();
|
||||
const prevNodeIfo = ref({
|
||||
prevNode: {} as IActionNode,
|
||||
inputPoint: ""
|
||||
});
|
||||
// const refFlow = ref<ActionFlow|null>(null);
|
||||
const showAddAction = ref(false);
|
||||
const drawerRight = ref(false);
|
||||
const filter=ref("");
|
||||
const model = ref("");
|
||||
const addActionNode = (action: IActionNode) => {
|
||||
// refFlow.value?.actionNodes.push(action);
|
||||
store.currentFlow?.actionNodes.push(action);
|
||||
}
|
||||
const rootNode = computed(()=>{
|
||||
return store.currentFlow?.getRoot();
|
||||
});
|
||||
const minPanelWidth=computed(()=>{
|
||||
const root = store.currentFlow?.getRoot();
|
||||
if(store.currentFlow && root){
|
||||
return store.currentFlow?.getColumns(root) * 300 + 'px';
|
||||
}else{
|
||||
return "300px";
|
||||
}
|
||||
});
|
||||
|
||||
const addNode = (node: IActionNode, inputPoint: string) => {
|
||||
if (drawerRight.value) {
|
||||
drawerRight.value = false;
|
||||
}
|
||||
showAddAction.value = true;
|
||||
prevNodeIfo.value.prevNode = node;
|
||||
prevNodeIfo.value.inputPoint = inputPoint;
|
||||
}
|
||||
|
||||
const onNodeSelected = (node: IActionNode) => {
|
||||
//右パネルが開いている場合、自動閉じる
|
||||
if (drawerRight.value && store.activeNode?.id !== node.id) {
|
||||
drawerRight.value = false;
|
||||
}
|
||||
store.setActiveNode(node);
|
||||
}
|
||||
|
||||
const onNodeEdit = (node: IActionNode) => {
|
||||
store.setActiveNode(node);
|
||||
drawerRight.value = true;
|
||||
}
|
||||
|
||||
const onDeleteNode = (node: IActionNode) => {
|
||||
if (!store.currentFlow) return;
|
||||
//右パネルが開いている場合、自動閉じる
|
||||
if (drawerRight.value && store.activeNode?.id === node.id) {
|
||||
drawerRight.value = false;
|
||||
}
|
||||
store.currentFlow?.removeNode(node);
|
||||
}
|
||||
|
||||
const onDeleteAllNextNodes = (node: IActionNode) => {
|
||||
if (!store.currentFlow) return;
|
||||
//右パネルが開いている場合、自動閉じる
|
||||
if (drawerRight.value) {
|
||||
drawerRight.value = false;
|
||||
}
|
||||
store.currentFlow?.removeAllNext(node.id);
|
||||
}
|
||||
const closeDg = (val: any) => {
|
||||
console.log("Dialog closed->", val);
|
||||
if (val == 'OK') {
|
||||
const data = appDg.value.selected[0];
|
||||
const actionProps = JSON.parse(data.property);
|
||||
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);
|
||||
}
|
||||
}
|
||||
/*
|
||||
*フローのデータをコピーする
|
||||
*/
|
||||
const onCopyFlow = () => {
|
||||
if (navigator.clipboard) {
|
||||
const jsonData =JSON.stringify(store.currentFlow) ;
|
||||
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 onDeploy = async () => {
|
||||
if (store.appInfo === undefined || store.flows?.length === 0) {
|
||||
$q.notify({
|
||||
type: 'negative',
|
||||
caption: "エラー",
|
||||
message: `設定されたフローがありません。`
|
||||
});
|
||||
return;
|
||||
}
|
||||
try {
|
||||
deployLoading.value = true;
|
||||
await store.deploy();
|
||||
deployLoading.value = false;
|
||||
$q.notify({
|
||||
type: 'positive',
|
||||
caption: "通知",
|
||||
message: `デプロイを成功しました。`
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
deployLoading.value = false;
|
||||
$q.notify({
|
||||
type: 'negative',
|
||||
caption: "エラー",
|
||||
message: `デプロイが失敗しました。`
|
||||
})
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const onSaveFlow = async () => {
|
||||
const targetFlow = store.selectedFlow;
|
||||
if (targetFlow === undefined) {
|
||||
$q.notify({
|
||||
type: 'negative',
|
||||
caption: "エラー",
|
||||
message: `編集中のフローがありません。`
|
||||
});
|
||||
return;
|
||||
}
|
||||
try {
|
||||
saveLoading.value = true;
|
||||
await store.saveFlow(targetFlow);
|
||||
saveLoading.value = false;
|
||||
$q.notify({
|
||||
type: 'positive',
|
||||
caption: "通知",
|
||||
message: `${targetFlow.getRoot()?.subTitle}のフロー設定を保存しました。`
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
saveLoading.value = false;
|
||||
$q.notify({
|
||||
type: 'negative',
|
||||
caption: "エラー",
|
||||
message: `${targetFlow.getRoot()?.subTitle}のフローの設定の保存が失敗しました。`
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const fetchData = async () => {
|
||||
drawerLeft.value = true;
|
||||
if (store.appInfo === undefined) return;
|
||||
const flowCtrl = new FlowCtrl();
|
||||
const actionFlows = await flowCtrl.getFlows(store.appInfo?.appId);
|
||||
if (actionFlows && actionFlows.length > 0) {
|
||||
store.setFlows(actionFlows);
|
||||
}
|
||||
if (actionFlows && actionFlows.length == 1) {
|
||||
store.selectFlow(actionFlows[0]);
|
||||
}
|
||||
const root = actionFlows[0].getRoot();
|
||||
if (root) {
|
||||
store.setActiveNode(root);
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
authStore.toggleLeftMenu();
|
||||
fetchData();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.app-selector {
|
||||
padding: 15px;
|
||||
z-index: 999;
|
||||
}
|
||||
|
||||
.flowchart {
|
||||
padding-top: 10px;
|
||||
}
|
||||
|
||||
.flow-toolbar {
|
||||
opacity: 50%;
|
||||
}
|
||||
|
||||
.event-tree .q-drawer {
|
||||
top: 50px;
|
||||
z-index: 999;
|
||||
}
|
||||
.expand{
|
||||
position: fixed;
|
||||
left: 0px;
|
||||
top: 50%;
|
||||
z-index: 9999;
|
||||
}
|
||||
</style>
|
||||
102
frontend/src/pages/FlowChartTest.vue
Normal file
102
frontend/src/pages/FlowChartTest.vue
Normal file
@@ -0,0 +1,102 @@
|
||||
<template>
|
||||
<q-page>
|
||||
|
||||
<div class="flowchart">
|
||||
<node-item v-for="(node,) in refFlow.actionNodes" :key="node.id"
|
||||
:isSelected="node===state.activeNode" :actionNode="node"
|
||||
@addNode="addNode"
|
||||
@nodeSelected="onNodeSelected"
|
||||
@nodeEdit="onNodeEdit"
|
||||
@deleteNode="onDeleteNode"
|
||||
@deleteAllNextNodes="onDeleteAllNextNodes"
|
||||
></node-item>
|
||||
</div>
|
||||
</q-page>
|
||||
<PropertyPanel :actionNode="state.activeNode" v-model:drawerRight="drawerRight"></PropertyPanel>
|
||||
<show-dialog v-model:visible="showAddAction" name="アクション" @close="closeDg" width="350px">
|
||||
<action-select ref="appDg" name="アクション" type="single"></action-select>
|
||||
</show-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {ref,reactive,computed} from 'vue';
|
||||
import {IActionNode, ActionNode, IActionFlow, ActionFlow,RootAction, IActionProperty } from 'src/types/ActionTypes';
|
||||
import NodeItem from 'src/components/main/NodeItem.vue';
|
||||
import ShowDialog from 'components/ShowDialog.vue';
|
||||
import ActionSelect from 'components/ActionSelect.vue';
|
||||
import PropertyPanel from 'components/right/PropertyPanel.vue';
|
||||
|
||||
|
||||
const rootNode:RootAction =new RootAction("app.record.create.submit","レコード追加画面","保存するとき");
|
||||
const actionFlow: ActionFlow = new ActionFlow(rootNode);
|
||||
const saibanProps:IActionProperty[]=[{
|
||||
component:"InputText",
|
||||
props:{
|
||||
displayName:"フォーマット",
|
||||
modelValue:"",
|
||||
name:"format",
|
||||
placeholder:"フォーマットを入力してください",
|
||||
}
|
||||
},{
|
||||
component:"FieldInput",
|
||||
props:{
|
||||
displayName:"採番項目",
|
||||
modelValue:"",
|
||||
name:"field",
|
||||
placeholder:"採番項目を選択してください",
|
||||
}
|
||||
}];
|
||||
|
||||
actionFlow.addNode(new ActionNode('自動採番','文書番号を自動採番する','',[],saibanProps));
|
||||
actionFlow.addNode(new ActionNode('入力データ取得','電話番号を取得する',''));
|
||||
const branchNode = actionFlow.addNode(new ActionNode('条件分岐','電話番号入力形式チャック','',['はい','いいえ'] ));
|
||||
// actionFlow.addNode(new ActionNode('入力データ取得','住所を取得する',''),branchNode,'はい');
|
||||
actionFlow.addNode(new ActionNode('エラー表示','エラー表示して保存しない',''),branchNode,'いいえ' );
|
||||
|
||||
// ref関数を使ってtemplateとバインド
|
||||
const state=reactive({
|
||||
activeNode:rootNode,
|
||||
})
|
||||
|
||||
const refFlow = ref(actionFlow);
|
||||
const showAddAction=ref(false);
|
||||
const drawerRight=ref(false);
|
||||
|
||||
const addActionNode=(action:IActionNode)=>{
|
||||
refFlow.value.actionNodes.push(action);
|
||||
}
|
||||
|
||||
const addNode=(node:IActionNode,inputPoint:string)=>{
|
||||
showAddAction.value=true;
|
||||
}
|
||||
|
||||
const onNodeSelected=(node:IActionNode)=>{
|
||||
//右パネルが開いている場合、自動閉じる
|
||||
if(drawerRight.value && state.activeNode.id!==node.id){
|
||||
drawerRight.value=false;
|
||||
}
|
||||
state.activeNode = node;
|
||||
}
|
||||
|
||||
const onNodeEdit=(node:IActionNode)=>{
|
||||
state.activeNode = node;
|
||||
drawerRight.value=true;
|
||||
}
|
||||
|
||||
const onDeleteNode=(node:IActionNode)=>{
|
||||
refFlow.value.removeNode(node);
|
||||
}
|
||||
|
||||
const onDeleteAllNextNodes=(node:IActionNode)=>{
|
||||
refFlow.value.removeAllNext(node.id);
|
||||
}
|
||||
const closeDg=(val :any)=>{
|
||||
console.log("Dialog closed->",val);
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.flowchart{
|
||||
padding-top: 10px;
|
||||
}
|
||||
</style>
|
||||
122
frontend/src/pages/FlowEditorPage.vue
Normal file
122
frontend/src/pages/FlowEditorPage.vue
Normal file
@@ -0,0 +1,122 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="q-ma-md">
|
||||
<div class="q-gutter-xs row items-start">
|
||||
<q-breadcrumbs class="q-pt-xs q-mr-sm" active-color="black">
|
||||
<q-breadcrumbs-el icon="home" />
|
||||
<q-breadcrumbs-el :label="actName" />
|
||||
<q-breadcrumbs-el
|
||||
v-for="flowName in flowNames"
|
||||
:key="flowName"
|
||||
:label="flowName"
|
||||
/>
|
||||
|
||||
<q-breadcrumbs-el :label="flowNames1" />
|
||||
</q-breadcrumbs>
|
||||
<q-separator vertical class="q-mr-xs" />
|
||||
<q-btn
|
||||
unelevated
|
||||
class="q-py-sm"
|
||||
padding="none md none sm"
|
||||
color="blue-1"
|
||||
text-color="primary"
|
||||
size="md"
|
||||
@click="drawerLeft = !drawerLeft"
|
||||
label="変 更"
|
||||
icon="expand_more"
|
||||
dense
|
||||
/>
|
||||
|
||||
<q-space />
|
||||
<q-btn
|
||||
class="q-px-sm q-mr-sm"
|
||||
color="white"
|
||||
size="sm"
|
||||
text-color="black"
|
||||
label="キャンセル"
|
||||
dense
|
||||
/>
|
||||
|
||||
<q-btn
|
||||
class="q-px-sm"
|
||||
color="primary"
|
||||
size="sm"
|
||||
label="保存する"
|
||||
dense
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<q-layout
|
||||
container
|
||||
style="height: 91.5dvb"
|
||||
class="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">
|
||||
<div class="row q-pl-md">
|
||||
<p class="text-h6">アクション選択</p>
|
||||
</div>
|
||||
<ItemSelector :actName="actName" />
|
||||
</div>
|
||||
</q-card-section>
|
||||
</div>
|
||||
<q-separator />
|
||||
<div class="q-mt-md q-pa-sm">
|
||||
<q-card-section>
|
||||
<p class="text-h6 q-pl-md q-mb-none">フロー選択</p>
|
||||
<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';
|
||||
|
||||
const actName = ref('勤怠管理 - 4');
|
||||
const flowNames = ref(['レコードを追加画面', '保存をクリックした時']);
|
||||
|
||||
const drawerLeft = ref(false);
|
||||
const store = useFlowEditorStore();
|
||||
const { flowNames1 } = storeToRefs(store);
|
||||
|
||||
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="sass"></style>
|
||||
112
frontend/src/pages/LoginPage.vue
Normal file
112
frontend/src/pages/LoginPage.vue
Normal file
@@ -0,0 +1,112 @@
|
||||
<template>
|
||||
<q-layout view="lHh Lpr fff">
|
||||
<q-page-container>
|
||||
<q-page class="window-height window-width row justify-center items-center">
|
||||
<div class="column q-pa-lg">
|
||||
<div class="row">
|
||||
<q-card :square="false" class="shadow-24" style="width:400px;height:540px;">
|
||||
<q-card-section class="bg-primary">
|
||||
<h4 class="text-h5 text-white q-my-md">{{ title}}</h4>
|
||||
</q-card-section>
|
||||
<q-card-section>
|
||||
<q-form class="q-px-sm q-pt-xl" ref="loginForm">
|
||||
<q-input square clearable v-model="email" type="email" lazy-rules
|
||||
:rules="[required,isEmail,short]" label="メール">
|
||||
<template v-slot:prepend>
|
||||
<q-icon name="email" />
|
||||
</template>
|
||||
</q-input>
|
||||
<q-input square clearable v-model="password" :type="passwordFieldType" lazy-rules
|
||||
:rules="[required, short]" label="パスワード">
|
||||
|
||||
<template v-slot:prepend>
|
||||
<q-icon name="lock" />
|
||||
</template>
|
||||
<template v-slot:append>
|
||||
<q-icon :name="visibilityIcon" @click="switchVisibility" class="cursor-pointer" />
|
||||
</template>
|
||||
</q-input>
|
||||
</q-form>
|
||||
</q-card-section>
|
||||
|
||||
<q-card-actions class="q-px-lg">
|
||||
<q-btn :loading="loading" unelevated size="lg" color="secondary" @click="submit" class="full-width text-white"
|
||||
label="ログイン" >
|
||||
<template v-slot:loading>
|
||||
<q-spinner class="on-left" />
|
||||
ログイン中...
|
||||
</template>
|
||||
</q-btn>
|
||||
</q-card-actions>
|
||||
|
||||
</q-card>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</q-page>
|
||||
</q-page-container>
|
||||
</q-layout>>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { useQuasar } from 'quasar'
|
||||
// import { useRouter } from 'vue-router';
|
||||
import { ref } from 'vue';
|
||||
// import { Auth } from '../control/auth'
|
||||
import { useAuthStore } from 'stores/useAuthStore';
|
||||
const authStore = useAuthStore();
|
||||
const $q = useQuasar()
|
||||
const loginForm = ref(null);
|
||||
const loading = ref(false);
|
||||
let title = ref('ログイン');
|
||||
let email = ref('');
|
||||
let password = ref('');
|
||||
let visibility = ref(false);
|
||||
let passwordFieldType = ref('password');
|
||||
let visibilityIcon = ref('visibility');
|
||||
const required = (val:string) => {
|
||||
return (val && val.length > 0 || '必須項目')
|
||||
}
|
||||
const isEmail = (val:string) => {
|
||||
const emailPattern = /^(?=[a-zA-Z0-9@._%+-]{6,254}$)[a-zA-Z0-9._%+-]{1,64}@(?:[a-zA-Z0-9-]{1,63}\.){1,8}[a-zA-Z]{2,63}$/
|
||||
return (emailPattern.test(val) || '無効なメールアドレス')
|
||||
}
|
||||
const short = (val:string) => {
|
||||
return (val && val.length > 3 || '値が短く過ぎる')
|
||||
}
|
||||
const switchVisibility = () => {
|
||||
visibility.value = !visibility.value
|
||||
passwordFieldType.value = visibility.value ? 'text' : 'password'
|
||||
visibilityIcon.value = visibility.value ? 'visibility_off' : 'visibility'
|
||||
}
|
||||
const submit = async () =>{
|
||||
loading.value=true;
|
||||
try {
|
||||
const result = await authStore.login(email.value,password.value);
|
||||
loading.value=false;
|
||||
if(result){
|
||||
$q.notify({
|
||||
icon: 'done',
|
||||
color: 'positive',
|
||||
message: 'ログイン成功'
|
||||
});
|
||||
}
|
||||
else{
|
||||
$q.notify({
|
||||
icon: 'error',
|
||||
color: 'negative',
|
||||
message: 'ログイン失敗'
|
||||
});
|
||||
}
|
||||
}catch (error) {
|
||||
console.error(error);
|
||||
loading.value=false;
|
||||
$q.notify({
|
||||
icon: 'error',
|
||||
color: 'negative',
|
||||
message: 'ログイン失敗'
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
</script>
|
||||
@@ -9,7 +9,7 @@
|
||||
</div>
|
||||
<div style="min-height: 100vh;">
|
||||
<div class="q-pa-md">
|
||||
<!-- <q-btn-dropdown split color="primary" label="ルール新規作成" size="lg">
|
||||
<q-btn-dropdown split color="primary" label="ルール新規作成" size="lg">
|
||||
<q-list>
|
||||
<q-item v-for="action in actions" clickable v-close-popup @click="onItemClick" :key="action">
|
||||
<q-item-section>
|
||||
@@ -17,14 +17,14 @@
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
</q-list>
|
||||
</q-btn-dropdown> -->
|
||||
</q-btn-dropdown>
|
||||
</div>
|
||||
<div class="q-pa-md">
|
||||
<q-select v-model="model" :options="options" label="ダイアログ選択"/>
|
||||
<q-select v-model="model" :options="options" label="Standard"/>
|
||||
<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=='アプリ'">
|
||||
<app-select ref="appDg" :name="model" type="single"></app-select>
|
||||
<app-select-box ref="appDg" :name="model" type="single"></app-select-box>
|
||||
</template>
|
||||
<template v-if="model=='フィールド'">
|
||||
<field-select ref="appDg" :name="model" type="multiple" :appId="1"></field-select>
|
||||
@@ -42,7 +42,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import ShowDialog from 'components/ShowDialog.vue';
|
||||
import AppSelect from 'components/AppSelect.vue';
|
||||
import AppSelectBox from 'components/AppSelectBox.vue';
|
||||
import FieldSelect from 'components/FieldSelect.vue';
|
||||
import ActionSelect from 'components/ActionSelect.vue';
|
||||
import { ref } from 'vue'
|
||||
|
||||
206
frontend/src/pages/TenantDomain.vue
Normal file
206
frontend/src/pages/TenantDomain.vue
Normal file
@@ -0,0 +1,206 @@
|
||||
<template>
|
||||
<div class="q-pa-md">
|
||||
<q-table title="Treats" :rows="rows" :columns="columns" row-key="id" selection="single" :filter="filter"
|
||||
:loading="loading" v-model:selected="selected">
|
||||
|
||||
<template v-slot:top>
|
||||
<q-btn color="primary" :disable="loading" label="新規" @click="addRow" />
|
||||
<q-btn class="q-ml-sm" color="primary" :disable="loading" label="編集" @click="editRow" />
|
||||
<q-btn class="q-ml-sm" color="primary" :disable="loading" label="削除" @click="removeRow" />
|
||||
<q-space />
|
||||
<q-input borderless dense debounce="300" color="primary" v-model="filter">
|
||||
<template v-slot:append>
|
||||
<q-icon name="search" />
|
||||
</template>
|
||||
</q-input>
|
||||
</template>
|
||||
|
||||
</q-table>
|
||||
|
||||
<q-dialog :model-value="show" persistent>
|
||||
<q-card style="min-width: 400px">
|
||||
<q-card-section>
|
||||
<div class="text-h6">Kintone Account</div>
|
||||
</q-card-section>
|
||||
|
||||
<q-card-section class="q-pt-none">
|
||||
<q-form class="q-gutter-md">
|
||||
<q-input filled v-model="tenantid" label="Tenant" hint="Tenant ID" lazy-rules
|
||||
:rules="[val => val && val.length > 0 || 'Please type something']" />
|
||||
|
||||
<q-input filled v-model="name" label="Your name *" hint="Kintone envirment name" lazy-rules
|
||||
:rules="[val => val && val.length > 0 || 'Please type something']" />
|
||||
|
||||
<q-input filled type="url" v-model="url" label="Kintone url" hint="Kintone domain address" lazy-rules
|
||||
:rules="[val => val && val.length > 0, isDomain || 'Please type something']" />
|
||||
|
||||
<q-input filled v-model="kintoneuser" label="Login user " hint="Kintone user name" lazy-rules
|
||||
:rules="[val => val && val.length > 0 || 'Please type something']" />
|
||||
|
||||
<q-input v-model="kintonepwd" filled :type="isPwd ? 'password' : 'text'" hint="Password with toggle"
|
||||
label="User password">
|
||||
<template v-slot:append>
|
||||
<q-icon :name="isPwd ? 'visibility_off' : 'visibility'" class="cursor-pointer" @click="isPwd = !isPwd" />
|
||||
</template>
|
||||
</q-input>
|
||||
</q-form>
|
||||
</q-card-section>
|
||||
<q-card-actions align="right" class="text-primary">
|
||||
<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-card-actions>
|
||||
</q-card>
|
||||
|
||||
</q-dialog>
|
||||
|
||||
<q-dialog v-model="confirm" persistent>
|
||||
<q-card>
|
||||
<q-card-section class="row items-center">
|
||||
<q-avatar icon="confirm" color="primary" text-color="white" />
|
||||
<span class="q-ml-sm">削除してもよろしいですか?</span>
|
||||
</q-card-section>
|
||||
|
||||
<q-card-actions align="right">
|
||||
<q-btn flat label="Cancel" color="primary" v-close-popup />
|
||||
<q-btn flat label="OK" color="primary" v-close-popup @click="deleteDomain()" />
|
||||
</q-card-actions>
|
||||
</q-card>
|
||||
</q-dialog>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, reactive } from 'vue';
|
||||
import { api } from 'boot/axios';
|
||||
|
||||
const columns = [
|
||||
{ name: 'id' },
|
||||
{
|
||||
name: 'tenantid',
|
||||
required: true,
|
||||
label: 'Tenant',
|
||||
align: 'left',
|
||||
field: row => row.tenantid,
|
||||
format: val => `${val}`,
|
||||
sortable: true
|
||||
},
|
||||
{ name: 'name', align: 'center', label: 'Name', field: 'name', sortable: true },
|
||||
{ name: 'url', align: 'left', label: 'URL', field: 'url', sortable: true },
|
||||
{ name: 'user', label: 'Account', field: 'user' },
|
||||
{ name: 'password', label: 'Password', field: 'password' }
|
||||
];
|
||||
|
||||
|
||||
const loading = ref(false);
|
||||
const filter = ref('');
|
||||
const rows = ref([]);
|
||||
const show = ref(false);
|
||||
const confirm = ref(false);
|
||||
const selected = ref([]);
|
||||
const tenantid = ref('');
|
||||
const name = ref('');
|
||||
const url = ref('');
|
||||
const isPwd = ref(true);
|
||||
const kintoneuser = ref('');
|
||||
const kintonepwd = ref('');
|
||||
|
||||
let editId = ref(0);
|
||||
|
||||
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(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;
|
||||
}
|
||||
|
||||
const deleteDomain = () => {
|
||||
api.delete(`api/domain/${editId.value}`).then(() => {
|
||||
getDomain();
|
||||
})
|
||||
editId.value = 0;
|
||||
selected.value = [];
|
||||
};
|
||||
|
||||
const editRow = () => {
|
||||
if (selected.value.length === 0) {
|
||||
return;
|
||||
}
|
||||
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();
|
||||
}
|
||||
|
||||
const onSubmit = () => {
|
||||
if (editId.value !== 0) {
|
||||
api.put(`api/domain`, {
|
||||
'id': editId.value,
|
||||
'tenantid': tenantid.value,
|
||||
'name': name.value,
|
||||
'url': url.value,
|
||||
'kintoneuser': kintoneuser.value,
|
||||
'kintonepwd': kintonepwd.value
|
||||
}).then(() => {
|
||||
getDomain();
|
||||
closeDg();
|
||||
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 onReset = () => {
|
||||
name.value = '';
|
||||
url.value = '';
|
||||
kintoneuser.value = '';
|
||||
kintonepwd.value = '';
|
||||
isPwd.value = true;
|
||||
editId.value = 0;
|
||||
}
|
||||
</script>
|
||||
270
frontend/src/pages/UserDomain.vue
Normal file
270
frontend/src/pages/UserDomain.vue
Normal file
@@ -0,0 +1,270 @@
|
||||
<!-- <template>
|
||||
<div class="q-pa-md" style="max-width: 400px">
|
||||
|
||||
<q-form
|
||||
@submit="onSubmit"
|
||||
@reset="onReset"
|
||||
class="q-gutter-md"
|
||||
>
|
||||
<q-input
|
||||
filled
|
||||
v-model="name"
|
||||
label="Your name *"
|
||||
hint="Kintone envirment name"
|
||||
lazy-rules
|
||||
:rules="[ val => val && val.length > 0 || 'Please type something']"
|
||||
/>
|
||||
|
||||
<q-input
|
||||
filled type="url"
|
||||
v-model="url"
|
||||
label="Kintone url"
|
||||
hint="Kintone domain address"
|
||||
lazy-rules
|
||||
:rules="[ val => val && val.length > 0,isDomain || 'Please type something']"
|
||||
/>
|
||||
|
||||
<q-input
|
||||
filled
|
||||
v-model="username"
|
||||
label="Login user "
|
||||
hint="Kintone user name"
|
||||
lazy-rules
|
||||
:rules="[ val => val && val.length > 0 || 'Please type something']"
|
||||
/>
|
||||
|
||||
<q-input v-model="password" filled :type="isPwd ? 'password' : 'text'" hint="Password with toggle" label="User password">
|
||||
<template v-slot:append>
|
||||
<q-icon
|
||||
:name="isPwd ? 'visibility_off' : 'visibility'"
|
||||
class="cursor-pointer"
|
||||
@click="isPwd = !isPwd"
|
||||
/>
|
||||
</template>
|
||||
</q-input>
|
||||
|
||||
<q-toggle v-model="accept" label="Active Domain" />
|
||||
|
||||
<div>
|
||||
<q-btn label="Submit" type="submit" color="primary"/>
|
||||
<q-btn label="Reset" type="reset" color="primary" flat class="q-ml-sm" />
|
||||
</div>
|
||||
</q-form>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { useQuasar } from 'quasar'
|
||||
import { ref } from 'vue'
|
||||
|
||||
export default {
|
||||
setup () {
|
||||
const $q = useQuasar()
|
||||
|
||||
const name = ref(null)
|
||||
const age = ref(null)
|
||||
const accept = ref(false)
|
||||
const isPwd =ref(true)
|
||||
|
||||
return {
|
||||
name,
|
||||
age,
|
||||
accept,
|
||||
isPwd,
|
||||
isDomain(val) {
|
||||
const domainPattern = /^https?\/\/:([a-zA-Z] +\.){1}([a-zA-Z]+)\.([a-zA-Z]+)$/;
|
||||
return (domainPattern.test(val) || '無効なURL')
|
||||
},
|
||||
|
||||
onSubmit () {
|
||||
if (accept.value !== true) {
|
||||
$q.notify({
|
||||
color: 'red-5',
|
||||
textColor: 'white',
|
||||
icon: 'warning',
|
||||
message: 'You need to accept the license and terms first'
|
||||
})
|
||||
}
|
||||
else {
|
||||
$q.notify({
|
||||
color: 'green-4',
|
||||
textColor: 'white',
|
||||
icon: 'cloud_done',
|
||||
message: 'Submitted'
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
onReset () {
|
||||
name.value = null
|
||||
age.value = null
|
||||
accept.value = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script> -->
|
||||
|
||||
|
||||
<template>
|
||||
<div class="q-pa-md">
|
||||
<q-table grid grid-header title="Domain" selection="single" :rows="rows" :columns="columns" v-model:selected="selected" row-key="name" :filter="filter" hide-header>
|
||||
<template v-slot:top>
|
||||
<div class="q-pa-md q-gutter-sm">
|
||||
<q-btn color="primary" label="追加" @click="newDomain()" dense />
|
||||
</div>
|
||||
<q-space />
|
||||
<q-input borderless dense debounce="300" v-model="filter" placeholder="Search">
|
||||
<template v-slot:append>
|
||||
<q-icon name="search" />
|
||||
</template>
|
||||
</q-input>
|
||||
</template>
|
||||
<template v-slot:item="props">
|
||||
<div class="q-pa-xs col-xs-12 col-sm-6 col-md-4">
|
||||
<q-card>
|
||||
<q-card-section>
|
||||
<div class="q-table__grid-item-row">
|
||||
<div class="q-table__grid-item-title">Domain</div>
|
||||
<div class="q-table__grid-item-value">{{ props.row.name }}</div>
|
||||
</div>
|
||||
<div class="q-table__grid-item-row">
|
||||
<div class="q-table__grid-item-title">URL</div>
|
||||
<div class="q-table__grid-item-value">{{ props.row.url }}</div>
|
||||
</div>
|
||||
<div class="q-table__grid-item-row">
|
||||
<div class="q-table__grid-item-title">Account</div>
|
||||
<div class="q-table__grid-item-value">{{ props.row.kintoneuser }}</div>
|
||||
</div>
|
||||
<div class="q-table__grid-item-row">
|
||||
<div class="q-table__grid-item-value">{{isActive(props.row.id) }}</div>
|
||||
</div>
|
||||
</q-card-section>
|
||||
<q-separator />
|
||||
<q-card-actions align="right">
|
||||
<q-btn flat @click = "activeDomain(props.row.id)">有効</q-btn>
|
||||
<q-btn flat @click = "deleteConfirm(props.row)">削除</q-btn>
|
||||
</q-card-actions>
|
||||
</q-card>
|
||||
</div>
|
||||
</template>
|
||||
</q-table>
|
||||
|
||||
<show-dialog v-model:visible="show" name="ドメイン" @close="closeDg" width="350px">
|
||||
<domain-select ref="domainDg" name="ドメイン" type="multiple"></domain-select>
|
||||
</show-dialog>
|
||||
|
||||
<q-dialog v-model="confirm" persistent>
|
||||
<q-card>
|
||||
<q-card-section class="row items-center">
|
||||
<q-avatar icon="confirm" color="primary" text-color="white" />
|
||||
<span class="q-ml-sm">削除してもよろしいですか?</span>
|
||||
</q-card-section>
|
||||
|
||||
<q-card-actions align="right">
|
||||
<q-btn flat label="Cancel" color="primary" v-close-popup />
|
||||
<q-btn flat label="OK" color="primary" v-close-popup @click = "deleteDomain()"/>
|
||||
</q-card-actions>
|
||||
</q-card>
|
||||
</q-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useQuasar } from 'quasar'
|
||||
import { ref, onMounted, reactive } from 'vue'
|
||||
import ShowDialog from 'components/ShowDialog.vue';
|
||||
import DomainSelect from 'components/DomainSelect.vue';
|
||||
import { useAuthStore } from 'stores/useAuthStore';
|
||||
|
||||
const authStore = useAuthStore();
|
||||
import { api } from 'boot/axios';
|
||||
import { domain } from 'process';
|
||||
|
||||
const $q = useQuasar()
|
||||
|
||||
const domainDg = ref();
|
||||
const selected = ref([])
|
||||
|
||||
const show = ref(false);
|
||||
const confirm = ref(false)
|
||||
|
||||
let editId = ref(0);
|
||||
let activedomainid = ref(0);
|
||||
|
||||
const columns = [
|
||||
{ name: 'id'},
|
||||
{name: 'name',required: true,label: 'Name',align: 'left',field: 'name',sortable: true},
|
||||
{ name: 'url', align: 'center', label: 'Domain', field: 'url', sortable: true },
|
||||
{ name: 'kintoneuser', label: 'User', field: 'kintoneuser', sortable: true },
|
||||
{ name: 'kintonepwd' },
|
||||
{ name: 'active', field: 'active'}
|
||||
]
|
||||
|
||||
const rows = ref([] as any[]);
|
||||
|
||||
const isActive = (id:number) =>{
|
||||
if(id == activedomainid.value)
|
||||
return "Active";
|
||||
else
|
||||
return "Inactive";
|
||||
}
|
||||
|
||||
const newDomain = () => {
|
||||
editId.value = 0;
|
||||
show.value = true;
|
||||
};
|
||||
|
||||
|
||||
const activeDomain = (id:number) => {
|
||||
api.put(`api/activedomain/`+ id).then(() =>{
|
||||
getDomain();
|
||||
})
|
||||
};
|
||||
|
||||
const deleteConfirm = (row:object) => {
|
||||
confirm.value = true;
|
||||
editId.value = row.id;
|
||||
};
|
||||
|
||||
const deleteDomain = () => {
|
||||
api.delete(`api/domain/`+ editId.value+'/1').then(() =>{
|
||||
getDomain();
|
||||
})
|
||||
editId.value = 0;
|
||||
};
|
||||
|
||||
const closeDg = (val:string) => {
|
||||
if (val == 'OK') {
|
||||
let dodmainids =[];
|
||||
let domains = JSON.parse(JSON.stringify(domainDg.value.selected));
|
||||
for(var key in domains)
|
||||
{
|
||||
dodmainids.push(domains[key].id);
|
||||
}
|
||||
api.post(`api/domain`, dodmainids).then(() =>{getDomain();});
|
||||
}
|
||||
|
||||
};
|
||||
const getDomain = async () => {
|
||||
const resp = await api.get(`api/activedomain`);
|
||||
activedomainid.value = resp.data.id;
|
||||
const domainResult = await api.get(`api/domain`);
|
||||
const domains = domainResult.data as any[];
|
||||
rows.value=domains.map((item)=>{
|
||||
return { id:item.id,name: item.name, url: item.url, kintoneuser: item.kintoneuser, kintonepwd: item.kintonepwd}
|
||||
});
|
||||
}
|
||||
onMounted(async () => {
|
||||
await getDomain();
|
||||
})
|
||||
|
||||
const isDomain = (val) =>{
|
||||
// const domainPattern = /^https\/\/:([a-zA-Z] +\.){1}([a-zA-Z]+)\.([a-zA-Z]+)$/;
|
||||
// return (domainPattern.test(val) || '無効なURL')
|
||||
return true;
|
||||
};
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
39
frontend/src/pages/conditionPage.vue
Normal file
39
frontend/src/pages/conditionPage.vue
Normal 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>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user