JamfSync
ยูทิลิตี้สำหรับการถ่ายโอนไฟล์ระหว่างโฟลเดอร์ไฟล์ จุดแจกจ่ายไฟล์ Jamf Pro และจุดแจกจ่าย Jamf Pro JDCS2 และการอัพเดตแพ็คเกจบนเซิร์ฟเวอร์ Jamf Pro
คำแนะนำ
ดูคู่มือผู้ใช้ JamfSync/Resources/Jamf Sync.pdf สำหรับความช่วยเหลือในการเรียกใช้ Jamf Sync หรือเมื่อเรียกใช้ "Jamf Sync.app" ให้คลิกวิธีใช้ / คู่มือผู้ใช้ Jamf Sync
คุณสมบัติ
- จัดการเซิร์ฟเวอร์ Jamf Pro หลายเครื่อง ช่วยให้สามารถย้ายแพ็คเกจจากเซิร์ฟเวอร์ทดสอบไปยังเซิร์ฟเวอร์ที่ใช้งานจริงหรือในทางกลับกัน
- สามารถคัดลอกไฟล์ระหว่าง Cloud DP, DP แชร์ไฟล์ หรือโฟลเดอร์ไฟล์ในเครื่อง
- สามารถเลือกลบไฟล์ออกจาก DP ปลายทางที่ไม่อยู่ใน DP ต้นทางได้
- สามารถเลือกลบแพ็คเกจบนเซิร์ฟเวอร์ Jamf Pro ที่ไม่ได้อยู่ใน DP ต้นทางได้
- สามารถถือว่าโฟลเดอร์ไฟล์ในเครื่องเป็นจุดแจกจ่าย ทำให้สามารถอัพโหลดและดาวน์โหลดไฟล์หลายแพ็คเกจได้
- จัดการการสร้างเช็คซัมสำหรับแพ็คเกจโดยอัตโนมัติ
- พารามิเตอร์บรรทัดคำสั่งอนุญาตให้มีการซิงโครไนซ์สคริปต์
คำอธิบาย
การตั้งค่า
- เพิ่มเซิร์ฟเวอร์และ/หรือโฟลเดอร์ Jamf Pro
- โฟลเดอร์จะถือเป็นจุดแจกจ่าย ซึ่งช่วยให้สามารถซิงค์ระหว่างจุดแจกจ่ายหรือโฟลเดอร์อื่นๆ ได้
- เมื่อคลิกปิด หากมีการเปลี่ยนแปลง ระบบจะติดต่อกับเซิร์ฟเวอร์ Jamf Pro แต่ละเซิร์ฟเวอร์เพื่อรับจุดแจกจ่าย และจะใช้จุดเหล่านั้นเพื่อเติมรายการการเลือกต้นทางและปลายทาง
เพิ่มเซิร์ฟเวอร์ Jamf Pro
- ผู้ใช้สามารถระบุ Jamf Pro Server URL และ ID ผู้ใช้และรหัสผ่าน หรือ ID ไคลเอ็นต์และความลับของไคลเอ็นต์ได้ ขึ้นอยู่กับว่ามีการใช้การรับรองความถูกต้องมาตรฐานหรือ OAuth (บทบาท API และไคลเอ็นต์)
- สามารถกดปุ่ม "ทดสอบ" เพื่อตรวจสอบว่าเซิร์ฟเวอร์ Jamf Pro สามารถเข้าถึงได้ด้วยการตั้งค่าหรือไม่
- สิ่งนี้จะเพิ่มเซิร์ฟเวอร์ DP บนคลาวด์โดยอัตโนมัติและ DP แชร์ไฟล์ทั้งหมดที่อยู่ใน Jamf Pro Server
- การตั้งค่าจะถูกจัดเก็บไว้ในเครื่อง ข้อมูลประจำตัวสำหรับ Jamf Pro และจุดกระจายการแชร์ไฟล์จะถูกเก็บไว้อย่างปลอดภัยในพวงกุญแจ
เพิ่มโฟลเดอร์ในเครื่อง
- อนุญาตให้เพิ่มโฟลเดอร์ในเครื่อง ซึ่งจะแสดงต่อผู้ใช้เป็นตัวเลือกเพื่อใช้เป็นแหล่งหรือปลายทาง โฟลเดอร์จะถือเป็นจุดแจกจ่าย ซึ่งช่วยให้สามารถซิงโครไนซ์ระหว่างจุดแจกจ่ายหรือโฟลเดอร์อื่นๆ ได้ มันจะมีชื่อและตำแหน่งไดเร็กทอรี ชื่อจะเป็นค่าเริ่มต้นเป็นชื่อไดเรกทอรี
แก้ไข
- ให้คุณแก้ไขรายการใดๆ ก็ตามที่เลือกไว้ หากไม่มีการเลือกสิ่งใด ปุ่มแก้ไขจะถูกปิดใช้งาน
ลบ
- ให้คุณลบรายการใดก็ตามที่เลือกไว้ มันจะแจ้งให้ยืนยันก่อนที่จะลบ หากไม่มีการเลือกสิ่งใด ปุ่มลบจะถูกปิดใช้งาน
มุมมองหลัก
- ปุ่มการซิงโครไนซ์จะเป็นสีเทาจนกว่าจะเลือกทั้งต้นทางและปลายทาง และต้นทางและปลายทางต่างกัน สิ่งนี้จะซิงโครไนซ์จากต้นทางไปยังปลายทาง
- ช่องทำเครื่องหมาย Force Sync จะทำให้รายการต้นฉบับทั้งหมดถูกคัดลอก แม้ว่าผลรวมตรวจสอบในปลายทางจะตรงกันก็ตาม เมื่อไม่ได้เลือก เฉพาะรายการที่เพิ่มหรือเปลี่ยนแปลงเท่านั้นที่จะถูกซิงโครไนซ์
- มีตัวเลือกสำหรับต้นทางและปลายทาง ซึ่งแต่ละรายการจะมีรายการโฟลเดอร์และจุดแจกจ่ายทั้งหมด ในตอนแรก "--" ถูกเลือก ซึ่งแสดงว่าไม่มีการเลือก
- รายการไฟล์บนจุดแจกจ่ายต้นทางที่เลือกจะอยู่ทางด้านซ้าย และรายการไฟล์บนจุดแจกจ่ายปลายทางจะอยู่ทางด้านขวา
- เมื่อเลือกจุดกระจายต้นทางและปลายทางแล้ว คอลัมน์ซิงค์จะแสดงไอคอนเพื่อระบุสิ่งที่จะเกิดขึ้นระหว่างการซิงโครไนซ์ เมื่อไอคอนซิงค์มีสี หมายความว่าไอคอนนั้นจะมีส่วนร่วมในการซิงโครไนซ์ ถ้าเป็นขาวดำก็จะไม่ ไอคอนซิงค์จะมีเครื่องหมาย + หากจะถูกเพิ่ม เครื่องหมายถูกว่าจะอัปเดตหรือไม่ มีเครื่องหมาย X หากจะถูกลบออกจากปลายทาง (หากพวกเขาเลือกหลังจากคลิกซิงโครไนซ์) และ = หากไฟล์บนทั้งสอง การจับคู่ต้นทางและปลายทาง
- สามารถเลือกรายการในรายการแหล่งที่มาได้ หากมีรายการที่เลือก ระบบจะซิงโครไนซ์เฉพาะไฟล์เหล่านี้และจะไม่มีการลบไฟล์ออกจากปลายทาง หากไม่มีการเลือกไฟล์ เมื่อกดปุ่มซิงโครไนซ์ คุณจะได้รับแจ้งว่าจะลบรายการที่ไม่ได้อยู่ในแหล่งที่มาหรือไม่ หากคุณเลือกใช่ ไฟล์ใดๆ บนปลายทางที่แสดงสัญลักษณ์ x สีแดงจะถูกลบออกจากปลายทางและรายการแพ็คเกจใน Jamf Pro
พารามิเตอร์บรรทัดคำสั่ง
หมายเหตุ: เรียกใช้ JamfSync โดยไม่มีพารามิเตอร์ก่อนเพื่อเพิ่มเซิร์ฟเวอร์และ/หรือโฟลเดอร์ Jamf Pro รหัสผ่านสำหรับเซิร์ฟเวอร์ Jamf Pro และจุดแจกจ่ายจะต้องจัดเก็บไว้ในพวงกุญแจเพื่อซิงโครไนซ์ผ่านอาร์กิวเมนต์บรรทัดคำสั่ง
การใช้งาน: JamfSync [(-s | --srcDp) ] [(-d | --dstDp) ] [(-f | --forceSync)] [(-r | --removeFilesNotOnSource)] [(-rp | -- RemovePackagesNotOnSource)] [-p | --ความคืบหน้า] JamfSync [-h | --help] JamfSync [-v | --รุ่น]
-s --srcDp: The name of the source distribution point or folder.
-d --dstDp: The name of the destination distribution point or folder.
-f --forceSync: Force synchronization of all files even if they appear to match on both the source and destination.
-r --removeFilesNotOnSource: Delete files on the destination that are not on the source. No delete is done if ommitted.
-rp --removePackagesNotOnSource: Delete packages on the destination's Jamf Pro instance that are not on the source. No delete is done if ommitted.
-p --progress: Show the progress of files being copied.
-v --version: Display the version number and build number.
-h --help: Shows this help text.
หมายเหตุ: หากชื่อจุดแจกจ่ายเหมือนกันในหลายอินสแตนซ์ Jamf Pro ให้ใช้ "dpName:jamfProName" เป็นชื่อ
ตัวอย่าง: "/Applications/Jamf Sync.app/Contents/MacOS/Jamf Sync" -srcDp localSourceName -dstDp DestinationSourceName --removeFilesNotOnSource --progress "/Applications/Jamf Sync.app/Contents/MacOS/Jamf Sync" -s "JCDS :Stage" -d "JCDS:Prod" -r -rp -p "/Applications/Jamf Sync.app/Contents/MacOS/Jamf Sync" -s localSourceName -d ปลายทางชื่อแหล่งที่มา
ภาพรวมซอร์สโค้ด
ซอร์สโค้ดอยู่ในหลายกลุ่ม
- โมเดลมีคลาสที่ไม่เกี่ยวข้องโดยตรงกับ UI
- ภายในกลุ่ม Model คือ ViewModels ซึ่งมีความเกี่ยวข้องอย่างใกล้ชิดกับ UI มากกว่า แต่ไม่ได้เชื่อมโยงโดยเฉพาะ โดยปกติเมื่อฟิลด์ในแบบจำลองมุมมองเปลี่ยนแปลง UI ที่เกี่ยวข้องจะถูกวาดใหม่โดยอัตโนมัติ
- ทรัพยากรประกอบด้วยรูปภาพและไฟล์อื่น ๆ ที่โปรแกรมใช้
- UI มีไฟล์ SwiftUI สำหรับมุมมองทั้งหมดที่ปรากฏในโปรแกรม เช่นเดียวกับ JamfSyncApp ซึ่งเป็นจุดเริ่มต้นของการเรียกใช้โปรแกรม
- ยูทิลิตี้ประกอบด้วยคลาสที่ทำงานทั่วไปมากกว่า
แบบอย่าง
ไฟล์ในกลุ่ม Model ใช้เพื่อติดตามและประมวลผลข้อมูลที่ใช้
DataModel: นี่แสดงถึงสถานะของเกือบทุกอย่างและมีข้อมูลและฟังก์ชันที่ดำเนินการกับข้อมูลนั้น มีหน้าที่โหลดข้อมูลจาก core storage และโหลดข้อมูลสำหรับจุดกระจายสินค้าด้วย มีการเผยแพร่ตัวแปรที่ใช้ในการควบคุม UI
SavableItem: คลาสพื้นฐานสำหรับทุกสิ่งที่สามารถบันทึกได้ (JamfProInstance และ FolderInstance) รายการเหล่านี้ถูกจัดเก็บไว้ใน Core Data รหัสผ่านจะไม่ถูกจัดเก็บไว้ในข้อมูลหลัก แต่จะถูกจัดเก็บไว้ในพวงกุญแจแทนตราบเท่าที่ผู้ใช้อนุญาต ฟังก์ชัน loadDps ถูกแทนที่โดยคลาสลูกที่ระบุ และมีหน้าที่รับผิดชอบในการโหลดจุดแจกจ่ายทั้งหมดที่เกี่ยวข้องกับรายการนั้น ฟังก์ชัน getDps ส่งคืนจุดแจกจ่ายที่เกี่ยวข้องกับรายการนั้น
- JamfProInstance - แสดงถึงอินสแตนซ์ Jamf Pro และมีหน้าที่โหลดข้อมูลที่จำเป็นและสื่อสารกับ Jamf Pro API นอกจากนี้ยังสร้างและโหลดข้อมูลสำหรับจุดแจกจ่ายที่เกี่ยวข้องกับเซิร์ฟเวอร์ Jamf Pro นอกจากนี้ยังโหลดและจัดเก็บข้อมูลแพ็คเกจจากเซิร์ฟเวอร์ Jamf Pro
- FolderInstance - หมายถึงไดเร็กทอรีในเครื่องคอมพิวเตอร์ มันสร้าง FolderDp เดียวที่ทำหน้าที่เหมือนจุดแจกจ่ายระหว่างการซิงโครไนซ์
DistributionPoint: คลาสพื้นฐานสำหรับออบเจ็กต์ DistributionPoint ทั้งหมด (FileShareDp, Jcds2Dp & FolderDp) ฟังก์ชัน copyFiles เป็นฟังก์ชันหลักสำหรับการซิงโครไนซ์ จะเรียกฟังก์ชันอื่นๆ ที่ถูกแทนที่โดยวัตถุจุดแจกจ่ายเฉพาะ
- FileShareDp - ออบเจ็กต์จุดแจกจ่ายเฉพาะสำหรับจุดแจกจ่ายไฟล์แชร์
- Jcds2Dp - ออบเจ็กต์จุดแจกจ่ายเฉพาะสำหรับจุดแจกจ่ายบนคลาวด์
- FolderDp - ออบเจ็กต์จุดแจกจ่ายเฉพาะสำหรับจุดกระจายโฟลเดอร์ (แสดงถึงโฟลเดอร์ไฟล์ในเครื่อง)
วัตถุอื่นๆ:
- DpFile - เก็บข้อมูลเป็นไฟล์เดียว นอกจากนี้ยังมีฟังก์ชันสำหรับรักษาและเปรียบเทียบเช็คซัมด้วย
- DpFiles - จัดเก็บรายการไฟล์ มีฟังก์ชันเพื่อช่วยค้นหาไฟล์เฉพาะ อัปเดตเช็คซัมสำหรับไฟล์ และอัปเดตสถานะของแต่ละไฟล์ที่ระบุสถานะของไฟล์
- Checksum - แสดงถึง Checksum ทุกประเภท (โดยปกติคือ SHA-512)
- เช็คซัม - ออบเจ็กต์ที่เก็บรายการของออบเจ็กต์เช็คซัมและมีฟังก์ชันสำหรับใช้ในการรวบรวมเช็คซัม
ดูโมเดล
ไฟล์ในกลุ่ม ViewModels ใช้เพื่อติดตามและประมวลผลข้อมูลที่ใช้ ตัวแปรที่เผยแพร่จะทำให้มุมมองที่เกี่ยวข้องต้องวาดใหม่ * LogViewModel - ใช้สำหรับมุมมองบันทึกและข้อความที่แสดงที่ด้านล่างของหน้าจอ * SetupViewModel - ใช้สำหรับมุมมองการตั้งค่า * PackageListViewModel - ใช้สำหรับรายการไฟล์ต้นทางและปลายทางในมุมมองหลัก * DpFileViewModel - ใช้สำหรับแต่ละไฟล์ใน PackageListViewModel มีตัวชี้ไปยังอินสแตนซ์ DpFile เฉพาะและมีฟิลด์เฉพาะ UI * DpFilesViewModel - มีอาร์เรย์ของวัตถุ DpFileViewModel และใช้เพื่อจัดเก็บไฟล์ในวัตถุ PackageListViewModel
UI
ไฟล์ในกลุ่ม UI คือไฟล์ SwiftUI สำหรับอินเทอร์เฟซผู้ใช้ ข้อมูลใน DataModel ใช้เพื่อควบคุมมุมมอง ทุกครั้งที่มีการเปลี่ยนแปลงคุณสมบัติที่มี @Published จะทำให้มุมมองใดๆ ที่ใช้ฟิลด์เหล่านั้นต้องวาดใหม่
- AboutView: มุมมองที่แสดงเวอร์ชันและข้อมูลอื่นๆ
- ChecksumView: แสดงว่าเช็คซัมใดที่ถูกคำนวณสำหรับไฟล์ และแสดงสตริงพร้อมกับเช็คซัมจริงเมื่อวางเมาส์เหนือ
- ConfirmationView: มุมมองที่ใช้ยืนยันสิ่งต่างๆ
- ContentView: มุมมองหลักที่ขับเคลื่อนทุกสิ่ง
- FileSharePasswordView: ใช้เพื่อขอรหัสผ่านการแชร์ไฟล์เมื่อไม่ได้จัดเก็บไว้ในพวงกุญแจ
- FolderView: มุมมองเมื่อเพิ่มหรือแก้ไขโฟลเดอร์
- HeaderView: ส่วนบนสุดของ ContentView ที่มีปุ่มการซิงโครไนซ์และช่องทำเครื่องหมาย Force Sync
- JamfProPasswordView: ใช้เพื่อแจ้งรหัสผ่าน Jamf Pro เมื่อไม่ได้จัดเก็บไว้ในพวงกุญแจ
- JamfProServerView: มุมมองเมื่อเพิ่มหรือแก้ไขเซิร์ฟเวอร์ Jamf Pro
- JamfSyncApp: แอปหลักที่เก็บ ContentView
- LogMessageView: มุมมองที่แสดงข้อความบันทึกสั้นๆ ที่ด้านล่างสุดของมุมมองหลัก
- LogView: มุมมองที่แสดงข้อความบันทึก
- PackageAnimationView: มุมมองที่แสดงภาพเคลื่อนไหวระหว่างการซิงโครไนซ์
- PackageListView: มุมมองสำหรับรายการแพ็คเกจในมุมมองหลัก
- SavableItemListView: มุมมองสำหรับรายการในมุมมองการตั้งค่า
- SetupView: มุมมองหลักสำหรับการตั้งค่า
- SourceDestinationView: ส่วนของ MainView พร้อมด้วยตัวเลือกต้นทางและปลายทางและรายการไฟล์
- SynchronizationProgressView: มุมมองการซิงโครไนซ์ที่แสดงความคืบหน้าสำหรับการซิงโครไนซ์ที่กำลังดำเนินอยู่ สิ่งนี้ยังเริ่มการซิงโครไนซ์ใน onAppear
คุณประโยชน์
ไฟล์ในกลุ่มยูทิลิตี้เป็นคลาสตัวช่วยสำหรับการประมวลผลข้อมูล และไม่มีการเชื่อมต่อโดยตรงกับ UI
- ArgumentParser: แยกวิเคราะห์อาร์กิวเมนต์บรรทัดคำสั่งหากมี
- CloudSessionDelegate: จัดการฟังก์ชัน URLSessionTaskDelegate และ URLSessionDownloadDelegatedelegate เมื่อไฟล์ถูกถ่ายโอนไปยังและจากระบบคลาวด์
- FileHash: สร้างค่าแฮชของไฟล์ นี่คือคลาสนักแสดง ดังนั้นฟังก์ชันจะประมวลผลไฟล์ครั้งละหนึ่งไฟล์เท่านั้น เพื่อป้องกันความขัดแย้ง
- FileShare: จัดการการเมานต์และยกเลิกการเมานท์ไฟล์แชร์ นี่คือคลาสนักแสดงเพื่อหลีกเลี่ยงความขัดแย้ง
- FileShares: เมานต์การแชร์หรือส่งคืนการแชร์ไฟล์ที่เมาท์แล้ว และยกเลิกการเมานท์ไฟล์ที่เมาท์ทั้งหมด นี่คือคลาสนักแสดงเพื่อหลีกเลี่ยงความขัดแย้ง
- KeychainHelper: ช่วยในการจัดเก็บและการเรียกค้นรายการพวงกุญแจ
- View+NSWindow: ใช้เพื่อแสดงมุมมองเป็นหน้าต่างของตัวเอง
- การตั้งค่าผู้ใช้: อ่าน บันทึก และติดตามข้อมูลที่เขียนไปยังการตั้งค่าของผู้ใช้
การขยายประเภทของจุดจำหน่ายที่รองรับโดย JamfSync
การเพิ่มจุดแจกจ่ายสำหรับการเชื่อมต่อระบบคลาวด์โดยตรง เช่น Rackspace, Amazon Web Services, Akamai จะเป็นประโยชน์อย่างยิ่ง จะต้องดำเนินการต่อไปนี้เพื่อรองรับหนึ่งในสิ่งเหล่านี้:
- สร้างออบเจ็กต์ที่สืบทอด SavableItem (เช่น FolderInstance) และเพิ่มตัวแปรสมาชิกสำหรับข้อมูลแต่ละส่วนที่จะต้องระบุ
- สร้างวัตถุที่สืบทอดมาจาก DistributionPoint ดู Jcds2Dp สำหรับตัวอย่างนี้ ดู FolderItem เพื่อดูว่าควรสร้างและส่งคืนสิ่งนี้อย่างไร (เช่น FolderDp)
- สร้างเอนทิตีใน StoredSettings.xcdatamodeld และตั้งค่าพาเรนต์เป็น SavableItemData
- แก้ไข SetupView เพื่อให้สามารถสร้าง แก้ไข และลบรายการใหม่ได้
- เพิ่มออบเจ็กต์ใหม่ เช่น JamfProServerView เพื่อให้ผู้ใช้สามารถป้อนข้อมูลที่จำเป็นได้
- ใช้ KeychainHelper เพื่อบันทึกข้อมูลรับรองลงในพวงกุญแจและเพิ่มชื่อบริการเพิ่มเติม (เช่น fileShareServiceName) หากจำเป็น
- ตรวจสอบให้แน่ใจว่าการทดสอบหน่วยที่มีอยู่ผ่านและเพิ่มการทดสอบหน่วยเพิ่มเติมเพื่อให้ครอบคลุมการเปลี่ยนแปลงที่คุณทำ
จำเป็นต้องมีการปรับปรุง
- สร้างการรองรับจุดกระจายบนคลาวด์เพิ่มเติม เช่น Rackspace, Amazon Web Services, Akamai ตามที่อธิบายไว้ข้างต้น
- จำเป็นต้องปรับปรุงการดำเนินการยกเลิก สำหรับ FileShareDps และ FolderDps จะไม่ยกเลิกไฟล์ปัจจุบันที่กำลังถ่ายโอน ดังนั้นจึงอาจใช้เวลานานในการยกเลิก
- ทำให้รายการสามารถจัดเรียงตามคอลัมน์ใดก็ได้
- ทำให้คอลัมน์รายการมีขนาดใหญ่
- เพิ่มความครอบคลุมการทดสอบหน่วยเพิ่มเติม (โดยเฉพาะเสร็จสิ้น Jcds2DpTests และเพิ่มการทดสอบหน่วยสำหรับ CommandLineProcessing)
- การปรับปรุงสามารถทำได้เพื่อให้สามารถเข้าถึงได้มากขึ้น
- คงจะดีถ้าทำให้เป็นภาษาท้องถิ่นและเริ่มเพิ่มการแปลเป็นภาษาท้องถิ่นบางส่วน
มีส่วนร่วม
หากต้องการตั้งค่าสำหรับการพัฒนาในพื้นที่ ให้ทำ Fork ของ Repo นี้ สร้างสาขาบน Fork ของคุณตั้งชื่อตามปัญหาหรือเวิร์กโฟลว์ที่คุณกำลังปรับปรุง ชำระเงินสาขาของคุณ จากนั้นเปิดโฟลเดอร์ใน Xcode
พื้นที่เก็บข้อมูลนี้ต้องมีการคอมมิตที่ลงนามและได้รับการยืนยันแล้ว คุณสามารถดูข้อมูลเพิ่มเติมเกี่ยวกับการลงนามคอมมิตได้ใน GitHub Docs
ดึงคำขอ
ก่อนที่จะส่งคำขอดึงของคุณ โปรดดำเนินการดังต่อไปนี้:
- หากคุณกำลังเพิ่มคำสั่งหรือคุณสมบัติใหม่ คำสั่งเหล่านั้นควรมีการทดสอบหน่วยด้วย หากคุณกำลังเปลี่ยนฟังก์ชันการทำงาน ให้อัปเดตการทดสอบหรือเพิ่มการทดสอบใหม่ตามความจำเป็น
- ตรวจสอบว่าการทดสอบหน่วยทั้งหมดผ่านการทดสอบ
- เพิ่มบันทึกลงใน CHANGELOG เพื่ออธิบายสิ่งที่คุณเปลี่ยนแปลง
- หากคำขอดึงของคุณเกี่ยวข้องกับปัญหา ให้เพิ่มลิงก์ไปยังปัญหาในคำอธิบาย
ผู้ร่วมให้ข้อมูล
- แฮร์รี่ สแตรนด์
- เลสลี่ เฮลู